Keep request() timeouts and cancellation armed while the body is read

Open
#22 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
76/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
javascript, node.js
Domain
api, backend

Research direction

Start with server/providers/util.mjs and trace how request() manages its timeout, abort signal, and response body. Then read server/library.mjs to follow the raw: true image path, and inspect test/providers.test.mjs for the suggested stalled-body test. Done means body reads are covered by the deadline and cancellation, aborts are not reported as retryable provider faults, and node --test test/providers.test.mjs passes.

Written by the indexing model from the issue text.

Description

help wanted

request() in server/providers/util.mjs tears down its timeout and its abort bridge in a finally that only wraps the fetch call, before anything reads the response body. Lines 41-45:

  } finally {
    clearTimeout(timer); signal?.removeEventListener('abort', onAbort);
  }
  if (raw && res.ok) return res;
  const text = await res.text();

So the documented timeout (default 120 s, 180 s for several providers) only covers time-to-first-byte. Once headers arrive, await res.text() has no deadline and no longer reacts to the caller's signal. The worst case is the raw: true path: server/library.mjs:25-26 passes timeout: 120_000 and then reads the image outside request() entirely:

const res = await request(meta.providerName || 'download', result.url, { raw: true, fetch, signal, timeout: 120_000 });
buffer = Buffer.from(await res.arrayBuffer());

Concretely: if a provider CDN opens the response and then stalls mid-image, POST /api/generate never returns and never times out, the server holds the socket open indefinitely, and closing the browser tab does not abort it even though abortOnClose(req) passed a signal in. The same applies to a stalled chat completion from a director model.

What to change
  • Move clearTimeout(timer) and the removeEventListener call so they run after the body has been consumed, not after fetch resolves.
  • For the raw: true path, keep the controller alive for the caller: either read the body inside request() and return a Buffer, or hand the caller a cleanup()/the AbortSignal so library.mjs can read the body under the same deadline.
  • While there, note that the catch at line 39 converts an abort into network error (...) with retryable: true; a client-initiated abort should not be reported as a retryable provider fault.
How to verify

Add a test in test/providers.test.mjs with a fake fetch that resolves a Response whose body stream never closes, call request('x', 'http://t', { timeout: 50, fetch }), and assert it rejects with a timed out ProviderError instead of hanging. Then run node --test test/providers.test.mjs.


New to the project? Comment here to claim it, and say hello in the blix open source Discord — questions about this issue get answered fastest there.

Dominant language
JavaScript
Stars
42
Forks
6
Avg merge
12h 31m
Merged PRs (30d)
3

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from blixvip/GenMotion

All issues in blixvip/GenMotion

Similar issues

More JavaScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.