[php-src] PR #24118: Rethrow exceptions from destructors called by the GC in a fiber

From: 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.

« previous php.git-pulls (#39132) next »