From 30245bda216005ad6051e7df183233b8ab038944 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Mon, 28 Sep 2026 01:50:33 +0200 Subject: [PATCH] Move the gate's teardown invariant to retrying() --- docs/decisions.md | 16 ++++++---------- src/session.ts | 1 + todo.md | 3 ++- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/docs/decisions.md b/docs/decisions.md index f71e77b..b39885f 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -761,16 +761,12 @@ rule and an index of the titles below. round trip before the bind is answered, so gating on `closed` let a send arriving in that window go out unbound and come back `ESME_RINVBNDSTS`. `LinkGate` owns the answer instead — `shut(returning)` on every teardown, `open()` only once `comeBackUp()` has a bound link — and - `OutgoingRequests.canCarry()` reads it rather than `closed`. The bind itself cannot wait for what it - creates, so `carry()` lets the three bind commands past the gate and the window, the same door - `unbind()` takes through `now()`. The gate is told what happened and never reads back into the - session: a collaborator that has to ask does not own its decision, which is how the first cut ended - up answering the same question two different ways at admit and at release. For the same reason the - retry in `carry()` asks `gate.awaitsNextLink()` rather than `canCarry()`, which also reads the - socket — a condition that loops on something the gate does not gate on spins against a gate that - admits it straight back. `LinkGate.returning` is a copy of `retrying()` taken at teardown, and stays true - only because nothing stops the reconnect loop without `emitClose()` following it: `drain()` and - `end()` are the only callers of `stop()`. A third caller has to shut the gate itself. + `OutgoingRequests.canCarry()` reads it rather than `closed`. The gate is told what happened and + never reads back into the session: a collaborator that has to ask does not own its decision, which + is how the first cut ended up answering the same question two different ways at admit and at + release. The retry in `carry()` asks `gate.awaitsNextLink()` rather than `canCarry()`, which also + reads the socket: a loop condition the gate does not gate on spins against a gate that admits it + straight back. ## Internals and tests diff --git a/src/session.ts b/src/session.ts index ffcd655..4b337f7 100644 --- a/src/session.ts +++ b/src/session.ts @@ -395,6 +395,7 @@ export class Session extends EventEmitter { else this.emitClose(); } + // Copied into the gate at teardown, so stopping the loop anywhere but drain() and end() has to shut the gate too. private retrying(): boolean { return this.reconnectLoop !== undefined && !this.reconnectLoop.isStopped(); } diff --git a/todo.md b/todo.md index 5ca968f..be7f61f 100644 --- a/todo.md +++ b/todo.md @@ -321,7 +321,8 @@ next work ([decision](docs/decisions.md#internals-and-tests)). - [ ] **Make `LinkGate.isUp()`'s doc true or its state match it.** It says a link attached but not yet bound cannot carry a request, while `up` starts `true`, so the first link and a server - session are up before any bind. From the comprehension panel of #25. + session are up before any bind. The gate decision in `docs/decisions.md` makes the same claim + in its title, and carries the same fix. From the comprehension panel of #25. - [ ] **Move `checkSessionOptions()`'s doc comment to what it describes.** It explains why a count below 1 is refused, which is `checkLimits`' job, and says nothing of the function it heads.