Recycle the session every ten minutes instead of holding one open - #7
Merged
Conversation
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
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.
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— aRecyclePolicycarryingmax_age, defaulting to the new spec constantsession_max_age_ms(10 minutes). Atmax_agethe model:Closed, neverLost);SessionRecycled { age };The tree and the receivers survive, so a client sees
Connected → Reconnecting { attempt: 1 } → Connectedand 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 = Nonerestores 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; spec0.9.0docs/11gains a section and a hazard-table row;docs/01,docs/10and all four READMEs updated0.2.0, Rust crate0.2.0Rust
cargo test/clippy/fmt, Pythonpytest/ruff, Swiftswift test/format lintall pass locally, as dogenerate.py --checkandgen_vectors.py.🤖 Generated with Claude Code
https://claude.ai/code/session_01XGu4MzoprAAryzADpHJm1P