Loop a held send on the gate's own answer, not on the socket too

This commit is contained in:
2026-09-01 10:15:07 +02:00
parent 35dd1e7678
commit 58817eebbf
2 changed files with 9 additions and 2 deletions
+7 -1
View File
@@ -379,4 +379,10 @@ exactly 140.
second `send()` on the public surface or a changed `ReconnectOptions.onConnected`.
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.
different ways at admit and at release. For the same reason the retry in `send()` asks
`gate.isUp()` rather than `linkDown()`, 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()`. An `OutgoingRequests` owning both would not need the copy; until then,
a third caller of `stop()` has to shut the gate itself.
+2 -1
View File
@@ -189,7 +189,8 @@ export class Session extends EventEmitter<SessionEvents> {
const attempt = await this.attempt(input, options).finally(() => { this.window.release(); });
// Nothing reached the socket, so the next link carries it instead of the caller resending.
if (!attempt.retryOnNextLink || !this.linkDown() || !this.retrying()) return attempt.result;
// The gate's own answer, or a loop round one that admits everything spins on a dead socket.
if (!attempt.retryOnNextLink || this.gate.isUp() || !this.retrying()) return attempt.result;
}
}