Allow fiber switching during destructor execution

Fiber switching was disabled during destructor execution due to conflicts
with the garbage collector. This unfortunately introduces a function color
problem: destructors can not call functions that may switch Fibers.

In this change we update the GC so that Fiber switching during GC is safe. In
turn we allow Fiber switching during destrutor execution.

The GC executes destructors in a dedicated Fiber. If a destructor suspends, the
Fiber is owned by userland and a new dedicated Fiber is created to execute the
remaining destructors. Destructor suspension results in a resurection of the
object, which is handled as usual: The object is not considered garbage anymore,
but may be collected in a later run.

When the GC is executed in the main context (not in a Fiber), then destructors
are executed in the main context as well because there is no risk of conflicting
with GC in this case (main context can not suspend).

Fixes GH-11389
Closes GH-13460
This commit is contained in:
Arnaud Le Blanc 2024-02-21 16:33:33 +01:00
parent 7c6ff87869
commit 3c56af9902
No known key found for this signature in database
GPG key ID: 0098C05DD15ABC13
22 changed files with 688 additions and 210 deletions

View file

@ -23,12 +23,12 @@ unset($foo);
gc_collect_cycles();
var_dump($bar);
?>
--EXPECT--
object(bad)#2 (2) {
--EXPECTF--
object(bad)#%d (2) {
["x"]=>
object(stdClass)#3 (0) {
object(stdClass)#%d (0) {
}
["y"]=>
object(stdClass)#4 (0) {
object(stdClass)#%d (0) {
}
}

View file

@ -0,0 +1,53 @@
--TEST--
Fibers in destructors 001: Suspend in destructor
--FILE--
<?php
register_shutdown_function(function () {
printf("Shutdown\n");
});
class Cycle {
public static $counter = 0;
public $self;
public function __construct() {
$this->self = $this;
}
public function __destruct() {
$id = self::$counter++;
printf("%d: Start destruct\n", $id);
if ($id === 0) {
global $f2;
$f2 = Fiber::getCurrent();
Fiber::suspend(new stdClass);
}
printf("%d: End destruct\n", $id);
}
}
$f = new Fiber(function () {
global $f2;
new Cycle();
new Cycle();
new Cycle();
new Cycle();
new Cycle();
gc_collect_cycles();
$f2->resume();
});
$f->start();
?>
--EXPECT--
0: Start destruct
1: Start destruct
1: End destruct
2: Start destruct
2: End destruct
3: Start destruct
3: End destruct
4: Start destruct
4: End destruct
0: End destruct
Shutdown

View file

@ -0,0 +1,35 @@
--TEST--
Fibers in destructors 002: Start in destructor
--FILE--
<?php
register_shutdown_function(function () {
printf("Shutdown\n");
});
class Cycle {
public static $counter = 0;
public $self;
public function __construct() {
$this->self = $this;
}
public function __destruct() {
$id = self::$counter++;
printf("%d: Start destruct\n", $id);
$f = new Fiber(function () { });
$f->start();
printf("%d: End destruct\n", $id);
}
}
new Cycle();
new Cycle();
gc_collect_cycles();
?>
--EXPECT--
0: Start destruct
0: End destruct
1: Start destruct
1: End destruct
Shutdown

View file

@ -0,0 +1,42 @@
--TEST--
Fibers in destructors 003: Resume in destructor
--FILE--
<?php
register_shutdown_function(function () {
printf("Shutdown\n");
});
class Cycle {
public static $counter = 0;
public $self;
public function __construct() {
$this->self = $this;
}
public function __destruct() {
$id = self::$counter++;
printf("%d: Start destruct\n", $id);
global $f;
$f->resume();
printf("%d: End destruct\n", $id);
}
}
$f = new Fiber(function () {
while (true) {
Fiber::suspend();
}
});
$f->start();
new Cycle();
new Cycle();
gc_collect_cycles();
?>
--EXPECT--
0: Start destruct
0: End destruct
1: Start destruct
1: End destruct
Shutdown

View file

