Skip to content

Commit 0386cf0

Browse files
committed
Implemented LoopInterface::futureTick().
1 parent f98ff70 commit 0386cf0

File tree

6 files changed

+232
-6
lines changed

6 files changed

+232
-6
lines changed

src/React/EventLoop/LibEvLoop.php

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use libev\EventLoop;
66
use libev\IOEvent;
77
use libev\TimerEvent;
8+
use React\EventLoop\Tick\FutureTickQueue;
89
use React\EventLoop\Tick\NextTickQueue;
910
use React\EventLoop\Timer\Timer;
1011
use React\EventLoop\Timer\TimerInterface;
@@ -18,6 +19,7 @@ class LibEvLoop implements LoopInterface
1819
{
1920
private $loop;
2021
private $nextTickQueue;
22+
private $futureTickQueue;
2123
private $timerEvents;
2224
private $readEvents = [];
2325
private $writeEvents = [];
@@ -27,6 +29,7 @@ public function __construct()
2729
{
2830
$this->loop = new EventLoop();
2931
$this->nextTickQueue = new NextTickQueue($this);
32+
$this->futureTickQueue = new FutureTickQueue($this);
3033
$this->timerEvents = new SplObjectStorage();
3134
}
3235

@@ -162,13 +165,23 @@ public function nextTick(callable $listener)
162165
$this->nextTickQueue->add($listener);
163166
}
164167

168+
/**
169+
* {@inheritdoc}
170+
*/
171+
public function futureTick(callable $listener)
172+
{
173+
$this->futureTickQueue->add($listener);
174+
}
175+
165176
/**
166177
* {@inheritdoc}
167178
*/
168179
public function tick()
169180
{
170181
$this->nextTickQueue->tick();
171182

183+
$this->futureTickQueue->tick();
184+
172185
$this->loop->run(EventLoop::RUN_ONCE | EventLoop::RUN_NOWAIT);
173186
}
174187

@@ -182,11 +195,16 @@ public function run()
182195
while ($this->running) {
183196
$this->nextTickQueue->tick();
184197

185-
if (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count()) {
198+
$this->futureTickQueue->tick();
199+
200+
$flags = EventLoop::RUN_ONCE;
201+
if (!$this->futureTickQueue->isEmpty()) {
202+
$flags |= EventLoop::RUN_NOWAIT;
203+
} elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count()) {
186204
break;
187205
}
188206

189-
$this->loop->run(EventLoop::RUN_ONCE);
207+
$this->loop->run($flags);
190208
}
191209
}
192210

src/React/EventLoop/LibEventLoop.php

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Event;
66
use EventBase;
7+
use React\EventLoop\Tick\FutureTickQueue;
78
use React\EventLoop\Tick\NextTickQueue;
89
use React\EventLoop\Timer\Timer;
910
use React\EventLoop\Timer\TimerInterface;
@@ -18,6 +19,7 @@ class LibEventLoop implements LoopInterface
1819

1920
private $eventBase;
2021
private $nextTickQueue;
22+
private $futureTickQueue;
2123
private $timerCallback;
2224
private $timerEvents;
2325
private $streamCallback;
@@ -31,6 +33,7 @@ public function __construct()
3133
{
3234
$this->eventBase = event_base_new();
3335
$this->nextTickQueue = new NextTickQueue($this);
36+
$this->futureTickQueue = new FutureTickQueue($this);
3437
$this->timerEvents = new SplObjectStorage();
3538

3639
$this->createTimerCallback();
@@ -166,13 +169,23 @@ public function nextTick(callable $listener)
166169
$this->nextTickQueue->add($listener);
167170
}
168171

172+
/**
173+
* {@inheritdoc}
174+
*/
175+
public function futureTick(callable $listener)
176+
{
177+
$this->futureTickQueue->add($listener);
178+
}
179+
169180
/**
170181
* {@inheritdoc}
171182
*/
172183
public function tick()
173184
{
174185
$this->nextTickQueue->tick();
175186

187+
$this->futureTickQueue->tick();
188+
176189
event_base_loop($this->eventBase, EVLOOP_ONCE | EVLOOP_NONBLOCK);
177190
}
178191

