Skip to content

Negotiate heart-beats as STOMP 1.2 specifies - #216

Merged
vrslev merged 6 commits into
community-of-python:mainfrom
GLeurquin:fix/heartbeat-negotiation
Nov 10, 2026
Merged

vrslev merged 6 commits into
community-of-python:mainfrom
GLeurquin:fix/heartbeat-negotiation

Conversation

@GLeurquin

Copy link
Copy Markdown
Contributor

Fixes #213.

Why

STOMP 1.2, Heart-beating: from CONNECT's heart-beat:<cx>,<cy> and CONNECTED's heart-beat:<sx>,<sy>, the client sends every MAX(cx, sy) ms, and none if cx or sy is 0; the server sends every MAX(sx, cy) ms, the same way; a missing header is treated as heart-beat:0,0.

stompman used the server's values alone:

  • The client's send interval was the server's sy. With sy = 0 (the server wants none), the heart-beat loop slept 0 s and sent EOLs continuously.
  • is_alive used the server's sx. With sx = 0 (the server sends none), every connection counted as dead.
  • A CONNECTED frame without heart-beat raised KeyError.

Summary

  • stompman.config.AgreedHeartbeat, with negotiate(client=…, server=…): the rule above, written once.
  • ConnectionLifespan.enter reads a missing header as 0,0, negotiates, and passes the AgreedHeartbeat to set_heartbeat_interval. EstablishedConnectionResult and ActiveConnectionState carry it in place of the raw server Heartbeat.
  • ConnectionManager._restart_background_tasks starts the heart-beat task only when the send interval is above 0.
  • ActiveConnectionState.is_alive waits on the agreed receive interval times check_server_alive_interval_factor, and returns true when no server heart-beats are agreed.
  • README: the caveat "Heartbeats are required" now describes the negotiation.

Compatibility: EstablishedConnectionResult.server_heartbeat and ActiveConnectionState.server_heartbeat become heartbeat: AgreedHeartbeat, and set_heartbeat_interval takes an AgreedHeartbeat. None of them is exported from stompman, but a custom ConnectionLifespanFactory would need the new type. Behaviour is unchanged whenever both sides send and want heart-beats at the same interval: test_client_heartbeats_ok passes as before.

Validation

  • just lint
  • just check-types
  • just test-fast: 458 passed. New tests:
    • test_agreed_heartbeat_negotiate: one case per branch of the rule, both directions;
    • test_client_sends_no_heartbeats_the_server_does_not_want: heart-beat:1000,0 and a missing header; no heart-beat is written and nothing loops on a 0 s interval. It fails without the change;
    • test_is_alive_waits_on_the_agreed_interval (MAX(sx, cy)) and test_is_alive_when_no_server_heartbeats_are_agreed.
  • just test (integration) not run.

GLeurquin and others added 2 commits November 3, 2026 12:41
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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread packages/stompman/stompman/config.py Outdated


@dataclass(frozen=True, slots=True)
class AgreedHeartbeat:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer updating Heartbeat instead of adding new class

@vrslev
vrslev merged commit cfdfdef into community-of-python:main Nov 10, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Client sends heart-beats in a loop that never sleeps when the server wants none

2 participants