opcache_compile_file() on a file with a fatal compile error disables the cycle collector for the rest of the request
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 22/100
Direzione di ricerca
Start with the zend_catch branch of ZEND_FUNCTION(opcache_compile_file) in ext/opcache/zend_accelerator_module.c, then look at _zend_bailout() in Zend/zend.c, which calls gc_protect(1). Reproduce with the script in the issue and check that gc_status()['protected'] returns to false after the failed compile. Done means the cycle collector runs again after the catch, or the behaviour is documented and the gc_protect state is restored.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
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
$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
- Lingua principale
- C
- Stelle
- 41.7k
- Fork
- 8.5k
- Merge medio
- 2g 18h
- PR unite (30g)
- 159
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di php/php-src
-
Windows CLI: get_ps_title() passes bytes to GetConsoleTitleW() instead of characters, overflowing the 260-wchar_t bufferForse già presa Una pull request collegata a questa issue è aperta o già unita. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 79/100
I maintainer di solito rispondono entro 1 giorno
-
zend_set_local_var() overwrites existing compiled variable (CV) zval without calling zval_ptr_dtor()Forse già presa Una pull request collegata a questa issue è aperta o già unita. ApertaBug Status: Needs Triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
I maintainer di solito rispondono entro 1 giorno
-
master: part of the mbstring bailout fix got lost in a mergeForse già presa @lazerg l’ha presa 7 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
Bug Status: Needs Triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 64/100
I maintainer di solito rispondono entro 1 giorno
-
quoted_printable_encode() produces lines longer than 76 characters for bytes 0xF5 to 0xFFForse già presa Una pull request collegata a questa issue è aperta o già unita. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
I maintainer di solito rispondono entro 1 giorno
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 66/100
I maintainer di solito rispondono entro 2 giorni
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
zephyrproject-rtos/zephyr#123881 ·
I maintainer di solito rispondono entro 2 giorni
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 77/100
eclipse-zenoh/zenoh-pico#1354 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 65/100
ly05010419/MacNativeGames#1252 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
I maintainer di solito rispondono entro 2 giorni