@@ -186,11 +199,16 @@ public function run()
186199
while ($this->running) {
187200
$this->nextTickQueue->tick();
188201

189-
if (!$this->streamEvents && !$this->timerEvents->count()) {
202+
$this->futureTickQueue->tick();
203+
204+
$flags = EVLOOP_ONCE;
205+
if (!$this->futureTickQueue->isEmpty()) {
206+
$flags |= EVLOOP_NONBLOCK;
207+
} elseif (!$this->streamEvents && !$this->timerEvents->count()) {
190208
break;
191209
}
192210

193-
event_base_loop($this->eventBase, EVLOOP_ONCE);
211+
event_base_loop($this->eventBase, $flags);
194212
}
195213
}
196214

src/React/EventLoop/LoopInterface.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,15 @@ public function isTimerActive(TimerInterface $timer);
9595
*/
9696
public function nextTick(callable $listener);
9797

98+
/**
99+
* Schedule a callback to be invoked on a future tick of the event loop.
100+
*
101+
* Callbacks are guaranteed to be executed in the order they are enqueued.
102+
*
103+
* @param callable $listener The callback to invoke.
104+
*/
105+
public function futureTick(callable $listener);
106+
98107
/**
99108
* Perform a single iteration of the event loop.
100109
*/

src/React/EventLoop/StreamSelectLoop.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace React\EventLoop;
44

5+
use React\EventLoop\Tick\FutureTickQueue;
56
use React\EventLoop\Tick\NextTickQueue;
67
use React\EventLoop\Timer\Timer;
78
use React\EventLoop\Timer\TimerInterface;
@@ -13,6 +14,7 @@
1314
class StreamSelectLoop implements LoopInterface
1415
{
1516
private $nextTickQueue;
17+
private $futureTickQueue;
1618
private $timers;
1719
private $readStreams = [];
1820
private $readListeners = [];
@@ -23,6 +25,7 @@ class StreamSelectLoop implements LoopInterface
2325
public function __construct()
2426
{
2527
$this->nextTickQueue = new NextTickQueue($this);
28+
$this->futureTickQueue = new FutureTickQueue($this);
2629
$this->timers = new Timers();
2730
}
2831

@@ -135,13 +138,23 @@ public function nextTick(callable $listener)
135138
$this->nextTickQueue->add($listener);
136139
}
137140

