Skip to content

A coroutine waiting for a file lock no longer holds a thread - #18

Merged
EdmondDantes merged 1 commit into
true-asyncfrom
221-flock-waiter-holds-a-thread
Aug 12, 2026
Merged

A coroutine waiting for a file lock no longer holds a thread#18
EdmondDantes merged 1 commit into
true-asyncfrom
221-flock-waiter-holds-a-thread

Conversation

@EdmondDantes

Copy link
Copy Markdown

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 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 — 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-worker in locks_lock_inode_wait, and an external flock -n refused
because 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 is
where 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_UN and LOCK_NB take the plain syscall as before, so does everything
outside a coroutine and inside the scheduler; flock() still returns only with the lock
held or with a real error, and errno is the failed attempt's.

Checked

  • The repro from the issue — 5, 8 and 60 coroutines — now finishes with every line on
    disk; each of them deadlocked before.
  • A coroutine cancelled while waiting unwinds, takes no lock, and leaves the process
    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, the
    same 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 failures
    are the pre-existing include '../../../../ext/curl/tests/server.inc' path, which does
    not resolve when ext/async is a symlink.

Not done

zend_async_thread_pool_is_enabled() now has no caller in php-src; it stays, being public
API. No NEWS entry — the fork does not keep one for the async runtime.

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
EdmondDantes force-pushed the 221-flock-waiter-holds-a-thread branch from 85f0ae2 to b850d76 Compare August 12, 2026 21:18
@EdmondDantes
EdmondDantes merged commit ec21ae9 into true-async Aug 12, 2026
9 of 17 checks passed
@EdmondDantes
EdmondDantes deleted the 221-flock-waiter-holds-a-thread branch August 13, 2026 05:22
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