Bound the messages held for a shutdown, and report a partial receipt
This commit is contained in:
+65
-6
@@ -1,24 +1,68 @@
|
||||
import type { PduObject } from './pdu.ts';
|
||||
import type { SmppLog } from './log.ts';
|
||||
import { ExpiringGroups } from './expiring-groups.ts';
|
||||
import { IdleWaiters } from './idle-waiters.ts';
|
||||
|
||||
export type HeldMessagesOptions = {
|
||||
log: SmppLog;
|
||||
max: number;
|
||||
timeout: number;
|
||||
};
|
||||
|
||||
/** The peer's own sequence number, which is what our answer to this message will carry. */
|
||||
function keyOf(pduObjs: PduObject[]): string | undefined {
|
||||
const first = pduObjs[0];
|
||||
|
||||
return first ? String(first.seqNr) : undefined;
|
||||
}
|
||||
|
||||
/** The messages handed to the application that it has not answered yet, held by their segments. */
|
||||
export class HeldMessages {
|
||||
private readonly held = new Set<PduObject[]>();
|
||||
private readonly held: ExpiringGroups<PduObject[]>;
|
||||
private readonly idleWaiters = new IdleWaiters();
|
||||
private readonly log: SmppLog;
|
||||
|
||||
constructor(options: HeldMessagesOptions) {
|
||||
this.held = new ExpiringGroups({
|
||||
max: options.max,
|
||||
onSweep: () => { this.sweep(); },
|
||||
timeout: options.timeout,
|
||||
});
|
||||
this.log = options.log;
|
||||
}
|
||||
|
||||
/** An application that answers no message at all may not grow this without end. */
|
||||
hold(pduObjs: PduObject[]): void {
|
||||
this.held.add(pduObjs);
|
||||
const key = keyOf(pduObjs);
|
||||
|
||||
if (key === undefined) return;
|
||||
|
||||
if (this.held.full) {
|
||||
const evicted = this.held.takeOldest();
|
||||
|
||||
if (evicted) {
|
||||
this.log.warn('heldMessages - dropping the message held longest', { seqNr: evicted[0] });
|
||||
}
|
||||
}
|
||||
|
||||
this.held.set(key, pduObjs);
|
||||
}
|
||||
|
||||
/** Whether a drain is still waiting for this message to be answered. */
|
||||
has(pduObjs: PduObject[]): boolean {
|
||||
return this.held.has(pduObjs);
|
||||
const key = keyOf(pduObjs);
|
||||
|
||||
return key !== undefined && this.held.get(key) === pduObjs;
|
||||
}
|
||||
|
||||
release(pduObjs: PduObject[]): void {
|
||||
if (!this.held.delete(pduObjs)) return;
|
||||
const key = keyOf(pduObjs);
|
||||
|
||||
if (this.held.size === 0) this.idleWaiters.settle();
|
||||
// Identity, not the key: a wrapped sequence number must not release someone else's message.
|
||||
if (key === undefined || this.held.get(key) !== pduObjs) return;
|
||||
|
||||
this.held.delete(key);
|
||||
this.settle();
|
||||
}
|
||||
|
||||
/** Drops every message: their segments went with the link, so no answer of ours correlates now. */
|
||||
@@ -28,7 +72,22 @@ export class HeldMessages {
|
||||
}
|
||||
|
||||
/** Resolves 0 once every message has been answered, or with how many have not. */
|
||||
idle(timeout: number, signal?: AbortSignal): Promise<number> {
|
||||
idle(timeout: number, signal: AbortSignal | undefined): Promise<number> {
|
||||
return this.idleWaiters.wait(() => this.held.size, timeout, signal);
|
||||
}
|
||||
|
||||
private sweep(): void {
|
||||
const expired = this.held.takeExpired();
|
||||
|
||||
if (expired.length === 0) return;
|
||||
|
||||
this.log.warn('heldMessages - messages the application never answered', {
|
||||
messages: expired.length,
|
||||
});
|
||||
this.settle();
|
||||
}
|
||||
|
||||
private settle(): void {
|
||||
if (this.held.size === 0) this.idleWaiters.settle();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user