feat(connection): add OWNSession.is_open and reset is_connected on close - #35
Conversation
… view MyHOME's _session_is_open() helper (OpenWebNet-HA/MyHOME#318) had to read _stream_reader / _stream_writer because is_connected reports the negotiated state, which close() does not reset: after an idle close it still reads True while send() would reopen the session based on the streams. is_open exposes exactly that transport view - True iff both streams are set - so the integration can drop its dependency on private names. Refs OpenWebNet-HA#34 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…close_streams() close() used to drop the streams but leave the negotiated flag as connect() last set it, so a consumer that closed a session still saw is_connected True. close() is now the explicit teardown: it releases the socket and flips the flag, notifying on_state_change on the transition. The no-flap rule for routine reconnects is preserved by construction: the stream teardown moves to _close_streams(), which _reconnect() and the retry loop in connect() call instead of close(), so a recycle that recovers never signals False to the consumer. connect() keeps owning the flag on its give-up and fatal-negotiation branches, as before. OWNEventSession overrides _close_streams() rather than close(), so the keepalive task is stopped on both the explicit close and an internal recycle. Closes OpenWebNet-HA#34 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Interstellar0verdrive
left a comment
There was a problem hiding this comment.
I checked the one thing that came out of the #318 finding, not the whole diff.
It does what it should: is_open reads the two streams, which is what send() actually looks at before reopening, and it's documented as distinct from is_connected. close() now releases the socket and clears the connected flag, while the internal recycling paths (_reconnect() and the retry loop inside connect()) go through the new _close_streams(), so a routine reconnect still doesn't flap availability. The command session's override moved to _close_streams() too, so the keepalive can't outlive the socket on either path. The three new tests cover exactly those cases, including the one that matters most: closed, flag False; recycled, flag untouched.
One inconsistency, and I'd rather ask than assert since the library isn't my ground: inside _locked_send the failure paths still call self.close() — the timeout, the connection-reset retry, the cancellation, the "session unavailable" branch. By the rationale in your own docstring those look like internal recycling, so _close_streams() would be the match; as written, a single command timeout now flips is_connected and notifies the consumer, where before the flag stayed put. It happens to be harmless for MyHOME today, because the command session is built without an on_state_change callback there (only the event session has one), so nothing downstream sees it. But it's a behaviour change that's invisible until someone wires that callback up, and then a busy bus would look like a gateway going offline and back.
Everything beyond this point in the diff needs someone who knows the library; I'd defer to @fedem95 or @xtimmy86x for the rest.
|
Thanks for this improvement. I reviewed In an isolated simulation using the PR’s actual methods, a status request encounters a connection reset, retries successfully, and returns success—but Please use A regression test should verify both cases: no offline/online transition during a successful retry, and a disconnection notification when recovery fails. The distinction between |
|
@xtimmy86x , @Interstellar0verdrive addressing this as we speak |
Review of 1fe9d81 (OpenWebNet-HA#35, @xtimmy86x): _locked_send() still called close() before retrying after a transport error, and close() now clears the connected flag - so a status request that lost its socket and recovered on the retry told the consumer [False, True] for a session that was never really gone. The recoverable path (status request, or a command not yet written) now releases the streams only, like _reconnect() does. The definitive loss - no retry left, or a written command whose acknowledgement was lost - reports disconnection as before. Three tests pin both halves: no transition on a successful retry; exactly one False when both attempts fail; exactly one False for a written command. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@xtimmy86x — confirmed and fixed in What changed — one path only, as you suggested, not a blanket replacement:
Regression tests (
The first one fails on #36 is rebased on top ( |
xtimmy86x
left a comment
There was a problem hiding this comment.
Thanks for addressing this in 54f61e9. I reviewed the fix and reran the isolated simulation: a successful retry now completes without an offline/online notification.
The change preserves disconnection reporting for definitive failures and avoids replaying commands already written. The three regression tests cover the requested cases, and CI is green across Python 3.11–3.14, with 630 tests passing.
My previous concern is resolved. No further blocking concerns from my review—approved.
…e, test) master stored five files with CRLF - .gitignore, LICENSE, __main__.py, connection.py, message.py - next to an LF majority, and connection.py mixed the two within the file. Any edit from a differently configured editor then rewrote the whole file: OpenWebNet-HA#35 rendered as +248/-218 for a +38/-8 change, OpenWebNet-HA#36 as +691/-615 for +324/-248, and a reviewer passed on both for that reason. `* text=auto eol=lf`, `git add --renormalize .`, and tests/test_line_endings.py so a CR that bypasses the clean filter fails the suite instead of the next reviewer. That test found one such path straight away: validate_corpus.py rewrote tests/golden/corpus.json with the platform newline on every run, so any Windows checkout that ran the suite had a CRLF corpus.json waiting to be committed. It now writes LF. Every byte other than line endings is unchanged: `git diff --ignore-cr-at-eol` shows only the two new files and the one-line newline argument. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e, test) master stored five files with CRLF - .gitignore, LICENSE, __main__.py, connection.py, message.py - next to an LF majority, and connection.py mixed the two within the file. Any edit from a differently configured editor then rewrote the whole file: OpenWebNet-HA#35 rendered as +248/-218 for a +38/-8 change, OpenWebNet-HA#36 as +691/-615 for +324/-248, and a reviewer passed on both for that reason. `* text=auto eol=lf`, `git add --renormalize .`, and tests/test_line_endings.py so a CR that bypasses the clean filter fails the suite instead of the next reviewer. That test found one such path straight away: validate_corpus.py rewrote tests/golden/corpus.json with the platform newline on every run, so any Windows checkout that ran the suite had a CRLF corpus.json waiting to be committed. It now writes LF. Every byte other than line endings is unchanged: `git diff --ignore-cr-at-eol` shows only the two new files and the one-line newline argument. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two jobs run on push and pull request, with this checkout installed in
place of the release MyHOME pins:
corpus - MyHOME's golden corpus and smoke gates (parser parity,
platform imports, mock-gateway loopback). Seconds.
suite - MyHOME's full test suite on core 2026.x / Python 3.14.
Minutes. This is the run that showed OpenWebNet-HA#35 breaking one
MyHOME test and the retry flap xtimmy86x found in review.
workflow_dispatch takes a MyHOME ref, so a release candidate can be
proven against a MyHOME branch before either side tags.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Summary
This PR addresses and closes #34 by providing a truthful transport view of the command session's socket state and aligning
close()semantics:OWNSession.is_open:Trueiff both_stream_readerand_stream_writerare set and active.MyHOME) to verify whether the command session has an open socket without reaching into private stream attributes.OWNSession.close()resetsis_connected:close()now drops the streams and clears_connected, notifyingon_state_changeon the transition so consumers don't seeis_connected == Trueafter teardown._reconnect()and retry loops) use_close_streams(), preserving the no-flap behavior for transient reconnects.OWNEventSession._close_streams()ensures keepalive tasks are cleanly canceled during teardown and recycles.Closes #34.