Skip to content

Commit 064b1d8

Browse files
committed
Actively wait for loop shutdown
1 parent 185bd9e commit 064b1d8

2 files changed

Lines changed: 73 additions & 20 deletions

File tree

lib/Loop.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
namespace danog\Loop;
1212

13+
use Amp\DeferredFuture;
1314
use AssertionError;
1415
use Revolt\EventLoop;
1516
use Stringable;
@@ -45,6 +46,10 @@ abstract class Loop implements Stringable
4546
* Resume deferred ID.
4647
*/
4748
private ?string $resumeImmediate = null;
49+
/**
50+
* Shutdown deferred.
51+
*/
52+
private ?DeferredFuture $shutdownDeferred = null;
4853

4954
/**
5055
* Report pause, can be overriden for logging.
@@ -65,6 +70,9 @@ public function start(): bool
6570
if ($this->running) {
6671
return false;
6772
}
73+
if ($this->shutdownDeferred) {
74+
$this->shutdownDeferred->getFuture()->await();
75+
}
6876
$this->running = true;
6977
if (!$this->resume()) {
7078
// @codeCoverageIgnoreStart
@@ -97,6 +105,14 @@ public function stop(): bool
97105
}
98106
if ($this->paused) {
99107
$this->exitedLoop();
108+
} else {
109+
if ($this->shutdownDeferred !== null) {
110+
// @codeCoverageIgnoreStart
111+
throw new AssertionError("Shutdown deferred is not null!");
112+
// @codeCoverageIgnoreEnd
113+
}
114+
$this->shutdownDeferred = new DeferredFuture;
115+
$this->shutdownDeferred->getFuture()->await();
100116
}
101117
return true;
102118
}
@@ -166,6 +182,11 @@ private function exitedLoopInternal(): void
166182
// @codeCoverageIgnoreEnd
167183
}
168184
$this->exitedLoop();
185+
if ($this->shutdownDeferred !== null) {
186+
$d = $this->shutdownDeferred;
187+
$this->shutdownDeferred = null;
188+
EventLoop::queue($d->complete(...));
189+
}
169190
}
170191
/**
171192
* Signal that loop was started.

test/LoopTest.php

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class LoopTest extends Fixtures
2323
/**
2424
* Execute pre-start assertions.
2525
*/
26-
protected function assertPreStart(BasicInterface&Loop $loop): void
26+
private function assertPreStart(BasicInterface&Loop $loop): void
2727
{
2828
$this->assertEquals(self::LOOP_NAME, "$loop");
2929

@@ -37,41 +37,38 @@ protected function assertPreStart(BasicInterface&Loop $loop): void
3737
}
3838
/**
3939
* Execute after-start assertions.
40-
*
41-
* @param bool $running Whether we should expect the loop to be running
42-
* @param bool $running Whether we should actually start the loop by returning control to the event loop
43-
*
4440
*/
45-
protected function assertAfterStart(BasicInterface&Loop $loop, bool $running = true, bool $start = true): void
41+
private function assertAfterStart(BasicInterface&Loop $loop, int $prevRun = 0): void
4642
{
47-
if ($start) {
48-
self::waitTick();
49-
}
43+
self::waitTick();
5044
$this->assertTrue($loop->inited());
5145

52-
if ($running) {
46+
if ($prevRun === 0) {
5347
$this->assertFalse($loop->ran());
48+
} else {
49+
$this->assertTrue($loop->ran());
5450
}
55-
$this->assertEquals($running, $loop->isRunning());
5651

57-
$this->assertEquals(1, $loop->startCounter());
58-
$this->assertEquals($running ? 0 : 1, $loop->endCounter());
52+
$this->assertTrue($loop->isRunning());
5953

60-
$this->assertEquals($running, !$loop->start());
61-
$this->assertEquals($running, !$loop->isPaused());
54+
$this->assertEquals($prevRun+1, $loop->startCounter());
55+
$this->assertEquals($prevRun, $loop->endCounter());
56+
57+
$this->assertFalse($loop->start());
58+
$this->assertFalse($loop->isPaused());
6259
}
6360
/**
6461
* Execute final assertions.
6562
*/
66-
protected function assertFinal(BasicInterface&Loop $loop): void
63+
private function assertFinal(BasicInterface&Loop $loop, int $count = 1): void
6764
{
6865
$this->assertTrue($loop->ran());
6966
$this->assertFalse($loop->isRunning());
7067

7168
$this->assertTrue($loop->inited());
7269

73-
$this->assertEquals(1, $loop->startCounter());
74-
$this->assertEquals(1, $loop->endCounter());
70+
$this->assertEquals($count, $loop->startCounter());
71+
$this->assertEquals($count, $loop->endCounter());
7572
}
7673
/**
7774
* Test basic loop.
@@ -92,7 +89,7 @@ public function testLoop(): void
9289
/**
9390
* Test basic loop.
9491
*/
95-
public function testLoopStopFromInside(): void
92+
public function testLoopStopFromOutside(): void
9693
{
9794
$loop = new class() extends Loop implements BasicInterface {
9895
use Basic;
@@ -103,7 +100,6 @@ public function loop(): ?float
103100
{
104101
$this->inited = true;
105102
delay(0.1);
106-
$this->stop();
107103
$this->ran = true;
108104
return 1000.0;
109105
}
@@ -112,10 +108,46 @@ public function loop(): ?float
112108
$this->assertTrue($loop->start());
113109
$this->assertAfterStart($loop);
114110

111+
$this->assertTrue($loop->stop());
115112
delay(0.110);
116113

117114
$this->assertFinal($loop);
118115
}
116+
/**
117+
* Test basic loop.
118+
*/
119+
public function testLoopStopFromOutsideRestart(): void
120+
{
121+
$loop = new class() extends Loop implements BasicInterface {
122+
use Basic;
123+
/**
124+
* Loop implementation.
125+
*/
126+
public function loop(): ?float
127+
{
128+
$this->inited = true;
129+
delay(0.1);
130+
$this->ran = true;
131+
return 1000.0;
132+
}
133+
};
134+
$this->assertPreStart($loop);
135+
$this->assertTrue($loop->start());
136+
$this->assertAfterStart($loop);
137+
138+
EventLoop::queue(function () use ($loop): void {
139+
$this->assertTrue($loop->stop());
140+
});
141+
self::waitTick();
142+
143+
$this->assertTrue($loop->start());
144+
$this->assertAfterStart($loop, 1);
145+
146+
$this->assertTrue($loop->stop());
147+
delay(0.110);
148+
149+
$this->assertFinal($loop, 2);
150+
}
119151
/**
120152
* Test basic exception in loop.
121153
*/

0 commit comments

Comments
 (0)