@ -0,0 +1,79 @@
--TEST--
Fibers in destructors 004: Suspend and throw in destructor
--FILE--
<?php
register_shutdown_function(function () {
printf("Shutdown\n");
});
class Cycle {
public static $counter = 0;
public $self;
public function __construct() {
$this->self = $this;
}
public function __destruct() {
$id = self::$counter++;
printf("%d: Start destruct\n", $id);
if ($id === 0) {
global $f2;
$f2 = Fiber::getCurrent();
Fiber::suspend(new stdClass);
}
printf("%d: End destruct\n", $id);
throw new \Exception(sprintf("%d exception", $id));
}
}
$f = new Fiber(function () {
global $f2;
new Cycle();
new Cycle();
new Cycle();
try {
gc_collect_cycles();
} catch (\Exception $e) {
echo $e, "\n";
}
$f2->resume();
});
$f->start();
?>
--EXPECTF--
0: Start destruct
1: Start destruct
1: End destruct
2: Start destruct
2: End destruct
Exception: 1 exception in %s:%d
Stack trace:
#0 [internal function]: Cycle->__destruct()
#1 [internal function]: gc_destructor_fiber()
#2 %s(%d): gc_collect_cycles()
#3 [internal function]: {closure:%s:%d}()
#4 %s(%d): Fiber->start()
#5 {main}
Next Exception: 2 exception in %s:%d
Stack trace:
#0 [internal function]: Cycle->__destruct()
#1 [internal function]: gc_destructor_fiber()
#2 %s(%d): gc_collect_cycles()
#3 [internal function]: {closure:%s:%d}()
#4 %s(%d): Fiber->start()
#5 {main}
0: End destruct
Fatal error: Uncaught Exception: 0 exception in %s:%d
Stack trace:
#0 [internal function]: Cycle->__destruct()
#1 [internal function]: gc_destructor_fiber()
#2 %s(%d): Fiber->resume()
#3 [internal function]: {closure:%s:%d}()
#4 %s(%d): Fiber->start()
#5 {main}
thrown in %s on line %d
Shutdown

View file

@ -0,0 +1,60 @@
--TEST--
Fibers in destructors 005: Suspended and not resumed destructor
--FILE--
<?php
register_shutdown_function(function () {
printf("Shutdown\n");
});
class Cycle {
public $self;
public function __construct(public int $id) {
$this->self = $this;
}
public function __destruct() {
printf("%d: Start destruct\n", $this->id);
try {
if ($this->id === 0) {
/* Fiber will be collected by GC because it's not referenced */
Fiber::suspend(new stdClass);
} else if ($this->id === 1) {
/* Fiber will be dtor during shutdown */
global $f2;
$f2 = Fiber::getCurrent();
Fiber::suspend(new stdClass);
}
} finally {
printf("%d: End destruct\n", $this->id);
}
}
}
$refs = [];
$f = new Fiber(function () use (&$refs) {
$refs[] = WeakReference::create(new Cycle(0));
$refs[] = WeakReference::create(new Cycle(1));
$refs[] = WeakReference::create(new Cycle(2));
gc_collect_cycles();
});
$f->start();
gc_collect_cycles();
foreach ($refs as $id => $ref) {
printf("%d: %s\n", $id, $ref->get() ? 'Live' : 'Collected');
}
?>
--EXPECT--
2: Start destruct
2: End destruct
0: Start destruct
0: End destruct
1: Start destruct
0: Collected
1: Live
2: Collected
Shutdown
1: End destruct

View file

@ -0,0 +1,52 @@
--TEST--
Fibers in destructors 006: multiple GC runs
--FILE--
<?php
register_shutdown_function(function () {
printf("Shutdown\n");
});
class Cycle {
public static $counter = 0;
public $self;
public function __construct() {
$this->self = $this;
}
public function __destruct() {
$id = self::$counter++;
printf("%d: Start destruct\n", $id);
if ($id === 0) {
global $f2;
$f2 = Fiber::getCurrent();
Fiber::suspend(new stdClass);
}
printf("%d: End destruct\n", $id);
}
}
$f = new Fiber(function () {
new Cycle();
new Cycle();
gc_collect_cycles();
});
$f->start();
new Cycle();
new Cycle();
gc_collect_cycles();
$f2->resume();
?>
--EXPECT--
0: Start destruct
1: Start destruct
1: End destruct
2: Start destruct
2: End destruct
3: Start destruct
3: End destruct
0: End destruct
Shutdown

