├── .php_cs ├── LICENSE ├── composer.json └── lib ├── EvLoop.php ├── EventLoop.php ├── Internal └── Watcher.php ├── Loop.php ├── LoopFactory.php ├── NativeLoop.php ├── UvLoop.php └── bootstrap.php /.php_cs: -------------------------------------------------------------------------------- 1 | level(Symfony\CS\FixerInterface::NONE_LEVEL) 5 | ->fixers([ 6 | "psr2", 7 | "-braces", 8 | "-psr0", 9 | ]) 10 | ->finder( 11 | Symfony\CS\Finder\DefaultFinder::create() 12 | ->in(__DIR__ . "/lib") 13 | ->in(__DIR__ . "/test") 14 | ) 15 | ; 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2016 amphp 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amphp/loop", 3 | "description": "", 4 | "keywords": [ 5 | "asynchronous", 6 | "async", 7 | "loop", 8 | "event", 9 | "reactor" 10 | ], 11 | "homepage": "http://amphp.org", 12 | "license": "MIT", 13 | "require": { 14 | "async-interop/event-loop": "^0.5" 15 | }, 16 | "require-dev": { 17 | "async-interop/event-loop-test": "^0.5", 18 | "friendsofphp/php-cs-fixer": "~1.9" 19 | }, 20 | "provide": { 21 | "async-interop/event-loop-implementation": "0.5" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Amp\\Loop\\": "lib" 26 | }, 27 | "files": [ 28 | "lib/bootstrap.php" 29 | ] 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { 33 | "Amp\\Test\\Loop\\": "test" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/EvLoop.php: -------------------------------------------------------------------------------- 1 | handle = new \EvLoop; 35 | 36 | if (self::$activeSignals === null) { 37 | self::$activeSignals = &$this->signals; 38 | } 39 | 40 | $this->ioCallback = function (\EvIO $event) { 41 | /** @var \Amp\Loop\Internal\Watcher $watcher */ 42 | $watcher = $event->data; 43 | 44 | $callback = $watcher->callback; 45 | $callback($watcher->id, $watcher->value, $watcher->data); 46 | }; 47 | 48 | $this->timerCallback = function (\EvTimer $event) { 49 | /** @var \Amp\Loop\Internal\Watcher $watcher */ 50 | $watcher = $event->data; 51 | 52 | if ($watcher->type & Watcher::DELAY) { 53 | $this->cancel($watcher->id); 54 | } 55 | 56 | $callback = $watcher->callback; 57 | $callback($watcher->id, $watcher->data); 58 | }; 59 | 60 | $this->signalCallback = function (\EvSignal $event) { 61 | /** @var \Amp\Loop\Internal\Watcher $watcher */ 62 | $watcher = $event->data; 63 | 64 | $callback = $watcher->callback; 65 | $callback($watcher->id, $watcher->value, $watcher->data); 66 | }; 67 | } 68 | 69 | public function __destruct() { 70 | foreach ($this->events as $event) { 71 | $event->stop(); 72 | } 73 | } 74 | 75 | /** 76 | * {@inheritdoc} 77 | */ 78 | public function run() { 79 | $active = self::$activeSignals; 80 | 81 | foreach ($active as $event) { 82 | $event->stop(); 83 | } 84 | 85 | self::$activeSignals = &$this->signals; 86 | 87 | foreach ($this->signals as $event) { 88 | $event->start(); 89 | } 90 | 91 | try { 92 | parent::run(); 93 | } finally { 94 | foreach ($this->signals as $event) { 95 | $event->stop(); 96 | } 97 | 98 | self::$activeSignals = &$active; 99 | 100 | foreach ($active as $event) { 101 | $event->start(); 102 | } 103 | } 104 | } 105 | 106 | /** 107 | * {@inheritdoc} 108 | */ 109 | public function stop() { 110 | $this->handle->stop(); 111 | parent::stop(); 112 | } 113 | 114 | /** 115 | * {@inheritdoc} 116 | */ 117 | protected function dispatch($blocking) { 118 | $this->handle->run($blocking ? \Ev::RUN_ONCE : \Ev::RUN_ONCE | \Ev::RUN_NOWAIT); 119 | } 120 | 121 | /** 122 | * {@inheritdoc} 123 | */ 124 | protected function activate(array $watchers) { 125 | foreach ($watchers as $watcher) { 126 | if (!isset($this->events[$id = $watcher->id])) { 127 | switch ($watcher->type) { 128 | case Watcher::READABLE: 129 | $this->events[$id] = $this->handle->io($watcher->value, \Ev::READ, $this->ioCallback, $watcher); 130 | break; 131 | 132 | case Watcher::WRITABLE: 133 | $this->events[$id] = $this->handle->io($watcher->value, \Ev::WRITE, $this->ioCallback, $watcher); 134 | break; 135 | 136 | case Watcher::DELAY: 137 | case Watcher::REPEAT: 138 | $interval = $watcher->value / self::MILLISEC_PER_SEC; 139 | $this->events[$id] = $this->handle->timer( 140 | $interval, 141 | $watcher->type & Watcher::REPEAT ? $interval : 0, 142 | $this->timerCallback, 143 | $watcher 144 | ); 145 | break; 146 | 147 | case Watcher::SIGNAL: 148 | $this->events[$id] = $this->handle->signal($watcher->value, $this->signalCallback, $watcher); 149 | break; 150 | 151 | default: 152 | throw new \DomainException("Unknown watcher type"); 153 | } 154 | } else { 155 | $this->events[$id]->start(); 156 | } 157 | 158 | if ($watcher->type === Watcher::SIGNAL) { 159 | $this->signals[$id] = $this->events[$id]; 160 | } 161 | } 162 | } 163 | 164 | /** 165 | * {@inheritdoc} 166 | */ 167 | protected function deactivate(Watcher $watcher) { 168 | if (isset($this->events[$id = $watcher->id])) { 169 | $this->events[$id]->stop(); 170 | if ($watcher->type === Watcher::SIGNAL) { 171 | unset($this->signals[$id]); 172 | } 173 | } 174 | } 175 | 176 | /** 177 | * {@inheritdoc} 178 | */ 179 | public function cancel($watcherIdentifier) { 180 | parent::cancel($watcherIdentifier); 181 | unset($this->events[$watcherIdentifier]); 182 | } 183 | 184 | /** 185 | * {@inheritdoc} 186 | */ 187 | public function getHandle() { 188 | return $this->handle; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /lib/EventLoop.php: -------------------------------------------------------------------------------- 1 | handle = new \EventBase; 35 | 36 | if (self::$activeSignals === null) { 37 | self::$activeSignals = &$this->signals; 38 | } 39 | 40 | $this->ioCallback = function ($resource, $what, Watcher $watcher) { 41 | $callback = $watcher->callback; 42 | $callback($watcher->id, $watcher->value, $watcher->data); 43 | }; 44 | 45 | $this->timerCallback = function ($resource, $what, Watcher $watcher) { 46 | if ($watcher->type & Watcher::DELAY) { 47 | $this->cancel($watcher->id); 48 | } 49 | 50 | $callback = $watcher->callback; 51 | $callback($watcher->id, $watcher->data); 52 | }; 53 | 54 | $this->signalCallback = function ($signum, $what, Watcher $watcher) { 55 | $callback = $watcher->callback; 56 | $callback($watcher->id, $watcher->value, $watcher->data); 57 | }; 58 | } 59 | 60 | public function __destruct() { 61 | foreach ($this->events as $event) { 62 | $event->free(); 63 | } 64 | } 65 | 66 | /** 67 | * {@inheritdoc} 68 | */ 69 | public function run() { 70 | $active = self::$activeSignals; 71 | 72 | foreach ($active as $event) { 73 | $event->del(); 74 | } 75 | 76 | self::$activeSignals = &$this->signals; 77 | 78 | foreach ($this->signals as $event) { 79 | $event->add(); 80 | } 81 | 82 | try { 83 | parent::run(); 84 | } finally { 85 | foreach ($this->signals as $event) { 86 | $event->del(); 87 | } 88 | 89 | self::$activeSignals = &$active; 90 | 91 | foreach ($active as $event) { 92 | $event->add(); 93 | } 94 | } 95 | } 96 | 97 | /** 98 | * {@inheritdoc} 99 | */ 100 | public function stop() { 101 | $this->handle->stop(); 102 | parent::stop(); 103 | } 104 | 105 | /** 106 | * {@inheritdoc} 107 | */ 108 | protected function dispatch($blocking) { 109 | $this->handle->loop($blocking ? \EventBase::LOOP_ONCE : \EventBase::LOOP_ONCE | \EventBase::LOOP_NONBLOCK); 110 | } 111 | 112 | /** 113 | * {@inheritdoc} 114 | */ 115 | protected function activate(array $watchers) { 116 | foreach ($watchers as $watcher) { 117 | if (!isset($this->events[$id = $watcher->id])) { 118 | switch ($watcher->type) { 119 | case Watcher::READABLE: 120 | $this->events[$id] = new \Event( 121 | $this->handle, 122 | $watcher->value, 123 | \Event::READ | \Event::PERSIST, 124 | $this->ioCallback, 125 | $watcher 126 | ); 127 | break; 128 | 129 | case Watcher::WRITABLE: 130 | $this->events[$id] = new \Event( 131 | $this->handle, 132 | $watcher->value, 133 | \Event::WRITE | \Event::PERSIST, 134 | $this->ioCallback, 135 | $watcher 136 | ); 137 | break; 138 | 139 | case Watcher::DELAY: 140 | case Watcher::REPEAT: 141 | $this->events[$id] = new \Event( 142 | $this->handle, 143 | -1, 144 | \Event::TIMEOUT | \Event::PERSIST, 145 | $this->timerCallback, 146 | $watcher 147 | ); 148 | break; 149 | 150 | case Watcher::SIGNAL: 151 | $this->events[$id] = new \Event( 152 | $this->handle, 153 | $watcher->value, 154 | \Event::SIGNAL | \Event::PERSIST, 155 | $this->signalCallback, 156 | $watcher 157 | ); 158 | break; 159 | 160 | default: 161 | throw new \DomainException("Unknown watcher type"); 162 | } 163 | } 164 | 165 | switch ($watcher->type) { 166 | case Watcher::DELAY: 167 | case Watcher::REPEAT: 168 | $this->events[$id]->add($watcher->value / self::MILLISEC_PER_SEC); 169 | break; 170 | 171 | case Watcher::SIGNAL: 172 | $this->signals[$id] = $this->events[$id]; 173 | // No break 174 | 175 | default: 176 | $this->events[$id]->add(); 177 | break; 178 | } 179 | } 180 | } 181 | 182 | /** 183 | * {@inheritdoc} 184 | */ 185 | protected function deactivate(Watcher $watcher) { 186 | if (isset($this->events[$id = $watcher->id])) { 187 | $this->events[$id]->del(); 188 | 189 | if ($watcher->type === Watcher::SIGNAL) { 190 | unset($this->signals[$id]); 191 | } 192 | } 193 | } 194 | 195 | /** 196 | * {@inheritdoc} 197 | */ 198 | public function cancel($watcherIdentifier) { 199 | parent::cancel($watcherIdentifier); 200 | 201 | if (isset($this->events[$watcherIdentifier])) { 202 | $this->events[$watcherIdentifier]->free(); 203 | unset($this->events[$watcherIdentifier]); 204 | } 205 | } 206 | 207 | /** 208 | * {@inheritdoc} 209 | */ 210 | public function getHandle() { 211 | return $this->handle; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /lib/Internal/Watcher.php: -------------------------------------------------------------------------------- 1 | running; 40 | ++$this->running; 41 | 42 | try { 43 | while ($this->running > $previous) { 44 | if ($this->isEmpty()) { 45 | return; 46 | } 47 | $this->tick(); 48 | } 49 | } finally { 50 | $this->running = $previous; 51 | } 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function stop() { 58 | --$this->running > 0 ?: $this->running = 0; 59 | } 60 | 61 | /** 62 | * @return bool True if no enabled and referenced watchers remain in the loop. 63 | */ 64 | private function isEmpty() { 65 | foreach ($this->watchers as $watcher) { 66 | if ($watcher->enabled && $watcher->referenced) { 67 | return false; 68 | } 69 | } 70 | 71 | return true; 72 | } 73 | 74 | /** 75 | * Executes a single tick of the event loop. 76 | */ 77 | private function tick() { 78 | $this->deferQueue = \array_merge($this->deferQueue, $this->nextTickQueue); 79 | $this->nextTickQueue = []; 80 | 81 | $this->activate($this->enableQueue); 82 | $this->enableQueue = []; 83 | 84 | try { 85 | foreach ($this->deferQueue as $watcher) { 86 | if (!isset($this->deferQueue[$watcher->id])) { 87 | continue; // Watcher disabled by another defer watcher. 88 | } 89 | 90 | unset($this->watchers[$watcher->id], $this->deferQueue[$watcher->id]); 91 | 92 | $callback = $watcher->callback; 93 | $callback($watcher->id, $watcher->data); 94 | } 95 | 96 | $this->dispatch(empty($this->nextTickQueue) && empty($this->enableQueue) && $this->running); 97 | 98 | } catch (\Throwable $exception) { 99 | if (null === $this->errorHandler) { 100 | throw $exception; 101 | } 102 | 103 | $errorHandler = $this->errorHandler; 104 | $errorHandler($exception); 105 | } catch (\Exception $exception) { // @todo Remove when PHP 5.x support is no longer needed. 106 | if (null === $this->errorHandler) { 107 | throw $exception; 108 | } 109 | 110 | $errorHandler = $this->errorHandler; 111 | $errorHandler($exception); 112 | } 113 | } 114 | 115 | /** 116 | * Dispatches any pending read/write, timer, and signal events. 117 | * 118 | * @param bool $blocking 119 | */ 120 | abstract protected function dispatch($blocking); 121 | 122 | /** 123 | * Activates (enables) all the given watchers. 124 | * 125 | * @param \Amp\Loop\Internal\Watcher[] $watchers 126 | */ 127 | abstract protected function activate(array $watchers); 128 | 129 | /** 130 | * Deactivates (disables) the given watcher. 131 | * 132 | * @param \Amp\Loop\Internal\Watcher $watcher 133 | */ 134 | abstract protected function deactivate(Watcher $watcher); 135 | 136 | /** 137 | * {@inheritdoc} 138 | */ 139 | public function defer(callable $callback, $data = null) { 140 | $watcher = new Watcher; 141 | $watcher->type = Watcher::DEFER; 142 | $watcher->id = $this->nextId++; 143 | $watcher->callback = $callback; 144 | $watcher->data = $data; 145 | 146 | $this->watchers[$watcher->id] = $watcher; 147 | $this->nextTickQueue[$watcher->id] = $watcher; 148 | 149 | return $watcher->id; 150 | } 151 | 152 | /** 153 | * {@inheritdoc} 154 | */ 155 | public function delay($delay, callable $callback, $data = null) { 156 | $delay = (int) $delay; 157 | 158 | if ($delay < 0) { 159 | throw new \InvalidArgumentException("Delay must be greater than or equal to zero"); 160 | } 161 | 162 | $watcher = new Watcher; 163 | $watcher->type = Watcher::DELAY; 164 | $watcher->id = $this->nextId++; 165 | $watcher->callback = $callback; 166 | $watcher->value = $delay; 167 | $watcher->data = $data; 168 | 169 | $this->watchers[$watcher->id] = $watcher; 170 | $this->enableQueue[$watcher->id] = $watcher; 171 | 172 | return $watcher->id; 173 | } 174 | 175 | /** 176 | * {@inheritdoc} 177 | */ 178 | public function repeat($interval, callable $callback, $data = null) { 179 | $interval = (int) $interval; 180 | 181 | if ($interval < 0) { 182 | throw new \InvalidArgumentException("Interval must be greater than or equal to zero"); 183 | } 184 | 185 | $watcher = new Watcher; 186 | $watcher->type = Watcher::REPEAT; 187 | $watcher->id = $this->nextId++; 188 | $watcher->callback = $callback; 189 | $watcher->value = $interval; 190 | $watcher->data = $data; 191 | 192 | $this->watchers[$watcher->id] = $watcher; 193 | $this->enableQueue[$watcher->id] = $watcher; 194 | 195 | return $watcher->id; 196 | } 197 | 198 | /** 199 | * {@inheritdoc} 200 | */ 201 | public function onReadable($stream, callable $callback, $data = null) { 202 | $watcher = new Watcher; 203 | $watcher->type = Watcher::READABLE; 204 | $watcher->id = $this->nextId++; 205 | $watcher->callback = $callback; 206 | $watcher->value = $stream; 207 | $watcher->data = $data; 208 | 209 | $this->watchers[$watcher->id] = $watcher; 210 | $this->enableQueue[$watcher->id] = $watcher; 211 | 212 | return $watcher->id; 213 | } 214 | 215 | /** 216 | * {@inheritdoc} 217 | */ 218 | public function onWritable($stream, callable $callback, $data = null) { 219 | $watcher = new Watcher; 220 | $watcher->type = Watcher::WRITABLE; 221 | $watcher->id = $this->nextId++; 222 | $watcher->callback = $callback; 223 | $watcher->value = $stream; 224 | $watcher->data = $data; 225 | 226 | $this->watchers[$watcher->id] = $watcher; 227 | $this->enableQueue[$watcher->id] = $watcher; 228 | 229 | return $watcher->id; 230 | } 231 | 232 | /** 233 | * {@inheritdoc} 234 | * 235 | * @throws \AsyncInterop\Loop\UnsupportedFeatureException If the pcntl extension is not available. 236 | * @throws \RuntimeException If creating the backend signal handler fails. 237 | */ 238 | public function onSignal($signo, callable $callback, $data = null) { 239 | $watcher = new Watcher; 240 | $watcher->type = Watcher::SIGNAL; 241 | $watcher->id = $this->nextId++; 242 | $watcher->callback = $callback; 243 | $watcher->value = $signo; 244 | $watcher->data = $data; 245 | 246 | $this->watchers[$watcher->id] = $watcher; 247 | $this->enableQueue[$watcher->id] = $watcher; 248 | 249 | return $watcher->id; 250 | } 251 | 252 | /** 253 | * {@inheritdoc} 254 | */ 255 | public function enable($watcherIdentifier) { 256 | if (!isset($this->watchers[$watcherIdentifier])) { 257 | throw new InvalidWatcherException($watcherIdentifier, "Cannot enable an invalid watcher identifier: '{$watcherIdentifier}'"); 258 | } 259 | 260 | $watcher = $this->watchers[$watcherIdentifier]; 261 | 262 | if ($watcher->enabled) { 263 | return; // Watcher already enabled. 264 | } 265 | 266 | $watcher->enabled = true; 267 | 268 | switch ($watcher->type) { 269 | case Watcher::DEFER: 270 | $this->nextTickQueue[$watcher->id] = $watcher; 271 | break; 272 | 273 | default: 274 | $this->enableQueue[$watcher->id] = $watcher; 275 | break; 276 | } 277 | } 278 | 279 | /** 280 | * {@inheritdoc} 281 | */ 282 | public function disable($watcherIdentifier) { 283 | if (!isset($this->watchers[$watcherIdentifier])) { 284 | return; 285 | } 286 | 287 | $watcher = $this->watchers[$watcherIdentifier]; 288 | 289 | if (!$watcher->enabled) { 290 | return; // Watcher already disabled. 291 | } 292 | 293 | $watcher->enabled = false; 294 | $id = $watcher->id; 295 | 296 | switch ($watcher->type) { 297 | case Watcher::DEFER: 298 | if (isset($this->nextTickQueue[$id])) { 299 | // Watcher was only queued to be enabled. 300 | unset($this->nextTickQueue[$id]); 301 | } else { 302 | unset($this->deferQueue[$id]); 303 | } 304 | break; 305 | 306 | default: 307 | if (isset($this->enableQueue[$id])) { 308 | // Watcher was only queued to be enabled. 309 | unset($this->enableQueue[$id]); 310 | } else { 311 | $this->deactivate($watcher); 312 | } 313 | break; 314 | } 315 | } 316 | 317 | /** 318 | * {@inheritdoc} 319 | */ 320 | public function cancel($watcherIdentifier) { 321 | $this->disable($watcherIdentifier); 322 | unset($this->watchers[$watcherIdentifier]); 323 | } 324 | 325 | /** 326 | * {@inheritdoc} 327 | */ 328 | public function reference($watcherIdentifier) { 329 | if (!isset($this->watchers[$watcherIdentifier])) { 330 | throw new InvalidWatcherException($watcherIdentifier, "Cannot reference an invalid watcher identifier: '{$watcherIdentifier}'"); 331 | } 332 | 333 | $this->watchers[$watcherIdentifier]->referenced = true; 334 | } 335 | 336 | /** 337 | * {@inheritdoc} 338 | */ 339 | public function unreference($watcherIdentifier) { 340 | if (!isset($this->watchers[$watcherIdentifier])) { 341 | throw new InvalidWatcherException($watcherIdentifier, "Cannot unreference an invalid watcher identifier: '{$watcherIdentifier}'"); 342 | } 343 | 344 | $this->watchers[$watcherIdentifier]->referenced = false; 345 | } 346 | 347 | /** 348 | * {@inheritdoc} 349 | */ 350 | public function setErrorHandler(callable $callback = null) { 351 | $previous = $this->errorHandler; 352 | $this->errorHandler = $callback; 353 | return $previous; 354 | } 355 | 356 | /** 357 | * {@inheritdoc} 358 | */ 359 | public function getInfo() { 360 | $watchers = [ 361 | "referenced" => 0, 362 | "unreferenced" => 0, 363 | ]; 364 | 365 | $defer = $delay = $repeat = $onReadable = $onWritable = $onSignal = [ 366 | "enabled" => 0, 367 | "disabled" => 0, 368 | ]; 369 | 370 | foreach ($this->watchers as $watcher) { 371 | switch ($watcher->type) { 372 | case Watcher::READABLE: $array = &$onReadable; break; 373 | case Watcher::WRITABLE: $array = &$onWritable; break; 374 | case Watcher::SIGNAL: $array = &$onSignal; break; 375 | case Watcher::DEFER: $array = &$defer; break; 376 | case Watcher::DELAY: $array = &$delay; break; 377 | case Watcher::REPEAT: $array = &$repeat; break; 378 | 379 | default: throw new \DomainException("Unknown watcher type"); 380 | } 381 | 382 | if ($watcher->enabled) { 383 | ++$array["enabled"]; 384 | 385 | if ($watcher->referenced) { 386 | ++$watchers["referenced"]; 387 | } else { 388 | ++$watchers["unreferenced"]; 389 | } 390 | } else { 391 | ++$array["disabled"]; 392 | } 393 | } 394 | 395 | return [ 396 | "watchers" => $watchers, 397 | "defer" => $defer, 398 | "delay" => $delay, 399 | "repeat" => $repeat, 400 | "on_readable" => $onReadable, 401 | "on_writable" => $onWritable, 402 | "on_signal" => $onSignal, 403 | "running" => (bool) $this->running, 404 | ]; 405 | } 406 | 407 | /** 408 | * Returns the same array of data as getInfo(). 409 | * 410 | * @return array 411 | */ 412 | public function __debugInfo() { 413 | return $this->getInfo(); 414 | } 415 | } 416 | -------------------------------------------------------------------------------- /lib/LoopFactory.php: -------------------------------------------------------------------------------- 1 | timerQueue = new \SplPriorityQueue(); 35 | $this->signalHandling = \extension_loaded("pcntl"); 36 | } 37 | 38 | protected function dispatch($blocking) { 39 | $this->selectStreams( 40 | $this->readStreams, 41 | $this->writeStreams, 42 | $blocking ? $this->getTimeout() : 0 43 | ); 44 | 45 | if (!empty($this->timerExpires)) { 46 | $time = (int) (\microtime(true) * self::MILLISEC_PER_SEC); 47 | 48 | while (!$this->timerQueue->isEmpty()) { 49 | list($watcher, $expiration) = $this->timerQueue->top(); 50 | 51 | $id = $watcher->id; 52 | 53 | if (!isset($this->timerExpires[$id]) || $expiration !== $this->timerExpires[$id]) { 54 | $this->timerQueue->extract(); // Timer was removed from queue. 55 | continue; 56 | } 57 | 58 | if ($this->timerExpires[$id] > $time) { // Timer at top of queue has not expired. 59 | break; 60 | } 61 | 62 | $this->timerQueue->extract(); 63 | 64 | if ($watcher->type & Watcher::REPEAT) { 65 | $this->activate([$watcher]); 66 | } else { 67 | $this->cancel($id); 68 | } 69 | 70 | // Execute the timer. 71 | $callback = $watcher->callback; 72 | $callback($id, $watcher->data); 73 | } 74 | } 75 | 76 | if ($this->signalHandling) { 77 | \pcntl_signal_dispatch(); 78 | } 79 | } 80 | 81 | /** 82 | * @param resource[] $read 83 | * @param resource[] $write 84 | * @param int $timeout 85 | */ 86 | private function selectStreams(array $read, array $write, $timeout) { 87 | $timeout /= self::MILLISEC_PER_SEC; 88 | 89 | if (!empty($read) || !empty($write)) { // Use stream_select() if there are any streams in the loop. 90 | if ($timeout >= 0) { 91 | $seconds = (int) $timeout; 92 | $microseconds = (int) (($timeout - $seconds) * self::MICROSEC_PER_SEC); 93 | } else { 94 | $seconds = null; 95 | $microseconds = null; 96 | } 97 | 98 | $except = null; 99 | 100 | // Error reporting suppressed since stream_select() emits an E_WARNING if it is interrupted by a signal. 101 | $count = @\stream_select($read, $write, $except, $seconds, $microseconds); 102 | 103 | if ($count) { 104 | foreach ($read as $stream) { 105 | $streamId = (int) $stream; 106 | if (isset($this->readWatchers[$streamId])) { 107 | foreach ($this->readWatchers[$streamId] as $watcher) { 108 | if (!isset($this->readWatchers[$streamId][$watcher->id])) { 109 | continue; // Watcher disabled by another IO watcher. 110 | } 111 | 112 | $callback = $watcher->callback; 113 | $callback($watcher->id, $stream, $watcher->data); 114 | } 115 | } 116 | } 117 | 118 | foreach ($write as $stream) { 119 | $streamId = (int) $stream; 120 | if (isset($this->writeWatchers[$streamId])) { 121 | foreach ($this->writeWatchers[$streamId] as $watcher) { 122 | if (!isset($this->writeWatchers[$streamId][$watcher->id])) { 123 | continue; // Watcher disabled by another IO watcher. 124 | } 125 | 126 | $callback = $watcher->callback; 127 | $callback($watcher->id, $stream, $watcher->data); 128 | } 129 | } 130 | } 131 | } 132 | 133 | return; 134 | } 135 | 136 | if ($timeout > 0) { // Otherwise sleep with usleep() if $timeout > 0. 137 | \usleep($timeout * self::MICROSEC_PER_SEC); 138 | } 139 | } 140 | 141 | /** 142 | * @return int Milliseconds until next timer expires or -1 if there are no pending times. 143 | */ 144 | private function getTimeout() { 145 | while (!$this->timerQueue->isEmpty()) { 146 | list($watcher, $expiration) = $this->timerQueue->top(); 147 | 148 | $id = $watcher->id; 149 | 150 | if (!isset($this->timerExpires[$id]) || $expiration !== $this->timerExpires[$id]) { 151 | $this->timerQueue->extract(); // Timer was removed from queue. 152 | continue; 153 | } 154 | 155 | $expiration -= (int) (\microtime(true) * self::MILLISEC_PER_SEC); 156 | 157 | if ($expiration < 0) { 158 | return 0; 159 | } 160 | 161 | return $expiration; 162 | } 163 | 164 | return -1; 165 | } 166 | 167 | /** 168 | * {@inheritdoc} 169 | * 170 | * @throws \AsyncInterop\Loop\UnsupportedFeatureException If the pcntl extension is not available. 171 | * @throws \RuntimeException If creating the backend signal handler fails. 172 | */ 173 | public function onSignal($signo, callable $callback, $data = null) { 174 | if (!$this->signalHandling) { 175 | throw new UnsupportedFeatureException("Signal handling requires the pcntl extension"); 176 | } 177 | 178 | return parent::onSignal($signo, $callback, $data); 179 | } 180 | 181 | /** 182 | * {@inheritdoc} 183 | */ 184 | protected function activate(array $watchers) { 185 | foreach ($watchers as $watcher) { 186 | switch ($watcher->type) { 187 | case Watcher::READABLE: 188 | $streamId = (int) $watcher->value; 189 | $this->readWatchers[$streamId][$watcher->id] = $watcher; 190 | $this->readStreams[$streamId] = $watcher->value; 191 | break; 192 | 193 | case Watcher::WRITABLE: 194 | $streamId = (int) $watcher->value; 195 | $this->writeWatchers[$streamId][$watcher->id] = $watcher; 196 | $this->writeStreams[$streamId] = $watcher->value; 197 | break; 198 | 199 | case Watcher::DELAY: 200 | case Watcher::REPEAT: 201 | $expiration = (int) (\microtime(true) * self::MILLISEC_PER_SEC) + $watcher->value; 202 | $this->timerExpires[$watcher->id] = $expiration; 203 | $this->timerQueue->insert([$watcher, $expiration], -$expiration); 204 | break; 205 | 206 | case Watcher::SIGNAL: 207 | if (!isset($this->signalWatchers[$watcher->value])) { 208 | if (!@\pcntl_signal($watcher->value, [$this, 'handleSignal'])) { 209 | throw new \RuntimeException("Failed to register signal handler"); 210 | } 211 | } 212 | 213 | $this->signalWatchers[$watcher->value][$watcher->id] = $watcher; 214 | break; 215 | 216 | default: 217 | throw new \DomainException("Unknown watcher type"); 218 | } 219 | } 220 | } 221 | 222 | /** 223 | * {@inheritdoc} 224 | */ 225 | protected function deactivate(Watcher $watcher) { 226 | switch ($watcher->type) { 227 | case Watcher::READABLE: 228 | $streamId = (int) $watcher->value; 229 | unset($this->readWatchers[$streamId][$watcher->id]); 230 | if (empty($this->readWatchers[$streamId])) { 231 | unset($this->readWatchers[$streamId], $this->readStreams[$streamId]); 232 | } 233 | break; 234 | 235 | case Watcher::WRITABLE: 236 | $streamId = (int) $watcher->value; 237 | unset($this->writeWatchers[$streamId][$watcher->id]); 238 | if (empty($this->writeWatchers[$streamId])) { 239 | unset($this->writeWatchers[$streamId], $this->writeStreams[$streamId]); 240 | } 241 | break; 242 | 243 | case Watcher::DELAY: 244 | case Watcher::REPEAT: 245 | unset($this->timerExpires[$watcher->id]); 246 | break; 247 | 248 | case Watcher::SIGNAL: 249 | if (isset($this->signalWatchers[$watcher->value])) { 250 | unset($this->signalWatchers[$watcher->value][$watcher->id]); 251 | 252 | if (empty($this->signalWatchers[$watcher->value])) { 253 | unset($this->signalWatchers[$watcher->value]); 254 | @\pcntl_signal($watcher->value, \SIG_DFL); 255 | } 256 | } 257 | break; 258 | 259 | default: throw new \DomainException("Unknown watcher type"); 260 | } 261 | } 262 | 263 | /** 264 | * @param int $signo 265 | */ 266 | private function handleSignal($signo) { 267 | foreach ($this->signalWatchers[$signo] as $watcher) { 268 | if (!isset($this->signalWatchers[$signo][$watcher->id])) { 269 | continue; 270 | } 271 | 272 | $callback = $watcher->callback; 273 | $callback($watcher->id, $signo, $watcher->data); 274 | } 275 | } 276 | 277 | /** 278 | * {@inheritdoc} 279 | */ 280 | public function getHandle() { 281 | return null; 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /lib/UvLoop.php: -------------------------------------------------------------------------------- 1 | handle = \uv_loop_new(); 38 | 39 | $this->ioCallback = function ($event, $status, $events, $resource) { 40 | switch ($status) { 41 | case 0: // OK 42 | break; 43 | 44 | // If $status is a severe error, stop the poll and throw an exception. 45 | case \UV::EACCES: 46 | case \UV::EBADF: 47 | case \UV::EINVAL: 48 | case \UV::ENOTSOCK: 49 | throw new \RuntimeException( 50 | \sprintf("UV_%s: %s", \uv_err_name($status), \ucfirst(\uv_strerror($status))) 51 | ); 52 | 53 | default: // Ignore other (probably) trivial warnings and continuing polling. 54 | return; 55 | } 56 | 57 | $watchers = $this->watchers[(int) $event]; 58 | 59 | foreach ($watchers as $watcher) { 60 | $callback = $watcher->callback; 61 | $callback($watcher->id, $resource, $watcher->data); 62 | } 63 | }; 64 | 65 | $this->timerCallback = function ($event) { 66 | $watcher = $this->watchers[(int) $event]; 67 | 68 | if ($watcher->type & Watcher::DELAY) { 69 | $this->cancel($watcher->id); 70 | } 71 | 72 | $callback = $watcher->callback; 73 | $callback($watcher->id, $watcher->data); 74 | }; 75 | 76 | $this->signalCallback = function ($event, $signo) { 77 | $watcher = $this->watchers[(int) $event]; 78 | 79 | $callback = $watcher->callback; 80 | $callback($watcher->id, $signo, $watcher->data); 81 | }; 82 | } 83 | 84 | /** 85 | * {@inheritdoc} 86 | */ 87 | protected function dispatch($blocking) { 88 | \uv_run($this->handle, $blocking ? \UV::RUN_ONCE : \UV::RUN_NOWAIT); 89 | } 90 | 91 | /** 92 | * {@inheritdoc} 93 | */ 94 | protected function activate(array $watchers) { 95 | foreach ($watchers as $watcher) { 96 | $id = $watcher->id; 97 | 98 | switch ($watcher->type) { 99 | case Watcher::READABLE: 100 | $streamId = (int) $watcher->value; 101 | 102 | if (isset($this->read[$streamId])) { 103 | $event = $this->read[$streamId]; 104 | } elseif (isset($this->events[$id])) { 105 | $event = $this->read[$streamId] = $this->events[$id]; 106 | } else { 107 | $event = $this->read[$streamId] = \uv_poll_init_socket($this->handle, $watcher->value); 108 | } 109 | 110 | $this->events[$id] = $event; 111 | $this->watchers[(int) $event][$id] = $watcher; 112 | 113 | if (!\uv_is_active($event)) { 114 | \uv_poll_start($event, \UV::READABLE, $this->ioCallback); 115 | } 116 | break; 117 | 118 | case Watcher::WRITABLE: 119 | $streamId = (int) $watcher->value; 120 | 121 | if (isset($this->write[$streamId])) { 122 | $event = $this->write[$streamId]; 123 | } elseif (isset($this->events[$id])) { 124 | $event = $this->write[$streamId] = $this->events[$id]; 125 | } else { 126 | $event = $this->write[$streamId] = \uv_poll_init_socket($this->handle, $watcher->value); 127 | } 128 | 129 | $this->events[$id] = $event; 130 | $this->watchers[(int) $event][$id] = $watcher; 131 | 132 | 133 | if (!\uv_is_active($event)) { 134 | \uv_poll_start($event, \UV::WRITABLE, $this->ioCallback); 135 | } 136 | break; 137 | 138 | case Watcher::DELAY: 139 | case Watcher::REPEAT: 140 | if (isset($this->events[$id])) { 141 | $event = $this->events[$id]; 142 | } else { 143 | $event = $this->events[$id] = \uv_timer_init($this->handle); 144 | } 145 | 146 | $this->watchers[(int) $event] = $watcher; 147 | 148 | \uv_timer_start( 149 | $event, 150 | $watcher->value, 151 | $watcher->type & Watcher::REPEAT ? $watcher->value : 0, 152 | $this->timerCallback 153 | ); 154 | break; 155 | 156 | case Watcher::SIGNAL: 157 | if (isset($this->events[$id])) { 158 | $event = $this->events[$id]; 159 | } else { 160 | $event = $this->events[$id] = \uv_signal_init($this->handle); 161 | } 162 | 163 | $this->watchers[(int) $event] = $watcher; 164 | 165 | \uv_signal_start($event, $this->signalCallback, $watcher->value); 166 | break; 167 | 168 | default: throw new \DomainException("Unknown watcher type"); 169 | } 170 | } 171 | } 172 | 173 | /** 174 | * {@inheritdoc} 175 | */ 176 | protected function deactivate(Watcher $watcher) { 177 | $id = $watcher->id; 178 | 179 | if (!isset($this->events[$id])) { 180 | return; 181 | } 182 | 183 | $event = $this->events[$id]; 184 | $eventId = (int) $event; 185 | 186 | switch ($watcher->type) { 187 | case Watcher::READABLE: 188 | unset($this->watchers[$eventId][$id]); 189 | 190 | if (empty($this->watchers[$eventId])) { 191 | unset($this->watchers[$eventId]); 192 | unset($this->read[(int) $watcher->value]); 193 | if (\uv_is_active($event)) { 194 | \uv_poll_stop($event); 195 | } 196 | } 197 | break; 198 | 199 | case Watcher::WRITABLE: 200 | unset($this->watchers[$eventId][$id]); 201 | 202 | if (empty($this->watchers[$eventId])) { 203 | unset($this->watchers[$eventId]); 204 | unset($this->write[(int) $watcher->value]); 205 | if (\uv_is_active($event)) { 206 | \uv_poll_stop($event); 207 | } 208 | } 209 | break; 210 | 211 | case Watcher::DELAY: 212 | case Watcher::REPEAT: 213 | unset($this->watchers[$eventId]); 214 | if (\uv_is_active($event)) { 215 | \uv_timer_stop($event); 216 | } 217 | break; 218 | 219 | case Watcher::SIGNAL: 220 | unset($this->watchers[$eventId]); 221 | if (\uv_is_active($event)) { 222 | \uv_signal_stop($event); 223 | } 224 | break; 225 | 226 | default: throw new \DomainException("Unknown watcher type"); 227 | } 228 | } 229 | 230 | /** 231 | * {@inheritdoc} 232 | */ 233 | public function cancel($watcherIdentifier) { 234 | parent::cancel($watcherIdentifier); 235 | 236 | if (!isset($this->events[$watcherIdentifier])) { 237 | return; 238 | } 239 | 240 | $event = $this->events[$watcherIdentifier]; 241 | 242 | if (empty($this->watchers[(int) $event])) { 243 | \uv_close($event); 244 | } 245 | 246 | unset($this->events[$watcherIdentifier]); 247 | } 248 | 249 | /** 250 | * {@inheritdoc} 251 | */ 252 | public function getHandle() { 253 | return $this->handle; 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /lib/bootstrap.php: -------------------------------------------------------------------------------- 1 |