Skip to content

fix: defer unpause in setDataStream to prevent hang on Linux#448

Merged
JCMais merged 3 commits into
developfrom
fix/mime-stream-unpause-hang
May 22, 2026
Merged

fix: defer unpause in setDataStream to prevent hang on Linux#448
JCMais merged 3 commits into
developfrom
fix/mime-stream-unpause-hang

Conversation

@JCMais
Copy link
Copy Markdown
Owner

@JCMais JCMais commented Apr 12, 2026

Summary

  • setDataStream called the unpause callback synchronously from stream event handlers (readable, end, error), which could invoke curl_easy_pause() while libcurl was still processing the READFUNC_PAUSE return from the read callback
  • This reentrant call caused hangs on Linux (observed as a 60s timeout in CurlMime.spec.ts > should handle basic stream upload)
  • Fixed by deferring unpause to the next event loop tick via setImmediate and tracking pause state, matching the pattern already used by setUploadStream in Curl.ts

Test plan

  • Existing test CurlMime.spec.ts > should handle basic stream upload should pass on Linux instead of timing out

JCMais added 3 commits April 12, 2026 12:00
The readable/end/error event handlers called unpause() synchronously,
which could invoke curl_easy_pause() while libcurl was still processing
the READFUNC_PAUSE return from the read callback. This reentrant call
caused hangs on Linux.

Defer the unpause to the next event loop tick via setImmediate, matching
the pattern used by setUploadStream in Curl.ts.
setImmediate fires in the check phase of the libuv event loop, but
libcurl's multi handle timeout fires in the timer phase. On Node.js 24
the setImmediate-based unpause wasn't being picked up reliably.

setTimeout(fn, 0) fires in the timer phase, matching libcurl's callback
scheduling and ensuring consistent behavior across Node.js versions.
The mime StaticReadCallback was returning CURL_READFUNC_PAUSE without
updating pauseState, so isPausedSend/isPausedRecv both stayed false.
This caused the test's conditional unpause (which checks pause state
before calling curl_easy_pause) to be a silent no-op, leaving the
transfer paused forever once libcurl needed a second read callback.

The test only passed when the stream's 'end' event fired before
libcurl's second read attempt — a race that resolved differently
across Node.js versions and platforms.

Additionally, the docs and examples incorrectly referenced
CurlPause.Recv. The mime read callback supplies upload data, so it
pauses SEND (matching Easy::ReadFunction). Updated docs, examples,
and tests to use Send.

Also reverted setTimeout back to setImmediate now that the underlying
race is properly fixed.
@JCMais JCMais merged commit 21ea9d1 into develop May 22, 2026
20 of 23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant