Repository navigation
Conversation
accel_unlock_all() runs at the end of every request and releases the whole lock file with F_UNLCK (l_start = 0, l_len = 0). fcntl() record locks are owned by the process, not by the thread, so under ZTS a thread finishing its request also releases the SHM write lock (byte 0) held by another thread of the same process inside zend_shared_alloc_lock(), and the restart lock (byte 2). With several multi-threaded processes sharing the cache (Apache mod_php with the event or worker MPM) another process can then take the write lock while the first one is still writing, and two processes modify SHM at the same time. Children crash during warm-up even without any OPcache restart (SIGSEGV in accel_find_interned_string() and zend_hash_find_bucket()). Bytes 0 and 2 are always released by the thread that took them (zend_shared_alloc_unlock(), zend_shared_alloc_safe_unlock(), accel_restart_leave()), so under ZTS only release the reader lock. NTS is unchanged.
|
Reproduced the over-release deterministically, no race needed. Thread A holds the byte-0 write lock, thread B of the same process runs accel_unlock_all, and another process can then take byte 0. The byte-1 unlock keeps it held. fcntl locks are per-process, so this is arch-independent, not just aarch64. Reachable only under ZTS with a threaded MPM and several processes on one cache, like Apache event/worker with mod_php ZTS. The trigger is many threads compiling uncached scripts at once, so warm-up on deploy or restart. php-fpm and single-process workers stay unaffected. The fix loses nothing, since byte 0 and byte 2 are released by their own paths, zend_shared_alloc_safe_unlock and accel_restart_leave. Narrowing accel_unlock_all to the reader lock is enough. LGTM. |
|
Unfortunately, releasing the read lock is also incorrect: This allows a restart to be performed while other threads are actively using SHM. I believe that the problem is not limited to accel_unlock_all(): accel_deactivate_sub() has the same issue. We should guard the unlock in accel_deactivate_sub() behind a process-wide counter in ZTS builds, so that we unlock only when the counter falls to zero (and lock only when 0->1). We likely need a mutex to serialize the increment + lock operations. Then we should remove accel_unlock_all(), and use accel_deactivate_now() instead. |
|
Thanks @arnaud-lb, you are right: the reader lock is per process, so dropping it at the end of one thread's request (here, or in This is what #24276 (GH-8739) does, built on top of this commit:
Since this PR alone is incomplete, I'm closing it in favour of #24276. Would you have time to look at #24276? |
Prepared with an AI assistant (Claude), which also ran the reproduction and the tests on my machine; I reviewed the result. The text below is AI-generated (quoted, as asked in CONTRIBUTING.md).
🤖 Generated with Claude Code