Move the gate's teardown invariant to retrying()
Test / lint (pull_request) Successful in 23s
Test / test (18) (pull_request) Successful in 32s
Test / test (20) (pull_request) Successful in 31s
Test / test (22) (pull_request) Successful in 32s
Test / test (24) (pull_request) Successful in 31s
Test / test (26) (pull_request) Successful in 31s
Mirror / push (push) Has been cancelled

This commit was merged in pull request #39.
This commit is contained in:
2026-09-28 01:50:33 +02:00
parent 5e132977f4
commit 30245bda21
3 changed files with 9 additions and 11 deletions
+6 -10
View File
@@ -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
+1
View File
@@ -395,6 +395,7 @@ export class Session extends EventEmitter<SessionEvents> {
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();
}
+2 -1
View File
@@ -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.