From 88066135fa21025855ef9ed0c18c8b6c39618350 Mon Sep 17 00:00:00 2001 From: Jonathan Cardoso Machado Date: Sun, 12 Apr 2026 12:00:35 -0300 Subject: [PATCH] fix: defer unpause in setDataStream to prevent libcurl reentrant hang 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. --- lib/CurlMimePart.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/CurlMimePart.ts b/lib/CurlMimePart.ts index d5e9d8e3..e2873d44 100644 --- a/lib/CurlMimePart.ts +++ b/lib/CurlMimePart.ts @@ -424,21 +424,35 @@ CurlMimePart.prototype.setDataStream = function ( ): typeof CurlMimePart.prototype { let streamEnded = false let streamError: Error | null = null + let paused = false + + // Defer unpause to the next event loop iteration to avoid calling + // curl_easy_pause() while libcurl is still processing the READFUNC_PAUSE + // return value from the read callback. Without this, the synchronous + // unpause can re-enter libcurl and cause a hang (observed on Linux). + const deferredUnpause = () => { + if (paused) { + paused = false + setImmediate(() => { + unpause() + }) + } + } const onReadable = () => { - unpause() + deferredUnpause() } const onEnd = () => { streamEnded = true - unpause() + deferredUnpause() cleanup() } const onError = (err: Error) => { streamError = err streamEnded = true - unpause() + deferredUnpause() cleanup() } @@ -473,6 +487,7 @@ CurlMimePart.prototype.setDataStream = function ( if (streamEnded) { return null } + paused = true return CurlReadFunc.Pause }