141+
/**
142+
* {@inheritdoc}
143+
*/
144+
public function futureTick(callable $listener)
145+
{
146+
$this->futureTickQueue->add($listener);
147+
}
148+
138149
/**
139150
* {@inheritdoc}
140151
*/
141152
public function tick()
142153
{
143154
$this->nextTickQueue->tick();
144155

156+
$this->futureTickQueue->tick();
157+
145158
$this->timers->tick();
146159

147160
$this->waitForStreamActivity(0);
@@ -157,10 +170,12 @@ public function run()
157170
while ($this->running) {
158171
$this->nextTickQueue->tick();
159172

173+
$this->futureTickQueue->tick();
174+
160175
$this->timers->tick();
161176

162-
// Timers have placed more items on the next-tick queue ...
163-
if (!$this->nextTickQueue->isEmpty()) {
177+
// Next-tick or future-tick queues have pending callbacks ...
178+
if (!$this->nextTickQueue->isEmpty() || !$this->futureTickQueue->isEmpty()) {
164179
$timeout = 0;
165180

166181
// There is a pending timer, only block until it is due ...
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace React\EventLoop\Tick;
4+
5+
use React\EventLoop\LoopInterface;
6+
use SplQueue;
7+
8+
class FutureTickQueue
9+
{
10+
private $eventLoop;
11+
private $queue;
12+
13+
/**
14+
* @param LoopInterface $eventLoop The event loop passed as the first parameter to callbacks.
15+
*/
16+
public function __construct(LoopInterface $eventLoop)
17+
{
18+
$this->eventLoop = $eventLoop;
19+
$this->queue = new SplQueue();
20+
}
21+
22+
/**
23+
* Add a callback to be invoked on a future tick of the event loop.
24+
*
25+
* Callbacks are guaranteed to be executed in the order they are enqueued.
26+
*
27+
* @param callable $listener The callback to invoke.
28+
*/
29+
public function add(callable $listener)
30+
{
31+
$this->queue->enqueue($listener);
32+
}
33+
34+
/**
35+
* Flush the callback queue.
36+
*/
37+
public function tick()
38+
{
39+
// Only invoke as many callbacks as were on the queue when tick() was called.
40+
$count = $this->queue->count();
41+
42+
while ($count--) {
43+
call_user_func(
44+
$this->queue->dequeue(),
45+
$this->eventLoop
46+
);
47+
}
48+
}
49+
50+
/**
51+
* Check if the next tick queue is empty.
52+
*
53+
* @return boolean
54+
*/
55+
public function isEmpty()
56+
{
57+
return $this->queue->isEmpty();
58+
}
59+
}

tests/React/Tests/EventLoop/AbstractLoopTest.php

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,113 @@ function () {
320320
$this->loop->run();
321321
}
322322

323+
public function testFutureTick()
324+
{
325+
$called = false;
326+
327+
$callback = function ($loop) use (&$called) {
328+
$this->assertSame($this->loop, $loop);
329+
$called = true;
330+
};
331+
332+
$this->loop->futureTick($callback);
333+
334+
$this->assertFalse($called);
335+
336+
$this->loop->tick();
337+
338+
$this->assertTrue($called);
339+
}
340+
341+
public function testFutureTickFiresBeforeIO()
342+
{
343+
$stream = $this->createStream();
344+
345+
$this->loop->addWriteStream(
346+
$stream,
347+
function () {
348+
echo 'stream' . PHP_EOL;
349+
}
350+
);
351+
352+
$this->loop->futureTick(
353+
function () {
354+
echo 'future-tick' . PHP_EOL;
355+
}
356+
);
357+
358+
$this->expectOutputString('future-tick' . PHP_EOL . 'stream' . PHP_EOL);
359+
360+
$this->loop->tick();
361+
}
362+
363+
public function testRecursiveFutureTick()
364+
{
365+
$stream = $this->createStream();
366+
367+
$this->loop->addWriteStream(
368+
$stream,
369+
function () use ($stream) {
370+
echo 'stream' . PHP_EOL;
371+
$this->loop->removeWriteStream($stream);
372+
}
373+
);
374+
375+
$this->loop->futureTick(
376+
function () {
377+
echo 'future-tick-1' . PHP_EOL;
378+
$this->loop->futureTick(
379+
function () {
380+
echo 'future-tick-2' . PHP_EOL;
381+
}
382+
);
383+
}
384+
);
385+
386+
$this->expectOutputString('future-tick-1' . PHP_EOL . 'stream' . PHP_EOL . 'future-tick-2' . PHP_EOL);
387+
388+
$this->loop->run();
389+
}
390+
391+
public function testRunWaitsForFutureTickEvents()
392+
{
393+
$stream = $this->createStream();
394+
395+
$this->loop->addWriteStream(
396+
$stream,
397+
function () use ($stream) {
398+
$this->loop->removeStream($stream);
399+
$this->loop->futureTick(
400+
function () {
401+
echo 'future-tick' . PHP_EOL;
402+
}
403+
);
404+
}
405+
);
406+
407+
$this->expectOutputString('future-tick' . PHP_EOL);
408+
409+
$this->loop->run();
410+
}
411+
412+
public function testFutureTickEventGeneratedByTimer()
413+
{
414+
$this->loop->addTimer(
415+
0.001,
416+
function () {
417+
$this->loop->futureTick(
418+
function () {
419+
echo 'future-tick' . PHP_EOL;
420+
}
421+
);
422+
}
423+
);
424+
425+
$this->expectOutputString('future-tick' . PHP_EOL);
426+
427+
$this->loop->run();
428+
}
429+
323430
private function assertRunFasterThan($maxInterval)
324431
{
325432
$start = microtime(true);

0 commit comments

Comments
 (0)