Skip to content

Recycle the session every ten minutes instead of holding one open - #7

Merged
gotwalt merged 1 commit into
mainfrom
feat/session-recycling
Aug 30, 2026
Merged

Recycle the session every ten minutes instead of holding one open#7
gotwalt merged 1 commit into
mainfrom
feat/session-recycling

Conversation

@gotwalt

@gotwalt gotwalt commented Aug 30, 2026

Copy link
Copy Markdown
Owner

A Profiler asked to hold a single connection for hours has been seen to stop serving and flash its LEDs red, needing a power cycle or a Tap-button reboot (forum thread). That thread ends with Kemper support saying the LED count means different things and each case needs diagnosing, so this is a hypothesis with a symptom attached rather than a measured fuse like the rig-load one. The cheapest response that makes the hypothesis moot is to stop asking: no session ever grows old.

What this adds

ConnectOptions.recycle — a RecyclePolicy carrying max_age, defaulting to the new spec constant session_max_age_ms (10 minutes). At max_age the model:

  • closes both links as a close, not a loss (the stream reads Closed, never Lost);
  • raises SessionRecycled { age };
  • dials again at once — the ledger's 1 s cooldown is the only wait, since the model is what closed the socket.

The tree and the receivers survive, so a client sees Connected → Reconnecting { attempt: 1 } → Connected and its readings stand across the second it takes. Requests in flight fail as they would on any drop.

Unlike ReconnectPolicy, this is on by default, for the opposite reason: reconnecting asks the device to serve a socket it would not otherwise have served, while recycling only refuses to make it hold one for longer than it is known to. recycle = None restores the old behaviour.

The immediate reopen is the swap's only attempt of its own. If it fails, what follows is an ordinary outage — the reconnect policy's backoff counted from attempt two, or Disconnected — so a client whose own reconnect includes rediscovery (the Home Assistant integration, for one) keeps that path.

Scope

One spec constant, three implementations, the same four tests in each: the swap, a refused swap without a backoff, a refused swap with one, and recycling turned off.

  • spec/protocol.toml + regenerated Rust/Python/Swift modules; spec 0.9.0
  • docs/11 gains a section and a hazard-table row; docs/01, docs/10 and all four READMEs updated
  • Python package 0.2.0, Rust crate 0.2.0

Rust cargo test / clippy / fmt, Python pytest / ruff, Swift swift test / format lint all pass locally, as do generate.py --check and gen_vectors.py.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XGu4MzoprAAryzADpHJm1P

A Profiler asked to hold a single connection for hours has been seen to
stop serving and flash its LEDs red, needing a power cycle or a Tap-button
reboot. Kemper's own forum thread on the fault ends with support saying the
LED count means different things and each case needs diagnosing, so this is
a hypothesis with a symptom attached rather than a measured fuse like the
rig-load one. The cheapest response that makes the hypothesis moot is to
stop asking: no session ever grows old.

So `ConnectOptions.recycle` — a `RecyclePolicy` carrying `max_age`,
`session_max_age_ms` (10 min) from the spec. At `max_age` the model closes
both links as a *close* (the stream reads `Closed`, never `Lost`), raises
`SessionRecycled { age }` and dials again at once; the ledger's cooldown is
the only wait, since the model is what closed the socket. The tree and the
receivers survive, so a client sees `Connected → Reconnecting { 1 } →
Connected` and its readings stand across the second it takes.

Unlike `ReconnectPolicy` this is **on by default**, for the opposite
reason: reconnecting asks the device to serve a socket it would not
otherwise have served, while recycling only refuses to make it hold one for
longer than it is known to. `recycle = None` restores the old behaviour.

The immediate reopen is the swap's only attempt of its own. If it fails,
what follows is an ordinary outage — the reconnect policy's backoff counted
from attempt two, or `Disconnected` — so a client whose reconnect includes
rediscovery keeps that path.

All three implementations, from one spec constant, with the same tests in
each: the swap, a refused swap with and without a backoff, and recycling
turned off. Spec 0.9.0; the Python package is 0.2.0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XGu4MzoprAAryzADpHJm1P
@gotwalt
gotwalt merged commit a8e1386 into main Aug 30, 2026
5 checks passed
@gotwalt
gotwalt deleted the feat/session-recycling branch August 30, 2026 20:18
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.

1 participant