Skip to content

Commit 12d714f

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

File tree

7 files changed

+312
-0
lines changed

7 files changed

+312
-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: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
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+
$this,
72+
function ($signal) {
73+
$this->signalEvents[$signal] = $this->loop->signal($signal, $f = function () use ($signal, &$f) {
74+
$this->signals->call($signal);
75+
// Ensure there are two copies of the callable around until it has been executed.
76+
// For more information see: https://bugs.php.net/bug.php?id=62452
77+
// Only an issue for PHP 5, this hack can be removed once PHP 5 support has been dropped.
78+
$g = $f;
79+
$f = $g;
80+
});
81+
},
82+
function ($signal) {
83+
if ($this->signals->count($signal) === 0) {
84+
$this->signalEvents[$signal]->stop();
85+
unset($this->signalEvents[$signal]);
86+
}
87+
}
88+
);
89+
}
90+
91+
public function addReadStream($stream, $listener)
92+
{
93+
$key = (int)$stream;
94+
95+
if (isset($this->readStreams[$key])) {
96+
return;
97+
}
98+
99+
$callback = $this->getStreamListenerClosure($stream, $listener);
100+
$event = $this->loop->io($stream, Ev::READ, $callback);
101+
$this->readStreams[$key] = $event;
102+
}
103+
104+
/**
105+
* @param resource $stream
106+
* @param callable $listener
107+
*
108+
* @return \Closure
109+
*/
110+
private function getStreamListenerClosure($stream, $listener)
111+
{
112+
return function () use ($stream, $listener) {
113+
call_user_func($listener, $stream);
114+
};
115+
}
116+
117+
public function addWriteStream($stream, $listener)
118+
{
119+
$key = (int)$stream;
120+
121+
if (isset($this->writeStreams[$key])) {
122+
return;
123+
}
124+
125+
$callback = $this->getStreamListenerClosure($stream, $listener);
126+
$event = $this->loop->io($stream, Ev::WRITE, $callback);
127+
$this->writeStreams[$key] = $event;
128+
}
129+
130+
public function removeReadStream($stream)
131+
{
132+
$key = (int)$stream;
133+
134+
if (!isset($this->readStreams[$key])) {
135+
return;
136+
}
137+
138+
$this->readStreams[$key]->stop();
139+
unset($this->readStreams[$key]);
140+
}
141+
142+
public function removeWriteStream($stream)
143+
{
144+
$key = (int)$stream;
145+
146+
if (!isset($this->writeStreams[$key])) {
147+
return;
148+
}
149+
150+
$this->writeStreams[$key]->stop();
151+
unset($this->writeStreams[$key]);
152+
}
153+
154+
public function addTimer($interval, $callback)
155+
{
156+
$timer = new Timer($interval, $callback, false);
157+
158+
$callback = function () use ($timer) {
159+
call_user_func($timer->getCallback(), $timer);
160+
161+
if ($this->isTimerActive($timer)) {
162+
$this->cancelTimer($timer);
163+
}
164+
};
165+
166+
$event = $this->loop->timer($timer->getInterval(), 0.0, $callback);
167+
$this->timers->attach($timer, $event);
168+
169+
return $timer;
170+
}
171+
172+
public function addPeriodicTimer($interval, $callback)
173+
{
174+
$timer = new Timer($interval, $callback, true);
175+
176+
$callback = function () use ($timer) {
177+
call_user_func($timer->getCallback(), $timer);
178+
};
179+
180+
$event = $this->loop->timer($interval, $interval, $callback);
181+
$this->timers->attach($timer, $event);
182+
183+
return $timer;
184+
}
185+
186+
public function cancelTimer(TimerInterface $timer)
187+
{
188+
if (!isset($this->timers[$timer])) {
189+
return;
190+
}
191+
192+
$event = $this->timers[$timer];
193+
$event->stop();
194+
$this->timers->detach($timer);
195+
}
196+
197+
public function isTimerActive(TimerInterface $timer)
198+
{
199+
return $this->timers->contains($timer);
200+
}
201+
202+
public function futureTick($listener)
203+
{
204+
$this->futureTickQueue->add($listener);
205+
}
206+
207+
public function run()
208+
{
209+
$this->running = true;
210+
211+
while ($this->running) {
212+
$this->futureTickQueue->tick();
213+
214+
$hasPendingCallbacks = !$this->futureTickQueue->isEmpty();
215+
$wasJustStopped = !$this->running;
216+
$nothingLeftToDo = !$this->readStreams && !$this->writeStreams && !$this->timers->count();
217+
218+
$flags = Ev::RUN_ONCE;
219+
if ($wasJustStopped || $hasPendingCallbacks) {
220+
$flags |= Ev::RUN_NOWAIT;
221+
} elseif ($nothingLeftToDo) {
222+
break;
223+
}
224+
225+
$this->loop->run($flags);
226+
}
227+
}
228+
229+
public function stop()
230+
{
231+
$this->running = false;
232+
}
233+
234+
public function __destruct()
235+
{
236+
/** @var TimerInterface $timer */
237+
foreach ($this->timers as $timer) {
238+
$this->cancelTimer($timer);
239+
}
240+
241+
foreach ($this->readStreams as $key => $stream) {
242+
$this->removeReadStream($key);
243+
}
244+
245+
foreach ($this->writeStreams as $key => $stream) {
246+
$this->removeWriteStream($key);
247+
}
248+
}
249+
250+
public function addSignal($signal, $listener)
251+
{
252+
$this->signals->add($signal, $listener);
253+
}
254+
255+
public function removeSignal($signal, $listener)
256+
{
257+
$this->signals->remove($signal, $listener);
258+
}
259+
}

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy