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}