Put a receipt's segments on the wire together, like a message's

This commit is contained in:
2026-09-01 17:03:33 +02:00
parent 8becbc0ab6
commit 1e7e113bd7
7 changed files with 56 additions and 23 deletions
+7 -6
View File
@@ -84,8 +84,12 @@ export class OutgoingRequests {
if (refused) return { err: refused };
// A bind is what makes a link usable, so it cannot wait for one.
if (bindCommands.includes(input.cmdName)) return this.now(input, options);
// A bind is what makes a link usable, so it cannot wait for one: it takes the gate's answer now.
if (bindCommands.includes(input.cmdName)) {
const shut = this.gate.refusal();
return shut ? { err: shut } : this.now(input, options);
}
const waitForLink = this.gate.hold(options.signal);
@@ -131,10 +135,7 @@ export class OutgoingRequests {
}
// Before the gate and the window, or an aborted call waits for what it will never use.
if (options.signal?.aborted === true) return abortedBeforeSend();
// A bind skips the gate below, so the answer it would have given is given here instead.
return bindCommands.includes(input.cmdName) ? this.gate.refusal() : undefined;
return options.signal?.aborted === true ? abortedBeforeSend() : undefined;
}
private async attempt(input: PduObjectInput, options: SendOptions): Promise<Attempt> {
+14 -3
View File
@@ -304,10 +304,8 @@ export class Session extends EventEmitter<SessionEvents> {
const timeout = this.options.shutdownTimeout ?? defaults.shutdownTimeout;
const deadline = timeout > 0 ? Date.now() + timeout : 0;
// Only the application answers a held message, so that half falls back rather than wait forever.
const answering = timeout > 0 ? timeout : (this.options.responseTimeout ?? defaults.responseTimeout);
// Answering a message can put a receipt on the wire; nothing on the wire produces a message.
const messages = await this.incoming.drain(answering, signal);
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.
@@ -318,6 +316,19 @@ export class Session extends EventEmitter<SessionEvents> {
return messages.err ? messages : requests;
}
/**
* How long the drain waits for the application, which is the only thing that can end that wait.
* Neither timeout may hand it "forever": both are answers about a peer, and a peer is not what
* this half is waiting for.
*/
private answering(timeout: number): number {
if (timeout > 0) return timeout;
const responseTimeout = this.options.responseTimeout ?? defaults.responseTimeout;
return responseTimeout > 0 ? responseTimeout : defaults.responseTimeout;
}
/** The session is over now, drained or not. Nothing brings it back. */
private end(): void {
this.reconnectLoop?.stop();
+9 -6
View File
@@ -138,11 +138,11 @@ async function sendDlr(
}
const total = sms.pduObjs.length;
const pduObjs: PduObject[] = [];
for (let index = 0; index < total; index++) {
// Together, not one after a response: a drain waiting for this message must see the whole receipt.
const sent = await Promise.all(sms.pduObjs.map((_segment, index) => {
const smsId = segmentId(sms.smsId, index, total);
const sent = await send({
return send({
cmdName: 'deliver_sm',
params: {
destination_addr: sms.from,
@@ -152,10 +152,13 @@ async function sendDlr(
},
...(sms.session.acceptsOptionalParams() ? { tlvs: receiptTlvs(smsId, status) } : {}),
});
}));
const pduObjs: PduObject[] = [];
if (sent.err) return { err: sent.err };
for (const one of sent) {
if (one.err) return { err: one.err };
pduObjs.push(sent.pduObj);
pduObjs.push(one.pduObj);
}
return { pduObjs };