From c19158a81286e1e362d5466393ddec43093e20d6 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 14:05:22 +0000 Subject: [PATCH 1/3] feat(js-sdk): drop undici 7, depend on undici@^8 only undici 8 honors the server's SETTINGS_MAX_CONCURRENT_STREAMS per h2 connection and spills to additional connections when saturated, so the dual undici/undici8 selection is no longer needed. Requires Node.js >= 22.19.0 (undici 8's engine floor); older Node falls back to the capped global fetch instead of loading undici. Co-Authored-By: mish@e2b.dev --- .changeset/drop-undici7.md | 5 +++++ .tool-versions | 2 +- packages/js-sdk/package.json | 7 ++----- packages/js-sdk/src/undici.ts | 10 +++++----- packages/js-sdk/tests/undici.test.ts | 8 ++++---- pnpm-lock.yaml | 11 +++-------- 6 files changed, 20 insertions(+), 23 deletions(-) create mode 100644 .changeset/drop-undici7.md diff --git a/.changeset/drop-undici7.md b/.changeset/drop-undici7.md new file mode 100644 index 0000000000..bac11e0174 --- /dev/null +++ b/.changeset/drop-undici7.md @@ -0,0 +1,5 @@ +--- +'e2b': major +--- + +Drop the dual `undici`/`undici8` dependency: the SDK now depends on `undici@^8` only and requires Node.js >= 22.19.0. undici 8 honors the server's `SETTINGS_MAX_CONCURRENT_STREAMS` per HTTP/2 connection and opens additional connections when a connection's streams are saturated, instead of undici 7's one-request-per-connection multiplexing. On Node.js older than 22.19.0 the SDK no longer loads undici and falls back to the global fetch (still subject to the in-flight cap). diff --git a/.tool-versions b/.tool-versions index 8cb195538e..405471b7a6 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,5 +1,5 @@ deno 2.8.1 -nodejs 22.18.0 +nodejs 22.19.0 pnpm 10.34.5 python 3.10 uv 0.10.0 diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index 3873857511..ee5f9ae1bf 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -102,13 +102,10 @@ "openapi-fetch": "^0.14.1", "platform": "^1.3.6", "tar": "^7.5.19", - "undici": "^7.29.0" - }, - "optionalDependencies": { - "undici8": "npm:undici@8.10.0" + "undici": "^8.10.0" }, "engines": { - "node": ">=20.18.1 <21 || >=22" + "node": ">=22.19.0" }, "browserslist": [ "defaults" diff --git a/packages/js-sdk/src/undici.ts b/packages/js-sdk/src/undici.ts index 74a2fd102c..a785d204d6 100644 --- a/packages/js-sdk/src/undici.ts +++ b/packages/js-sdk/src/undici.ts @@ -20,14 +20,14 @@ export type UndiciModule = { fetch: unknown } -const UNDICI_8_MIN_NODE = '22.19.0' +const UNDICI_MIN_NODE = '22.19.0' export function getUndiciPackageCandidates(nodeVersion: string): string[] { - if (compareVersions(nodeVersion, UNDICI_8_MIN_NODE) >= 0) { - return ['undici8', 'undici'] + if (compareVersions(nodeVersion, UNDICI_MIN_NODE) >= 0) { + return ['undici'] } - return ['undici'] + return [] } export async function loadUndici(): Promise { @@ -35,7 +35,7 @@ export async function loadUndici(): Promise { try { return await dynamicImport(packageName) } catch { - // Try the next package supported by this Node version. + // Fall back to the capped global fetch when undici cannot load. } } diff --git a/packages/js-sdk/tests/undici.test.ts b/packages/js-sdk/tests/undici.test.ts index 1d32d9c039..6605485f30 100644 --- a/packages/js-sdk/tests/undici.test.ts +++ b/packages/js-sdk/tests/undici.test.ts @@ -14,10 +14,10 @@ import { } from './foreignPlatformObjects' test.each([ - ['20.20.2', ['undici']], - ['22.18.0', ['undici']], - ['22.19.0', ['undici8', 'undici']], - ['24.0.0', ['undici8', 'undici']], + ['20.20.2', []], + ['22.18.0', []], + ['22.19.0', ['undici']], + ['24.0.0', ['undici']], ])('selects the packages supported by Node %s', (version, expected) => { expect(getUndiciPackageCandidates(version as string)).toEqual(expected) }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 501ee35f6e..0f30813dab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -169,8 +169,8 @@ importers: specifier: ^7.5.19 version: 7.5.21 undici: - specifier: ^7.29.0 - version: 7.29.0 + specifier: ^8.10.0 + version: 8.10.0 devDependencies: '@cloudflare/vitest-pool-workers': specifier: ^0.18.7 @@ -250,10 +250,6 @@ importers: wrangler: specifier: ^4.113.0 version: 4.113.0(@types/node@20.19.43)(bufferutil@4.0.8)(utf-8-validate@6.0.3) - optionalDependencies: - undici8: - specifier: npm:undici@8.10.0 - version: undici@8.10.0 packages/python-sdk: {} @@ -7558,8 +7554,7 @@ snapshots: undici@7.29.0: {} - undici@8.10.0: - optional: true + undici@8.10.0: {} unenv@2.0.0-rc.24: dependencies: From 389ee00829f99957a92183516ca7380d286041d5 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 14:10:14 +0000 Subject: [PATCH 2/3] chore: bump codegen image Node to 22.19.0 to match .tool-versions Co-Authored-By: mish@e2b.dev --- codegen.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codegen.Dockerfile b/codegen.Dockerfile index 32df16df8e..142eee7c9a 100644 --- a/codegen.Dockerfile +++ b/codegen.Dockerfile @@ -24,7 +24,7 @@ ENV PATH="/go/bin:${PATH}" RUN pip install black==26.3.1 e2b-openapi-python-client==0.26.2 datamodel-code-generator==0.64.0 protoc-gen-connectrpc==0.11.1 protoc-gen-py==0.1.1 # Install Node.js (pinned to match .tool-versions) -ENV NODE_VERSION=22.18.0 +ENV NODE_VERSION=22.19.0 RUN ARCH=$(uname -m) && \ case "$ARCH" in \ x86_64) NODE_ARCH="x64" ;; \ From 245f93b5d80679d52a8f6174c5c40496b7d4863e Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 14:24:47 +0000 Subject: [PATCH 3/3] feat(js-sdk)!: remove the SDK-level in-flight request cap Concurrency is governed by undici 8's dispatcher: per-connection HTTP/2 stream limits advertised by the server plus the configured connection pools. Removes limitConcurrency and the E2B_API_INFLIGHT_REQUESTS, E2B_ENVD_INFLIGHT_REQUESTS, and E2B_ENVD_RPC_INFLIGHT_REQUESTS env vars. Co-Authored-By: mish@e2b.dev --- .changeset/drop-undici7.md | 4 +- packages/js-sdk/src/api/http2.ts | 21 +---- packages/js-sdk/src/api/inflight.ts | 82 ----------------- packages/js-sdk/src/api/metadata.ts | 21 ----- packages/js-sdk/src/envd/http2.ts | 40 +------- packages/js-sdk/src/undici.ts | 15 +-- packages/js-sdk/tests/api/http2.test.ts | 25 ----- packages/js-sdk/tests/api/inflight.test.ts | 101 --------------------- packages/js-sdk/tests/envd/http2.test.ts | 50 ---------- packages/js-sdk/tests/undici.test.ts | 3 - 10 files changed, 10 insertions(+), 352 deletions(-) delete mode 100644 packages/js-sdk/src/api/inflight.ts delete mode 100644 packages/js-sdk/tests/api/inflight.test.ts diff --git a/.changeset/drop-undici7.md b/.changeset/drop-undici7.md index bac11e0174..c991c13718 100644 --- a/.changeset/drop-undici7.md +++ b/.changeset/drop-undici7.md @@ -2,4 +2,6 @@ 'e2b': major --- -Drop the dual `undici`/`undici8` dependency: the SDK now depends on `undici@^8` only and requires Node.js >= 22.19.0. undici 8 honors the server's `SETTINGS_MAX_CONCURRENT_STREAMS` per HTTP/2 connection and opens additional connections when a connection's streams are saturated, instead of undici 7's one-request-per-connection multiplexing. On Node.js older than 22.19.0 the SDK no longer loads undici and falls back to the global fetch (still subject to the in-flight cap). +Drop the dual `undici`/`undici8` dependency: the SDK now depends on `undici@^8` only and requires Node.js >= 22.19.0. undici 8 honors the server's `SETTINGS_MAX_CONCURRENT_STREAMS` per HTTP/2 connection and opens additional connections when a connection's streams are saturated, instead of undici 7's one-request-per-connection multiplexing. On Node.js older than 22.19.0 the SDK no longer loads undici and falls back to the global fetch. + +Remove the SDK-level in-flight request cap: `limitConcurrency` and the `E2B_API_INFLIGHT_REQUESTS`, `E2B_ENVD_INFLIGHT_REQUESTS`, and `E2B_ENVD_RPC_INFLIGHT_REQUESTS` env vars are gone. Concurrency is now governed by undici's dispatcher (per-connection HTTP/2 stream limits from the server plus the `E2B_API_CONNECTIONS`/`E2B_ENVD_RPC_CONNECTIONS` connection pools). diff --git a/packages/js-sdk/src/api/http2.ts b/packages/js-sdk/src/api/http2.ts index 2a46c3fcb4..57a9da4194 100644 --- a/packages/js-sdk/src/api/http2.ts +++ b/packages/js-sdk/src/api/http2.ts @@ -1,5 +1,5 @@ import { runtime } from '../utils' -import { parseInflightLimitEnv, parsePositiveIntEnv } from './metadata' +import { parsePositiveIntEnv } from './metadata' import { buildDispatchedFetch, createRuntimeFetch, @@ -7,9 +7,6 @@ import { } from '../undici' const DEFAULT_API_CONNECTION_LIMIT = 100 -// 1000 = ~10 streams per connection (with the 100-conn default). -// Override via env if your workload needs different. -const DEFAULT_API_INFLIGHT_LIMIT = 1000 // Fetchers are cached per proxy so requests without a proxy keep sharing a // single dispatcher while each distinct proxy URL gets its own. @@ -33,7 +30,6 @@ export function createApiFetchForRuntime( currentRuntime = runtime, options: { connectionLimit?: number - inflightLimit?: number proxy?: string loadUndici?: () => Promise } = {} @@ -43,7 +39,6 @@ export function createApiFetchForRuntime( return createRuntimeFetch(currentRuntime, () => buildDispatchedFetch({ connections: options.connectionLimit ?? getApiConnectionLimit(), - inflightLimit: options.inflightLimit ?? getApiInflightLimit(), proxy: options.proxy, loadUndici: options.loadUndici, }) @@ -56,17 +51,3 @@ export function getApiConnectionLimit(): number { DEFAULT_API_CONNECTION_LIMIT ) } - -/** - * Returns the configured max number of API requests that can be in flight at - * once, or `0` to disable the cap. - * - * Defaults to `1000` ({@link DEFAULT_API_INFLIGHT_LIMIT}). Override via - * `E2B_API_INFLIGHT_REQUESTS` env var; set to `0` to disable the cap entirely. - */ -export function getApiInflightLimit(): number { - return parseInflightLimitEnv( - 'E2B_API_INFLIGHT_REQUESTS', - DEFAULT_API_INFLIGHT_LIMIT - ) -} diff --git a/packages/js-sdk/src/api/inflight.ts b/packages/js-sdk/src/api/inflight.ts deleted file mode 100644 index 16401467af..0000000000 --- a/packages/js-sdk/src/api/inflight.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { isRequestLike } from '../is' - -/** - * 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) => { - // A Request the current global class disowns still carries the signal we - // have to honor while it waits for a slot. - const signal = - init?.signal ?? (isRequestLike(input) ? input.signal : undefined) - const release = await sem.acquire(signal) - try { - return await fetcher(input, init) - } finally { - release() - } - }) as typeof fetch -} diff --git a/packages/js-sdk/src/api/metadata.ts b/packages/js-sdk/src/api/metadata.ts index b863b1c592..da7b7caebd 100644 --- a/packages/js-sdk/src/api/metadata.ts +++ b/packages/js-sdk/src/api/metadata.ts @@ -62,24 +62,3 @@ export function parsePositiveIntEnv( return parsed } - -/** - * Parse an inflight-limit env var. Returns `0` to disable the cap (documented - * opt-out) or a positive integer to cap concurrency. Throws on non-integer or - * negative values so misconfiguration is surfaced loudly rather than silently - * removing the cap. A return value of `0` is recognized by - * {@link limitConcurrency} as "no cap". - */ -export function parseInflightLimitEnv( - name: string, - defaultValue: number -): number { - const parsed = parseIntEnv(name, defaultValue) - if (parsed < 0) { - throw new Error( - `Invalid ${name}=${parsed}: expected a non-negative integer ` + - '(use 0 to disable the cap).' - ) - } - return parsed -} diff --git a/packages/js-sdk/src/envd/http2.ts b/packages/js-sdk/src/envd/http2.ts index db674df3cd..53d315f3da 100644 --- a/packages/js-sdk/src/envd/http2.ts +++ b/packages/js-sdk/src/envd/http2.ts @@ -1,5 +1,5 @@ import { runtime } from '../utils' -import { parseInflightLimitEnv, parsePositiveIntEnv } from '../api/metadata' +import { parsePositiveIntEnv } from '../api/metadata' import { buildDispatchedFetch, createRuntimeFetch, @@ -8,7 +8,6 @@ import { type EnvdFetchOptions = { connectionLimit?: number - inflightLimit?: number proxy?: string loadUndici?: () => Promise } @@ -19,8 +18,6 @@ const envdFetchers = new Map() const envdRpcFetchers = new Map() const DEFAULT_ENVD_CONNECTION_LIMIT = 10 const DEFAULT_ENVD_RPC_CONNECTION_LIMIT = 200 -const DEFAULT_ENVD_INFLIGHT_LIMIT = 2000 -const DEFAULT_ENVD_RPC_INFLIGHT_LIMIT = 2000 export function createEnvdFetchForRuntime( currentRuntime = runtime, @@ -29,7 +26,6 @@ export function createEnvdFetchForRuntime( return createRuntimeFetch(currentRuntime, () => buildDispatchedFetch({ connections: options.connectionLimit ?? DEFAULT_ENVD_CONNECTION_LIMIT, - inflightLimit: options.inflightLimit ?? 0, proxy: options.proxy, loadUndici: options.loadUndici, }) @@ -47,7 +43,6 @@ export function createEnvdFetch(proxy?: string): typeof fetch { // Keep one origin connection for short envd REST calls. If ALPN falls back // to h1, this favors connection pressure over per-sandbox throughput. const envdFetch = createEnvdFetchForRuntime(runtime, { - inflightLimit: getEnvdInflightLimit(), proxy, }) envdFetchers.set(key, envdFetch) @@ -65,7 +60,6 @@ export function createEnvdRpcFetch(proxy?: string): typeof fetch { const envdRpcFetch = createEnvdFetchForRuntime(runtime, { connectionLimit: getEnvdRpcConnectionLimit(), - inflightLimit: getEnvdRpcInflightLimit(), proxy, }) envdRpcFetchers.set(key, envdRpcFetch) @@ -79,35 +73,3 @@ export function getEnvdRpcConnectionLimit(): number { DEFAULT_ENVD_RPC_CONNECTION_LIMIT ) } - -/** - * Returns the configured max number of envd REST requests (e.g. - * `files.read`/`files.write`) that can be in flight at once across all - * sandboxes in this SDK process, or `0` to disable the cap. - * - * Defaults to `2000` ({@link DEFAULT_ENVD_INFLIGHT_LIMIT}). Override - * via `E2B_ENVD_INFLIGHT_REQUESTS` env var; set to `0` to disable the cap - * entirely. - */ -export function getEnvdInflightLimit(): number { - return parseInflightLimitEnv( - 'E2B_ENVD_INFLIGHT_REQUESTS', - DEFAULT_ENVD_INFLIGHT_LIMIT - ) -} - -/** - * Returns the configured max number of envd RPC requests that - * can be in flight at once across all sandboxes in this SDK process, - * or `0` to disable the cap. - * - * Defaults to `2000` ({@link DEFAULT_ENVD_RPC_INFLIGHT_LIMIT}). Override - * via `E2B_ENVD_RPC_INFLIGHT_REQUESTS` env var; set to `0` to disable the cap - * entirely. - */ -export function getEnvdRpcInflightLimit(): number { - return parseInflightLimitEnv( - 'E2B_ENVD_RPC_INFLIGHT_REQUESTS', - DEFAULT_ENVD_RPC_INFLIGHT_LIMIT - ) -} diff --git a/packages/js-sdk/src/undici.ts b/packages/js-sdk/src/undici.ts index a785d204d6..1f87e19251 100644 --- a/packages/js-sdk/src/undici.ts +++ b/packages/js-sdk/src/undici.ts @@ -1,6 +1,5 @@ import { compareVersions } from 'compare-versions' -import { limitConcurrency } from './api/inflight' import { isReadableStreamLike, isRequestLike } from './is' import { dynamicImport, toDispatchableStream } from './utils' @@ -35,7 +34,7 @@ export async function loadUndici(): Promise { try { return await dynamicImport(packageName) } catch { - // Fall back to the capped global fetch when undici cannot load. + // Fall back to the global fetch when undici cannot load. } } @@ -88,20 +87,18 @@ export function createRuntimeFetch( /** * Build a fetch bound to a bounded undici dispatcher (HTTP/2 enabled, - * `connections` origin connections, optional proxy tunnel), capped at - * `inflightLimit` in-flight requests (`0` disables the cap). Falls back to - * the global fetch — still capped — when undici cannot be loaded. + * `connections` origin connections, optional proxy tunnel). Falls back to + * the global fetch when undici cannot be loaded. */ export async function buildDispatchedFetch(options: { connections: number - inflightLimit: number proxy?: string loadUndici?: () => Promise }): Promise { const undici = await (options.loadUndici ?? loadUndici)() if (!undici) { - return limitConcurrency(lateBoundGlobalFetch(), options.inflightLimit) + return lateBoundGlobalFetch() } const { Agent, ProxyAgent, fetch: undiciFetch } = undici @@ -121,7 +118,7 @@ export async function buildDispatchedFetch(options: { init?: UndiciRequestInit ) => Promise - const wrapped: typeof fetch = ((input, init) => { + return ((input, init) => { const request = toUndiciRequestInput(input, init) return fetchWithDispatcher(request.input, { @@ -129,8 +126,6 @@ export async function buildDispatchedFetch(options: { dispatcher, }) }) as typeof fetch - - return limitConcurrency(wrapped, options.inflightLimit) } function toUndiciRequestInput( diff --git a/packages/js-sdk/tests/api/http2.test.ts b/packages/js-sdk/tests/api/http2.test.ts index 262bf96bf3..6192002342 100644 --- a/packages/js-sdk/tests/api/http2.test.ts +++ b/packages/js-sdk/tests/api/http2.test.ts @@ -6,7 +6,6 @@ afterEach(() => { vi.doUnmock('undici') vi.doUnmock('../../src/utils') delete process.env.E2B_API_CONNECTIONS - delete process.env.E2B_API_INFLIGHT_REQUESTS }) test('uses undici with a bounded HTTP/2 dispatcher for API requests', async () => { @@ -166,27 +165,3 @@ test('getApiConnectionLimit throws on a malformed env value', async () => { expect(() => getApiConnectionLimit()).toThrow(/E2B_API_CONNECTIONS/) }) - -test('getApiInflightLimit throws on a malformed env value', async () => { - process.env.E2B_API_INFLIGHT_REQUESTS = 'not-a-number' - - const { getApiInflightLimit } = await import('../../src/api/http2') - - expect(() => getApiInflightLimit()).toThrow(/E2B_API_INFLIGHT_REQUESTS/) -}) - -test('getApiInflightLimit returns 0 when explicitly disabled', async () => { - process.env.E2B_API_INFLIGHT_REQUESTS = '0' - - const { getApiInflightLimit } = await import('../../src/api/http2') - - expect(getApiInflightLimit()).toBe(0) -}) - -test('getApiInflightLimit throws on negative env value', async () => { - process.env.E2B_API_INFLIGHT_REQUESTS = '-5' - - const { getApiInflightLimit } = await import('../../src/api/http2') - - expect(() => getApiInflightLimit()).toThrow(/E2B_API_INFLIGHT_REQUESTS=-5/) -}) diff --git a/packages/js-sdk/tests/api/inflight.test.ts b/packages/js-sdk/tests/api/inflight.test.ts deleted file mode 100644 index bd23eb34ca..0000000000 --- a/packages/js-sdk/tests/api/inflight.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { expect, test, vi } from 'vitest' - -import { limitConcurrency } from '../../src/api/inflight' -import { foreignRequestClasses } from '../foreignPlatformObjects' - -function deferred() { - let resolve!: (value: T) => void - let reject!: (reason?: unknown) => void - const promise = new Promise((res, rej) => { - resolve = res - reject = rej - }) - return { promise, resolve, reject } -} - -test('limitConcurrency queues requests over the cap and releases on response', async () => { - const gate = deferred() - let secondStarted = false - const inner = vi.fn(async (input: RequestInfo | URL) => { - if (String(input).endsWith('/first')) return gate.promise - secondStarted = true - return new Response('second') - }) as unknown as typeof fetch - - const limited = limitConcurrency(inner, 1) - const first = limited('https://example.com/first') - const second = limited('https://example.com/second') - - await Promise.resolve() - await Promise.resolve() - expect(secondStarted).toBe(false) - - gate.resolve(new Response('first')) - expect(await (await first).text()).toBe('first') - expect(await (await second).text()).toBe('second') - expect(secondStarted).toBe(true) -}) - -test('limitConcurrency releases when the underlying fetch rejects', async () => { - let calls = 0 - const inner = vi.fn(async () => { - calls++ - if (calls === 1) throw new Error('boom') - return new Response('ok') - }) as unknown as typeof fetch - - const limited = limitConcurrency(inner, 1) - await expect(limited('https://example.com/a')).rejects.toThrow('boom') - - // Slot should be free for the next request. - const res = await limited('https://example.com/b') - expect(await res.text()).toBe('ok') -}) - -test('limitConcurrency aborts queued requests when their signal fires', async () => { - const gate = deferred() - const inner = vi.fn(async () => gate.promise) as unknown as typeof fetch - const limited = limitConcurrency(inner, 1) - - // Occupy the only slot. - const first = limited('https://example.com/first') - - const controller = new AbortController() - const queued = limited('https://example.com/queued', { - signal: controller.signal, - }) - - // Abort the queued request before the slot frees. - controller.abort() - await expect(queued).rejects.toMatchObject({ name: 'AbortError' }) - - // Release the first request to make sure cleanup did not break the slot. - gate.resolve(new Response('done')) - const resp = await first - expect(await resp.text()).toBe('done') -}) - -test('limitConcurrency honors the signal of a Request the global class disowns', async () => { - const { MintingRequest, GlobalShimRequest } = foreignRequestClasses() - - const inner = vi.fn(async () => new Response('ok')) as unknown as typeof fetch - const limited = limitConcurrency(inner, 1) - - const controller = new AbortController() - controller.abort() - const request = new MintingRequest('https://example.com/aborted', { - signal: controller.signal, - }) - - vi.stubGlobal('Request', GlobalShimRequest) - try { - expect(request instanceof globalThis.Request).toBe(false) - await expect(limited(request)).rejects.toMatchObject({ - name: 'AbortError', - }) - } finally { - vi.unstubAllGlobals() - } - - expect(inner).not.toHaveBeenCalled() -}) diff --git a/packages/js-sdk/tests/envd/http2.test.ts b/packages/js-sdk/tests/envd/http2.test.ts index 9ad6e9ee0f..c71e3ead77 100644 --- a/packages/js-sdk/tests/envd/http2.test.ts +++ b/packages/js-sdk/tests/envd/http2.test.ts @@ -6,8 +6,6 @@ afterEach(() => { vi.doUnmock('undici') vi.doUnmock('../../src/utils') delete process.env.E2B_ENVD_RPC_CONNECTIONS - delete process.env.E2B_ENVD_INFLIGHT_REQUESTS - delete process.env.E2B_ENVD_RPC_INFLIGHT_REQUESTS }) test('uses undici with HTTP/2 enabled in Node', async () => { @@ -180,54 +178,6 @@ test('getEnvdRpcConnectionLimit throws on malformed env value', async () => { expect(() => getEnvdRpcConnectionLimit()).toThrow(/E2B_ENVD_RPC_CONNECTIONS/) }) -test('getEnvdInflightLimit throws on malformed env value', async () => { - process.env.E2B_ENVD_INFLIGHT_REQUESTS = 'bogus' - - const { getEnvdInflightLimit } = await import('../../src/envd/http2') - - expect(() => getEnvdInflightLimit()).toThrow(/E2B_ENVD_INFLIGHT_REQUESTS/) -}) - -test('getEnvdRpcInflightLimit throws on malformed env value', async () => { - process.env.E2B_ENVD_RPC_INFLIGHT_REQUESTS = 'bogus' - - const { getEnvdRpcInflightLimit } = await import('../../src/envd/http2') - - expect(() => getEnvdRpcInflightLimit()).toThrow( - /E2B_ENVD_RPC_INFLIGHT_REQUESTS/ - ) -}) - -test('inflight limit env vars return 0 when explicitly disabled', async () => { - process.env.E2B_ENVD_INFLIGHT_REQUESTS = '0' - process.env.E2B_ENVD_RPC_INFLIGHT_REQUESTS = '0' - - const { getEnvdInflightLimit, getEnvdRpcInflightLimit } = await import( - '../../src/envd/http2' - ) - - expect(getEnvdInflightLimit()).toBe(0) - expect(getEnvdRpcInflightLimit()).toBe(0) -}) - -test('getEnvdInflightLimit throws on negative env value', async () => { - process.env.E2B_ENVD_INFLIGHT_REQUESTS = '-1' - - const { getEnvdInflightLimit } = await import('../../src/envd/http2') - - expect(() => getEnvdInflightLimit()).toThrow(/E2B_ENVD_INFLIGHT_REQUESTS=-1/) -}) - -test('getEnvdRpcInflightLimit throws on negative env value', async () => { - process.env.E2B_ENVD_RPC_INFLIGHT_REQUESTS = '-5' - - const { getEnvdRpcInflightLimit } = await import('../../src/envd/http2') - - expect(() => getEnvdRpcInflightLimit()).toThrow( - /E2B_ENVD_RPC_INFLIGHT_REQUESTS=-5/ - ) -}) - test('defers loading undici until the first Node request', async () => { const Agent = vi.fn() const undiciFetch = vi.fn(() => Promise.resolve(new Response('ok'))) diff --git a/packages/js-sdk/tests/undici.test.ts b/packages/js-sdk/tests/undici.test.ts index 6605485f30..d3de0c29ce 100644 --- a/packages/js-sdk/tests/undici.test.ts +++ b/packages/js-sdk/tests/undici.test.ts @@ -83,7 +83,6 @@ test('takes apart a Request the current global Request class disowns', async () const fetcher = await buildDispatchedFetch({ connections: 1, - inflightLimit: 0, loadUndici: async () => fakeUndici, }) await fetcher(request) @@ -128,7 +127,6 @@ test('adopts the body of a Request from another fetch implementation', async () const fetcher = await buildDispatchedFetch({ connections: 1, - inflightLimit: 0, loadUndici: async () => fakeUndici, }) await fetcher(foreignRequest as unknown as Request) @@ -195,7 +193,6 @@ test.skipIf(runtime !== 'node')( try { const fetcher = await buildDispatchedFetch({ connections: 1, - inflightLimit: 0, loadUndici: async () => ({ ...undici, // buildDispatchedFetch builds its own dispatcher; hand it the mock.