Refuse a message past the held-message bound with ESME_RTHROTTLED, and answer a full reassembly buffer the same way #29
@@ -304,8 +304,8 @@ the file.
|
|||||||
- The drain's wait on the application ignores `shutdownTimeout: 0`.
|
- The drain's wait on the application ignores `shutdownTimeout: 0`.
|
||||||
- What the application holds unanswered is capped on constants, and a message past the cap is
|
- What the application holds unanswered is capped on constants, and a message past the cap is
|
||||||
refused.
|
refused.
|
||||||
- A store at its bound answers `ESME_RTHROTTLED` to a `submit_sm` and `ESME_RX_T_APPN` to a
|
- A store at its bound answers `ESME_RTHROTTLED` to a submission and `ESME_RX_T_APPN` to a delivery,
|
||||||
`deliver_sm`.
|
a `data_sm` by whichever it stands in for.
|
||||||
- A reconnect keeps the delivery-receipt merges; everything else the link held is dropped.
|
- A reconnect keeps the delivery-receipt merges; everything else the link held is dropped.
|
||||||
- A message id base is merged at most once.
|
- A message id base is merged at most once.
|
||||||
- A send that never reached the socket waits for the next link; one that did is counted, not resent.
|
- A send that never reached the socket waits for the next link; one that did is counted, not resent.
|
||||||
|
|||||||
+1
-1
@@ -42,7 +42,7 @@
|
|||||||
the cap is lost, since its segments were already answered.
|
the cap is lost, since its segments were already answered.
|
||||||
- A message arriving while the application holds 1000 unanswered, or 64 MiB of them counted the way
|
- A message arriving while the application holds 1000 unanswered, or 64 MiB of them counted the way
|
||||||
`maxOctets` counts segments, is refused with `ESME_RTHROTTLED` (`ESME_RX_T_APPN` on a
|
`maxOctets` counts segments, is refused with `ESME_RTHROTTLED` (`ESME_RX_T_APPN` on a
|
||||||
`deliver_sm`), so the peer keeps it and retries. **Call `sendResp()` on every `sms`, multipart
|
delivery), so the peer keeps it and retries. **Call `sendResp()` on every `sms`, multipart
|
||||||
included**: 1000 left unanswered now stop inbound traffic for up to five minutes, where the oldest
|
included**: 1000 left unanswered now stop inbound traffic for up to five minutes, where the oldest
|
||||||
used to be dropped with a warning.
|
used to be dropped with a warning.
|
||||||
- A `submit_sm` segment the reassembly buffer has no room for is refused with `ESME_RTHROTTLED`,
|
- A `submit_sm` segment the reassembly buffer has no room for is refused with `ESME_RTHROTTLED`,
|
||||||
|
|||||||
@@ -432,13 +432,12 @@ const { err, pduObj } = await session.send({
|
|||||||
each is two messages.
|
each is two messages.
|
||||||
- **Answered on arrival.** Each segment was answered as it landed, before you see the message:
|
- **Answered on arrival.** Each segment was answered as it landed, before you see the message:
|
||||||
[Server in depth](#server-in-depth).
|
[Server in depth](#server-in-depth).
|
||||||
- **Unanswered messages.** While 1000 messages you have not called `sendResp()` on, or 64 MiB of
|
- **Unanswered messages.** While a session holds 1000 messages you have not called `sendResp()` on,
|
||||||
them counted the way `maxOctets` counts segments, are held, every new message is refused with
|
or 64 MiB of them counted the way `maxOctets` counts segments, every new message and segment is
|
||||||
`ESME_RTHROTTLED` (`ESME_RX_T_APPN` on a `deliver_sm`) so the peer retries it, and no `sms` fires.
|
refused so the peer retries it: `ESME_RTHROTTLED` on a submission, `ESME_RX_T_APPN` on a delivery.
|
||||||
Reaching the bound logs one `warn`, and the first message accepted once both are down to half one
|
No `sms` fires. Reaching the bound logs one `warn`, and the first message accepted once both are
|
||||||
`info`. A message left five
|
down to half one `info`. A message left five minutes is dropped from the count with a `warn`; a
|
||||||
minutes is dropped from the count with a `warn`; a later `sendResp()` still answers it. Neither
|
later `sendResp()` still answers it. None of the three is an option.
|
||||||
bound is an option.
|
|
||||||
- **Where the body is.** A body in the `message_payload` TLV, SMPP's way of carrying up to 64 KB and
|
- **Where the body is.** A body in the `message_payload` TLV, SMPP's way of carrying up to 64 KB and
|
||||||
the only place a `data_sm` has, reads exactly like one in `short_message`, concatenated messages
|
the only place a `data_sm` has, reads exactly like one in `short_message`, concatenated messages
|
||||||
and receipts included. A PDU filling both is read from `short_message`.
|
and receipts included. A PDU filling both is read from `short_message`.
|
||||||
|
|||||||
+18
-18
@@ -679,25 +679,25 @@ rule and an index of the titles below.
|
|||||||
|
|
||||||
- **What the application holds unanswered is capped on constants, and a message past the cap is
|
- **What the application holds unanswered is capped on constants, and a message past the cap is
|
||||||
refused.** A bound the application cannot raise is the point: an application that answers nothing
|
refused.** A bound the application cannot raise is the point: an application that answers nothing
|
||||||
would otherwise grow it for the life of the link, which goal 4 forbids. Reassembly's `maxOctets`
|
would otherwise hold ever more messages, which goal 4 forbids. Reassembly's `maxOctets` is an
|
||||||
is an option because it bounds what the peer sends; this bounds what the application leaves
|
option because it bounds what the peer sends; this bounds what the application leaves unanswered.
|
||||||
unanswered. Maintainer's call, 2026-09-26: a message arriving past the cap is refused with the
|
Maintainer's call, 2026-09-26. Refusing leaves the message with the peer, which will send it again
|
||||||
retry status, so the peer keeps it (goal 2). Rejected: dropping the oldest to make room, which
|
(goal 2). Rejected: dropping the oldest to make room, which frees nothing while the application
|
||||||
frees nothing while the application still holds its `Sms`, and stops the drain waiting for a
|
still holds its `Sms`, and stops the drain waiting for a message the peer is owed. Rejected:
|
||||||
message the peer is owed. Rejected: pausing the socket, which also stalls every answer and
|
pausing the socket, which also stalls every answer and `enquire_link` on the link. Reaching the
|
||||||
`enquire_link` on the link. Reaching the bound is a log line and nothing more (goal 8): an event
|
bound shows only in the log (goal 8): an event or a public count would be surface for what the
|
||||||
or a public count would be surface for what the application already knows, since it is the one
|
application already knows, since it is the one not answering. A message held past its timeout is
|
||||||
not answering. A message held past its timeout is still dropped, so `close()` can
|
still dropped, so `close()` can report fewer unanswered than there were — accepted, because the
|
||||||
report fewer unanswered than there were — accepted, because the alternative is holding what
|
alternative is holding what nothing will answer.
|
||||||
nothing will answer.
|
|
||||||
|
|
||||||
- **A store at its bound answers `ESME_RTHROTTLED` to a `submit_sm` and `ESME_RX_T_APPN` to a
|
- **A store at its bound answers `ESME_RTHROTTLED` to a submission and `ESME_RX_T_APPN` to a
|
||||||
`deliver_sm`.** Maintainer's call, 2026-09-26, for reassembly and held messages alike, so "keep it
|
delivery, a `data_sm` by whichever it stands in for.** Maintainer's call, 2026-09-26, for
|
||||||
and retry" has one spelling per direction. `ESME_RTHROTTLED` asks the sender to slow down, which
|
reassembly and held messages alike, so "keep it and retry" has one spelling per direction.
|
||||||
is what the peer outrunning us needs, and operators send it (Vonage, LINK Mobility, Route Mobile,
|
`ESME_RTHROTTLED` asks the sender to slow down, which is what the peer outrunning us needs, and
|
||||||
Jasmin), so clients built against them meet it (goal 1). Rejected: `ESME_RMSGQFUL`, which names an
|
operators send it (Vonage, LINK Mobility, Route Mobile, Jasmin), so clients built against them
|
||||||
exhausted queue and no rate. `ESME_RTHROTTLED` is the SMSC's to send, so an ESME answers with
|
meet it (goal 1). Rejected: `ESME_RMSGQFUL`, which names an exhausted queue and no rate.
|
||||||
SMPP 3.4's temporary receiver error, the one an SMSC retries on (goal 3).
|
`ESME_RTHROTTLED` is the SMSC's to send, so an ESME answers with SMPP 3.4's temporary receiver
|
||||||
|
error, the one an SMSC retries on (goal 3).
|
||||||
|
|
||||||
- **A reconnect keeps the delivery-receipt merges; everything else the link held is dropped.**
|
- **A reconnect keeps the delivery-receipt merges; everything else the link held is dropped.**
|
||||||
`onDelivery()` answers each receipt before the group it belongs to is complete, and `teardown()`
|
`onDelivery()` answers each receipt before the group it belongs to is complete, and `teardown()`
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { paramText } from './defs/types.ts';
|
|||||||
import { respIdParams, segmentId } from './sms-id.ts';
|
import { respIdParams, segmentId } from './sms-id.ts';
|
||||||
import { respNameFor } from './defs/commands.ts';
|
import { respNameFor } from './defs/commands.ts';
|
||||||
|
|
||||||
/** Asks the peer to keep the message and retry: RTHROTTLED is the SMSC's to send, so an ESME's is another. */
|
/** Asks the peer to keep the message and retry. */
|
||||||
function throttledStatus(carriedAs: string): ErrorName {
|
function throttledStatus(carriedAs: string): ErrorName {
|
||||||
return carriedAs === 'submit_sm' ? 'ESME_RTHROTTLED' : 'ESME_RX_T_APPN';
|
return carriedAs === 'submit_sm' ? 'ESME_RTHROTTLED' : 'ESME_RX_T_APPN';
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user