Skip to content

Commit d7f3e1d

Browse files
committed
test_scheduler: test the core's paths where a coroutine cannot be created or queued
1 parent cc9907b commit d7f3e1d

7 files changed

Lines changed: 262 additions & 0 deletions
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
test_scheduler: a Fiber whose coroutine cannot be queued fails to start and may start again
3+
--EXTENSIONS--
4+
test_scheduler
5+
--INI--
6+
test_scheduler.enable=1
7+
--FILE--
8+
<?php
9+
ini_set('test_scheduler.fail_enqueue', 1);
10+
11+
$fiber = new Fiber(function () {
12+
echo "body\n";
13+
});
14+
15+
try {
16+
$fiber->start();
17+
} catch (Error $e) {
18+
echo $e->getMessage(), "\n";
19+
}
20+
21+
var_dump($fiber->isStarted(), $fiber->isTerminated());
22+
23+
$fiber->start();
24+
var_dump($fiber->isTerminated());
25+
?>
26+
--EXPECT--
27+
Cannot enqueue the coroutine: test_scheduler.fail_enqueue
28+
bool(false)
29+
bool(false)
30+
body
31+
bool(true)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
--TEST--
2+
test_scheduler: gc_collect_cycles() when its coroutine or its destructor iterator cannot be created or queued
3+
--EXTENSIONS--
4+
test_scheduler
5+
--INI--
6+
test_scheduler.enable=1
7+
--FILE--
8+
<?php
9+
class Node {
10+
public $self;
11+
12+
public function __destruct() {
13+
echo "destructor\n";
14+
}
15+
}
16+
17+
function make_cycle() {
18+
$node = new Node();
19+
$node->self = $node;
20+
}
21+
22+
/* The GC coroutine is the first coroutine the call creates, the destructor
23+
* iterator the second. */
24+
$faults = [['fail_new_coroutine', 1], ['fail_enqueue', 1], ['fail_new_coroutine', 2], ['fail_enqueue', 2]];
25+
foreach ($faults as [$setting, $nth]) {
26+
echo "$setting=$nth\n";
27+
make_cycle();
28+
ini_set("test_scheduler.$setting", $nth);
29+
30+
try {
31+
var_dump(gc_collect_cycles());
32+
} catch (Error $e) {
33+
echo $e->getMessage(), "\n";
34+
}
35+
36+
ini_set("test_scheduler.$setting", 0);
37+
var_dump(gc_collect_cycles());
38+
}
39+
?>
40+
--EXPECT--
41+
fail_new_coroutine=1
42+
int(0)
43+
destructor
44+
int(1)
45+
fail_enqueue=1
46+
Cannot enqueue the coroutine: test_scheduler.fail_enqueue
47+
destructor
48+
int(1)
49+
fail_new_coroutine=2
50+
destructor
51+
int(0)
52+
int(1)
53+
fail_enqueue=2
54+
destructor
55+
int(0)
56+
int(1)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
test_scheduler: the shutdown destructors go on in the driving coroutine when no iterator can be created
3+
--EXTENSIONS--
4+
test_scheduler
5+
--INI--
6+
test_scheduler.enable=1
7+
--FILE--
8+
<?php
9+
class Waiter {
10+
public function __construct(public string $name) {}
11+
12+
public function __destruct() {
13+
echo "{$this->name} start\n";
14+
TestScheduler\await(TestScheduler\spawn(fn() => null));
15+
echo "{$this->name} end\n";
16+
}
17+
}
18+
19+
$a = new Waiter('a');
20+
$b = new Waiter('b');
21+
22+
/* Each destructor's await would start an iterator to carry the pass on. */
23+
ini_set('test_scheduler.fail_new_coroutine', 1);
24+
echo "end of script\n";
25+
?>
26+
--EXPECT--
27+
end of script
28+
b start
29+
b end
30+
a start
31+
a end
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
test_scheduler: the shutdown destructors go on in the driving coroutine when their iterator cannot be queued
3+
--EXTENSIONS--
4+
test_scheduler
5+
--INI--
6+
test_scheduler.enable=1
7+
--FILE--
8+
<?php
9+
class Waiter {
10+
public function __construct(public string $name) {}
11+
12+
public function __destruct() {
13+
echo "{$this->name} start\n";
14+
TestScheduler\await(TestScheduler\spawn(function () {
15+
echo "{$this->name} spawned\n";
16+
}));
17+
echo "{$this->name} end\n";
18+
}
19+
}
20+
21+
$a = new Waiter('a');
22+
23+
/* The spawn is the first queued coroutine, the iterator the second. */
24+
ini_set('test_scheduler.fail_enqueue', 2);
25+
echo "end of script\n";
26+
?>
27+
--EXPECT--
28+
end of script
29+
a start
30+
a spawned
31+
a end
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
--TEST--
2+
test_scheduler: a shutdown destructor that parks with no iterator and no loop running ends in a deadlock, not a crash
3+
--EXTENSIONS--
4+
test_scheduler
5+
--INI--
6+
test_scheduler.enable=1
7+
--FILE--
8+
<?php
9+
class Parker {
10+
public function __destruct() {
11+
echo "parking\n";
12+
TestScheduler\suspend();
13+
echo "not reached\n";
14+
}
15+
}
16+
17+
$parker = new Parker();
18+
19+
/* Nothing was spawned, so no loop runs after the script, and the iterator
20+
* that would start one cannot be created. */
21+
ini_set('test_scheduler.fail_new_coroutine', 1);
22+
echo "end of script\n";
23+
?>
24+
--EXPECTF--
25+
end of script
26+
parking
27+
28+
Fatal error: Uncaught TestScheduler\CancellationError: Deadlock detected in %s:%d
29+
Stack trace:
30+
#0 {main}
31+
thrown in %s on line %d
32+
33+
Fatal error: Uncaught TestScheduler\DeadlockError: Deadlock detected: no active coroutines, 1 coroutines in waiting in %s:%d
34+
Stack trace:
35+
#0 {main}
36+
thrown in %s on line %d
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
--TEST--
2+
test_scheduler: the object store's destructors go on in the driving coroutine when no iterator can be created
3+
--EXTENSIONS--
4+
test_scheduler
5+
--INI--
6+
test_scheduler.enable=1
7+
--FILE--
8+
<?php
9+
class Waiter {
10+
public $self;
11+
12+
public function __construct(public string $name) {}
13+
14+
public function __destruct() {
15+
echo "{$this->name} start\n";
16+
TestScheduler\await(TestScheduler\spawn(fn() => null));
17+
echo "{$this->name} end\n";
18+
}
19+
}
20+
21+
/* Cycles outlive the symbol table: the object store's pass destroys them. */
22+
foreach (['x', 'y'] as $name) {
23+
$waiter = new Waiter($name);
24+
$waiter->self = $waiter;
25+
}
26+
unset($waiter);
27+
28+
ini_set('test_scheduler.fail_new_coroutine', 1);
29+
echo "end of script\n";
30+
?>
31+
--EXPECT--
32+
end of script
33+
x start
34+
x end
35+
y start
36+
y end
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
test_scheduler: the object store's destructors go on in the driving coroutine when their iterator cannot be queued
3+
--EXTENSIONS--
4+
test_scheduler
5+
--INI--
6+
test_scheduler.enable=1
7+
--FILE--
8+
<?php
9+
class Waiter {
10+
public $self;
11+
12+
public function __construct(public string $name) {}
13+
14+
public function __destruct() {
15+
echo "{$this->name} start\n";
16+
TestScheduler\await(TestScheduler\spawn(function () {
17+
echo "{$this->name} spawned\n";
18+
}));
19+
echo "{$this->name} end\n";
20+
}
21+
}
22+
23+
/* Cycles outlive the symbol table: the object store's pass destroys them. */
24+
foreach (['x', 'y'] as $name) {
25+
$waiter = new Waiter($name);
26+
$waiter->self = $waiter;
27+
}
28+
unset($waiter);
29+
30+
/* The spawn is the first queued coroutine, the iterator the second. */
31+
ini_set('test_scheduler.fail_enqueue', 2);
32+
echo "end of script\n";
33+
?>
34+
--EXPECT--
35+
end of script
36+
x start
37+
x spawned
38+
x end
39+
y start
40+
y spawned
41+
y end

0 commit comments

Comments
 (0)