Skip to content

Commit 629e874

Browse files
committed
Add lock to async requests, correct logging and length calc.
1 parent 69fe423 commit 629e874

File tree

4 files changed

+31
-20
lines changed

4 files changed

+31
-20
lines changed

pymodbus/client/base.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ def __init__(
9999
self.state = ModbusTransactionState.IDLE
100100
self.last_frame_end: float | None = 0
101101
self.silent_interval: float = 0
102+
self._lock = asyncio.Lock()
102103

103104
# ----------------------------------------------------------------------- #
104105
# Client external interface
@@ -162,19 +163,21 @@ async def async_execute(self, request) -> ModbusResponse:
162163

163164
count = 0
164165
while count <= self.retries:
165-
req = self.build_response(request.transaction_id)
166-
if not count or not self.no_resend_on_retry:
167-
self.send(packet)
168-
if self.broadcast_enable and not request.slave_id:
169-
resp = None
170-
break
171-
try:
172-
resp = await asyncio.wait_for(
173-
req, timeout=self.comm_params.timeout_connect
174-
)
175-
break
176-
except asyncio.exceptions.TimeoutError:
177-
count += 1
166+
async with self._lock:
167+
req = self.build_response(request.transaction_id)
168+
if not count or not self.no_resend_on_retry:
169+
self.framer.resetFrame()
170+
self.send(packet)
171+
if self.broadcast_enable and not request.slave_id:
172+
resp = None
173+
break
174+
try:
175+
resp = await asyncio.wait_for(
176+
req, timeout=self.comm_params.timeout_connect
177+
)
178+
break
179+
except asyncio.exceptions.TimeoutError:
180+
count += 1
178181
if count > self.retries:
179182
self.close(reconnect=True)
180183
raise ModbusIOException(
@@ -190,6 +193,7 @@ def callback_connected(self) -> None:
190193
"""Call when connection is succcesfull."""
191194
if self.on_reconnect_callback:
192195
self.on_reconnect_callback()
196+
self.framer.resetFrame()
193197

194198
def callback_disconnected(self, exc: Exception | None) -> None:
195199
"""Call when connection is lost."""

pymodbus/framer/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,8 @@ def processIncomingPacket(self, data, callback, slave, **kwargs):
131131
:param kwargs:
132132
:raises ModbusIOException:
133133
"""
134-
Log.debug("Processing: {}", data, ":hex")
135134
self._buffer += data
135+
Log.debug("Processing: {}", self._buffer, ":hex")
136136
if not isinstance(slave, (list, tuple)):
137137
slave = [slave]
138138
single = kwargs.pop("single", False)

pymodbus/framer/socket_framer.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def decode_data(self, data):
6060
}
6161
return {}
6262

63-
def frameProcessIncomingPacket(self, single, callback, slave, tid=None, **kwargs):
63+
def frameProcessIncomingPacket(self, single, callback, slave, tid=None, **kwargs): # noqa: C901
6464
"""Process new packet pattern.
6565
6666
This takes in a new request packet, adds it to the current
@@ -74,7 +74,11 @@ def frameProcessIncomingPacket(self, single, callback, slave, tid=None, **kwargs
7474
"""
7575
def check_frame(self):
7676
"""Check and decode the next frame."""
77-
if not len(self._buffer) > self._hsize:
77+
if not self._buffer:
78+
Log.debug("Frame check, no more data!")
79+
return False
80+
if not len(self._buffer) >= self._hsize +1:
81+
Log.debug("Frame check failed, short frame {} >= {} !!", len(self._buffer), self._hsize+2)
7882
return False
7983
(
8084
self._header["tid"],
@@ -88,7 +92,7 @@ def check_frame(self):
8892
self._header = {"tid": 0, "pid": 0, "len": 0, "uid": 0}
8993
elif len(self._buffer) - self._hsize + 1 >= self._header["len"]:
9094
return True
91-
Log.debug("Frame check failed, missing part of message!!")
95+
Log.debug("Frame check failed, missing part of message len {}, MBAP len {} !!", len(self._buffer), self._header["len"])
9296
return False
9397

9498
while True:

test/test_network.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@ async def test_stub(self, use_port, use_cls):
8080
client.close()
8181
stub.close()
8282

83-
async def test_double_packet(self, use_port, use_cls):
83+
async def test_parallel_requests(self, use_port, use_cls):
8484
"""Test double packet on network."""
8585
old_data = b''
86-
client = AsyncModbusTcpClient(NULLMODEM_HOST, port=use_port, retries=0)
86+
client = AsyncModbusTcpClient(NULLMODEM_HOST, port=use_port, retries=0, timeout=30)
8787

8888
def local_handle_data(data: bytes) -> bytes | None:
8989
"""Handle server side for this test case."""
@@ -128,6 +128,9 @@ async def local_call(addr: int) -> bool:
128128
await stub.start_run()
129129

130130
assert await client.connect()
131-
await asyncio.gather(*[local_call(x) for x in range(1, 10)])
131+
try:
132+
await asyncio.gather(*[local_call(1) for x in range(1, 10)])
133+
except Exception as exc: # pylint: disable=broad-exception-caught
134+
pytest.fail(exc)
132135
client.close()
133136
stub.close()

0 commit comments

Comments
 (0)