Skip to content

Commit 78f7f43

Browse files
authored
Merge pull request #232 from clue-labs/loop-autorun
Automatically run Loop at end of program (autorun)
2 parents 81d17c1 + 9712eea commit 78f7f43

23 files changed

+247
-39
lines changed

README.md

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ single [`run()`](#run) call that is controlled by the user.
1515
* [Usage](#usage)
1616
* [Loop](#loop)
1717
* [Loop methods](#loop-methods)
18+
* [Loop autorun](#loop-autorun)
1819
* [get()](#get)
1920
* [~~Factory~~](#factory)
2021
* [~~create()~~](#create)
@@ -76,8 +77,6 @@ Loop::addPeriodicTimer(5, function () {
7677
$formatted = number_format($memory, 3).'K';
7778
echo "Current memory usage: {$formatted}\n";
7879
});
79-
80-
Loop::run();
8180
```
8281

8382
See also the [examples](examples).
@@ -98,8 +97,6 @@ Loop::addTimer(1.0, function () use ($timer) {
9897
Loop::cancelTimer($timer);
9998
echo 'Done' . PHP_EOL;
10099
});
101-
102-
Loop::run();
103100
```
104101

105102
As an alternative, you can also explicitly create an event loop instance at the
@@ -127,12 +124,13 @@ In both cases, the program would perform the exact same steps.
127124
1. The event loop instance is created at the beginning of the program. This is
128125
implicitly done the first time you call the [`Loop` class](#loop) or
129126
explicitly when using the deprecated [`Factory::create() method`](#create)
130-
(or manually instantiating any of the [loop implementation](#loop-implementations)).
127+
(or manually instantiating any of the [loop implementations](#loop-implementations)).
131128
2. The event loop is used directly or passed as an instance to library and
132129
application code. In this example, a periodic timer is registered with the
133130
event loop which simply outputs `Tick` every fraction of a second until another
134131
timer stops the periodic timer after a second.
135-
3. The event loop is run at the end of the program with a single [`run()`](#run)
132+
3. The event loop is run at the end of the program. This is automatically done
133+
when using [`Loop` class](#loop) or explicitly with a single [`run()`](#run)
136134
call at the end of the program.
137135

138136
As of `v1.2.0`, we highly recommend using the [`Loop` class](#loop).
@@ -176,8 +174,6 @@ Loop::addTimer(1.0, function () use ($timer) {
176174
Loop::cancelTimer($timer);
177175
echo 'Done' . PHP_EOL;
178176
});
179-
180-
Loop::run();
181177
```
182178

183179
On the other hand, if you're familiar with object-oriented programming (OOP) and
@@ -208,14 +204,50 @@ class Greeter
208204
$greeter = new Greeter(Loop::get());
209205
$greeter->greet('Alice');
210206
$greeter->greet('Bob');
211-
212-
Loop::run();
213207
```
214208

215209
Each static method call will be forwarded as-is to the underlying event loop
216210
instance by using the [`Loop::get()`](#get) call internally.
217211
See [`LoopInterface`](#loopinterface) for more details about available methods.
218212

213+
#### Loop autorun
214+
215+
When using the `Loop` class, it will automatically execute the loop at the end of
216+
the program. This means the following example will schedule a timer and will
217+
automatically execute the program until the timer event fires:
218+
219+
```php
220+
use React\EventLoop\Loop;
221+
222+
Loop::addTimer(1.0, function () {
223+
echo 'Hello' . PHP_EOL;
224+
});
225+
```
226+
227+
As of `v1.2.0`, we highly recommend using the `Loop` class this way and omitting any
228+
explicit [`run()`](#run) calls. For BC reasons, the explicit [`run()`](#run)
229+
method is still valid and may still be useful in some applications, especially
230+
for a transition period towards the more concise style.
231+
232+
If you don't want the `Loop` to run automatically, you can either explicitly
233+
[`run()`](#run) or [`stop()`](#stop) it. This can be useful if you're using
234+
a global exception handler like this:
235+
236+
```php
237+
use React\EventLoop\Loop;
238+
239+
Loop::addTimer(10.0, function () {
240+
echo 'Never happens';
241+
});
242+
243+
set_exception_handler(function (Throwable $e) {
244+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
245+
Loop::stop();
246+
});
247+
248+
throw new RuntimeException('Demo');
249+
```
250+
219251
#### get()
220252

221253
The `get(): LoopInterface` method can be used to
@@ -262,8 +294,6 @@ class Greeter
262294
$greeter = new Greeter(Loop::get());
263295
$greeter->greet('Alice');
264296
$greeter->greet('Bob');
265-
266-
Loop::run();
267297
```
268298

269299
See [`LoopInterface`](#loopinterface) for more details about available methods.

examples/01-timers.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,3 @@
1111
Loop::addTimer(0.3, function () {
1212
echo 'hello ';
1313
});
14-
15-
Loop::run();

examples/02-periodic.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,3 @@
1212
Loop::cancelTimer($timer);
1313
echo 'Done' . PHP_EOL;
1414
});
15-
16-
Loop::run();

examples/03-ticks.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,3 @@
1111
echo 'c';
1212
});
1313
echo 'a';
14-
15-
Loop::run();

examples/04-signals.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,3 @@
1515
});
1616

1717
echo 'Listening for SIGINT. Use "kill -SIGINT ' . getmypid() . '" or CTRL+C' . PHP_EOL;
18-
19-
Loop::run();

examples/11-consume-stdin.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,3 @@
2424

2525
echo strlen($chunk) . ' bytes' . PHP_EOL;
2626
});
27-
28-
Loop::run();

examples/12-generate-yes.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,3 @@
3737
$data = substr($data, $r) . substr($data, 0, $r);
3838
}
3939
});
40-
41-
Loop::run();

examples/13-http-client-blocking.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,3 @@
2929

3030
echo $chunk;
3131
});
32-
33-
Loop::run();

examples/14-http-client-async.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,3 @@
5858
echo $chunk;
5959
});
6060
});
61-
62-
Loop::run();

examples/21-http-server.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,3 @@
3232
$formatted = number_format($memory, 3).'K';
3333
echo "Current memory usage: {$formatted}\n";
3434
});
35-
36-
Loop::run();

examples/91-benchmark-ticks.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,3 @@
99
for ($i = 0; $i < $n; ++$i) {
1010
Loop::futureTick(function () { });
1111
}
12-
13-
Loop::run();

examples/92-benchmark-timers.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,3 @@
99
for ($i = 0; $i < $n; ++$i) {
1010
Loop::addTimer(0, function () { });
1111
}
12-
13-
Loop::run();

examples/93-benchmark-ticks-delay.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,3 @@
1616
};
1717

1818
$tick();
19-
20-
Loop::run();

examples/94-benchmark-timers-delay.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,3 @@
1616
};
1717

1818
$tick();
19-
20-
Loop::run();

src/Loop.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ final class Loop
1212
*/
1313
private static $instance;
1414

15+
/** @var bool */
16+
private static $stopped = false;
1517

1618
/**
1719
* Returns the event loop.
@@ -31,7 +33,29 @@ public static function get()
3133
return self::$instance;
3234
}
3335

34-
self::$instance = Factory::create();
36+
self::$instance = $loop = Factory::create();
37+
38+
// Automatically run loop at end of program, unless already started or stopped explicitly.
39+
// This is tested using child processes, so coverage is actually 100%, see BinTest.
40+
// @codeCoverageIgnoreStart
41+
$hasRun = false;
42+
$loop->futureTick(function () use (&$hasRun) {
43+
$hasRun = true;
44+
});
45+
46+
$stopped =& self::$stopped;
47+
register_shutdown_function(function () use ($loop, &$hasRun, &$stopped) {
48+
// Don't run if we're coming from a fatal error (uncaught exception).
49+
$error = error_get_last();
50+
if ((isset($error['type']) ? $error['type'] : 0) & (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR)) {
51+
return;
52+
}
53+
54+
if (!$hasRun && !$stopped) {
55+
$loop->run();
56+
}
57+
});
58+
// @codeCoverageIgnoreEnd
3559

3660
return self::$instance;
3761
}
@@ -195,6 +219,7 @@ public static function run()
195219
*/
196220
public static function stop()
197221
{
222+
self::$stopped = true;
198223
self::get()->stop();
199224
}
200225
}

tests/BinTest.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
namespace React\Tests\EventLoop;
4+
5+
class BinTest extends TestCase
6+
{
7+
/**
8+
* @before
9+
*/
10+
public function setUpBin()
11+
{
12+
if (!defined('PHP_BINARY') || defined('HHVM_VERSION')) {
13+
$this->markTestSkipped('Tests not supported on legacy PHP 5.3 or HHVM');
14+
}
15+
16+
chdir(__DIR__ . '/bin/');
17+
}
18+
19+
public function testExecuteExampleWithoutLoopRunRunsLoopAndExecutesTicks()
20+
{
21+
$output = exec(escapeshellarg(PHP_BINARY) . ' 01-ticks-loop-class.php');
22+
23+
$this->assertEquals('abc', $output);
24+
}
25+
26+
public function testExecuteExampleWithExplicitLoopRunRunsLoopAndExecutesTicks()
27+
{
28+
$output = exec(escapeshellarg(PHP_BINARY) . ' 02-ticks-loop-instance.php');
29+
30+
$this->assertEquals('abc', $output);
31+
}
32+
33+
public function testExecuteExampleWithExplicitLoopRunAndStopRunsLoopAndExecutesTicksUntilStopped()
34+
{
35+
$output = exec(escapeshellarg(PHP_BINARY) . ' 03-ticks-loop-stop.php');
36+
37+
$this->assertEquals('abc', $output);
38+
}
39+
40+
public function testExecuteExampleWithUncaughtExceptionShouldNotRunLoop()
41+
{
42+
$time = microtime(true);
43+
exec(escapeshellarg(PHP_BINARY) . ' 11-uncaught.php 2>/dev/null');
44+
$time = microtime(true) - $time;
45+
46+
$this->assertLessThan(1.0, $time);
47+
}
48+
49+
public function testExecuteExampleWithUndefinedVariableShouldNotRunLoop()
50+
{
51+
$time = microtime(true);
52+
exec(escapeshellarg(PHP_BINARY) . ' 12-undefined.php 2>/dev/null');
53+
$time = microtime(true) - $time;
54+
55+
$this->assertLessThan(1.0, $time);
56+
}
57+
58+
public function testExecuteExampleWithExplicitStopShouldNotRunLoop()
59+
{
60+
$time = microtime(true);
61+
exec(escapeshellarg(PHP_BINARY) . ' 21-stop.php 2>/dev/null');
62+
$time = microtime(true) - $time;
63+
64+
$this->assertLessThan(1.0, $time);
65+
}
66+
67+
public function testExecuteExampleWithExplicitStopInExceptionHandlerShouldNotRunLoop()
68+
{
69+
$time = microtime(true);
70+
exec(escapeshellarg(PHP_BINARY) . ' 22-uncaught-stop.php 2>/dev/null');
71+
$time = microtime(true) - $time;
72+
73+
$this->assertLessThan(1.0, $time);
74+
}
75+
}

tests/bin/01-ticks-loop-class.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
use React\EventLoop\Loop;
4+
5+
require __DIR__ . '/../../vendor/autoload.php';
6+
7+
Loop::futureTick(function () {
8+
echo 'b';
9+
});
10+
Loop::futureTick(function () {
11+
echo 'c';
12+
});
13+
echo 'a';

tests/bin/02-ticks-loop-instance.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
use React\EventLoop\Loop;
4+
5+
require __DIR__ . '/../../vendor/autoload.php';
6+
7+
$loop = Loop::get();
8+
9+
$loop->futureTick(function () {
10+
echo 'b';
11+
});
12+
13+
$loop->futureTick(function () {
14+
echo 'c';
15+
});
16+
17+
echo 'a';
18+
19+
$loop->run();

tests/bin/03-ticks-loop-stop.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
use React\EventLoop\Loop;
4+
5+
require __DIR__ . '/../../vendor/autoload.php';
6+
7+
$loop = Loop::get();
8+
9+
$loop->futureTick(function () use ($loop) {
10+
echo 'b';
11+
12+
$loop->stop();
13+
14+
$loop->futureTick(function () {
15+
echo 'never';
16+
});
17+
});
18+
19+
echo 'a';
20+
21+
$loop->run();
22+
23+
echo 'c';

tests/bin/11-uncaught.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
use React\EventLoop\Loop;
4+
5+
require __DIR__ . '/../../vendor/autoload.php';
6+
7+
Loop::addTimer(10.0, function () {
8+
echo 'never';
9+
});
10+
11+
throw new RuntimeException();

tests/bin/12-undefined.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
use React\EventLoop\Loop;
4+
5+
require __DIR__ . '/../../vendor/autoload.php';
6+
7+
Loop::get()->addTimer(10.0, function () {
8+
echo 'never';
9+
});
10+
11+
$undefined->foo('bar');

0 commit comments

Comments
 (0)