Files
skills/docs/api-stack/api/api-2pc/src/limits/outstanding-cap.md
2026-05-29 08:20:56 +01:00

3.3 KiB

limits/outstanding-cap

Purpose

Admission control for in-flight 2PC transactions. The coordinator holds a fixed-size budget of "outstanding prepared transactions"; once the budget is exhausted new run2pc calls refuse rather than queue. The default is 256 (design doc §14's open question, pinned by this package's spec) and operators can tune it via host config.

Refusing rather than queuing is intentional: Postgres holds row-level locks for every prepared transaction, so a backed-up coordinator would otherwise convert a transient overload into a global lock storm. Failing fast lets callers route to a degraded path (degraded-write, retry queue, etc.).

Features

  • OutstandingPreparedCap — synchronous counter with admit(), release(), snapshot(), capacity().
  • Constructor validates that the cap is a positive integer (rejects zero, negatives, fractions, NaN).
  • release() is idempotent below zero — a buggy double-release in caller code cannot silently expand the real cap.
  • Synchronous-only — relies on the event-loop atomicity guarantee between admit / release pairs in JavaScript runtimes.

Usage: Basic

import { OutstandingPreparedCap } from "@suaveplan/api-2pc/limits";

const cap = new OutstandingPreparedCap(256);

cap.admit();
try {
    // run the 2PC transaction
} finally {
    cap.release();
}

Usage: Advanced

Wrap admission inside the coordinator's public entry to centralise the release in one finally. The snapshot fuels the OTel gauge:

import { TWOPC_CODES, TwoPcError } from "@suaveplan/api-2pc/error-codes";
import { OutstandingPreparedCap } from "@suaveplan/api-2pc/limits";

class MiniCoordinator {
    private readonly cap = new OutstandingPreparedCap(256);

    snapshot() {
        return this.cap.snapshot();
    }

    async run<T>(fn: () => Promise<T>): Promise<T> {
        try {
            this.cap.admit();
        } catch (err) {
            if (
                err instanceof TwoPcError &&
                err.twoPcCode === TWOPC_CODES.OUTSTANDING_LIMIT_EXCEEDED
            ) {
                // route to degraded path
                throw err;
            }
            throw err;
        }
        try {
            return await fn();
        } finally {
            this.cap.release();
        }
    }
}

API Reference

  • class OutstandingPreparedCap
    • constructor(cap: number) — throws TWOPC_OUTSTANDING_LIMIT_EXCEEDED on invalid cap.
    • admit(): void — throws TWOPC_OUTSTANDING_LIMIT_EXCEEDED when at capacity.
    • release(): void — idempotent below zero.
    • snapshot(): number — current outstanding count.
    • capacity(): number — configured cap.

Implementation Notes

Construction-time validation re-uses the same error code as the runtime cap-exceeded path. This is deliberate: operators tuning the cap via host config want one error string to alert on, regardless of whether the misconfiguration was a fractional value at startup or a real overload at steady state. The context object on the thrown TwoPcError distinguishes the two cases (cap only vs. cap + outstanding).

The class is intentionally tiny — no async, no internal queue, no external dependencies. Adding a queue here would re-introduce the lock-storm risk the cap exists to prevent.