Skip to content

Commit 99e0d3f

Browse files
authored
Fix destruction of generator running in fibers during shutdown (#15158)
The destructor of generators is a no-op when the generator is running in a fiber, because the fiber may resume the generator. Normally the destructor is not called in this case, but this can happen during shutdown. We detect that a generator is running in a fiber with the ZEND_GENERATOR_IN_FIBER flag. This change fixes two cases not handled by this mechanism: - The ZEND_GENERATOR_IN_FIBER flag was not added when resuming a "yield from $nonGenerator" - When a generator that is running in a fiber has multiple children (aka multiple generators yielding from it), all of them could be considered to also run in a fiber (only one actually is), and could leak if not destroyed before shutdown.
1 parent d214a35 commit 99e0d3f

9 files changed

+364
-10
lines changed

Zend/tests/gh15108-001.phpt

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
--TEST--
2+
GH-15108 001: Segfault with delegated generator in suspended fiber
3+
--FILE--
4+
<?php
5+
6+
class It implements \IteratorAggregate
7+
{
8+
public function getIterator(): \Generator
9+
{
10+
yield 'foo';
11+
Fiber::suspend();
12+
var_dump("not executed");
13+
}
14+
}
15+
16+
function f() {
17+
yield from new It();
18+
}
19+
20+
$iterable = f();
21+
22+
$fiber = new Fiber(function () use ($iterable) {
23+
var_dump($iterable->current());
24+
$iterable->next();
25+
var_dump("not executed");
26+
});
27+
28+
$ref = $fiber;
29+
30+
$fiber->start();
31+
32+
?>
33+
==DONE==
34+
--EXPECT--
35+
string(3) "foo"
36+
==DONE==

Zend/tests/gh15108-002.phpt

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
--TEST--
2+
GH-15108 002: Segfault with delegated generator in suspended fiber
3+
--FILE--
4+
<?php
5+
6+
class It implements \IteratorAggregate
7+
{
8+
public function getIterator(): \Generator
9+
{
10+
yield 'foo';
11+
Fiber::suspend();
12+
var_dump("not executed");
13+
}
14+
}
15+
16+
function g() {
17+
yield from new It();
18+
}
19+
20+
function f() {
21+
yield from g();
22+
}
23+
24+
$iterable = f();
25+
26+
$fiber = new Fiber(function () use ($iterable) {
27+
var_dump($iterable->current());
28+
$iterable->next();
29+
var_dump("not executed");
30+
});
31+
32+
$ref = $fiber;
33+
34+
$fiber->start();
35+
36+
?>
37+
==DONE==
38+
--EXPECT--
39+
string(3) "foo"
40+
==DONE==

Zend/tests/gh15108-003.phpt

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
--TEST--
2+
GH-15108 003: Segfault with delegated generator in suspended fiber
3+
--FILE--
4+
<?php
5+
6+
class It implements \IteratorAggregate
7+
{
8+
public function getIterator(): \Generator
9+
{
10+
yield 'foo';
11+
Fiber::suspend();
12+
var_dump("not executed");
13+
}
14+
}
15+
16+
function f($gen) {
17+
yield from $gen;
18+
}
19+
20+
$a = new It();
21+
$b = f($a);
22+
$c = f($a);
23+
24+
$fiber = new Fiber(function () use ($a, $b, $c) {
25+
var_dump($b->current());
26+
$b->next();
27+
var_dump("not executed");
28+
});
29+
30+
$ref = $fiber;
31+
32+
$fiber->start();
33+
34+
?>
35+
==DONE==
36+
--EXPECT--
37+
string(3) "foo"
38+
==DONE==

Zend/tests/gh15108-004.phpt

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
--TEST--
2+
GH-15108 004: Segfault with delegated generator in suspended fiber
3+
--FILE--
4+
<?php
5+
6+
function gen1() {
7+
yield 'foo';
8+
Fiber::suspend();
9+
var_dump("not executed");
10+
};
11+
12+
function gen2($gen) {
13+
yield from $gen;
14+
var_dump("not executed");
15+
}
16+
17+
$a = gen1();
18+
/* Both $b and $c have a root marked with IN_FIBER, but only $b is actually
19+
* running in a fiber (at shutdown) */
20+
$b = gen2($a);
21+
$c = gen2($a);
22+
23+
$fiber = new Fiber(function () use ($a, $b, $c) {
24+
var_dump($b->current());
25+
var_dump($c->current());
26+
$b->next();
27+
var_dump("not executed");
28+
});
29+
30+
$ref = $fiber;
31+
32+
$fiber->start();
33+
34+
?>
35+
==DONE==
36+
--EXPECT--
37+
string(3) "foo"
38+
string(3) "foo"
39+
==DONE==

Zend/tests/gh15108-005.phpt

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
--TEST--
2+
GH-15108 005: Segfault with delegated generator in suspended fiber
3+
--FILE--
4+
<?php
5+
6+
class It implements \IteratorAggregate
7+
{
8+
public function getIterator(): \Generator
9+
{
10+
yield 'foo';
11+
Fiber::suspend();
12+
var_dump("not executed");
13+
}
14+
}
15+
16+
function f() {
17+
yield from new It();
18+
}
19+
20+
function g() {
21+
yield from f();
22+
}
23+
24+
function h() {
25+
/* g() is an intermediate node and will not be marked with IN_FIBER */
26+
yield from g();
27+
}
28+
29+
$iterable = h();
30+
var_dump($iterable->current());
31+
32+
$fiber = new Fiber(function () use ($iterable) {
33+
$iterable->next();
34+
var_dump("not executed");
35+
});
36+
37+
$ref = $fiber;
38+
39+
$fiber->start();
40+
41+
?>
42+
==DONE==
43+
--EXPECT--
44+
string(3) "foo"
45+
==DONE==

Zend/tests/gh15108-006.phpt

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
--TEST--
2+
GH-15108 006: Segfault with delegated generator in suspended fiber
3+
--FILE--
4+
<?php
5+
6+
class It implements \IteratorAggregate
7+
{
8+
public function getIterator(): \Generator
9+
{
10+
yield 'foo';
11+
Fiber::suspend();
12+
var_dump("not executed");
13+
}
14+
}
15+
16+
function f() {
17+
yield from new It();
18+
}
19+
20+
function g() {
21+
yield from f();
22+
}
23+
24+
function gen($gen) {
25+
/* $gen is an intermediate node and will not be marked with IN_FIBER */
26+
yield from $gen;
27+
}
28+
29+
$g = g();
30+
$a = gen($g);
31+
$b = gen($g);
32+
var_dump($a->current());
33+
var_dump($b->current());
34+
35+
$fiber = new Fiber(function () use ($a, $b, $g) {
36+
$a->next();
37+
var_dump("not executed");
38+
});
39+
40+
$ref = $fiber;
41+
42+
$fiber->start();
43+
44+
?>
45+
==DONE==
46+
--EXPECT--
47+
string(3) "foo"
48+
string(3) "foo"
49+
==DONE==

Zend/tests/gh15108-007.phpt

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
--TEST--
2+
GH-15108 007: Segfault with delegated generator in suspended fiber
3+
--FILE--
4+
<?php
5+
6+
class It implements \IteratorAggregate
7+
{
8+
public function getIterator(): \Generator
9+
{
10+
yield 'foo';
11+
Fiber::suspend();
12+
var_dump("not executed");
13+
}
14+
}
15+
16+
function f() {
17+
yield from new It();
18+
}
19+
20+
function g() {
21+
yield from f();
22+
}
23+
24+
function gen($gen) {
25+
/* $gen is an intermediate node and will not be marked with IN_FIBER */
26+
yield from $gen;
27+
}
28+
29+
$g = g();
30+
$a = gen($g);
31+
$b = gen($g);
32+
$c = gen($g);
33+
$d = gen($g);
34+
var_dump($a->current());
35+
var_dump($b->current());
36+
37+
$fiber = new Fiber(function () use ($a, $b, $c, $d, $g) {
38+
$b->next();
39+
var_dump("not executed");
40+
});
41+
42+
$ref = $fiber;
43+
44+
$fiber->start();
45+
46+
?>
47+
==DONE==
48+
--EXPECT--
49+
string(3) "foo"
50+
string(3) "foo"
51+
==DONE==

0 commit comments

Comments
 (0)