View file

@ -0,0 +1,54 @@
--TEST--
Fibers in destructors 007: scope destructor
--FILE--
<?php
register_shutdown_function(function () {
printf("Shutdown\n");
});
class Cycle {
public static $counter = 0;
public function __destruct() {
$id = self::$counter++;
printf("%d: Start destruct\n", $id);
switch ($id) {
case 0:
global $f2;
$f2 = Fiber::getCurrent();
Fiber::suspend(new stdClass);
break;
case 1:
$f3 = new Fiber(function () use ($id) {
printf("%d: Fiber\n", $id);
});
$f3->start();
break;
case 2:
global $f2;
$f2->resume();
break;
}
printf("%d: End destruct\n", $id);
}
}
$f = new Fiber(function () {
new Cycle();
});
$f->start();
new Cycle();
new Cycle();
?>
--EXPECT--
0: Start destruct
1: Start destruct
1: Fiber
1: End destruct
2: Start destruct
0: End destruct
2: End destruct
Shutdown

View file

@ -0,0 +1,39 @@
--TEST--
Fibers in destructors 008: Fibers in shutdown sequence
--FILE--
<?php
register_shutdown_function(function () {
printf("Shutdown\n");
});
class C {
public static $instance;
public function __destruct() {
$f = new Fiber(function () {
printf("Started\n");
Fiber::suspend();
printf("Resumed\n");
Fiber::suspend();
});
$f->start();
$f->resume();
// Can not suspend main fiber
Fiber::suspend();
}
}
C::$instance = new C();
?>
--EXPECTF--
Shutdown
Started
Resumed
Fatal error: Uncaught FiberError: Cannot suspend outside of a fiber in %s:%d
Stack trace:
#0 %s(%d): Fiber::suspend()
#1 [internal function]: C->__destruct()
#2 {main}
thrown in %s on line %d

View file

@ -0,0 +1,39 @@
--TEST--
Fibers in destructors 009: Destructor resurrects object, suspends
--FILE--
<?php
register_shutdown_function(function () {
printf("Shutdown\n");
});
class Cycle {
public $self;
public function __construct() {
$this->self = $this;
}
public function __destruct() {
global $ref, $f2;
$ref = $this;
$f2 = Fiber::getCurrent();
Fiber::suspend();
}
}
$f = new Fiber(function () {
global $weakRef;
$weakRef = WeakReference::create(new Cycle());
gc_collect_cycles();
});
$f->start();
var_dump((bool) $weakRef->get());
gc_collect_cycles();
$f2->resume();
gc_collect_cycles();
var_dump((bool) $weakRef->get());
?>
--EXPECT--
bool(true)
bool(true)
Shutdown

View file

@ -0,0 +1,40 @@
--TEST--
Fibers in destructors 010: Destructor resurrects object, suspends, unrefs
--FILE--
<?php
register_shutdown_function(function () {
printf("Shutdown\n");
});
class Cycle {
public $self;
public function __construct() {
$this->self = $this;
}
public function __destruct() {
global $ref, $f2;
$ref = $this;
$f2 = Fiber::getCurrent();
Fiber::suspend();
$ref = null;
}
}
$f = new Fiber(function () {
global $weakRef;
$weakRef = WeakReference::create(new Cycle());
gc_collect_cycles();
});
$f->start();
var_dump((bool) $weakRef->get());
gc_collect_cycles();
$f2->resume();
gc_collect_cycles();
var_dump((bool) $weakRef->get());
?>
--EXPECT--
bool(true)
bool(false)
Shutdown

View file

