Skip to content

Commit 73eadfe

Browse files
committed
tests: port async suite to interface-based scheduler registration
1 parent 0b1ba73 commit 73eadfe

12 files changed

Lines changed: 153 additions & 176 deletions

Zend/tests/async/fiber_lowlevel_null.phpt

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
--TEST--
2-
intercept_fiber returning null keeps the fiber on the low-level path
2+
interceptFiber returning null keeps the fiber on the low-level path
33
--FILE--
44
<?php
5-
Async\SchedulerHook::register('test', [
6-
Async\SchedulerHook::INTERCEPT_FIBER => function (Fiber $fiber): ?object {
5+
Async\SchedulerHook::register('test', new class extends Async\AbstractScheduler {
6+
public function interceptFiber(Fiber $fiber): ?object {
77
echo "intercept: null\n";
88
return null;
9-
},
10-
Async\SchedulerHook::SUSPEND => function (bool $fromMain, bool $isBailout): bool {
9+
}
10+
public function enqueue(object $coroutine): bool { return true; }
11+
public function suspend(bool $fromMain, bool $isBailout): bool {
1112
// Never called for the low-level fiber itself; the engine invokes it
1213
// for the after-main handover (script end, then after destructors).
1314
echo "suspend(fromMain: ", var_export($fromMain, true), ")\n";
1415
return true;
15-
},
16-
]);
16+
}
17+
});
1718

