mirror of
https://github.com/php/php-src.git
synced 2025-08-15 13:38:49 +02:00
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:
parent
7c6ff87869
commit
3c56af9902
22 changed files with 688 additions and 210 deletions
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
|
53
Zend/tests/fibers/destructors_001.phpt
Normal file
53
Zend/tests/fibers/destructors_001.phpt
Normal 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
|
35
Zend/tests/fibers/destructors_002.phpt
Normal file
35
Zend/tests/fibers/destructors_002.phpt
Normal 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
|
42
Zend/tests/fibers/destructors_003.phpt
Normal file
42
Zend/tests/fibers/destructors_003.phpt
Normal 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
|
79
Zend/tests/fibers/destructors_004.phpt
Normal file
79
Zend/tests/fibers/destructors_004.phpt
Normal 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
|
60
Zend/tests/fibers/destructors_005.phpt
Normal file
60
Zend/tests/fibers/destructors_005.phpt
Normal 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
|
52
Zend/tests/fibers/destructors_006.phpt
Normal file
52
Zend/tests/fibers/destructors_006.phpt
Normal 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
|
54
Zend/tests/fibers/destructors_007.phpt
Normal file
54
Zend/tests/fibers/destructors_007.phpt
Normal 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
|
39
Zend/tests/fibers/destructors_008.phpt
Normal file
39
Zend/tests/fibers/destructors_008.phpt
Normal 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
|
39
Zend/tests/fibers/destructors_009.phpt
Normal file
39
Zend/tests/fibers/destructors_009.phpt
Normal 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
|
40
Zend/tests/fibers/destructors_010.phpt
Normal file
40
Zend/tests/fibers/destructors_010.phpt
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1128,6 +1128,7 @@ zend_result zend_post_startup(void) /* {{{ */
|
|||
#ifdef ZEND_CHECK_STACK_LIMIT
|
||||
zend_call_stack_init();
|
||||
#endif
|
||||
gc_init();
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
214
Zend/zend_gc.c
214
Zend/zend_gc.c
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue