2.6 KiB
limits/timeout-ceiling
Purpose
Enforces the hard 30 000 ms ceiling on caller-supplied timeoutMs
values. The check fires at the public entry point of run2pc so
above-ceiling calls never contact a participant; the operational
warning in design doc §9.2 is that long prepares hold row-level locks,
so surfacing the misuse loudly is more useful than silently clamping.
This module is intentionally tiny — one function — because the behaviour is small but the gate is one of the most-asserted contracts in the spec.
Features
assertTimeoutBelowCeiling(timeoutMs)— throwsTWOPC_TIMEOUT_CEILING_EXCEEDEDfor above-ceiling, non-positive, non-finite, orNaNinputs.- Re-uses the
TIMEOUT_CEILING_MSconstant fromoptions/so the ceiling stays a single source of truth.
Usage: Basic
import { assertTimeoutBelowCeiling } from "@suaveplan/api-2pc/limits";
assertTimeoutBelowCeiling(5000); // ok
assertTimeoutBelowCeiling(30001); // throws TWOPC_TIMEOUT_CEILING_EXCEEDED
Usage: Advanced
The function is intentionally side-effect-free so callers can wrap it in a per-tenant override path:
import { TIMEOUT_CEILING_MS } from "@suaveplan/api-2pc/options";
import { assertTimeoutBelowCeiling } from "@suaveplan/api-2pc/limits";
function resolveTimeout(
requested: number | undefined,
tenantMax: number
): number {
const effective = requested ?? DEFAULT_TIMEOUT_MS;
assertTimeoutBelowCeiling(effective);
if (effective > tenantMax) {
return tenantMax;
}
if (effective > TIMEOUT_CEILING_MS) {
// unreachable given the assert above — included only to
// illustrate that the ceiling acts as the strict upper bound.
return TIMEOUT_CEILING_MS;
}
return effective;
}
API Reference
function assertTimeoutBelowCeiling(timeoutMs: number): void- Throws
TwoPcError(TWOPC_CODES.TIMEOUT_CEILING_EXCEEDED)for any of:NaN,Infinity,-Infinity, values<= 0, values> TIMEOUT_CEILING_MS.
- Throws
Implementation Notes
The check rejects non-positive values for the same reason it rejects above-ceiling ones: an immediate or backwards-in-time deadline would collapse phase 1 to a single tick and the rollback path would fire before any participant could finish a real prepare. Treating that as the same misuse class as "30 minutes" is a deliberate ergonomic choice — operators get one error code to grep for.
Number.isFinite is used in preference to Number.isNaN because the
former covers Infinity and -Infinity in addition to NaN. The
explicit <= 0 check then excludes zero and negative-finite values.