1819
// A low-level fiber behaves exactly as classic Fiber even with a scheduler.
1920
$fiber = new Fiber(function (int $x): int {

Zend/tests/async/fiber_managed_after_main.phpt

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,30 @@
22
After-main handover runs deferred managed coroutines; abandoned ones die cleanly
33
--FILE--
44
<?php
5-
$queue = new SplQueue();
6-
7-
Async\SchedulerHook::register('test', [
8-
Async\SchedulerHook::INTERCEPT_FIBER => fn (Fiber $fiber): object
9-
=> new class($fiber) {
5+
Async\SchedulerHook::register('test', new class extends Async\AbstractScheduler {
6+
public SplQueue $queue;
7+
public function __construct() { $this->queue = new SplQueue(); }
8+
public function interceptFiber(Fiber $fiber): ?object {
9+
return new class($fiber) {
1010
public function __construct(public readonly Fiber $fiber) {}
11-
},
12-
Async\SchedulerHook::ENQUEUE => function (object $coroutine) use ($queue): bool {
13-
$queue->enqueue($coroutine);
11+
};
12+
}
13+
public function enqueue(object $coroutine): bool {
14+
$this->queue->enqueue($coroutine);
1415
return true;
15-
},
16-
Async\SchedulerHook::SUSPEND => function (bool $fromMain, bool $isBailout) use ($queue): bool {
16+
}
17+
public function suspend(bool $fromMain, bool $isBailout): bool {
1718
// This scheduler defers everything: nothing runs until after main.
1819
if (!$fromMain) {
1920
return true;
2021
}
21-
22-
while (!$queue->isEmpty()) {
23-
$fiber = $queue->dequeue()->fiber;
22+
while (!$this->queue->isEmpty()) {
23+
$fiber = $this->queue->dequeue()->fiber;
2424
$fiber->isStarted() ? $fiber->resume() : $fiber->start();
2525
}
26-
2726
return true;
28-
},
29-
]);
27+
}
28+
});
3029

3130
$fiber = new Fiber(function (): void {
3231
echo "ran after main\n";
@@ -39,9 +38,6 @@ var_dump($fiber->start());
3938
var_dump($fiber->isStarted());
4039

4140
echo "end of script\n";
42-
// After-main handover: the scheduler drains its queue, the fiber runs and
43-
// suspends. Nobody resumes it, so it is destroyed at shutdown without
44-
// completing; the second echo never happens.
4541
?>
4642
--EXPECT--
4743
NULL

Zend/tests/async/fiber_managed_basic.phpt

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,40 @@
22
A fiber adopted by the scheduler runs through the coroutine path
33
--FILE--
44
<?php
5-
$queue = new SplQueue();
6-
$log = [];
7-
8-
Async\SchedulerHook::register('test', [
9-
Async\SchedulerHook::INTERCEPT_FIBER => function (Fiber $fiber) use (&$log): object {
10-
$log[] = 'intercept';
11-
12-
// The scheduler defines the coroutine; here it simply remembers
13-
// which fiber the coroutine drives.
5+
$scheduler = new class extends Async\AbstractScheduler {
6+
public SplQueue $queue;
7+
public array $log = [];
8+
public function __construct() { $this->queue = new SplQueue(); }
9+
public function interceptFiber(Fiber $fiber): ?object {
10+
$this->log[] = 'intercept';
1411
return new class($fiber) {
1512
public function __construct(public readonly Fiber $fiber) {}
1613
};
17-
},
18-
Async\SchedulerHook::ENQUEUE => function (object $coroutine) use ($queue, &$log): bool {
19-
$log[] = 'enqueue';
20-
$queue->enqueue($coroutine);
14+
}
15+
public function enqueue(object $coroutine): bool {
16+
$this->log[] = 'enqueue';
17+
$this->queue->enqueue($coroutine);
2118
return true;
22-
},
23-
Async\SchedulerHook::RESUME => function (object $coroutine, ?Throwable $error) use ($queue, &$log): bool {
24-
$log[] = 'resume';
25-
$queue->enqueue($coroutine);
19+
}
20+
public function resume(object $coroutine, ?Throwable $error = null): bool {
21+
$this->log[] = 'resume';
22+
$this->queue->enqueue($coroutine);
2623
return true;
27-
},
28-
Async\SchedulerHook::SUSPEND => function (bool $fromMain, bool $isBailout) use ($queue, &$log): bool {
29-
$log[] = 'suspend';
30-
31-
while (!$queue->isEmpty()) {
32-
$fiber = $queue->dequeue()->fiber;
24+
}
25+
public function suspend(bool $fromMain, bool $isBailout): bool {
26+
$this->log[] = 'suspend';
27+
while (!$this->queue->isEmpty()) {
28+
$fiber = $this->queue->dequeue()->fiber;
3329
$fiber->isStarted() ? $fiber->resume() : $fiber->start();
34-
3530
if (!$fromMain) {
3631
return true;
3732
}
3833
}
39-
4034
return true;
41-
},
42-
]);
35+
}
36+
};
37+
38+
Async\SchedulerHook::register('test', $scheduler);
4339

4440
$fiber = new Fiber(function (int $x): int {
4541
$y = Fiber::suspend($x + 1);
@@ -51,7 +47,7 @@ var_dump($fiber->isSuspended());
5147
var_dump($fiber->resume(4));
5248
var_dump($fiber->isTerminated());
5349
var_dump($fiber->getReturn());
54-
echo implode(',', $log), "\n";
50+
echo implode(',', $scheduler->log), "\n";
5551
?>
5652
--EXPECT--
5753
int(6)

Zend/tests/async/fiber_managed_throw.phpt

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,41 @@
22
Fiber::throw() on a managed fiber delivers the exception at the suspension point
33
--FILE--
44
<?php
5-
$queue = new SplQueue();
6-
7-
Async\SchedulerHook::register('test', [
8-
Async\SchedulerHook::INTERCEPT_FIBER => fn (Fiber $fiber): object
9-
=> new class($fiber) {
5+
Async\SchedulerHook::register('test', new class extends Async\AbstractScheduler {
6+
public SplQueue $queue;
7+
public function __construct() { $this->queue = new SplQueue(); }
8+
public function interceptFiber(Fiber $fiber): ?object {
9+
return new class($fiber) {
1010
public function __construct(public readonly Fiber $fiber) {}
11-
},
12-
Async\SchedulerHook::ENQUEUE => function (object $coroutine) use ($queue): bool {
13-
$queue->enqueue($coroutine);
11+
};
12+
}
13+
public function enqueue(object $coroutine): bool {
14+
$this->queue->enqueue($coroutine);
1415
return true;
15-
},
16-
Async\SchedulerHook::RESUME => function (object $coroutine, ?Throwable $error) use ($queue): bool {
16+
}
17+
public function resume(object $coroutine, ?Throwable $error = null): bool {
1718
echo "resume hook error: ", $error === null ? 'none' : get_class($error), "\n";
18-
$queue->enqueue($coroutine);
19+
$this->queue->enqueue($coroutine);
1920
return true;
20-
},
21-
Async\SchedulerHook::SUSPEND => function (bool $fromMain, bool $isBailout) use ($queue): bool {
22-
while (!$queue->isEmpty()) {
23-
$fiber = $queue->dequeue()->fiber;
21+
}
22+
public function suspend(bool $fromMain, bool $isBailout): bool {
23+
while (!$this->queue->isEmpty()) {
24+
$fiber = $this->queue->dequeue()->fiber;
2425
$fiber->isStarted() ? $fiber->resume() : $fiber->start();
25-
2626
if (!$fromMain) {
2727
return true;
2828
}
2929
}
30-
3130
return true;
32-
},
33-
]);
31+
}
32+
});
3433

3534
$fiber = new Fiber(function (): string {
3635
try {
3736
Fiber::suspend('waiting');
3837
} catch (RuntimeException $e) {
3938
return 'caught: ' . $e->getMessage();
4039
}
41-
4240
return 'not reached';
4341
});
4442

Zend/tests/async/fiber_managed_uncaught.phpt

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,29 @@
22
An uncaught exception in a managed fiber surfaces at the start()/resume() caller
33
--FILE--
44
<?php
5-
$queue = new SplQueue();
6-
7-
Async\SchedulerHook::register('test', [
8-
Async\SchedulerHook::INTERCEPT_FIBER => fn (Fiber $fiber): object
9-
=> new class($fiber) {
5+
Async\SchedulerHook::register('test', new class extends Async\AbstractScheduler {
6+
public SplQueue $queue;
7+
public function __construct() { $this->queue = new SplQueue(); }
8+
public function interceptFiber(Fiber $fiber): ?object {
9+
return new class($fiber) {
1010
public function __construct(public readonly Fiber $fiber) {}
11-
},
12-
Async\SchedulerHook::ENQUEUE => function (object $coroutine) use ($queue): bool {
13-
$queue->enqueue($coroutine);
11+
};
12+
}
13+
public function enqueue(object $coroutine): bool {
14+
$this->queue->enqueue($coroutine);
1415
return true;
15-
},
16-
Async\SchedulerHook::SUSPEND => function (bool $fromMain, bool $isBailout) use ($queue): bool {
17-
while (!$queue->isEmpty()) {
18-
$fiber = $queue->dequeue()->fiber;
16+
}
17+
public function suspend(bool $fromMain, bool $isBailout): bool {
18+
while (!$this->queue->isEmpty()) {
19+
$fiber = $this->queue->dequeue()->fiber;
1920
$fiber->isStarted() ? $fiber->resume() : $fiber->start();
20-
2121
if (!$fromMain) {
2222
return true;
2323
}
2424
}
25-
2625
return true;
27-
},
28-
]);
26+
}
27+
});
2928

3029
$fiber = new Fiber(function (): void {
3130
throw new RuntimeException('escaped');

Zend/tests/async/microtasks_basic.phpt

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
--TEST--
2-
Microtasks: defer() forwards to the scheduler's DEFER hook; the queue is the scheduler's
2+
Microtasks: defer() forwards to the scheduler's defer() hook; the queue is the scheduler's
33
--FILE--
44
<?php
5-
$tasks = new SplQueue();
6-
7-
Async\SchedulerHook::register('test', [
8-
Async\SchedulerHook::SUSPEND => function (bool $fromMain, bool $isBailout): bool {
9-
return true;
10-
},
11-
Async\SchedulerHook::DEFER => function (callable $task) use ($tasks): bool {
5+
$scheduler = new class extends Async\AbstractScheduler {
6+
public SplQueue $tasks;
7+
public function __construct() { $this->tasks = new SplQueue(); }
8+
public function enqueue(object $coroutine): bool { return true; }
9+
public function suspend(bool $fromMain, bool $isBailout): bool { return true; }
10+
public function defer(callable $task): bool {
1211
// The queue is owned by the scheduler, not by the engine.
13-
$tasks->enqueue($task);
12+
$this->tasks->enqueue($task);
1413
return true;
15-
},
16-
]);
14+
}
15+
};
1716

18-
Async\SchedulerHook::defer(function () use ($tasks): void {
17+
Async\SchedulerHook::register('test', $scheduler);
18+
19+
Async\SchedulerHook::defer(function (): void {
1920
echo "task 1\n";
2021

2122
// Queued while draining: the scheduler decides the semantics; this
@@ -30,8 +31,8 @@ Async\SchedulerHook::defer(function (): void {
3031
});
3132

3233
// The scheduler drains its own queue on its tick; simulate one here.
33-
while (!$tasks->isEmpty()) {
34-
($tasks->dequeue())();
34+
while (!$scheduler->tasks->isEmpty()) {
35+
($scheduler->tasks->dequeue())();
3536
}
3637

3738
echo "drained\n";

Zend/tests/async/scheduler_context.phpt

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,28 @@
22
Fiber operations on a bound fiber: direct inside the scheduler, routed outside
33
--FILE--
44
<?php
5-
$queue = new SplQueue();
6-
$log = [];
7-
8-
Async\SchedulerHook::register('test', [
9-
Async\SchedulerHook::INTERCEPT_FIBER => fn (Fiber $fiber): object
10-
=> new class($fiber) {
5+
$scheduler = new class extends Async\AbstractScheduler {
6+
public SplQueue $queue;
7+
public array $log = [];
8+
public function __construct() { $this->queue = new SplQueue(); }
9+
public function interceptFiber(Fiber $fiber): ?object {
10+
return new class($fiber) {
1111
public function __construct(public readonly Fiber $fiber) {}
12-
},
13-
Async\SchedulerHook::ENQUEUE => function (object $coroutine) use ($queue, &$log): bool {
14-
$log[] = 'enqueue';
15-
$queue->enqueue($coroutine);
12+
};
13+
}
14+
public function enqueue(object $coroutine): bool {
15+
$this->log[] = 'enqueue';
16+
$this->queue->enqueue($coroutine);
1617
return true;
17-
},
18-
Async\SchedulerHook::RESUME => function (object $coroutine, ?Throwable $error) use ($queue, &$log): bool {
19-
$log[] = 'resume-hook';
20-
$queue->enqueue($coroutine);
18+
}
19+
public function resume(object $coroutine, ?Throwable $error = null): bool {
20+
$this->log[] = 'resume-hook';
21+
$this->queue->enqueue($coroutine);
2122
return true;
22-
},
23-
Async\SchedulerHook::SUSPEND => function (bool $fromMain, bool $isBailout) use ($queue, &$log): bool {
24-
while (!$queue->isEmpty()) {
25-
$fiber = $queue->dequeue()->fiber;
23+
}
24+
public function suspend(bool $fromMain, bool $isBailout): bool {
25+
while (!$this->queue->isEmpty()) {
26+
$fiber = $this->queue->dequeue()->fiber;
2627

2728
// Inside a hook this is a DIRECT switch: no hooks re-enter.
2829
$fiber->isStarted() ? $fiber->resume() : $fiber->start();
@@ -31,10 +32,11 @@ Async\SchedulerHook::register('test', [
3132
return true;
3233
}
3334
}
34-
3535
return true;
36-
},
37-
]);
36+
}
37+
};
38+
39+
Async\SchedulerHook::register('test', $scheduler);
3840

3941
$fiber = new Fiber(function (): string {
4042
Fiber::suspend('first');
@@ -47,7 +49,7 @@ var_dump($fiber->resume());
4749
var_dump($fiber->getReturn());
4850

4951
// The scheduler's direct switches fired no extra hook calls:
50-
echo implode(',', $log), "\n";
52+
echo implode(',', $scheduler->log), "\n";
5153

5254
// A finished bound fiber cannot be resumed, same rule as classic fibers.
5355
try {

0 commit comments

Comments
 (0)