import type { TaskStore } from "./store/base.js"; /** Per-queue depth limits. A number applies one limit to every queue; a record * gates only the queues it names and leaves the rest unbounded. */ export type QueueDepthLimit = number | Record; export interface BackpressureOptions { /** Queued tasks a queue may hold before `submit` blocks. */ maxQueueDepth: QueueDepthLimit; /** How long a blocked submit waits before raising QueueFull. Default 600_000. */ maxQueueWaitMs?: number; /** First backoff between depth probes; doubles to a 5s ceiling. Default 250. */ queuePollIntervalMs?: number; } /** * Blocks `submit` while a queue is at its depth limit. * * Without one of these a producer that outruns its workers is only bounded by * disk: the backlog grows, every task's queue wait grows with it, and the * failure is a database that filled up rather than a producer that slowed down. * A queue is the wrong place to buffer an overload — pushing back on the * producer is the point. * * **A soft limit under several producers.** The check is a read followed by a * write that other producers can interleave with, and each holds its own grant, * so N producers can overshoot the limit by up to (N-1) * MAX_GRANT tasks. Made * exact it would need the depth check inside insert_task's transaction, which * puts an unbounded-scan predicate on the hot path of every submit and turns * concurrent submits into lock contention — a steep price for a bound whose * whole purpose is approximate. Size the limit for the pushback you want, not as * a capacity assertion. */ export declare class QueueDepthGate { private readonly store; /** Remaining grant per queue: submits allowed before the next probe. */ private readonly headroom; /** In-flight probe per queue, so concurrent submits share one read rather * than each issuing their own against a queue that is already known full. */ private readonly probing; private readonly limits; private readonly maxWaitMs; private readonly initialProbeMs; constructor(store: TaskStore, opts: BackpressureOptions); private validate; /** The limit for `queue`, or null when it is not gated. */ limitFor(queue: string): number | null; /** * Consume one unit of headroom for `queue`, waiting for room if it is full. * Returns immediately for an ungated queue. Raises QueueFull on timeout, * having enqueued nothing. */ acquire(queue: string): Promise; /** * Refresh `queue`'s grant from the store, at most one probe in flight. * * Callers re-read `headroom` afterwards rather than using a returned value: * only the caller that started the probe writes the grant, so waiters that * joined it cannot overwrite the units already handed out. */ private probe; }