[PHP-CVS] [php-src] PHP-8.5: Merge branch 'PHP-8.4' into PHP-8.5
[email protected] (Arnaud Le Blanc)
| Newsgroups | php.cvs |
|---|---|
| Message-ID | <[email protected]> |
Author: Arnaud Le Blanc (arnaud-lb)
Date: 2026-08-14T10:36:02+02:00
Commit: https://github.com/php/php-src/commit/7989ee9df803b5973a1fe731efab667509c36bf2
Raw diff: https://github.com/php/php-src/commit/7989ee9df803b5973a1fe731efab667509c36bf2.diff
Merge branch 'PHP-8.4' into PHP-8.5
* PHP-8.4:
[ci skip] NEWS
Fix OPcache memory protection race under ZTS (#23081)
Changed paths:
M NEWS
M ext/opcache/ZendAccelerator.h
M ext/opcache/zend_shared_alloc.c
Diff:
diff --git a/NEWS b/NEWS
index 6eebf9d3d37d..c21dfad8e195 100644
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,9 @@ PHP NEWS
. Fixed bug GH-15375 (Nested "yield from" skips items after a valid() or
next() call on the inner generator). (iliaal)
+- Opcache:
+ . Fixed opcache.protect_memory race under ZTS. (realFlowControl)
+
- Readline:
. Fixed the interactive shell not waiting for the pager process to exit.
(Weilin Du)
diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h
index 524a6f5e1213..2ca3b7129f37 100644
--- a/ext/opcache/ZendAccelerator.h
+++ b/ext/opcache/ZendAccelerator.h
@@ -200,6 +200,9 @@ typedef struct _zend_accel_globals {
bool counted; /* the process uses shared memory */
bool enabled;
bool locked; /* thread obtained exclusive lock */
+#ifdef ZTS
+ uint32_t unprotect_depth;
+#endif
bool accelerator_enabled; /* accelerator enabled for current request */
bool pcre_reseted;
zend_accel_directives accel_directives;
diff --git a/ext/opcache/zend_shared_alloc.c b/ext/opcache/zend_shared_alloc.c
index 80ef36b8749d..62b55df50ac7 100644
--- a/ext/opcache/zend_shared_alloc.c
+++ b/ext/opcache/zend_shared_alloc.c
@@ -55,6 +55,11 @@ static const char *g_shared_model;
/* pointer to globals allocated in SHM and shared across processes */
ZEND_EXT_API zend_smm_shared_globals *smm_shared_globals;
+#ifdef ZTS
+static MUTEX_T zts_protect_lock;
+static uint32_t zts_unprotected_threads;
+#endif
+
#ifndef ZEND_WIN32
#ifdef ZTS
static MUTEX_T zts_lock;
@@ -186,6 +191,11 @@ int zend_shared_alloc_startup(size_t requested_size, size_t reserved_size)
int res = ALLOC_FAILURE;
int i;
+#ifdef ZTS
+ zts_protect_lock = tsrm_mutex_alloc();
+ zts_unprotected_threads = 0;
+#endif
+
/* shared_free must be valid before we call zend_shared_alloc()
* - make it temporarily point to a local variable
*/
@@ -343,6 +353,9 @@ void zend_shared_alloc_shutdown(void)
tsrm_mutex_free(zts_lock);
# endif
#endif
+#ifdef ZTS
+ tsrm_mutex_free(zts_protect_lock);
+#endif
}
static size_t zend_shared_alloc_get_largest_free_block(void)
@@ -630,25 +643,37 @@ const char *zend_accel_get_shared_model(void)
void zend_accel_shared_protect(bool protected)
{
-#ifdef HAVE_MPROTECT
+#if defined(HAVE_MPROTECT) || defined(ZEND_WIN32)
int i;
if (!smm_shared_globals) {
return;
}
+# ifdef ZTS
+ /* Memory protection is process-wide, so overlapping writers must be tracked across threads. */
+ tsrm_mutex_lock(zts_protect_lock);
+ if (protected) {
+ if (ZCG(unprotect_depth) && --ZCG(unprotect_depth) == 0) {
+ ZEND_ASSERT(zts_unprotected_threads > 0);
+ zts_unprotected_threads--;
+ }
+ if (zts_unprotected_threads) {
+ tsrm_mutex_unlock(zts_protect_lock);
+ return;
+ }
+ } else if (ZCG(unprotect_depth)++ == 0) {
+ zts_unprotected_threads++;
+ }
+# endif
+
+# ifdef HAVE_MPROTECT
const int mode = protected ? PROT_READ : PROT_READ|PROT_WRITE;
for (i = 0; i < ZSMMG(shared_segments_count); i++) {
mprotect(ZSMMG(shared_segments)[i]->p, ZSMMG(shared_segments)[i]->end, mode);
}
-#elif defined(ZEND_WIN32)
- int i;
-
- if (!smm_shared_globals) {
- return;
- }
-
+# elif defined(ZEND_WIN32)
const int mode = protected ? PAGE_READONLY : PAGE_READWRITE;
for (i = 0; i < ZSMMG(shared_segments_count); i++) {
@@ -657,6 +682,11 @@ void zend_accel_shared_protect(bool protected)
zend_accel_error_noreturn(ACCEL_LOG_ERROR, "Failed to protect memory");
}
}
+# endif
+
+# ifdef ZTS
+ tsrm_mutex_unlock(zts_protect_lock);
+# endif
#endif
}