@ -1,30 +0,0 @@
--TEST--
Cannot resume fiber within destructor
--FILE--
<?php
$fiber = new Fiber(function () {
Fiber::suspend();
});
$fiber->start();
return new class ($fiber) {
private $fiber;
public function __construct(Fiber $fiber) {
$this->fiber = $fiber;
}
public function __destruct() {
$this->fiber->resume(1);
}
};
?>
--EXPECTF--
Fatal error: Uncaught FiberError: Cannot switch fibers in current execution context in %sno-switch-dtor-resume.php:%d
Stack trace:
#0 %sno-switch-dtor-resume.php(%d): Fiber->resume(1)
#1 %sno-switch-dtor-resume.php(%d): class@anonymous->__destruct()
#2 {main}
thrown in %sno-switch-dtor-resume.php on line %d

View file

@ -1,20 +0,0 @@
--TEST--
Cannot start fiber within destructor
--FILE--
<?php
return new class () {
public function __destruct() {
$fiber = new Fiber(fn () => null);
$fiber->start();
}
};
?>
--EXPECTF--
Fatal error: Uncaught FiberError: Cannot switch fibers in current execution context in %sno-switch-dtor-start.php:%d
Stack trace:
#0 %sno-switch-dtor-start.php(%d): Fiber->start()
#1 %sno-switch-dtor-start.php(%d): class@anonymous->__destruct()
#2 {main}
thrown in %sno-switch-dtor-start.php on line %d

View file

@ -1,24 +0,0 @@
--TEST--
Cannot suspend fiber within destructor
--FILE--
<?php
$fiber = new Fiber(function () {
$a = new class () {
public function __destruct() {
Fiber::suspend();
}
};
});
$fiber->start();
?>
--EXPECTF--
Fatal error: Uncaught FiberError: Cannot switch fibers in current execution context in %sno-switch-dtor-suspend.php:%d
Stack trace:
#0 %sno-switch-dtor-suspend.php(%d): Fiber::suspend()
#1 [internal function]: class@anonymous->__destruct()
#2 %sno-switch-dtor-suspend.php(%d): Fiber->start()
#3 {main}
thrown in %sno-switch-dtor-suspend.php on line %d

View file

@ -1,30 +0,0 @@
--TEST--
Cannot resume fiber within destructor
--FILE--
<?php
$fiber = new Fiber(function () {
Fiber::suspend();
});
$fiber->start();
return new class ($fiber) {
private $fiber;
public function __construct(Fiber $fiber) {
$this->fiber = $fiber;
}
public function __destruct() {
$this->fiber->throw(new Error());
}
};
?>
--EXPECTF--
Fatal error: Uncaught FiberError: Cannot switch fibers in current execution context in %sno-switch-dtor-throw.php:%d
Stack trace:
#0 %sno-switch-dtor-throw.php(%d): Fiber->throw(Object(Error))
#1 %sno-switch-dtor-throw.php(%d): class@anonymous->__destruct()
#2 {main}
thrown in %sno-switch-dtor-throw.php on line %d

View file

@ -1,31 +0,0 @@
--TEST--
Cannot start a new fiber in a finally block in a force-closed fiber
--FILE--
<?php
$fiber = new Fiber(function() {
try {
Fiber::suspend();
} finally {
echo "finally\n";
$fiber = new Fiber(function() {
echo "not executed\n";
});
$fiber->start();
}
});
$fiber->start();
?>
--EXPECTF--
finally
Fatal error: Uncaught FiberError: Cannot switch fibers in current execution context in %sno-switch-force-close-finally.php:%d
Stack trace:
#0 %sno-switch-force-close-finally.php(%d): Fiber->start()
#1 [internal function]: {closure:%s:%d}()
#2 {main}
thrown in %sno-switch-force-close-finally.php on line %d

View file

@ -1,36 +0,0 @@
--TEST--
Context switches are prevented during GC collect cycles
--FILE--
<?php
$fiber = new Fiber(function () {
call_user_func(function () {
$a = new class { public $next; };
$b = new class {
public $next;
public function __destruct() {
Fiber::suspend();
}
};
$a->next = $b;
$b->next = $a;
});
gc_collect_cycles();
});
$fiber->start();
?>
--EXPECTF--
Fatal error: Uncaught FiberError: Cannot switch fibers in current execution context in %sno-switch-gc.php:%d
Stack trace:
#0 %sno-switch-gc.php(%d): Fiber::suspend()
#1 [internal function]: class@anonymous->__destruct()
#2 %sno-switch-gc.php(%d): gc_collect_cycles()
#3 [internal function]: {closure:%s:%d}()
#4 %sno-switch-gc.php(%d): Fiber->start()
#5 {main}
thrown in %sno-switch-gc.php on line %d

