Skip to content

Commit 5330d42

Browse files
danogbwoebitrowski
authored
Fix #105 (#121)
Co-authored-by: Bob Weinand <bobwei9@hotmail.com> Co-authored-by: Aaron Piotrowski <aaron@trowski.com>
1 parent 1cb5ce8 commit 5330d42

12 files changed

Lines changed: 249 additions & 7 deletions

examples/benchmark-ticks.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
$n = isset($argv[1]) ? (int) $argv[1] : 1000 * 100;
1010

1111
for ($i = 0; $i < $n; ++$i) {
12-
EventLoop::defer(function () { });
12+
EventLoop::defer(function () {
13+
});
1314
}
1415

1516
EventLoop::run();

examples/benchmark-timers.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
$n = isset($argv[1]) ? (int) $argv[1] : 1000 * 100;
1010

1111
for ($i = 0; $i < $n; ++$i) {
12-
EventLoop::delay(0, function () { });
12+
EventLoop::delay(0, function () {
13+
});
1314
}
1415

1516
EventLoop::run();

phpunit.xml.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<testsuites>
1414
<testsuite name="Main">
1515
<directory>test</directory>
16+
<directory suffix=".phpt">test</directory>
1617
</testsuite>
1718
</testsuites>
1819
</phpunit>

src/EventLoop/Driver/EvDriver.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ public function __destruct()
9191
// We need to clear all references to events manually, see
9292
// https://bitbucket.org/osmanov/pecl-ev/issues/31/segfault-in-ev_timer_stop
9393
$this->events = [];
94+
95+
// Reinitialize the loop handle due to indeterminate destruct order.
96+
// See https://github.com/revoltphp/event-loop/issues/105
97+
$this->handle = new \EvLoop();
9498
}
9599

96100
/**

src/EventLoop/Driver/EventDriver.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,11 @@ public function __destruct()
9191
/** @psalm-suppress RedundantPropertyInitializationCheck */
9292
if (isset($this->handle)) {
9393
$this->handle->free();
94-
unset($this->handle);
94+
95+
// Reinitialize the loop handle due to indeterminate destruct order.
96+
// See https://github.com/revoltphp/event-loop/issues/105
97+
/** @psalm-suppress TooFewArguments https://github.com/JetBrains/phpstorm-stubs/pull/763 */
98+
$this->handle = new \EventBase();
9599
}
96100
}
97101

src/EventLoop/Driver/UvDriver.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,17 @@ public function cancel(string $callbackId): void
110110
unset($this->events[$callbackId]);
111111
}
112112

113+
public function __destruct()
114+
{
115+
$this->events = [];
116+
$this->uvCallbacks = [];
117+
$this->streams = [];
118+
119+
// Reinitialize the loop handle due to indeterminate destruct order.
120+
// See https://github.com/revoltphp/event-loop/issues/105
121+
$this->handle = \uv_loop_new();
122+
}
123+
113124
/**
114125
* @return \UVLoop|resource
115126
*/

src/EventLoop/Internal/AbstractDriver.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -498,9 +498,14 @@ private function invokeCallbacks(): void
498498
{
499499
while (!$this->microtaskQueue->isEmpty() || !$this->callbackQueue->isEmpty()) {
500500
/** @noinspection PhpUnhandledExceptionInspection */
501-
$yielded = $this->callbackFiber->isStarted()
502-
? $this->callbackFiber->resume()
503-
: $this->callbackFiber->start();
501+
if ($this->callbackFiber->isSuspended()) {
502+
$yielded = $this->callbackFiber->resume();
503+
} else {
504+
if ($this->callbackFiber->isTerminated()) {
505+
$this->createCallbackFiber();
506+
}
507+
$yielded = $this->callbackFiber->start();
508+
}
504509

505510
if ($yielded !== $this->internalSuspensionMarker) {
506511
$this->createCallbackFiber();

test/Driver/TimerQueueTest.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,16 @@ public function testHeapOrder(): void
4747
$id = 'a';
4848
$callbacks = [];
4949
foreach ($values as $value) {
50-
$callback = new TimerCallback($id++, $value, static function () {
50+
$callback = new TimerCallback($id, $value, static function () {
5151
}, $value);
5252
$callbacks[] = $callback;
53+
54+
if (\PHP_VERSION_ID >= 80300) {
55+
/** @psalm-suppress UndefinedFunction */
56+
$id = \str_increment($id);
57+
} else {
58+
$id++;
59+
}
5360
}
5461

5562
$toRemove = $callbacks[$indexToRemove];
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
--TEST--
2+
Issue #105: Ensure the callback fiber is always alive as long as the event loop lives (ev driver)
3+
--SKIPIF--
4+
<?php
5+
6+
if (PHP_VERSION_ID < 80400) {
7+
echo 'skip PHP 8.4+ required';
8+
}
9+
10+
if (!\extension_loaded('ev')) {
11+
echo 'skip ev extension required';
12+
}
13+
14+
?>
15+
--FILE--
16+
<?php
17+
18+
use Revolt\EventLoop;
19+
use Revolt\EventLoop\Driver\EvDriver;
20+
21+
require 'vendor/autoload.php';
22+
23+
EventLoop::setDriver(new EvDriver());
24+
25+
final class a {
26+
private static self $a;
27+
public static function getInstance(): self {
28+
return self::$a ??= new self;
29+
}
30+
31+
public function __destruct()
32+
{
33+
echo "Destroying ", self::class, "\n";
34+
$suspension = EventLoop::getSuspension();
35+
EventLoop::delay(1.0, $suspension->resume(...));
36+
$suspension->suspend();
37+
echo "Finished " . self::class, "\n";
38+
}
39+
}
40+
41+
EventLoop::defer(function () {
42+
echo "start\n";
43+
});
44+
45+
a::getInstance();
46+
47+
EventLoop::run();
48+
49+
?>
50+
--EXPECT--
51+
start
52+
Destroying a
53+
Finished a
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
--TEST--
2+
Issue #105: Ensure the callback fiber is always alive as long as the event loop lives (event driver)
3+
--SKIPIF--
4+
<?php
5+
6+
if (PHP_VERSION_ID < 80400) {
7+
echo 'skip PHP 8.4+ required';
8+
}
9+
10+
if (!\extension_loaded('event')) {
11+
echo 'skip event extension required';
12+
}
13+
14+
?>
15+
--FILE--
16+
<?php
17+
18+
use Revolt\EventLoop;
19+
use Revolt\EventLoop\Driver\EventDriver;
20+
21+
require 'vendor/autoload.php';
22+
23+
EventLoop::setDriver(new EventDriver());
24+
25+
final class a {
26+
private static self $a;
27+
public static function getInstance(): self {
28+
return self::$a ??= new self;
29+
}
30+
31+
public function __destruct()
32+
{
33+
echo "Destroying ", self::class, "\n";
34+
$suspension = EventLoop::getSuspension();
35+
EventLoop::delay(1.0, $suspension->resume(...));
36+
$suspension->suspend();
37+
echo "Finished " . self::class, "\n";
38+
}
39+
}
40+
41+
EventLoop::defer(function () {
42+
echo "start\n";
43+
});
44+
45+
a::getInstance();
46+
47+
EventLoop::run();
48+
49+
?>
50+
--EXPECT--
51+
start
52+
Destroying a
53+
Finished a

0 commit comments

Comments
 (0)