friendship ended with social-app. php is my new best friend
1<?php
2
3namespace React\EventLoop;
4
5use BadMethodCallException;
6use Event;
7use EventBase;
8use React\EventLoop\Tick\FutureTickQueue;
9use React\EventLoop\Timer\Timer;
10use SplObjectStorage;
11
12/**
13 * [Deprecated] An `ext-libevent` based event loop.
14 *
15 * This uses the [`libevent` PECL extension](https://pecl.php.net/package/libevent),
16 * that provides an interface to `libevent` library.
17 * `libevent` itself supports a number of system-specific backends (epoll, kqueue).
18 *
19 * This event loop does only work with PHP 5.
20 * An [unofficial update](https://github.com/php/pecl-event-libevent/pull/2) for
21 * PHP 7 does exist, but it is known to cause regular crashes due to `SEGFAULT`s.
22 * To reiterate: Using this event loop on PHP 7 is not recommended.
23 * Accordingly, neither the [`Loop` class](#loop) nor the deprecated
24 * [`Factory` class](#factory) will try to use this event loop on PHP 7.
25 *
26 * This event loop is known to trigger a readable listener only if
27 * the stream *becomes* readable (edge-triggered) and may not trigger if the
28 * stream has already been readable from the beginning.
29 * This also implies that a stream may not be recognized as readable when data
30 * is still left in PHP's internal stream buffers.
31 * As such, it's recommended to use `stream_set_read_buffer($stream, 0);`
32 * to disable PHP's internal read buffer in this case.
33 * See also [`addReadStream()`](#addreadstream) for more details.
34 *
35 * @link https://pecl.php.net/package/libevent
36 * @deprecated 1.2.0, use [`ExtEventLoop`](#exteventloop) instead.
37 */
38final class ExtLibeventLoop implements LoopInterface
39{
40 /** @internal */
41 const MICROSECONDS_PER_SECOND = 1000000;
42
43 private $eventBase;
44 private $futureTickQueue;
45 private $timerCallback;
46 private $timerEvents;
47 private $streamCallback;
48 private $readEvents = array();
49 private $writeEvents = array();
50 private $readListeners = array();
51 private $writeListeners = array();
52 private $running;
53 private $signals;
54 private $signalEvents = array();
55
56 public function __construct()
57 {
58 if (!\function_exists('event_base_new')) {
59 throw new BadMethodCallException('Cannot create ExtLibeventLoop, ext-libevent extension missing');
60 }
61
62 $this->eventBase = \event_base_new();
63 $this->futureTickQueue = new FutureTickQueue();
64 $this->timerEvents = new SplObjectStorage();
65 $this->signals = new SignalsHandler();
66
67 $this->createTimerCallback();
68 $this->createStreamCallback();
69 }
70
71 public function addReadStream($stream, $listener)
72 {
73 $key = (int) $stream;
74 if (isset($this->readListeners[$key])) {
75 return;
76 }
77
78 $event = \event_new();
79 \event_set($event, $stream, \EV_PERSIST | \EV_READ, $this->streamCallback);
80 \event_base_set($event, $this->eventBase);
81 \event_add($event);
82
83 $this->readEvents[$key] = $event;
84 $this->readListeners[$key] = $listener;
85 }
86
87 public function addWriteStream($stream, $listener)
88 {
89 $key = (int) $stream;
90 if (isset($this->writeListeners[$key])) {
91 return;
92 }
93
94 $event = \event_new();
95 \event_set($event, $stream, \EV_PERSIST | \EV_WRITE, $this->streamCallback);
96 \event_base_set($event, $this->eventBase);
97 \event_add($event);
98
99 $this->writeEvents[$key] = $event;
100 $this->writeListeners[$key] = $listener;
101 }
102
103 public function removeReadStream($stream)
104 {
105 $key = (int) $stream;
106
107 if (isset($this->readListeners[$key])) {
108 $event = $this->readEvents[$key];
109 \event_del($event);
110 \event_free($event);
111
112 unset(
113 $this->readEvents[$key],
114 $this->readListeners[$key]
115 );
116 }
117 }
118
119 public function removeWriteStream($stream)
120 {
121 $key = (int) $stream;
122
123 if (isset($this->writeListeners[$key])) {
124 $event = $this->writeEvents[$key];
125 \event_del($event);
126 \event_free($event);
127
128 unset(
129 $this->writeEvents[$key],
130 $this->writeListeners[$key]
131 );
132 }
133 }
134
135 public function addTimer($interval, $callback)
136 {
137 $timer = new Timer($interval, $callback, false);
138
139 $this->scheduleTimer($timer);
140
141 return $timer;
142 }
143
144 public function addPeriodicTimer($interval, $callback)
145 {
146 $timer = new Timer($interval, $callback, true);
147
148 $this->scheduleTimer($timer);
149
150 return $timer;
151 }
152
153 public function cancelTimer(TimerInterface $timer)
154 {
155 if ($this->timerEvents->contains($timer)) {
156 $event = $this->timerEvents[$timer];
157 \event_del($event);
158 \event_free($event);
159
160 $this->timerEvents->detach($timer);
161 }
162 }
163
164 public function futureTick($listener)
165 {
166 $this->futureTickQueue->add($listener);
167 }
168
169 public function addSignal($signal, $listener)
170 {
171 $this->signals->add($signal, $listener);
172
173 if (!isset($this->signalEvents[$signal])) {
174 $this->signalEvents[$signal] = \event_new();
175 \event_set($this->signalEvents[$signal], $signal, \EV_PERSIST | \EV_SIGNAL, array($this->signals, 'call'));
176 \event_base_set($this->signalEvents[$signal], $this->eventBase);
177 \event_add($this->signalEvents[$signal]);
178 }
179 }
180
181 public function removeSignal($signal, $listener)
182 {
183 $this->signals->remove($signal, $listener);
184
185 if (isset($this->signalEvents[$signal]) && $this->signals->count($signal) === 0) {
186 \event_del($this->signalEvents[$signal]);
187 \event_free($this->signalEvents[$signal]);
188 unset($this->signalEvents[$signal]);
189 }
190 }
191
192 public function run()
193 {
194 $this->running = true;
195
196 while ($this->running) {
197 $this->futureTickQueue->tick();
198
199 $flags = \EVLOOP_ONCE;
200 if (!$this->running || !$this->futureTickQueue->isEmpty()) {
201 $flags |= \EVLOOP_NONBLOCK;
202 } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) {
203 break;
204 }
205
206 \event_base_loop($this->eventBase, $flags);
207 }
208 }
209
210 public function stop()
211 {
212 $this->running = false;
213 }
214
215 /**
216 * Schedule a timer for execution.
217 *
218 * @param TimerInterface $timer
219 */
220 private function scheduleTimer(TimerInterface $timer)
221 {
222 $this->timerEvents[$timer] = $event = \event_timer_new();
223
224 \event_timer_set($event, $this->timerCallback, $timer);
225 \event_base_set($event, $this->eventBase);
226 \event_add($event, $timer->getInterval() * self::MICROSECONDS_PER_SECOND);
227 }
228
229 /**
230 * Create a callback used as the target of timer events.
231 *
232 * A reference is kept to the callback for the lifetime of the loop
233 * to prevent "Cannot destroy active lambda function" fatal error from
234 * the event extension.
235 */
236 private function createTimerCallback()
237 {
238 $that = $this;
239 $timers = $this->timerEvents;
240 $this->timerCallback = function ($_, $__, $timer) use ($timers, $that) {
241 \call_user_func($timer->getCallback(), $timer);
242
243 // Timer already cancelled ...
244 if (!$timers->contains($timer)) {
245 return;
246 }
247
248 // Reschedule periodic timers ...
249 if ($timer->isPeriodic()) {
250 \event_add(
251 $timers[$timer],
252 $timer->getInterval() * ExtLibeventLoop::MICROSECONDS_PER_SECOND
253 );
254
255 // Clean-up one shot timers ...
256 } else {
257 $that->cancelTimer($timer);
258 }
259 };
260 }
261
262 /**
263 * Create a callback used as the target of stream events.
264 *
265 * A reference is kept to the callback for the lifetime of the loop
266 * to prevent "Cannot destroy active lambda function" fatal error from
267 * the event extension.
268 */
269 private function createStreamCallback()
270 {
271 $read =& $this->readListeners;
272 $write =& $this->writeListeners;
273 $this->streamCallback = function ($stream, $flags) use (&$read, &$write) {
274 $key = (int) $stream;
275
276 if (\EV_READ === (\EV_READ & $flags) && isset($read[$key])) {
277 \call_user_func($read[$key], $stream);
278 }
279
280 if (\EV_WRITE === (\EV_WRITE & $flags) && isset($write[$key])) {
281 \call_user_func($write[$key], $stream);
282 }
283 };
284 }
285}