Skip to content

fix: curl_exec() in a coroutine resumes after curl_pause(CONT) - #3

Merged
frodeborli merged 2 commits into
mainfrom
fix/curl-pause-resume
Oct 19, 2026
Merged

frodeborli merged 2 commits into
mainfrom
fix/curl-pause-resume

Conversation

@frodeborli

Copy link
Copy Markdown
Contributor

A transfer paused by its WRITEFUNCTION (CURL_WRITEFUNC_PAUSE) keeps its socket ready, so curl_multi_select() reports activity immediately while curl_multi_exec() makes no progress. The cooperative curl_exec() loop in io.c therefore spun at 100% CPU without yielding, and the coroutine that would call curl_pause($h, CURLPAUSE_CONT) never got to run: curl_exec() hung forever.

Fix: when the select reported activity but the handle's byte count (CURLINFO_SIZE_DOWNLOAD_T + CURLINFO_SIZE_UPLOAD_T) has not changed since the last round, sleep through the scheduler (1ms, doubling to 20ms) before trying again.

Regression test: tests/curl_exec_pause_resume.phpt streams 4 MiB through a WRITEFUNCTION that pauses above 256 KiB. A second coroutine drains the buffer and resumes the transfer. The child process is killed after 15s, so a hang shows up as a failure. On main the test hangs. With the fix, the full suite passes (169 passed, 16 skipped).

Found by fiber-interop/guzzle PR #1, which pauses streaming bodies above 1 MiB buffered.

🤖 Generated with Claude Code

https://claude.ai/code/session_01B3u6DU5NmXoDe7i56CMT49

frodeborli and others added 2 commits October 19, 2026 17:02
…l_pause(CONT)

A paused transfer (a WRITEFUNCTION returning CURL_WRITEFUNC_PAUSE) keeps its
socket ready, so curl_multi_select() reports activity at once while
curl_multi_exec() moves nothing. curl_exec()'s cooperative loop then spun
without ever yielding, and the coroutine that would call curl_pause(CONT)
never ran. When the select reported activity but no bytes moved since the
last round, the loop now sleeps through the scheduler (1ms, doubling to
20ms) before trying again.

Found by fiber-interop/guzzle (PR #1), which pauses streaming bodies above
1 MiB buffered. curl_exec_pause_resume.phpt streams 4 MiB through a
WRITEFUNCTION that pauses above 256 KiB, drained and resumed by a second
coroutine; it hung on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B3u6DU5NmXoDe7i56CMT49
…nd TLS handshake rounds

The stall check counted body bytes only, so the ready rounds of a connect, a TLS handshake
and the header receipt looked stuck and added sleeps to every HTTPS request (p50 +2ms on
localhost). Header bytes now count as progress, and only four ready rounds in a row without
progress back off. The preempt test waits up to 200ms for a joined thread's /proc entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B3u6DU5NmXoDe7i56CMT49
@frodeborli
frodeborli merged commit f3c194e into main Oct 19, 2026
3 checks passed
@frodeborli
frodeborli deleted the fix/curl-pause-resume branch October 19, 2026 17:52
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