From 02fdf5aa1a6651487baac3d4fbc6e47c2ef7c551 Mon Sep 17 00:00:00 2001 From: Pavel Gushcha Date: Tue, 6 Oct 2026 17:32:26 +0000 Subject: [PATCH] Fix OPcache releasing other threads' SHM locks under ZTS 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. --- ext/opcache/ZendAccelerator.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 79c7668ba5e9..63dcf702fb72 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -393,8 +393,16 @@ static inline void accel_unlock_all(void) mem_usage_unlock_all.l_type = F_UNLCK; mem_usage_unlock_all.l_whence = SEEK_SET; +#ifdef ZTS + /* fcntl() locks belong to the process: unlocking the whole file would also release + * the SHM write lock (byte 0) and the restart lock (byte 2) held by other threads. + * Those are always released by the thread that took them. */ + mem_usage_unlock_all.l_start = 1; + mem_usage_unlock_all.l_len = 1; +#else mem_usage_unlock_all.l_start = 0; mem_usage_unlock_all.l_len = 0; +#endif if (fcntl(lock_file, F_SETLK, &mem_usage_unlock_all) == -1) { zend_accel_error(ACCEL_LOG_DEBUG, "UnlockAll: %s (%d)", strerror(errno), errno);