From 1e5db4747ddc755e1123fc439de3d2581ea2d3f9 Mon Sep 17 00:00:00 2001 From: "SYM.BOT" Date: Wed, 29 Apr 2026 21:05:26 +0100 Subject: [PATCH] 0.3.83: lower stale-prior threshold from 10s to 1s MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors @sym-bot/sym v0.5.5. The 10s window from v0.3.81 was too lenient — peer-restart-with-recent-CMB hit dedup-reject path and legitimate redials died with "connection ready → immediate disconnect". 1s tolerates sub-second TCP-retry races, lets peer restarts (≥1s gap) recover at application layer instead of waiting OS keepalive (~100s). 71/71 unit tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 19 +++++++++++++++++++ Sources/SYM/SymNode.swift | 9 ++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e459a8..aca972c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ > **Note:** Versions 0.3.24 – 0.3.54 were released as git tags without changelog entries. Changelog resumes at 0.3.55 below. +## 0.3.83 + +### Fixed + +- **Stale-prior threshold lowered from 10s to 1s** in `SymNode.addPeer` + dedup. Mirrors `@sym-bot/sym` v0.5.5 on the Node side. The 10s window + shipped in v0.3.81 was too lenient: when a peer process was killed + and quickly relaunched, the old run had typically sent a CMB seconds + before death, so `lastSeen` was still within the 10s window. The + dedup logic then rejected the legitimate redial as a + same-direction-duplicate, producing `connection ready → immediate + disconnect` with no handshake-complete on the dialing side. + + Lowered to 1s. Sub-second TCP-retry races during initial handshake + still keep prior (the case the same-direction-duplicate rule was + designed for); peer restarts with ≥1s between kill and re-dial now + recover within the application layer instead of being blocked until + OS keepalive reaps the socket (~100s). + ## 0.3.82 ### Fixed diff --git a/Sources/SYM/SymNode.swift b/Sources/SYM/SymNode.swift index aa1b9b1..5061dc6 100644 --- a/Sources/SYM/SymNode.swift +++ b/Sources/SYM/SymNode.swift @@ -987,7 +987,14 @@ public final class SymNode { /// TCP keepalive (set in SymPeerSession.tcpParametersWithKeepalive) /// reaps within ~4s, but until that fires the lastSeen-age check is /// the application-level guard. - static let staleAfterSeconds: TimeInterval = 10 + /// 1-second threshold (NOT heartbeat-interval=10s). When a peer + /// process is killed and quickly relaunches, its old run sent a CMB + /// seconds before death, so lastSeen is still recent. A 10s threshold + /// missed this and the dedup-reject path killed the legitimate redial. + /// 1s tolerates sub-second TCP-retry races during initial handshake + /// while letting normal peer-restart (≥1s gap between kill and re-dial) + /// recover within the application layer. + static let staleAfterSeconds: TimeInterval = 1 private func addPeer(_ session: SymPeerSession, nodeId: String, peerName: String, isOutbound: Bool) { let outcome: AddPeerOutcome = peerQueue.sync {