Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ PHP NEWS
- Opcache:
. Fixed tracing JIT crash when a VM interrupt is handled during an observed
user function call. (Levi Morrison)
. Fixed bug GH-8739 (OPcache restart corrupts shared memory under threaded
SAPIs: fcntl()-based reader tracking cannot see threads of the same
process). (Yoan Arnaudov)
. Fixed bug GH-22004 (Assertion failure at ext/opcache/jit/zend_jit_trace.c).
(Arnaud)

Expand Down
58 changes: 58 additions & 0 deletions ext/opcache/ZendAccelerator.c
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,38 @@ static inline void accel_unlock_all(void)
#endif
}

#if defined(ZTS) && !defined(ZEND_WIN32)
/* In threaded SAPIs (FrankenPHP, mod_php with a threaded MPM, ...) requests
* run as threads of one process. The fcntl() record locks used by
* accel_activate_add()/accel_is_inactive() are per-process: a process never
* conflicts with its own locks, so in-flight readers running as threads are
* invisible to accel_is_inactive(). It then wrongly reports the cache as
* inactive and a scheduled restart wipes SHM (hash table + interned strings)
* under running requests, corrupting the heap (GH-8739, GH-14471, GH-18517).
*
* Mirror the approach ZEND_WIN32 already uses for the same reason (threaded
* SAPIs): track in-flight requests with an atomic counter in SHM and have
* accel_is_inactive() refuse to restart while it is non-zero. Registration
* is gated on ZCG(accelerator_enabled), so once a restart is pending (the
* accelerator is disabled) new requests no longer register and the counter
* drains within the duration of the longest in-flight request. */
static zend_always_inline void accel_ts_request_enter(void)
{
if (!ZCG(ts_reader_registered)) {
__atomic_add_fetch(&ZCSG(ts_active_requests), 1, __ATOMIC_SEQ_CST);
ZCG(ts_reader_registered) = true;
}
}

static zend_always_inline void accel_ts_request_leave(void)
{
if (ZCG(ts_reader_registered)) {
__atomic_sub_fetch(&ZCSG(ts_active_requests), 1, __ATOMIC_SEQ_CST);
ZCG(ts_reader_registered) = false;
}
}
#endif

/* Interned strings support */

/* O+ disables creation of interned strings by regular PHP compiler, instead,
Expand Down Expand Up @@ -894,6 +926,13 @@ static inline void kill_all_lockers(struct flock *mem_usage_check)

static inline bool accel_is_inactive(void)
{
#if defined(ZTS) && !defined(ZEND_WIN32)
/* Threads of this process may still be inside requests registered as
* SHM readers; the fcntl() probe below cannot detect them. */
if (__atomic_load_n(&ZCSG(ts_active_requests), __ATOMIC_SEQ_CST) != 0) {
return false;
}
#endif
#ifdef ZEND_WIN32
/* on Windows, we don't need kill_all_lockers() because SAPIs
that work on Windows don't manage child processes (and we
Expand Down Expand Up @@ -2742,6 +2781,17 @@ ZEND_RINIT_FUNCTION(zend_accelerator)

ZCG(accelerator_enabled) = ZCSG(accelerator_enabled);

#if defined(ZTS) && !defined(ZEND_WIN32)
if (ZCG(accelerator_enabled)) {
accel_ts_request_enter();
if (UNEXPECTED(ZCSG(restart_in_progress))) {
/* lost the race against a restart that passed accel_is_inactive()
* before our registration became visible; run uncached */
ZCG(accelerator_enabled) = false;
}
}
#endif

SHM_PROTECT();
HANDLE_UNBLOCK_INTERRUPTIONS();

Expand Down Expand Up @@ -2793,6 +2843,14 @@ zend_result accel_post_deactivate(void)
accel_unlock_all();
ZCG(counted) = false;

#if defined(ZTS) && !defined(ZEND_WIN32)
if (!file_cache_only && accel_shared_globals) {
SHM_UNPROTECT();
accel_ts_request_leave();
SHM_PROTECT();
}
#endif

return SUCCESS;
}

Expand Down
7 changes: 7 additions & 0 deletions ext/opcache/ZendAccelerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ typedef struct _zend_accel_directives {

typedef struct _zend_accel_globals {
bool counted; /* the process uses shared memory */
bool ts_reader_registered; /* this request is counted in ZCSG(ts_active_requests) (ZTS only) */
bool enabled;
bool locked; /* thread obtained exclusive lock */
bool accelerator_enabled; /* accelerator enabled for current request */
Expand Down Expand Up @@ -267,6 +268,12 @@ typedef struct _zend_accel_shared_globals {
#ifdef ZEND_WIN32
LONGLONG mem_usage;
LONGLONG restart_in;
#elif defined(ZTS)
/* In-flight requests registered as SHM readers. POSIX fcntl() record
* locks cannot track readers that are threads of one process (a process
* never conflicts with its own locks), so threaded SAPIs need an
* explicit counter — mirroring what ZEND_WIN32 does with mem_usage. */
uint32_t ts_active_requests;
#endif
bool restart_in_progress;
bool jit_counters_stopped;
Expand Down
Loading