Skip to content

Commit 8aef041

Browse files
committed
Implement ExtEvLoop.
ExtEvLoop implements event loop based on PECL ev extension.
1 parent 502b4b6 commit 8aef041

File tree

7 files changed

+307
-0
lines changed

7 files changed

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

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
@@ -8,6 +8,9 @@ if [[ "$TRAVIS_PHP_VERSION" != "hhvm" &&
88
# install 'event' PHP extension
99
echo "yes" | pecl install event
1010

11+
# install 'ev' PHP extension
12+
echo "yes" | pecl install ev
13+
1114
# install 'libevent' PHP extension (does not support php 7)
1215
if [[ "$TRAVIS_PHP_VERSION" != "7.0" &&
1316
"$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