Files
e2b-dev--e2b/packages/js-sdk/src/api/inflight.ts
T
2026-07-13 12:47:58 +08:00

79 lines
2.2 KiB
TypeScript

/**
* Simple FIFO semaphore used to cap the number of in-flight requests sent
* through a fetch dispatcher.
*/
class Semaphore {
private active = 0
private readonly queue: Array<() => void> = []
constructor(private readonly max: number) {}
async acquire(signal?: AbortSignal): Promise<() => void> {
if (signal?.aborted) throw abortReason(signal)
if (this.active < this.max) {
this.active++
return () => this.release()
}
return new Promise<() => void>((resolve, reject) => {
const onAcquire = () => {
signal?.removeEventListener('abort', onAbort)
this.active++
resolve(() => this.release())
}
const onAbort = () => {
const i = this.queue.indexOf(onAcquire)
if (i >= 0) this.queue.splice(i, 1)
reject(abortReason(signal))
}
this.queue.push(onAcquire)
signal?.addEventListener('abort', onAbort, { once: true })
})
}
private release() {
this.active--
const next = this.queue.shift()
if (next) next()
}
}
function abortReason(signal: AbortSignal | undefined): unknown {
return signal?.reason ?? new DOMException('Aborted', 'AbortError')
}
/**
* Wrap `fetcher` so at most `max` requests are in-flight at any time.
* Subsequent requests are FIFO-queued inside the SDK process and dispatched
* as earlier requests settle.
*
* NOTE: the slot is released as soon as `fetcher` resolves with the response
* headers, not when the response body is fully consumed. This means the
* effective concurrency can be higher than `max` while bodies are
* still streaming.
*
* TODO: release on body end (consume/cancel/error) so the
* SDK-level cap aligns with the dispatcher's connection accounting
*/
export function limitConcurrency(
fetcher: typeof fetch,
max: number
): typeof fetch {
if (!Number.isFinite(max) || max <= 0) {
return fetcher
}
const sem = new Semaphore(max)
return (async (input, init) => {
const signal =
init?.signal ?? (input instanceof Request ? input.signal : undefined)
const release = await sem.acquire(signal)
try {
return await fetcher(input, init)
} finally {
release()
}
}) as typeof fetch
}