A coroutine waiting for a file lock no longer holds a thread - #18
Merged
Conversation
A blocking flock() ran as a thread-pool task, and reads and writes of a regular file are uv_fs requests on the same pool. Once the waiters filled it the holder's own write had no thread left to run on, so the lock was never released and the process served nothing further: five coroutines locking one file were enough with the default pool of four. The threshold was exactly UV_THREADPOOL_SIZE. The wait moves into the coroutine — flock(LOCK_NB), sleep on a timer, try again, with the pause doubling from 1 ms to 100 ms and then dropping back so that a waiter does not keep losing to newcomers. Swoole's coroutine flock() polls the same way. No thread is held, so no size of pool can be exhausted, and the task struct that php#146 had to keep alive across cancellation is gone with the task. Fixes true-async/php-async#221. Test: ext/async tests/io/084.
EdmondDantes
force-pushed
the
221-flock-waiter-holds-a-thread
branch
from
August 12, 2026 21:18
85f0ae2 to
b850d76
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes true-async/php-async#221.
What was wrong
A blocking
flock()ran as a thread-pool task, and reads and writes of a regular file areuv_fsrequests on the same pool. Once the waiters filled it, the holder's own write hadno thread left to run on, so the lock was never released and the process served nothing
further — not the other coroutines, not timers. Five coroutines locking one file were
enough with the default pool of four, and the threshold was exactly
UV_THREADPOOL_SIZE.The thread states of a stuck process say it plainly: the loop thread in
do_epoll_wait,every
libuv-workerinlocks_lock_inode_wait, and an externalflock -nrefusedbecause the first coroutine still holds the lock — parked inside its own
fwrite.What this does
The wait moves into the coroutine:
flock(fd, op | LOCK_NB), sleep on a timer, try again.The pause doubles from 1 ms to 100 ms and then drops back to 1 ms rather than staying at
the maximum — a waiter that only ever backs off keeps losing to whoever asks next. Swoole
polls its coroutine
flock()on the same sawtooth (src/coroutine/file_lock.cc), which iswhere the sawtooth comes from.
No thread is held, so no size of pool can be exhausted. The task struct that php#146 had to
keep alive across cancellation goes away with the task: there is no worker left to write
into it.
Unchanged:
LOCK_UNandLOCK_NBtake the plain syscall as before, so does everythingoutside a coroutine and inside the scheduler;
flock()still returns only with the lockheld or with a real error, and
errnois the failed attempt's.Checked
disk; each of them deadlocked before.
healthy: the holder's release is still visible to everyone afterwards.
ext/async/tests/io/084-flock_waiters_exceed_threadpool.phpt(true-async/php-async, thesame branch name) is red before this change — run-tests kills it on the timeout — and
green after.
ext/standard/tests/file: 622 passed, 0 failed.ext/async/tests: the three failuresare the pre-existing
include '../../../../ext/curl/tests/server.inc'path, which doesnot resolve when
ext/asyncis a symlink.Not done
zend_async_thread_pool_is_enabled()now has no caller in php-src; it stays, being publicAPI. No
NEWSentry — the fork does not keep one for the async runtime.