ExtLibeventLoop.php 8.5 KB

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