View file

@ -1128,6 +1128,7 @@ zend_result zend_post_startup(void) /* {{{ */
#ifdef ZEND_CHECK_STACK_LIMIT
zend_call_stack_init();
#endif
gc_init();
return SUCCESS;
}

View file

@ -132,6 +132,10 @@ struct _zend_fiber {
zval result;
};
ZEND_API zend_result zend_fiber_start(zend_fiber *fiber, zval *return_value);
ZEND_API void zend_fiber_resume(zend_fiber *fiber, zval *value, zval *return_value);
ZEND_API void zend_fiber_suspend(zend_fiber *fiber, zval *value, zval *return_value);
/* These functions may be used to create custom fiber objects using the bundled fiber switching context. */
ZEND_API zend_result zend_fiber_init_context(zend_fiber_context *context, void *kind, zend_fiber_coroutine coroutine, size_t stack_size);
ZEND_API void zend_fiber_destroy_context(zend_fiber_context *context);

View file

@ -68,9 +68,14 @@
*/
#include "zend.h"
#include "zend_API.h"
#include "zend_compile.h"
#include "zend_errors.h"
#include "zend_fibers.h"
#include "zend_hrtime.h"
#include "zend_portability.h"
#include "zend_types.h"
#include "zend_weakrefs.h"
#include "zend_string.h"
#ifndef GC_BENCH
# define GC_BENCH 0
@ -265,6 +270,11 @@ typedef struct _zend_gc_globals {
zend_hrtime_t dtor_time;
zend_hrtime_t free_time;
uint32_t dtor_idx; /* root buffer index */
uint32_t dtor_end;
zend_fiber *dtor_fiber;
bool dtor_fiber_running;
#if GC_BENCH
uint32_t root_buf_length;
uint32_t root_buf_peak;
@ -489,6 +499,11 @@ static void gc_globals_ctor_ex(zend_gc_globals *gc_globals)
gc_globals->free_time = 0;
gc_globals->activated_at = 0;
gc_globals->dtor_idx = GC_FIRST_ROOT;
gc_globals->dtor_end = 0;
gc_globals->dtor_fiber = NULL;
gc_globals->dtor_fiber_running = false;
#if GC_BENCH
gc_globals->root_buf_length = 0;
gc_globals->root_buf_peak = 0;
@ -532,6 +547,11 @@ void gc_reset(void)
GC_G(dtor_time) = 0;
GC_G(free_time) = 0;
GC_G(dtor_idx) = GC_FIRST_ROOT;
GC_G(dtor_end) = 0;
GC_G(dtor_fiber) = NULL;
GC_G(dtor_fiber_running) = false;
#if GC_BENCH
GC_G(root_buf_length) = 0;
GC_G(root_buf_peak) = 0;
@ -1776,6 +1796,120 @@ static void zend_get_gc_buffer_release(void);
static void zend_gc_check_root_tmpvars(void);
static void zend_gc_remove_root_tmpvars(void);
static zend_internal_function gc_destructor_fiber;
static ZEND_COLD ZEND_NORETURN void gc_create_destructor_fiber_error(void)
{
zend_error_noreturn(E_ERROR, "Unable to create destructor fiber");
}
static ZEND_COLD ZEND_NORETURN void gc_start_destructor_fiber_error(void)
{
zend_error_noreturn(E_ERROR, "Unable to start destructor fiber");
}
static zend_always_inline zend_result gc_call_destructors(uint32_t idx, uint32_t end, zend_fiber *fiber)
{
gc_root_buffer *current;
zend_refcounted *p;
/* The root buffer might be reallocated during destructors calls,
* make sure to reload pointers as necessary. */
while (idx != end) {
current = GC_IDX2PTR(idx);
if (GC_IS_DTOR_GARBAGE(current->ref)) {
p = GC_GET_PTR(current->ref);
/* Mark this is as a normal root for the next GC run */
current->ref = p;
/* Double check that the destructor hasn't been called yet. It
* could have already been invoked indirectly by some other
* destructor. */
if (!(OBJ_FLAGS(p) & IS_OBJ_DESTRUCTOR_CALLED)) {
if (fiber != NULL) {
GC_G(dtor_idx) = idx;
}
zend_object *obj = (zend_object*)p;
GC_TRACE_REF(obj, "calling destructor");
GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED);
GC_ADDREF(obj);
obj->handlers->dtor_obj(obj);
GC_TRACE_REF(obj, "returned from destructor");
GC_DELREF(obj);
if (UNEXPECTED(fiber != NULL && GC_G(dtor_fiber) != fiber)) {
/* We resumed after suspension */
gc_check_possible_root((zend_refcounted*)&obj->gc);
return FAILURE;
}
}
}
idx++;
}
return SUCCESS;
}
static zend_fiber *gc_create_destructor_fiber(void)
{
zval zobj;
zend_fiber *fiber;
GC_TRACE("starting destructor fiber");
if (UNEXPECTED(object_init_ex(&zobj, zend_ce_fiber) == FAILURE)) {
gc_create_destructor_fiber_error();
}
fiber = (zend_fiber *)Z_OBJ(zobj);
fiber->fci.size = sizeof(fiber->fci);
fiber->fci_cache.function_handler = (zend_function*) &gc_destructor_fiber;
GC_G(dtor_fiber) = fiber;
if (UNEXPECTED(zend_fiber_start(fiber, NULL) == FAILURE)) {
gc_start_destructor_fiber_error();
}
return fiber;
}
static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end)
{
ZEND_ASSERT(!GC_G(dtor_fiber_running));
zend_fiber *fiber = GC_G(dtor_fiber);
GC_G(dtor_idx) = GC_FIRST_ROOT;
GC_G(dtor_end) = GC_G(first_unused);
if (UNEXPECTED(!fiber)) {
fiber = gc_create_destructor_fiber();
} else {
zend_fiber_resume(fiber, NULL, NULL);
}
for (;;) {
/* At this point, fiber has executed until suspension */
GC_TRACE("resumed from destructor fiber");
if (UNEXPECTED(GC_G(dtor_fiber_running))) {
/* Fiber was suspended by a destructor. Start a new one for the
* remaining destructors. */
GC_TRACE("destructor fiber suspended by destructor");
GC_G(dtor_fiber) = NULL;
GC_G(dtor_idx)++;
/* We do not own the fiber anymore. It may be collected if the
* application does not reference it. */
zend_object_release(&fiber->std);
fiber = gc_create_destructor_fiber();
continue;
} else {
/* Fiber suspended itself after calling all destructors */
GC_TRACE("destructor fiber suspended itself");
break;
}
}
}
ZEND_API int zend_gc_collect_cycles(void)
{
int total_count = 0;
@ -1824,8 +1958,6 @@ rerun_gc:
goto finish;
}
zend_fiber_switch_block();
end = GC_G(first_unused);
if (gc_flags & GC_HAS_DESTRUCTORS) {
@ -1876,38 +2008,18 @@ rerun_gc:
idx++;
}
/* Actually call destructors.
*
* The root buffer might be reallocated during destructors calls,
* make sure to reload pointers as necessary. */
/* Actually call destructors. */
zend_hrtime_t dtor_start_time = zend_hrtime();
idx = GC_FIRST_ROOT;
while (idx != end) {
current = GC_IDX2PTR(idx);
if (GC_IS_DTOR_GARBAGE(current->ref)) {
p = GC_GET_PTR(current->ref);
/* Mark this is as a normal root for the next GC run,
* it's no longer garbage for this run. */
current->ref = p;
/* Double check that the destructor hasn't been called yet. It could have
* already been invoked indirectly by some other destructor. */
if (!(OBJ_FLAGS(p) & IS_OBJ_DESTRUCTOR_CALLED)) {
zend_object *obj = (zend_object*)p;
GC_TRACE_REF(obj, "calling destructor");
GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED);
GC_ADDREF(obj);
obj->handlers->dtor_obj(obj);
GC_DELREF(obj);
}
}
idx++;
if (EXPECTED(!EG(active_fiber))) {
gc_call_destructors(GC_FIRST_ROOT, end, NULL);
} else {
gc_call_destructors_in_fiber(end);
}
GC_G(dtor_time) += zend_hrtime() - dtor_start_time;
if (GC_G(gc_protected)) {
/* something went wrong */
zend_get_gc_buffer_release();
zend_fiber_switch_unblock();
GC_G(collector_time) += zend_hrtime() - start_time;
return 0;
}
@ -1970,8 +2082,6 @@ rerun_gc:
GC_G(free_time) += zend_hrtime() - free_start_time;
zend_fiber_switch_unblock();
GC_TRACE("Collection finished");
GC_G(collected) += count;
total_count += count;
@ -2125,3 +2235,49 @@ size_t zend_gc_globals_size(void)
return sizeof(zend_gc_globals);
}
#endif
static ZEND_FUNCTION(gc_destructor_fiber)
{
uint32_t idx, end;
zend_fiber *fiber = GC_G(dtor_fiber);
ZEND_ASSERT(fiber != NULL);
ZEND_ASSERT(fiber == EG(active_fiber));
for (;;) {
GC_G(dtor_fiber_running) = true;
idx = GC_G(dtor_idx);
end = GC_G(dtor_end);
if (UNEXPECTED(gc_call_destructors(idx, end, fiber) == FAILURE)) {
/* We resumed after being suspended by a destructor */
return;
}
/* We have called all destructors. Suspend fiber until the next GC run
*/
GC_G(dtor_fiber_running) = false;
zend_fiber_suspend(fiber, NULL, NULL);
if (UNEXPECTED(fiber->flags & ZEND_FIBER_FLAG_DESTROYED)) {
/* Fiber is being destroyed by shutdown sequence */
GC_DELREF(&fiber->std);
gc_check_possible_root((zend_refcounted*)&fiber->std.gc);
return;
}
}
}
static zend_internal_function gc_destructor_fiber = {
.type = ZEND_INTERNAL_FUNCTION,
.fn_flags = ZEND_ACC_PUBLIC,
.handler = ZEND_FN(gc_destructor_fiber),
};
void gc_init(void)
{
gc_destructor_fiber.function_name = zend_string_init_interned(
"gc_destructor_fiber",
strlen("gc_destructor_fiber"),
true);
}

