10000 Implement ExtEvLoop. · reactphp/event-loop@b325a88 · GitHub
[go: up one dir, main page]

Skip to content

Commit b325a88

Browse files
committed
Implement ExtEvLoop.
ExtEvLoop implements event loop based on PECL ev extension.
1 parent e295575 commit b325a88

File tree

7 files changed

+308
-0
lines changed

7 files changed

+308
-0
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,16 @@ It supports the same backends as libevent.
201201

202202
This loop is known to work with PHP 5.4 through PHP 7+.
203203

204+
#### ExtEvLoop
205+
206+
An `ext-ev` based event loop.
207+
208+
This loop uses the [`ev` PECL extension](https://pecl.php.net/package/ev), that
209+
provides an interface to `libev` library.
210+
211+
This loop is known to work with PHP 5.4 through PHP 7+.
212+
213+
204214
#### ExtLibeventLoop
205215

206216
An `ext-libevent` based event loop.

src/ExtEvLoop.php

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
<?php
2+
3+
namespace React\EventLoop;
4+
5+
use Ev;
6+
use EvIo;
7+
use EvLoop;
8+
use React\EventLoop\Tick\FutureTickQueue;
9+
use React\EventLoop\Timer\Timer;
10+
use SplObjectStorage;
11+
12+
/**
13+
* An `ext-ev` based event loop.
14+
*
15+
* This loop uses the [`ev` PECL extension](https://pecl.php.net/package/ev),
16+
* that provides an interface to `libev` library.
17+
*
18+
* This loop is known to work with PHP 5.4 through PHP 7+.
19+
*
20+
* @see http://php.net/manual/en/book.ev.php
21+
* @see https://bitbucket.org/osmanov/pecl-ev/overview
22+
*/
23+
class ExtEvLoop implements LoopInterface
24+
{
25+
/**
26+
* @var EvLoop
27+
*/
28+
private $loop;
29+
30+
/**
31+
* @var FutureTickQueue
32+
*/
33+
private $futureTickQueue;
34+
35+
/**
36+
* @var SplObjectStorage
37+
*/
38+
private $timers;
39+
40+
/**
41+
* @var EvIo[]
42+
*/
43+
private $readStreams = [];
44+
45+
/**
46+
* @var EvIo[]
47+
*/
48+
private $writeStreams = [];
49+
50+
/**
51+
* @var bool
52+
*/
53+
private $running;
54+
55+
/**
56+
* @var SignalsHandler
57+
*/
58+
private $signals;
59+
60+
/**
61+
* @var \EvSignal[]
62+
*/
63+
private $signalEvents = [];
64+
65+
public function __construct()
66+
{
67+
$this->loop = new EvLoop();
68+
$this->futureTickQueue = new FutureTickQueue();
69+
$this->timers = new SplObjectStorage();
70+
$this->signals = new SignalsHandler();
71+
}
72+
73+
public function addReadStream($stream, $listener)
74+
{
75+
$key = (int)$stream;
76+
77+
if (isset($this->readStreams[$key])) {
78+
return;
79+
}
80+
81+
$callback = $this->getStreamListenerClosure($stream, $listener);
82+
$event = $this->loop->io($stream, Ev::READ, $callback);
83+
$this->readStreams[$key] = $event;
84+
}
85+
86+
/**
87+
* @param resource $stream
88+
* @param callable $listener
89+
*
90+
* @return \Closure
91+
*/
92+
private function getStreamListenerClosure($stream, $listener)
93+
{
94+
return function () use ($stream, $listener) {
95+
call_user_func($listener, $stream);
96+
};
97+
}
98+
99+
public function addWriteStream($stream, $listener)
100+
{
101+
$key = (int)$stream;
102+
103+
if (isset($this->writeStreams[$key])) {
104+
return;
105+
}
106+
107+
$callback = $this->getStreamListenerClosure($stream, $listener);
108+
$event = $this->loop->io($stream, Ev::WRITE, $callback);
109+
$this->writeStreams[$key] = $event;
110+
}
111+
112+
public function removeReadStream($stream)
113+
{
114+
$key = (int)$stream;
115+
116+
if (!isset($this->readStreams[$key])) {
117+
return;
118+
}
119+
120+
$this->readStreams[$key]->stop();
121+
unset($this->readStreams[$key]);
122+
}
123+
124+
public function removeWriteStream($stream)
125+
{
126+
$key = (int)$stream;
127+
128+
if (!isset($this->writeStreams[$key])) {
129+
return;
130+
}
131+
132+
$this->writeStreams[$key]->stop();
133+
unset($this->writeStreams[$key]);
134+
}
135+
136+
public function addTimer($interval, $callback)
137+
{
138+
$timer = new Timer($interval, $callback, false);
139+
140+
$callback = function () use ($timer) {
141+
call_user_func($timer->getCallback(), $timer);
142+
143+
if ($this->isTimerActive($timer)) {
144+
$this->cancelTimer($timer);
145+
}
146+
};
147+
148+
$event = $this->loop->timer($timer->getInterval(), 0.0, $callback);
149+
$this->timers->attach($timer, $event);
150+
151+
return $timer;
152+
}
153+
154+
public function addPeriodicTimer($interval, $callback)
155+
{
156+
$timer = new Timer($interval, $callback, true);
157+
158+
$callback = function () use ($timer) {
159+
call_user_func($timer->getCallback(), $timer);
160+
};
161+
162+
$event = $this->loop->timer($interval, $interval, $callback);
163+
$this->timers->attach($timer, $event);
164+
165+
return $timer;
166+
}
167+
168+
public function cancelTimer(TimerInterface $timer)
169+
{
170+
if (!isset($this->timers[$timer])) {
171+
return;
172+
}
173+
174+
$event = $this->timers[$timer];
175+
$event->stop();
176+
$this->timers->detach($timer);
177+
}
178+
179+
public function isTimerActive(TimerInterface $timer)
180+
{
181+
return $this->timers->contains($timer);
182+
}
183+
184+
public function futureTick($listener)
185+
{
186+
$this->futureTickQueue->add($listener);
187+
}
188+
189+
public function run()
190+
{
191+
$this->running = true;
192+
193+
while ($this->running) {
194+
$this->futureTickQueue->tick();
195+
196+
$hasPendingCallbacks = !$this->futureTickQueue->isEmpty();
197+
$wasJustStopped = !$this->running;
198+
$nothingLeftToDo = !$this->readStreams
199+
&& !$this->writeStreams
200+
&& !$this->timers->count()
201+
&& $this->signals->isEmpty();
202+
203+
$flags = Ev::RUN_ONCE;
204+
if ($wasJustStopped || $hasPendingCallbacks) {
205+
$flags |= Ev::RUN_NOWAIT;
206+
} elseif ($nothingLeftToDo) {
207+
break;
208+
}
209+
210+
$this->loop->run($flags);
211+
}
212+
}
213+
214+
public function stop()
215+
{
216+
$this->running = false;
217+
}
218+
219+
public function __destruct()
220+
{
221+
/** @var TimerInterface $timer */
222+
foreach ($this->timers as $timer) {
223+
$this->cancelTimer($timer);
224+
}
225+
226+
foreach ($this->readStreams as $key => $stream) {
227+
$this->removeReadStream($key);
228+
}
229+
230+
foreach ($this->writeStreams as $key => $stream) {
231+
$this->removeWriteStream($key);
232+
}
233+
}
234+
235+
public function addSignal($signal, $listener)
236+
{
237+
$this->signals->add($signal, $listener);
238+
239+
if (!isset($this->signalEvents[$signal])) {
240+
$this->signalEvents[$signal] = $this->loop->signal($signal, function() use ($signal) {
241+
$this->signals->call($signal);
242+
});
243+
}
244+
}
245+
246+
public function removeSignal($signal, $listener)
247+
{
248+
$this->signals->remove($signal, $listener);
249+
250+
if (isset($this->signalEvents[$signal])) {
251+
$this->signalEvents[$signal]->stop();
252+
unset($this->signalEvents[$signal]);
253+
}
254+
}
255+
}

src/Factory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ public static function create()
2626
// @codeCoverageIgnoreStart
2727
if (class_exists('libev\EventLoop', false)) {
2828
return new ExtLibevLoop();
29+
} elseif (class_exists('EvLoop', false)) {
30+
return new ExtEvLoop();
2931
} elseif (class_exists('EventBase', false)) {
3032
return new ExtEventLoop();
3133
} elseif (function_exists('event_base_new') && PHP_VERSION_ID < 70000) {

tests/ExtEvLoopTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace React\Tests\EventLoop;
4+
5+
use React\EventLoop\ExtEvLoop;
6+
7+
class ExtEvLoopTest extends AbstractLoopTest
8+
{
9+
public function createLoop()
10+
{
11+
if (!class_exists('EvLoop')) {
12+
$this->markTestSkipped('ExtEvLoop tests skipped because ext-ev extension is not installed.');
13+
}
14+
15+
return new ExtEvLoop();
16+
}
17+
}

tests/Timer/AbstractTimerTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22

33
namespace React\Tests\EventLoop\Timer;
44

5+
use React\EventLoop\LoopInterface;
56
use React\Tests\EventLoop\TestCase;
67

78
abstract class AbstractTimerTest extends TestCase
89
{
10+
/**
11+
* @return LoopInterface
12+
*/
913
abstract public function createLoop();
1014

1115
public function testAddTimerReturnsNonPeriodicTimerInstance()

tests/Timer/ExtEvTimerTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace React\Tests\EventLoop\Timer;
4+
5+
use React\EventLoop\ExtEvLoop;
6+
7+
class ExtEvTimerTest extends AbstractTimerTest
8+
{
9+
public function createLoop()
10+
{
11+
if (!class_exists('EvLoop')) {
12+
$this->markTestSkipped('ExtEvLoop tests skipped because ext-ev extension is not installed.');
13+
}
14+
15+
return new ExtEvLoop();
16+
}
17+
}

travis-init.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ if [[ "$TRAVIS_PHP_VERSION" != "hhvm" &&
1010
echo "yes" | pecl install event
1111
fi
1212

13+
# install 'ev' PHP extension
14+
echo "yes" | pecl install ev
15+
1316
# install 'libevent' PHP extension (does not support php 7)
1417
if [[ "$TRAVIS_PHP_VERSION" != "7.0" &&
1518
"$TRAVIS_PHP_VERSION" != "7.1" &&

0 commit comments

Comments
 (0)
0