Repository: pymodbus-dev/pymodbus

Issue:
`trace_connect` is called twice
OS: macOS
pymodbus version 3.9.2

I supply the following dummy hook to the `trace_connect` argument during `ModbusTcpServer` instantiation:

```python
def print_connect(connected):
    print("CONNECT:", connected)
```

## Observed behaviour

Upon client connection (and subsequent disconnection), I get the following debug print on my console:

```
(venv) $ python ./server.py
CONNECT: True
CONNECT: True
CONNECT: False
```

Apparently, the callback is invoked _twice_ upon connect, and _once_ upon disconnect.

## Expected behaviour

I would expect only a single callback invocation upon connect:

```
(venv) $ python ./server.py
CONNECT: True
CONNECT: False
```

## Debugging

A quick, narrowed down, Git grep reveals the following invocations:

```shell
$ git grep -n 'self\.trace_connect('
pymodbus/server/base.py:64:            self.trace_connect(True)
pymodbus/transaction/transaction.py:205:        self.trace_connect(True)
pymodbus/transaction/transaction.py:209:        self.trace_connect(False)
```

It seems to me the invocation in `server/base.py` is superfluous, and should possibly be removed (?). OTOH, changing the behaviour would be a breaking change. _OTOH_ again, this API is not exactly very well documented.

The superfluous invocation was added by @janiversen in commit bebccceb582b4ff1f43026bbd18568f48feb8519, PR #2549, issue #2546.


Discussion:
Pull requests are welcome.

If you want the API better documented, feel free to submit a PR. It is currently documented at the same level as other API calls.



Patch:
diff --git a/pymodbus/server/base.py b/pymodbus/server/base.py
index e7d73ec1c..2b752d897 100644
--- a/pymodbus/server/base.py
+++ b/pymodbus/server/base.py
@@ -60,8 +60,6 @@ def __init__(  # pylint: disable=too-many-arguments
 
     def callback_new_connection(self):
         """Handle incoming connect."""
-        if self.trace_connect:
-            self.trace_connect(True)
         return ServerRequestHandler(
             self,
             self.trace_packet,


Test patch:
diff --git a/test/server/test_server_asyncio.py b/test/server/test_server_asyncio.py
index 860c059d4..544d7e090 100755
--- a/test/server/test_server_asyncio.py
+++ b/test/server/test_server_asyncio.py
@@ -255,6 +255,19 @@ async def test_async_server_file_descriptors(self):  # pragma: no cover
             assert not response.isError()
             client.close()
 
+    async def test_async_server_trace_connect_disconnect(self):
+        """Test connect/disconnect trace handler."""
+        trace_connect = mock.Mock()
+        await self.start_server()
+        self.server.trace_connect = trace_connect
+        await self.connect_server()
+        trace_connect.assert_called_once_with(True)
+        trace_connect.reset_mock()
+
+        BasicClient.transport.close()
+        await asyncio.sleep(0.2)  # so we have to wait a bit
+        trace_connect.assert_called_once_with(False)
+
     async def test_async_tcp_server_connection_lost(self):
         """Test tcp stream interruption."""
         await self.start_server()
diff --git a/test/transaction/test_transaction.py b/test/transaction/test_transaction.py
index 8a39ae071..dc9d7b5f9 100755
--- a/test/transaction/test_transaction.py
+++ b/test/transaction/test_transaction.py
@@ -86,6 +86,25 @@ async def test_transaction_sync_pdu_send(self, use_clc):
         transact.pdu_send(ExceptionResponse(0xff), (0,0))
         assert transact.sent_buffer == b'\x01\xff\x00a\xf0'
 
+    async def test_transaction_connect(self, use_clc):
+        """Test tracers in disconnect."""
+        transact = TransactionManager(
+            use_clc,
+            FramerRTU(DecodePDU(False)),
+            5,
+            False,
+            None,
+            None,
+            None,
+        )
+        transact.trace_packet = mock.Mock()
+        transact.trace_pdu = mock.Mock()
+        transact.trace_connect = mock.Mock()
+        transact.callback_connected()
+        transact.trace_connect.assert_called_once_with(True)
+        transact.trace_packet.assert_not_called()
+        transact.trace_pdu.assert_not_called()
+
     async def test_transaction_disconnect(self, use_clc):
         """Test tracers in disconnect."""
         transact = TransactionManager(