Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix no auto reconnect after close/connect in TCPclient #1298

Merged
merged 10 commits into from
Jan 27, 2023
27 changes: 25 additions & 2 deletions pymodbus/client/tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,15 @@ def __init__(
self.loop = None
self.connected = False
self.delay_ms = self.params.reconnect_delay
self._reconnect_future = None

async def connect(self): # pylint: disable=invalid-overridden-method
"""Initiate connection to start client."""

# if delay_ms was set to 0 by close(), we need to set it back again
# so this instance will work
self.reset_delay()

# force reconnect if required:
self.loop = asyncio.get_running_loop()

Expand All @@ -70,6 +76,11 @@ async def connect(self): # pylint: disable=invalid-overridden-method
async def close(self): # pylint: disable=invalid-overridden-method
"""Stop client."""

# if there is an unfinished delayed reconnection attempt pending, cancel it
if self._reconnect_future:
self._reconnect_future.cancel()
self._reconnect_future = None

# prevent reconnect:
self.delay_ms = 0
if self.connected:
Expand Down Expand Up @@ -121,7 +132,7 @@ async def _connect(self):
txt = f"Failed to connect: {exc}"
_logger.warning(txt)
if self.delay_ms > 0:
asyncio.ensure_future(self._reconnect())
self._launch_reconnect()
else:
txt = f"Connected to {self.params.host}:{self.params.port}."
_logger.info(txt)
Expand Down Expand Up @@ -150,7 +161,18 @@ def protocol_lost_connection(self, protocol):
del self.protocol
self.protocol = None
if self.delay_ms > 0:
asyncio.ensure_future(self._reconnect())
self._launch_reconnect()

def _launch_reconnect(self):
"""Launch delayed reconnection coroutine"""
if self._reconnect_future:
_logger.warning(
"Ignoring attempt to launch a delayed reconnection while another is already in progress"
)
else:
# store the future in a member variable so we know we have a pending reconnection attempt
# also prevents its garbage collection
self._reconnect_future = asyncio.ensure_future(self._reconnect())

async def _reconnect(self):
"""Reconnect."""
Expand All @@ -159,6 +181,7 @@ async def _reconnect(self):
await asyncio.sleep(self.delay_ms / 1000)
self.delay_ms = min(2 * self.delay_ms, self.params.reconnect_delay_max)

self._reconnect_future = None
return await self._connect()


Expand Down
4 changes: 3 additions & 1 deletion test/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,9 @@ async def test_client_reconnect(mock_sleep):
client = lib_client.AsyncModbusTcpClient(
"127.0.0.1", protocol_class=mock_protocol_class
)
client.delay_ms = 5000

# set delay long enough so we have only one connection attempt below
client.params.reconnect_delay = 5000
await client.connect()

run_coroutine(client._reconnect()) # pylint: disable=protected-access
Expand Down