[php-src] PR #24118: Rethrow exceptions from destructors called by the GC in a fiber
| From: | nicolas-grekas | Date: | Sun, 04 Oct 2026 14:46:39 +0000 |
| Subject: | [php-src] PR #24118: Rethrow exceptions from destructors called by the GC in a fiber | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-39132@lists.php.net to get a copy of this message | ||
Pull Request: https://github.com/php/php-src/pull/24118
Author: nicolas-grekas
Found while working on #24117.
Since 8.4, when the GC is triggered inside a fiber, it calls destructors in a dedicated fiber. If
one of them throws, the exception isn't rethrown into the frame that triggered the GC: the code
after the triggering statement keeps running, and the exception then escapes the enclosing catch
block.
```php
class Cycle {
public $self;
public function __construct() { $this->self = $this; }
public function __destruct() { throw new Exception('from __destruct'); }
}
$objects = [];
for ($i = 0; $i < 20000; $i++) { $objects[] = new stdClass(); }
function g() {
global $objects;
try {
$copies = [...$objects];
new Cycle();
$copies = null; // fills the GC root buffer
echo "after assign\n";
} catch (Exception $e) {
echo "caught in g()\n";
}
}
(new Fiber(function () {
try {
g();
} catch (Exception $e) {
echo "escaped g()\n";
}
}))->start();
```
8.3 prints
caught in g(), 8.4 and up print after assign then escaped
g().
zend_call_function() can't rethrow into that frame from the destructor fiber, so
gc_call_destructors_in_fiber() now does it when no exception was pending before the GC
run, like zend_fiber_object_destroy() does.
This conflicts with #24117 on the last lines of gc_call_destructors_in_fiber(): the
EG(opline_before_exception) restore has to come before the rethrow, which
destructors_013.phpt checks.