/** * Module-scope (== per-isolate, same reasoning as * `packages/hosts/cloudflare/src/mcp/session-runtime-residency.ts`) semaphore * bounding concurrent COLD `buildMcpServer` builds. * * A burst of new sessions can land on one isolate at once. Each cold build * runs execution-stack setup and MCP server construction, which is real CPU * — concentrating several of those builds at the same instant is what drags * the isolate's request p95 during a wave. Capping how many builds run at * once smooths that burst into a short FIFO queue instead of letting every * arrival pay full concurrent cost. * * Deliberately dependency-free: a tiny promise-chain queue, not a library. */ const MAX_CONCURRENT_BUILDS = 4; /** * Max time a build waits in the FIFO queue for a slot before proceeding * without one. Admission control exists only to smooth bursts into a short * queue — it must never turn a *slow* build into a *stuck* one. If the * isolate is wedged behind a hung dependency (e.g. a stalled DB during an * incident) and a slot never frees up, every waiter behind it would otherwise * block forever. Past this timeout a waiter gives up on the queue and builds * uncapped instead, same as if the semaphore did not exist, and reports * `timedOut: true` so it is visible in telemetry rather than silently * degrading. */ export const MAX_QUEUE_WAIT_MS = 10_000; let activeBuilds = 0; /** Lifecycle of one queued waiter. See `acquireBuildSlot` for the full state machine. */ type WaiterState = "queued" | "granted" | "timed-out" | "cancelled"; interface Waiter { state: WaiterState; readonly grant: () => void; } const waitQueue: Waiter[] = []; export interface BuildSlotResult { /** False only when the wait timed out; the caller must NOT call `releaseBuildSlot` for it. */ readonly acquired: boolean; /** Milliseconds spent waiting for a slot (0 when one was immediately free). */ readonly waitMs: number; /** True when the wait hit `MAX_QUEUE_WAIT_MS` and proceeded without a slot. */ readonly timedOut: boolean; } export interface BuildSlotHandle { /** Resolves exactly once, with the outcome of this acquisition. Never rejects. */ readonly promise: Promise; /** * Cancel this acquisition. Safe to call at any point in its lifecycle, * including redundantly or after `promise` has already settled — it is * idempotent and only ever frees a slot once: * - Still queued: dequeues the waiter. Nothing was ever granted, so there * is nothing to release. * - Already granted a slot — this races `releaseBuildSlot` handing the * slot to this waiter (that happens synchronously inside * `releaseBuildSlot`) against this `cancel` — releases that slot * immediately so it is not held with no owner, and wakes the next FIFO * waiter. * - Already timed out or already cancelled: no-op. * * Callers should call this from a `finally`/`ensuring` unconditionally * (success, failure, or interruption alike) rather than trying to track * separately whether a slot was actually granted — idempotency here makes * that bookkeeping unnecessary and closes the acquire/release race that * bookkeeping was prone to. */ readonly cancel: () => void; } /** * Reserve a build slot, queueing FIFO when the cap is already held, up to * `maxQueueWaitMs` (defaults to `MAX_QUEUE_WAIT_MS`; overridable for tests). * * Returns a handle rather than a bare promise so a caller that stops waiting * — most notably an `Effect` fiber interrupted mid-queue on a client * disconnect — can cancel cleanly instead of leaking the slot a later * `releaseBuildSlot` would otherwise hand to a waiter nobody is listening to * anymore. */ export const acquireBuildSlot = (maxQueueWaitMs: number = MAX_QUEUE_WAIT_MS): BuildSlotHandle => { const requestedAt = Date.now(); if (activeBuilds < MAX_CONCURRENT_BUILDS) { activeBuilds += 1; let released = false; return { promise: Promise.resolve({ acquired: true, waitMs: 0, timedOut: false }), cancel: () => { if (released) return; released = true; releaseBuildSlot(); }, }; } let resolvePromise!: (value: BuildSlotResult) => void; const promise = new Promise((resolve) => { resolvePromise = resolve; }); const waiter: Waiter = { state: "queued", grant: () => { // Guaranteed "queued" here: `releaseBuildSlot` only reaches a waiter by // shifting it out of `waitQueue`, and both other transitions // (timeout, cancel) remove the waiter from the queue in the same tick // they change its state, so a granted waiter can't have been anything // else. waiter.state = "granted"; activeBuilds += 1; resolvePromise({ acquired: true, waitMs: Date.now() - requestedAt, timedOut: false }); }, }; waitQueue.push(waiter); const timer = setTimeout(() => { if (waiter.state !== "queued") return; waiter.state = "timed-out"; const idx = waitQueue.indexOf(waiter); if (idx !== -1) waitQueue.splice(idx, 1); resolvePromise({ acquired: false, waitMs: Date.now() - requestedAt, timedOut: true }); }, maxQueueWaitMs); return { promise, cancel: () => { clearTimeout(timer); if (waiter.state === "queued") { waiter.state = "cancelled"; const idx = waitQueue.indexOf(waiter); if (idx !== -1) waitQueue.splice(idx, 1); return; } if (waiter.state === "granted") { // Grant already happened (raced with a concurrent `releaseBuildSlot` // shifting this waiter off the queue) before this cancel ran, and // the caller never got to consume the slot — hand it back now // instead of leaking it. waiter.state = "cancelled"; releaseBuildSlot(); return; } // "timed-out": never held a slot. "cancelled": a prior call already // handled this. Either way, nothing to do — idempotent no-op. }, }; }; /** * Release a build slot. Must be called exactly once per slot a caller * actually acquired (i.e. `acquireBuildSlot`'s `promise` resolved with * `acquired: true`) — callers release from a `finally`/`ensuring` (typically * via the returned handle's `cancel`, which is idempotent) so a build that * throws, or is interrupted, still frees its slot and never deadlocks the * queue behind it. * * Wakes the next FIFO waiter, if any, handing it the freed slot directly * rather than making it race a fresh `acquireBuildSlot` caller. */ export const releaseBuildSlot = (): void => { activeBuilds = Math.max(0, activeBuilds - 1); const next = waitQueue.shift(); if (next) next.grant(); }; /** Test-only: isolate-scoped module state outlives a single test case. */ export const resetBuildSlotsForTest = (): void => { activeBuilds = 0; waitQueue.length = 0; }; export const currentActiveBuildsForTest = (): number => activeBuilds; export const currentQueueLengthForTest = (): number => waitQueue.length;