Skip to content

Commit 7a3f113

Browse files
committed
#23: + edge cases for fiber from spawn and spawn from fiber
1 parent cd415ba commit 7a3f113

File tree

4 files changed

+232
-0
lines changed

4 files changed

+232
-0
lines changed

scheduler.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,11 @@ void async_scheduler_launch(void)
523523
return;
524524
}
525525

526+
if (EG(active_fiber)) {
527+
async_throw_error("The True Async Scheduler cannot be started from within a Fiber");
528+
return;
529+
}
530+
526531
if (false == zend_async_reactor_is_enabled()) {
527532
async_throw_error("The scheduler cannot be started without the Reactor");
528533
return;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
--TEST--
2+
Fiber created first, then spawn operation - should detect incompatible context
3+
--FILE--
4+
<?php
5+
6+
use function Async\spawn;
7+
use function Async\suspend;
8+
9+
echo "Test: Fiber first, then spawn\n";
10+
11+
try {
12+
$fiber = new Fiber(function() {
13+
echo "Inside Fiber\n";
14+
15+
// This should cause issues - spawning from within a Fiber context
16+
$coroutine = spawn(function() {
17+
echo "Inside spawned coroutine from Fiber\n";
18+
suspend();
19+
echo "Coroutine completed\n";
20+
});
21+
22+
echo "Fiber attempting to continue after spawn\n";
23+
Fiber::suspend("fiber suspended");
24+
echo "Fiber resumed\n";
25+
26+
return "fiber done";
27+
});
28+
29+
echo "Starting Fiber\n";
30+
$result = $fiber->start();
31+
echo "Fiber suspended with: " . $result . "\n";
32+
33+
echo "Resuming Fiber\n";
34+
$result = $fiber->resume("resume value");
35+
echo "Fiber returned: " . $result . "\n";
36+
37+
} catch (Async\AsyncException $e) {
38+
echo "Async exception caught: " . $e->getMessage() . "\n";
39+
} catch (Exception $e) {
40+
echo "Exception caught: " . $e->getMessage() . "\n";
41+
}
42+
43+
echo "Test completed\n";
44+
?>
45+
--EXPECTF--
46+
Test: Fiber first, then spawn
47+
Starting Fiber
48+
Inside Fiber
49+
Async exception caught: The True Async Scheduler cannot be started from within a Fiber
50+
Test completed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
--TEST--
2+
Spawn coroutine first, then create Fiber - should detect context conflicts
3+
--FILE--
4+
<?php
5+
6+
use function Async\spawn;
7+
use function Async\suspend;
8+
use function Async\await;
9+
10+
echo "Test: Spawn first, then Fiber\n";
11+
12+
try {
13+
// First spawn a coroutine that will suspend and wait
14+
$coroutine = spawn(function() {
15+
echo "Coroutine started\n";
16+
suspend(); // This activates the async scheduler
17+
echo "Coroutine resumed\n";
18+
return "coroutine result";
19+
});
20+
21+
echo "Coroutine spawned, now creating Fiber\n";
22+
23+
// Now try to create and use a Fiber while async scheduler is active
24+
$fiber = new Fiber(function() {
25+
echo "Inside Fiber - this should conflict with active scheduler\n";
26+
27+
// Try to interact with the active coroutine from within Fiber
28+
// This creates a context conflict
29+
Fiber::suspend("fiber suspended");
30+
31+
echo "Fiber resumed\n";
32+
return "fiber done";
33+
});
34+
35+
echo "Starting Fiber\n";
36+
$fiberResult = $fiber->start();
37+
echo "Fiber suspended with: " . $fiberResult . "\n";
38+
39+
echo "Resuming Fiber\n";
40+
$fiberResult = $fiber->resume("resume data");
41+
echo "Fiber completed with: " . $fiberResult . "\n";
42+
43+
echo "Getting coroutine result\n";
44+
$coroutineResult = await($coroutine);
45+
echo "Coroutine completed with: " . $coroutineResult . "\n";
46+
47+
} catch (Error $e) {
48+
echo "Error caught: " . $e->getMessage() . "\n";
49+
} catch (Exception $e) {
50+
echo "Exception caught: " . $e->getMessage() . "\n";
51+
}
52+
53+
echo "Test completed\n";
54+
?>
55+
--EXPECTF--
56+
Test: Spawn first, then Fiber
57+
Coroutine spawned, now creating Fiber
58+
Error caught: Cannot create a fiber while an True Async is active
59+
Test completed
60+
Coroutine started
61+
Coroutine resumed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
--TEST--
2+
Fiber and spawn operations in destructors - memory management conflicts
3+
--FILE--
4+
<?php
5+
6+
use function Async\spawn;
7+
use function Async\suspend;
8+
use function Async\await;
9+
10+
echo "Test: Fiber and spawn in destructors\n";
11+
12+
class FiberSpawner {
13+
private $name;
14+
15+
public function __construct($name) {
16+
$this->name = $name;
17+
echo "Created: {$this->name}\n";
18+
}
19+
20+
public function __destruct() {
21+
echo "Destructing: {$this->name}\n";
22+
23+
try {
24+
if ($this->name === 'FiberInDestructor') {
25+
// Create Fiber in destructor
26+
$fiber = new Fiber(function() {
27+
echo "Fiber running in destructor\n";
28+
Fiber::suspend("destructor fiber");
29+
echo "Fiber resumed in destructor\n";
30+
return "destructor done";
31+
});
32+
33+
echo "Starting fiber in destructor\n";
34+
$result = $fiber->start();
35+
echo "Fiber suspended with: " . $result . "\n";
36+
37+
$result = $fiber->resume("resume in destructor");
38+
echo "Fiber completed with: " . $result . "\n";
39+
40+
} elseif ($this->name === 'SpawnInDestructor') {
41+
// Spawn coroutine in destructor
42+
echo "Spawning coroutine in destructor\n";
43+
$coroutine = spawn(function() {
44+
echo "Coroutine running in destructor\n";
45+
suspend();
46+
echo "Coroutine resumed in destructor\n";
47+
return "destructor coroutine done";
48+
});
49+
50+
echo "Waiting for coroutine in destructor\n";
51+
$result = await($coroutine);
52+
echo "Coroutine completed with: " . $result . "\n";
53+
}
54+
} catch (Error $e) {
55+
echo "Error in destructor: " . $e->getMessage() . "\n";
56+
} catch (Exception $e) {
57+
echo "Exception in destructor: " . $e->getMessage() . "\n";
58+
}
59+
60+
echo "Destructor finished: {$this->name}\n";
61+
}
62+
}
63+
64+
try {
65+
echo "Creating objects that will spawn/fiber in destructors\n";
66+
67+
$obj1 = new FiberSpawner('FiberInDestructor');
68+
$obj2 = new FiberSpawner('SpawnInDestructor');
69+
70+
echo "Starting some async operations\n";
71+
$mainCoroutine = spawn(function() {
72+
echo "Main coroutine running\n";
73+
suspend();
74+
echo "Main coroutine resumed\n";
75+
return "main done";
76+
});
77+
78+
// Force destruction by unsetting
79+
echo "Unsetting objects to trigger destructors\n";
80+
unset($obj1);
81+
unset($obj2);
82+
83+
echo "Completing main coroutine\n";
84+
$result = await($mainCoroutine);
85+
echo "Main coroutine result: " . $result . "\n";
86+
87+
} catch (Error $e) {
88+
echo "Error caught: " . $e->getMessage() . "\n";
89+
} catch (Exception $e) {
90+
echo "Exception caught: " . $e->getMessage() . "\n";
91+
}
92+
93+
echo "Test completed\n";
94+
?>
95+
--EXPECTF--
96+
Test: Fiber and spawn in destructors
97+
Creating objects that will spawn/fiber in destructors
98+
Created: FiberInDestructor
99+
Created: SpawnInDestructor
100+
Starting some async operations
101+
Unsetting objects to trigger destructors
102+
Destructing: FiberInDestructor
103+
Error in destructor: Cannot create a fiber while an True Async is active
104+
Destructor finished: FiberInDestructor
105+
Destructing: SpawnInDestructor
106+
Spawning coroutine in destructor
107+
Waiting for coroutine in destructor
108+
Main coroutine running
109+
Coroutine running in destructor
110+
Main coroutine resumed
111+
Coroutine resumed in destructor
112+
Coroutine completed with: destructor coroutine done
113+
Destructor finished: SpawnInDestructor
114+
Completing main coroutine
115+
Main coroutine result: main done
116+
Test completed

0 commit comments

Comments
 (0)