8000 Merge pull request #112 from WyriHaximus-labs/blacklight · reactphp/event-loop@1ed8f4b · GitHub
[go: up one dir, main page]

Skip to content

Commit 1ed8f4b

Browse files
authored
Merge pull request #112 from WyriHaximus-labs/blacklight
UV Event Loop (PECL ext-uv)
2 parents d57bc26 + d589f9f commit 1ed8f4b

File tree

8 files changed

+379
-4
lines changed

8 files changed

+379
-4
lines changed

.travis.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ cache:
3232
directories:
3333
- $HOME/.composer/cache/files
3434

35+
before_install:
36+
- sudo add-apt-repository ppa:ondrej/php -y
37+
- sudo apt-get update -q
38+
- sudo apt-get install libuv1-dev || true
39+
3540
install:
3641
- ./travis-init.sh
3742
- composer install

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ single [`run()`](#run) call that is controlled by the user.
2121
* [ExtLibeventLoop](#extlibeventloop)
2222
* [ExtLibevLoop](#extlibevloop)
2323
* [ExtEvLoop](#extevloop)
24+
* [ExtUvLoop](#extuvloop)
2425
* [LoopInterface](#loopinterface)
2526
* [run()](#run)
2627
* [stop()](#stop)
@@ -208,6 +209,14 @@ provides an interface to `libev` library.
208209

209210
This loop is known to work with PHP 5.4 through PHP 7+.
210211

212+
#### ExtUvLoop
213+
214+
An `ext-uv` based event loop.
215+
216+
This loop uses the [`uv` PECL extension](https://pecl.php.net/package/uv), that
217+
provides an interface to `libuv` library.
218+
219+
This loop is known to work with PHP 7+.
211220

212221
#### ExtLibeventLoop
213222

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
},
1212
"suggest": {
1313
"ext-event": "~1.0 for ExtEventLoop",
14-
"ext-pcntl": "For signal handling support when using the StreamSelectLoop"
14+
"ext-pcntl": "For signal handling support when using the StreamSelectLoop",
15+
"ext-uv": "* for ExtUvLoop"
1516
},
1617
"autoload": {
1718
"psr-4": {

src/ExtUvLoop.php

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
<?php
2+
3+
namespace React\EventLoop;
4+
5+
use React\EventLoop\Tick\FutureTickQueue;
6+
use React\EventLoop\Timer\Timer;
7+
use SplObjectStorage;
8+
9+
/**
10+
* An `ext-uv` based event loop.
11+
*
12+
* This loop uses the [`uv` PECL extension](https://pecl.php.net/package/uv),
13+
* that provides an interface to `libuv` library.
14+
*
15+
* This loop is known to work with PHP 7+.
16+
*
17+
* @see https://github.com/bwoebi/php-uv
18+
*/
19+
final class ExtUvLoop implements LoopInterface
20+
{
21+
private $uv;
22+
private $futureTickQueue;
23+
private $timers;
24+
private $streamEvents = array();
25+
private $readStreams = array();
26+
private $writeStreams = array();
27+
private $running;
28+
private $signals;
29+
private $signalEvents = array();
30+
private $streamListener;
31+
32+
public function __construct()
33+
{
34+
if (!\function_exists('uv_loop_new')) {
35+
throw new \BadMethodCallException('Cannot create LibUvLoop, ext-uv extension missing');
36+
}
37+
38+
$this->uv = \uv_loop_new();
39+
$this->futureTickQueue = new FutureTickQueue();
40+
$this->timers = new SplObjectStorage();
41+
$this->streamListener = $this->createStreamListener();
42+
$this->signals = new SignalsHandler();
43+
}
44+
45+
/**
46+
* Returns the underlying ext-uv event loop. (Internal ReactPHP use only.)
47+
*
48+
* @internal
49+
*
50+
* @return resource
51+
*/
52+
public function getUvLoop()
53+
{
54+
return $this->uv;
55+
}
56+
57+
/**
58+
* {@inheritdoc}
59+
*/
60+
public function addReadStream($stream, $listener)
61+
{
62+
if (isset($this->readStreams[(int) $stream])) {
63+
return;
64+
}
65+
66+
$this->readStreams[(int) $stream] = $listener;
67+
$this->addStream($stream);
68+
}
69+
70+
/**
71+
* {@inheritdoc}
72+
*/
73+
public function addWriteStream($stream, $listener)
74+
{
75+
if (isset($this->writeStreams[(int) $stream])) {
76+
return;
77+
}
78+
79+
$this->writeStreams[(int) $stream] = $listener;
80+
$this->addStream($stream);
81+
}
82+
83+
/**
84+
* {@inheritdoc}
85+
*/
86+
public function removeReadStream($stream)
87+
{
88+
if (!isset($this->streamEvents[(int) $stream])) {
89+
return;
90+
}
91+
92+
unset($this->readStreams[(int) $stream]);
93+
$this->removeStream($stream);
94+
}
95+
96+
/**
97+
* {@inheritdoc}
98+
*/
99+
public function removeWriteStream($stream)
100+
{
101+
if (!isset($this->streamEvents[(int) $stream])) {
102+
return;
103+
}
104+
105+
unset($this->writeStreams[(int) $stream]);
106+
$this->removeStream($stream);
107+
}
108+
109+
/**
110+
* {@inheritdoc}
111+
*/
112+
public function addTimer($interval, $callback)
113+
{
114+
$timer = new Timer($interval, $callback, false);
115+
116+
$that = $this;
117+
$timers = $this->timers;
118+
$callback = function () use ($timer, $timers, $that) {
119+
\call_user_func($timer->getCallback(), $timer);
120+
121+
if ($timers->contains($timer)) {
122+
$that->cancelTimer($timer);
123+
}
124+
};
125+
126+
$event = \uv_timer_init($this->uv);
127+
$this->timers->attach($timer, $event);
128+
\uv_timer_start(
129+
$event,
130+
(int) ($interval * 1000) + 1,
131+
0,
132+
$callback
133+
);
134+
135+
return $timer;
136+
}
137+
138+
/**
139+
* {@inheritdoc}
140+
*/
141+
public function addPeriodicTimer($interval, $callback)
142+
{
143+
$timer = new Timer($interval, $callback, true);
144+
145+
$callback = function () use ($timer) {
146+
\call_user_func($timer->getCallback(), $timer);
147+
};
148+
149+
$event = \uv_timer_init($this->uv);
150+
$this->timers->attach($timer, $event);
151+
\uv_timer_start(
152+
$event,
153+
(int) ($interval * 1000) + 1,
154+
(int) ($interval * 1000) + 1,
155+
$callback
156+
);
157+
158+
return $timer;
159+
}
160+
161+
/**
162+
* {@inheritdoc}
163+
*/
164+
public function cancelTimer(TimerInterface $timer)
165+
{
166+
if (isset($this->timers[$timer])) {
167+
@\uv_timer_stop($this->timers[$timer]);
168+
$this->timers->detach($timer);
169+
}
170+
}
171+
172+
/**
173+
* {@inheritdoc}
174+
*/
175+
public function futureTick($listener)
176+
{
177+
$this->futureTickQueue->add($listener);
178+
}
179+
180+
public function addSignal($signal, $listener)
181+
{
182+
$this->signals->add($signal, $listener);
183+
184+
if (!isset($this->signalEvents[$signal])) {
185+
$signals = $this->signals;
186+
$this->signalEvents[$signal] = \uv_signal_init($this->uv);
187+
\uv_signal_start($this->signalEvents[$signal], function () use ($signals, $signal) {
188+
$signals->call($signal);
189+
}, $signal);
190+
}
191+
}
192+
193+
public function removeSignal($signal, $listener)
194+
{
195+
$this->signals->remove($signal, $listener);
196+
197+
if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
198+
\uv_signal_stop($this->signalEvents[$signal]);
199+
unset($this->signalEvents[$signal]);
200+
}
201+
}
202+
203+
/**
204+
* {@inheritdoc}
205+
*/
206+
public function run()
207+
{
208+
$this->running = true;
209+
210+
while ($this->running) {
211+
$this->futureTickQueue->tick();
212+
213+
$hasPendingCallbacks = !$this->futureTickQueue->isEmpty();
214+
$wasJustStopped = !$this->running;
215+
$nothingLeftToDo = !$this->readStreams
216+
&& !$this->writeStreams
217+
&& !$this->timers->count()
218+
&& $this->signals->isEmpty();
219+
220+
// Use UV::RUN_ONCE when there are only I/O events active in the loop and block until one of those triggers,
221+
// otherwise use UV::RUN_NOWAIT.
222+
// @link http://docs.libuv.org/en/v1.x/loop.html#c.uv_run
223+
$flags = \UV::RUN_ONCE;
224+
if ($wasJustStopped || $hasPendingCallbacks) {
225+
$flags = \UV::RUN_NOWAIT;
226+
} elseif ($nothingLeftToDo) {
227+
break;
228+
}
229+
230+
\uv_run($this->uv, $flags);
231+
}
232+
}
233+
234+
/**
235+
* {@inheritdoc}
236+
*/
237+
public function stop()
238+
{
239+
$this->running = false;
240+
}
241+
242+
private function addStream($stream)
243+
{
244+
if (!isset($this->streamEvents[(int) $stream])) {
245+
$this->streamEvents[(int)$stream] = \uv_poll_init_socket($this->uv, $stream);
246+
}
247+
248+
if ($this->streamEvents[(int) $stream] !== false) {
249+
$this->pollStream($stream);
250+
}
251+
}
252+
253+
private function removeStream($stream)
254+
{
255+
if (!isset($this->streamEvents[(int) $stream])) {
256+
return;
257+
}
258+
259+
if (!isset($this->readStreams[(int) $stream])
260+
&& !isset($this->writeStreams[(int) $stream])) {
261+
\uv_poll_stop($this->streamEvents[(int) $stream]);
262+
\uv_close($this->streamEvents[(int) $stream]);
263+
unset($this->streamEvents[(int) $stream]);
264+
return;
265+
}
266+
267+
$this->pollStream($stream);
268+
}
269+
270+
private function pollStream($stream)
271+
{
272+
if (!isset($this->streamEvents[(int) $stream])) {
273+
return;
274+
}
275+
276+
$flags = 0;
277+
if (isset($this->readStreams[(int) $stream])) {
278+
$flags |= \UV::READABLE;
279+
}
280+
281+
if (isset($this->writeStreams[(int) $stream])) {
282+
$flags |= \UV::WRITABLE;
283+
}
284+
285+
\uv_poll_start($this->streamEvents[(int) $stream], $flags, $this->streamListener);
286+
}
287+
288+
/**
289+
* Create a stream listener
290+
*
291+
* @return callable Returns a callback
292+
*/
293+
private function createStreamListener()
294+
{
295+
$callback = function ($event, $status, $events, $stream) {
296+
if (!isset($this->streamEvents[(int) $stream])) {
297+
return;
298+
}
299+
300+
if (($events | 4) === 4) {
301+
// Disconnected
302+
return;
303+
}
304+
305+
if (isset($this->readStreams[(int) $stream]) && ($events & \UV::READABLE)) {
306+
\call_user_func($this->readStreams[(int) $stream], $stream);
307+
}
308+
309+
if (isset($this->writeStreams[(int) $stream]) && ($events & \UV::WRITABLE)) {
310+
\call_user_func($this->writeStreams[(int) $stream], $stream);
311+
}
312+
};
313+
314+
return $callback;
315+
}
316+
}

src/Factory.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,17 @@ final class Factory
2424
public static function create()
2525
{
2626
// @codeCoverageIgnoreStart
27-
if (\class_exists('libev\EventLoop', false)) {
27+
if (\function_exists('uv_loop_new')) {
28+
// only use ext-uv on PHP 7
29+
return new ExtUvLoop();
30+
} elseif (\class_exists('libev\EventLoop', false)) {
2831
return new ExtLibevLoop();
2932
} elseif (\class_exists('EvLoop', false)) {
3033
return new ExtEvLoop();
3134
} elseif (\class_exists('EventBase', false)) {
3235
return new ExtEventLoop();
33-
} elseif (\function_exists('event_base_new') && \PHP_VERSION_ID < 70000) {
34-
// only use ext-libevent on PHP < 7 for now
36+
} elseif (\function_exists('event_base_new') && \PHP_MAJOR_VERSION === 5) {
37+
// only use ext-libevent on PHP 5 for now
3538
return new ExtLibeventLoop();
3639
}
3740

0 commit comments

Comments
 (0)
0