[php-src] Issue #24361: opcache_compile_file() on a file with a fatal compile error disables the cycle collector for the rest of the request

From: Date: Thu, 15 Oct 2026 19:18:13 +0000
Subject: [php-src] Issue #24361: opcache_compile_file() on a file with a fatal compile error disables the cycle collector for the rest of the request
Groups: php.bugs 
Request: Send a blank email to php-bugs+get-253005@lists.php.net to get a copy of this message
Issue: https://github.com/php/php-src/issues/24361 Author: likemusic ### Description When opcache_compile_file() is given a file that fails with a fatal compile error, the function catches the bailout, emits a warning, returns false and lets the script continue. But zend_bailout() has already called gc_protect(1), and nothing resets it until the next request. From that point on gc_possible_root() drops every new root candidate, so cycles created later in the request are never collected, not even by an explicit gc_collect_cycles(). The following code: ```php <?php $bad = sys_get_temp_dir() . "/bad.php"; file_put_contents($bad, "<?php\nbreak 2;\n"); class Node { public $self; } var_dump(gc_status()["protected"]); var_dump(@opcache_compile_file($bad)); var_dump(gc_status()["protected"]); for ($i = 0; $i < 100000; $i++) { $o = new Node; $o->self = $o; } var_dump(gc_collect_cycles()); var_dump(gc_status()["collected"]); ``` Run with php -n -d opcache.enable_cli=1 (master, debug build). Resulted in this output: ``` bool(false) Fatal error: 'break' not in the 'loop' or 'switch' context in /tmp/bad.php on line 2 Stack trace: #0 /path/to/test.php(8): opcache_compile_file('/tmp/bad.php') #1 {main} bool(false) bool(true) int(0) int(0) ``` But I expected this output instead (the second protected stays false and the cycles get collected; the last two numbers are what the same script prints with the opcache_compile_file() line removed): ``` bool(false) Fatal error: 'break' not in the 'loop' or 'switch' context in /tmp/bad.php on line 2 Stack trace: #0 /path/to/test.php(8): opcache_compile_file('/tmp/bad.php') #1 {main} bool(false) bool(false) int(9999) int(99999) ``` Measured with a slightly longer script (prints gc_status() and memory_get_usage() after creating 100 000 self-referencing objects): | build | opcache_compile_file() called | protected after | GC runs | collected | memory at end | |---|---|---|---|---|---| | master 0b4a5a84492, debug, NTS | no | false | 10 | 100 000 | 583 136 | | master 0b4a5a84492, debug, NTS | yes | **true** | 0 | **0** | 11 141 648 | | master, debug, opcache.enable_cli=0 | yes (returns false without compiling) | false | 10 | 100 000 | 635 656 | | PHP-8.4 81fd6105bc6, release + ASAN, NTS | no | false | 10 | 100 000 | 414 792 | | PHP-8.4 81fd6105bc6, release + ASAN, NTS | yes | **true** | 0 | **0** | 6 942 856 | | PHP-8.3 9c6ad6caeeb, release, NTS | no | false | 10 | 100 000 | 471 080 | | PHP-8.3 9c6ad6caeeb, release, NTS | yes | **true** | 0 | **0** | 7 023 624 | Where it happens: ext/opcache/zend_accelerator_module.c, ZEND_FUNCTION(opcache_compile_file): the zend_try { persistent_compile_file(...) } zend_catch { ... zend_error(E_WARNING, "... could not compile file %s") } branch (non-preload path). The catch restores EG(current_execute_data) but not the GC protection that zend_bailout() set (Zend/zend.c, _zend_bailout() → gc_protect(1)). The catch dates back to the introduction of the function (69454d9f0f7, 2013), the gc_protect(1) in _zend_bailout() to f844d40fdc3 (2018, PHP 7.3.0), so presumably every version since 7.3 is affected. The same class of problem is reported for ext/soap in #10026 (SoapClient converts fatal errors into SoapFault after a bailout). I found this second instance while auditing the zend_catch sites that continue execution after a bailout. Possible directions (not a proposal of one in particular): - report compile failures from opcache_compile_file() without a bailout, if the compiler path allows it; - or document that after a fatal compile error the rest of the request runs without the cycle collector. ### PHP Version ``` PHP 8.7.0-dev (cli) (NTS DEBUG), master 0b4a5a84492 PHP 8.4.28-dev (cli) (NTS), PHP-8.4 81fd6105bc6 PHP 8.3.36-dev (cli) (NTS), PHP-8.3 9c6ad6caeeb ``` ### Operating System Ubuntu 24.04, x86_64

« previous php.bugs (#253005) next »