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.
Readers of the shared memory are tracked with an fcntl() read lock on
byte 1 of the lock file, and a restart only runs when no process holds
it. Record locks belong to the process, so under ZTS this does not see
threads: F_GETLK ignores the locks of the calling process, and the
restart (OOM, hash overflow, opcache_reset()) wipes SHM while other
threads of the same process still execute code from it. With several
multi-threaded processes (Apache event/worker MPM) any thread ending
its request also released the reader lock of the whole process.
Result: crashes ("zend_mm_heap corrupted", SIGSEGV) under FrankenPHP
and Apache mod_php ZTS.
Under ZTS on POSIX (NTS and Windows are unchanged):
- A thread becomes a reader only in RINIT, before the restart
generation is checked, and stays one until post_deactivate. The
threads of a process are counted in a process-local atomic counter;
the process holds the reader lock on byte 1 whenever the counter is
not zero. Only the 0 <-> 1 transitions take a mutex and call fcntl(),
other requests do a single compare-and-swap on enter and on leave.
- Lazy reader locks (accel_activate_add()) fail under ZTS, so a request
that was not admitted in RINIT never starts to use SHM later (e.g.
through the file cache or interned strings).
- A restart takes the mutex, requires a zero counter and a write lock
on byte 1, and holds both until it is finished. Threads of the
process cannot become readers meanwhile, and the kernel neither
grants the write lock while another process holds the reader lock
nor the reader lock while the write lock is held. This replaces the
F_GETLK probe, so there is no window between checking for readers
and starting the restart.
- Threads cannot be killed after opcache.force_restart_timeout: a
warning is logged once per timeout period instead. Other processes
are still killed by kill_all_lockers().
- A thread retired without its request cleanup releases its reference
in the globals destructor.
- Without real atomics (HAVE_NO_ATOMICS) every enter and leave takes
the mutex.
FrankenPHP worker threads stay readers for their whole life, so a
restart waits until the workers are restarted. Not covered: requests
that start while a restart is pending still run preloaded functions,
including their JIT code.
Co-authored-by: Yoan Arnaudov <1738437+nacholibre@users.noreply.github.com>
This was referenced Oct 12, 2026
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.
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