Skip to content

Commit a1938dc

Browse files
Auke Willem Oosterhoffproelke
Auke Willem Oosterhoff
authored andcommitted
Fix potential deadlock
ocpp.ChargePoint.call() implements flow control as defined in the OCPP specification. It makes sure that no call can be send after an answer on the previous request has been received. A lock is used to implement flow control. If the call `await self._send(call.json())` crashed it the lock wouldn't be released which could lead to a deadlock. Fixes: mobilityhouse#46
1 parent 29eb0c1 commit a1938dc

File tree

2 files changed

+23
-5
lines changed

2 files changed

+23
-5
lines changed

ocpp/charge_point.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -248,18 +248,16 @@ async def call(self, payload):
248248
# Use a lock to prevent make sure that only 1 message can be send at a
249249
# a time.
250250
await self._call_lock.acquire()
251-
await self._send(call.to_json())
252251

253252
try:
253+
await self._send(call.to_json())
254254
response = \
255255
await self._get_specific_response(call.unique_id,
256256
self._response_timeout)
257-
except asyncio.TimeoutError:
257+
finally:
258258
self._call_lock.release()
259259
raise
260260

261-
self._call_lock.release()
262-
263261
if response.message_type_id == MessageType.CallError:
264262
LOGGER.warning("Received a CALLError: %s'", response)
265263
return

tests/v16/test_v16_charge_point.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import json
22
import pytest
3+
import asyncio
34

45
from ocpp.exceptions import NotImplementedError
56
from ocpp.routing import on, after, create_route_map
67
from ocpp.v16.enums import Action
7-
from ocpp.v16 import call_result
8+
from ocpp.v16 import call_result, call, ChargePoint
89

910

1011
@pytest.mark.asyncio
@@ -64,3 +65,22 @@ async def test_route_message_with_no_route(base_central_system,
6465

6566
with pytest.raises(NotImplementedError):
6667
await base_central_system.route_message(heartbeat_call)
68+
69+
70+
@pytest.mark.asyncio
71+
async def test_send_call_with_timeout(connection):
72+
cs = ChargePoint(
73+
id=1234,
74+
connection=connection,
75+
response_timeout=0.1
76+
)
77+
78+
payload = call.ResetPayload(type="Hard")
79+
80+
with pytest.raises(asyncio.TimeoutError):
81+
await cs.call(payload)
82+
83+
# Verify that lock is released if call() crahses. Not releasing the lock
84+
# in case of an exception could lead to a deadlock. See
85+
# https://github.com/mobilityhouse/ocpp/issues/46
86+
assert cs._call_lock.locked() is False

0 commit comments

Comments
 (0)