Close the udp socket in close(), not just drop the reference - #3011
Merged
janiversen merged 2 commits intoAug 27, 2026
Conversation
ModbusUdpClient.close() documents that it closes the underlying socket connection but only cleared the attribute, so the socket was left open. ModbusTcpClient.close() and ModbusSerialClient.close() both close it first.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ModbusUdpClient.close()leaves the socket openclose()documents that it closes the underlying socket connection, but it only clears theattribute:
Its two siblings both close the socket first —
ModbusTcpClient.close()(client/tcp.py:210)and
ModbusSerialClient.close()(client/serial.py:248):Reproduction
Two lines of the documented API, on
devat b4ce31d:Before:
After:
close()reports success andconnectedflips toFalse, so the client looks correctly torndown while the socket is still open. Under CPython the fd is usually reclaimed by refcounting
once the last reference goes away — which is why this has gone unnoticed — but it is not
reclaimed whenever a reference survives (a retry handler, a traceback frame, a caller holding
client.socket), and CPython itself reports the socket as unclosed on the plain path above.connect()already routes its bind failure through this sameclose()atclient/udp.py:196,and #3000 / #3008 just made
send()/recv()callself.close()onOSErrorfor the tcp andserial clients. If udp gets that same treatment,
close()needs to actually close.Change
Two lines in
close(), matching tcp and serial. Plus a test inTestSyncClientUdpassertingthe socket is released rather than only dereferenced; it needs no network, since the udp
connect()only creates the socket.Verified red/green: with the source reverted the test fails
assert 16 == -1; with the fix thefile's 35 tests pass.
ruff format --check,ruff checkandcodespellare clean, and pylintis unchanged at 9.97/10 (the one
C0411ontest_client_sync.py:23is present atdevtoo).In a full
pytestrun the 20 failures are all pre-existing TLS-certificate and live-serverexample tests, identical at
devand on this branch.Disclosure: this fix was written with AI assistance (Claude). I verified the reproduction, the
red/green runs and the test-suite comparison myself.