Name the session's lifecycle state and the invariant that keeps ended final
Mirror / push (push) Has been cancelled
Test / lint (pull_request) Successful in 24s
Test / test (18) (pull_request) Successful in 40s
Test / test (20) (pull_request) Successful in 32s
Test / test (22) (pull_request) Successful in 32s
Test / test (24) (pull_request) Successful in 31s
Test / test (26) (pull_request) Successful in 31s

This commit is contained in:
2026-09-28 02:39:49 +02:00
parent 20aa2828c2
commit c32c1bc65b
+11 -11
View File
@@ -72,8 +72,8 @@ export class Session extends EventEmitter<SessionEvents> {
private readonly timers: LinkTimers; private readonly timers: LinkTimers;
private readonly transport: PduTransport; private readonly transport: PduTransport;
/** `torn-down` goes back to `attached` when the reconnect loop brings a link up. */ /** `ended` is final: end() stops the reconnect loop before any attach() can run. */
private link: 'attached' | 'ended' | 'torn-down' = 'attached'; private lifecycle: 'attached' | 'ended' | 'torn-down' = 'attached';
/** A listener that throws is the application's bug; it must not become ours. Hard rule 1. */ /** A listener that throws is the application's bug; it must not become ours. Hard rule 1. */
override emit<K extends keyof SessionEvents>( override emit<K extends keyof SessionEvents>(
@@ -186,7 +186,7 @@ export class Session extends EventEmitter<SessionEvents> {
const sent = built.err ? { err: built.err } : this.transport.write(built.buffer); const sent = built.err ? { err: built.err } : this.transport.write(built.buffer);
// A peer that unbinds and drops the link takes our response with it; that is not a failure. // A peer that unbinds and drops the link takes our response with it; that is not a failure.
if (sent.err && this.link === 'attached') { if (sent.err && this.lifecycle === 'attached') {
this.log.warn('session - could not answer a request', { this.log.warn('session - could not answer a request', {
cmdName, cmdName,
message: sent.err.message, message: sent.err.message,
@@ -221,12 +221,12 @@ export class Session extends EventEmitter<SessionEvents> {
*/ */
async unbind(): Promise<VoidResult> { async unbind(): Promise<VoidResult> {
const drained = await this.drain(undefined); const drained = await this.drain(undefined);
const wasOpen = this.link === 'attached'; const wasOpen = this.lifecycle === 'attached';
// now(), not send(): a drain refuses a send, and the unbind goes out either way. // now(), not send(): a drain refuses a send, and the unbind goes out either way.
const sent = wasOpen const sent = wasOpen
? await this.outgoing.now({ cmdName: 'unbind' }) ? await this.outgoing.now({ cmdName: 'unbind' })
: { err: new Error('Session is closed') }; : { err: new Error('Session is closed') };
const closedOnUnbind = wasOpen && this.link !== 'attached'; const closedOnUnbind = wasOpen && this.lifecycle !== 'attached';
this.end(); this.end();
@@ -325,7 +325,7 @@ export class Session extends EventEmitter<SessionEvents> {
private attach(sock: Socket): void { private attach(sock: Socket): void {
this.transport.attach(sock); this.transport.attach(sock);
this.link = 'attached'; this.lifecycle = 'attached';
} }
/** Stops new sends and waits out the messages we hold and the requests already issued. */ /** Stops new sends and waits out the messages we hold and the requests already issued. */
@@ -371,17 +371,17 @@ export class Session extends EventEmitter<SessionEvents> {
} }
private emitClose(): void { private emitClose(): void {
if (this.link === 'ended') return; if (this.lifecycle === 'ended') return;
this.link = 'ended'; this.lifecycle = 'ended';
this.outgoing.linkLost(false); this.outgoing.linkLost(false);
this.emit('close'); this.emit('close');
} }
private teardown(): void { private teardown(): void {
if (this.link !== 'attached') return; if (this.lifecycle !== 'attached') return;
this.link = 'torn-down'; this.lifecycle = 'torn-down';
// Read once: clear() reports lost segments, and a listener could stop the loop between reads. // Read once: clear() reports lost segments, and a listener could stop the loop between reads.
const retrying = this.retrying(); const retrying = this.retrying();
@@ -445,7 +445,7 @@ export class Session extends EventEmitter<SessionEvents> {
} }
private resetTimers(): void { private resetTimers(): void {
if (this.link !== 'attached') return; if (this.lifecycle !== 'attached') return;
this.timers.reset(); this.timers.reset();
} }