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
|
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)`
|
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
|
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
|
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
|
`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
|
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
|
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
|
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
|
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
|
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 });
|
this.window = new SendWindow({ limit: options.maxOutstanding, log: options.log });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Read through a method: a drop can land while a request is awaiting. */
|
canCarry(): boolean {
|
||||||
linkDown(): boolean {
|
return this.gate.isUp() && !this.transport.sock.destroyed;
|
||||||
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. */
|
/** 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 });
|
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.
|
// With no link, the request is refused as closed further on.
|
||||||
if (this.draining && !this.linkDown()) {
|
if (this.draining && this.canCarry()) {
|
||||||
return Promise.resolve({ err: new Error('Session is shutting down') });
|
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.reconnectLoop?.stop();
|
||||||
this.outgoing.stopAccepting();
|
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 timeout = this.options.shutdownTimeout ?? defaults.shutdownTimeout;
|
||||||
const deadline = timeout > 0 ? Date.now() + timeout : 0;
|
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 messages = await this.incoming.drain(this.answering(timeout), signal);
|
||||||
const requests = await this.outgoing.drain(leftOf(deadline), 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.droppedWhileDraining()) {
|
||||||
if (this.outgoing.linkDown()) {
|
|
||||||
return { err: new Error('The session closed before the drain finished') };
|
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
|
### 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
|
- [ ] **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
|
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
|
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
|
- [ ] **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
|
chooses both whether to overwrite `data_coding` and which params to read it from, across four
|
||||||
|
|||||||
Reference in New Issue
Block a user