View file

@ -65,6 +65,7 @@ ZEND_API int zend_gc_collect_cycles(void);
ZEND_API void zend_gc_get_status(zend_gc_status *status);
void gc_init(void);
void gc_globals_ctor(void);
void gc_globals_dtor(void);
void gc_reset(void);

View file

@ -44,8 +44,6 @@ ZEND_API void ZEND_FASTCALL zend_objects_store_call_destructors(zend_objects_sto
{
EG(flags) |= EG_FLAGS_OBJECT_STORE_NO_REUSE;
if (objects->top > 1) {
zend_fiber_switch_block();
uint32_t i;
for (i = 1; i < objects->top; i++) {
zend_object *obj = objects->object_buckets[i];
@ -62,8 +60,6 @@ ZEND_API void ZEND_FASTCALL zend_objects_store_call_destructors(zend_objects_sto
}
}
}
zend_fiber_switch_unblock();
}
}
@ -179,11 +175,9 @@ ZEND_API void ZEND_FASTCALL zend_objects_store_del(zend_object *object) /* {{{ *
if (object->handlers->dtor_obj != zend_objects_destroy_object
|| object->ce->destructor) {
zend_fiber_switch_block();
GC_SET_REFCOUNT(object, 1);
object->handlers->dtor_obj(object);
GC_DELREF(object);
zend_fiber_switch_unblock();
}
}