SHM_PROTECT/SHM_UNPROTECT race in opcache under ZTS with multiple threads and protect_memory=1
还没有人认领这个 Issue。
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 38/100
- Issue 类型
- 缺陷
- 描述清晰度
- 基本清楚
- 活跃度
- 冷清
- 领域
- backend, performance
调研方向
从 ZendAccelerator.c 第 2714–2778 行的 SHM_PROTECT/SHM_UNPROTECT 对开始,然后检查 zend_jit_trace.c 第 7512 行附近的 JIT 窗口,以及第 227 行的崩溃位置。使用提供的 TrueAsync 脚本和 tracing-JIT 选项,通过 ZTS 构建进行复现。完成的标准是并发测试不再触发 SIGSEGV,并且已确定合适的回归覆盖范围。
由索引模型根据 Issue 内容生成。
描述
Description
When multiple PHP threads run concurrently in the same process (ZTS build), opcache's SHM_PROTECT()/SHM_UNPROTECT() calls race with each other because mprotect() is process-global, but there is no coordination (refcounting) between threads.
I'm not 100% sure this is a bug vs. a known limitation. But the behavior is surprising and I wanted to report it for discussion.
What happens
SHM_PROTECT() calls mprotect(shm, PROT_READ) and SHM_UNPROTECT() calls mprotect(shm, PROT_READ|PROT_WRITE). These are process-wide — they affect all threads.
In ZEND_RINIT_FUNCTION(zend_accelerator) (ZendAccelerator.c, lines 2714–2778), every php_request_startup() executes an SHM_UNPROTECT() / SHM_PROTECT() pair. In a multi-threaded ZTS process, each thread calls php_request_startup() independently.
When tracing JIT is enabled and compiling a hot trace, it also holds SHM unprotected (zend_jit_trace.c, line 7512). If a second thread's RINIT calls SHM_PROTECT() while the first thread is still writing to SHM inside JIT compilation, the first thread gets SIGSEGV (write to read-only page).
Timeline:
Main thread Worker thread
─────────── ─────────────
zend_jit_compile_root_trace:
zend_shared_alloc_lock()
SHM_UNPROTECT() ← PROT_READ|WRITE
writing to zend_jit_traces[N]... php_request_startup():
... RINIT(accel):
... SHM_UNPROTECT() ← no-op
... ...checks restart_pending...
... SHM_PROTECT() ← mprotect(PROT_READ)
... done, returns
t->code_start = start → SIGSEGV
(page is now read-only)
The zend_shared_alloc_lock() serializes JIT compilation between threads, but it does NOT prevent RINIT's SHM_PROTECT() from running concurrently — RINIT doesn't acquire that lock for its SHM_UNPROTECT/PROTECT pair.
How to reproduce
Any ZTS setup with multiple threads running PHP code concurrently + opcache.protect_memory=1 + opcache.jit=tracing.
Minimal reproduction with TrueAsync threads (but should be reproducible with any ZTS threading extension — parallel, pmmpthread, etc.):
<?php
use Async\ThreadPool;
use function Async\spawn;
use function Async\await;
spawn(function() {
$pool = new ThreadPool(2);
$future = $pool->submit(fn() => 42);
echo await($future) . "\n";
$pool->close();
echo "Done\n";
});
Run with:
php -d opcache.enable_cli=1 \
-d opcache.jit=tracing \
-d opcache.jit_buffer_size=64M \
-d opcache.protect_memory=1 \
-d opcache.jit_hot_loop=1 \
-d opcache.jit_hot_func=1 \
test.php
Result: correct output 42\nDone followed by SIGSEGV in zend_jit_trace_add_code (zend_jit_trace.c:227).
The crash does NOT happen with:
opcache.protect_memory=0(default) —mprotectis never called, no raceopcache.jit=function— functions are compiled before threads start, no runtime compilation during concurrent execution
Notes
- The
krakjoe/parallelextension has the same architecture (callsphp_request_startup()per thread) and avoids this by only testing withopcache.jit=functionoropcache.jit=disablein CI. Their ASAN+JIT job explicitly uses-d opcache.jit=function. run-tests.phphardcodesopcache.protect_memory=1(line 300), so any multi-threaded test suite usingrun-tests.phpwith tracing JIT will hit this.- The underlying issue is that
mprotect()is process-global butSHM_UNPROTECT/PROTECTpairs are not coordinated across threads — there is no refcount to prevent one thread'sPROTECTfrom overriding another thread's activeUNPROTECTwindow.
PHP Version
PHP 8.6.0-dev (cli) (ZTS DEBUG)
Zend Engine v4.6.0-dev
with Zend OPcache v8.6.0-dev
Operating System
Ubuntu 24.04 (Linux 6.17)
- 主要语言
- C
- 星标
- 40.4k
- 派生
- 8.2k
- 平均合并
- 2 天 15 小时
- 30 天内合并 PR
- 103
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
php/php-src 的其他 Issue
-
Bug SAPI: cli_server Status: Verified
难度 2/5 1-3 小时 新手友好度 88/100
-
Bug Status: Needs Triage
难度 2/5 1-3 小时 新手友好度 76/100
-
Bug Status: Needs Triage
难度 1/5 1 小时以内 新手友好度 90/100
-
Bug Status: Needs Triage
难度 2/5 1-3 小时 新手友好度 78/100
-
Bug Category: Tests Status: Verified
难度 2/5 1-3 小时 新手友好度 68/100
相似的 Issue
-
[adam] AdamNet network read doesn't cap to MAX_ADAM_PACKET_LEN, overflows client receive buffers 未关闭
难度 2/5 1-3 小时 新手友好度 88/100
FujiNetWIFI/fujinet-firmware#1649 · 2 条评论 ·
-
难度 2/5 1-3 小时 新手友好度 68/100
HarbourMasters/Shipwright#7229 ·
-
难度 2/5 1-3 小时 新手友好度 72/100
riscv-software-src/riscv-isa-sim#2435 · 1 条评论 ·
-
bug Self Built Image SNAPSHOT Supported Device target/ramips
难度 2/5 1-3 小时 新手友好度 88/100
-
难度 2/5 1-3 小时 新手友好度 76/100