Split linkDown() into canCarry() and droppedWhileDraining() #38
+2
-2
@@ -761,12 +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.linkDown()` reads it rather than `closed`. The bind itself cannot wait for what it
|
||||
`OutgoingRequests.canCarry()` reads it rather than `closed`. The bind itself cannot wait for what it
|
||||
creates, so `pastDrain()` 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 `pastDrain()` asks `gate.isUp()` rather than `linkDown()`, which also reads the socket — a
|
||||
retry in `pastDrain()` asks `gate.isUp()` 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
|
||||
|
||||
@@ -51,9 +51,13 @@ export class OutgoingRequests {
|
||||
this.window = new SendWindow({ limit: options.maxOutstanding, log: options.log });
|
||||
}
|
||||
|
||||
/** Read through a method: a drop can land while a request is awaiting. */
|
||||
linkDown(): boolean {
|
||||
return !this.gate.isUp() || this.transport.sock.destroyed;
|
||||
canCarry(): boolean {
|
||||
return this.gate.isUp() && !this.transport.sock.destroyed;
|
||||
}
|
||||
|
||||
/** The link went before the drain finished, so an empty window says nothing about the peer. */
|
||||
droppedWhileDraining(): boolean {
|
||||
return this.draining && !this.canCarry();
|
||||
}
|
||||
|
||||
/** A link is up and bound, so everything held for one goes out on it. */
|
||||
@@ -84,8 +88,8 @@ export class OutgoingRequests {
|
||||
|
||||
if (wrong) return Promise.resolve({ err: wrong });
|
||||
|
||||
// A drain on a live link. A link that is down is the gate's answer, which says closed instead.
|
||||
if (this.draining && !this.linkDown()) {
|
||||
// With no link, the request is refused as closed further on.
|
||||
if (this.draining && this.canCarry()) {
|
||||
return Promise.resolve({ err: new Error('Session is shutting down') });
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -333,7 +333,8 @@ export class Session extends EventEmitter<SessionEvents> {
|
||||
this.reconnectLoop?.stop();
|
||||
this.outgoing.stopAccepting();
|
||||
|
||||
if (this.outgoing.linkDown()) return {};
|
||||
// No link, so nothing is on the wire to wait out.
|
||||
if (!this.outgoing.canCarry()) return {};
|
||||
|
||||
const timeout = this.options.shutdownTimeout ?? defaults.shutdownTimeout;
|
||||
const deadline = timeout > 0 ? Date.now() + timeout : 0;
|
||||
@@ -341,8 +342,7 @@ export class Session extends EventEmitter<SessionEvents> {
|
||||
const messages = await this.incoming.drain(this.answering(timeout), signal);
|
||||
const requests = await this.outgoing.drain(leftOf(deadline), signal);
|
||||
|
||||
// The window empties on a teardown too, which settles everything the link was carrying.
|
||||
if (this.outgoing.linkDown()) {
|
||||
if (this.outgoing.droppedWhileDraining()) {
|
||||
return { err: new Error('The session closed before the drain finished') };
|
||||
}
|
||||
|
||||
|
||||
@@ -199,15 +199,10 @@ next work ([decision](docs/decisions.md#internals-and-tests)).
|
||||
|
||||
### Locality — next, ahead of everything below; 5–6 today, and the gate is 7
|
||||
|
||||
- [ ] **Split the two questions `OutgoingRequests.linkDown()` answers.** `Session.drain()` calls it
|
||||
twice for opposite conclusions — "nothing to drain, success" and "the link died under us,
|
||||
failure" — and `outgoing-requests.ts` reads it a third way. Two named predicates. Named by 7
|
||||
of 9 readers, who each reconstructed the ordering by hand.
|
||||
|
||||
- [ ] **Name `pastDrain()`'s retry condition and what makes the loop end.** The exit is a
|
||||
three-term disjunction over two collaborators, whose comment covers the first term only, and
|
||||
the method is named for what it bypasses. Do not change what it asks: `gate.isUp()` rather than
|
||||
`linkDown()` is deliberate and recorded.
|
||||
`canCarry()` is deliberate and recorded.
|
||||
|
||||
- [ ] **Replace `resolveBody`'s `settles` boolean with the decision it stands for.** One boolean
|
||||
chooses both whether to overwrite `data_coding` and which params to read it from, across four
|
||||
|
||||
Reference in New Issue
Block a user