Repository navigation
Add opt-in reconnect when the server's heart-beats stop - #217
Merged
vrslev merged 11 commits intoNov 10, 2026
Merged
Conversation
The client sent heart-beats at the server's wanted interval alone, so a server
answering "heart-beat:x,0" (it wants none) made the client send them in a loop
that never sleeps. The aliveness check read the server's interval alone too, so
a server sending none ("heart-beat:0,y") made every connection look dead, and a
CONNECTED frame without a heart-beat header raised KeyError.
STOMP 1.2, Heart-beating: client heart-beats come every MAX(cx, sy) ms, and
none if cx or sy is 0; server heart-beats every MAX(sx, cy) ms, the same way;
a missing heart-beat header is "heart-beat:0,0". AgreedHeartbeat.negotiate does
exactly that once, in the connection lifespan. The heart-beat task starts only
when the client sends any, and is_alive waits on the agreed server interval and
holds the connection alive when none is agreed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client reads the server's heart-beats but does nothing when they stop: since community-of-python#138 and community-of-python#154 a stalled server is noticed only through Client.is_alive(), by a caller polling it. STOMP 1.2, Heart-beating: "if, inside a time window of at least <n> milliseconds, the receiver did not receive any new data, it MAY consider the connection as dead", and it "SHOULD be tolerant". With check_server_heartbeat, a monitor wakes every agreed receive interval and, when is_alive fails (no data for check_server_alive_interval_factor intervals), drops the connection like any lost one, so the client reconnects and subscribes again. It runs only when server heart-beats are agreed, and the factor must then be at least 1, the window the specification requires. Off by default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rtbeat # Conflicts: # packages/stompman/test_stompman/test_aliveness.py
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.
Closes #214. Builds on #216, whose two commits ("Negotiate heart-beats as STOMP 1.2 specifies", "Describe heart-beat negotiation in the README") are also in this branch; only "Add opt-in reconnect when the server's heart-beats stop" is new here. I'll rebase once that PR is merged.
Why
STOMP 1.2, Heart-beating: any new data received shows the remote end is alive; when heart-beats are expected every
<n>ms and no data arrives inside a window of at least<n>ms, the receiver MAY consider the connection dead, and SHOULD be tolerant of timing inaccuracies. Today the client never acts on that; a stalled server is noticed only by a caller pollingClient.is_alive().Summary
Client.check_server_heartbeat: bool = False, passed toConnectionManager. Off by default._restart_background_tasks), and the option is on and the agreed receive interval is above 0, a monitor task starts. It wakes every receive interval; whenActiveConnectionState.is_alive(check_server_alive_interval_factor)fails, it drops the connection through_discard_failed_connection_state(reason"no server heart-beat within timeout"). The read loop then reconnects and subscriptions are restored, as for any lost connection, and the next connection starts its own monitor.is_alivecounts any data, sincelast_read_timeis updated on every read, heart-beat or frame.check_server_alive_interval_factor. With the option on, a factor below 1 raisesValueErrorat construction: the specification allows declaring a connection dead only after a window of at least<n>ms.Validation
just lintjust check-typesjust test-fast: 467 passed. New tests:test_server_heartbeat_check_reconnects_after_silence: no data for more than three agreed intervals, then a reconnect. It fails without the change;test_server_heartbeat_check_keeps_a_connection_that_receives_data;test_server_heartbeat_check_inactive: option off, or no server heart-beats agreed;test_server_heartbeat_check_needs_a_window_of_at_least_the_interval.just test(integration) not run.