See also: Interop\Async\Loop\Driver
Example #1
0
 protected function pollFilesystem()
 {
     if (empty($this->watchers)) {
         return;
     }
     $watchers = [];
     foreach ($this->watchers as $id => list($path, $events, $callback)) {
         $sync = $this->pool->invokeStaticMethod(static::class, 'collectStats', $path);
         $sync->when(function ($e, $v = null) use($id, $path, $events, $callback) {
             if (!$e) {
                 try {
                     if (isset($this->state[$id])) {
                         $this->triggerEvents($id, $this->state[$id], $v, $events, $callback);
                     }
                 } finally {
                     $this->state[$id] = $v;
                 }
             }
         });
         $watchers[] = $sync;
     }
     $await = new AwaitPending($watchers);
     $await->when(function () {
         $this->timer = Loop::delay($this->interval, function () {
             $this->pollFilesystem();
         });
     });
     return $await;
 }
Example #2
0
 /**
  * {@inheritdoc}
  */
 public function cancel(\Throwable $e) : array
 {
     if ($this->state === self::PENDING) {
         Loop::cancel($this->watcher);
         $this->fail($e);
     }
     return [];
 }
Example #3
0
 /**
  * {@inheritdoc}
  */
 public function when(callable $onResolved)
 {
     try {
         $onResolved($this->exception, null);
     } catch (\Throwable $exception) {
         Loop::defer(static function () use($exception) {
             throw $exception;
         });
     }
 }
Example #4
0
 public function when(callable $onResolved)
 {
     try {
         $onResolved($this->error);
     } catch (\Throwable $e) {
         Loop::defer(function () use($e) {
             throw $e;
         });
     }
 }
Example #5
0
 protected function runServer(callable $server, callable $client) : \Generator
 {
     $socket = (yield $this->serverFactory->createSocketServer());
     try {
         return (yield new AwaitAll([new Coroutine($this->serverTask($socket, $server)), new Coroutine($this->clientTask($socket, $client))]));
     } finally {
         Loop::defer(function () use($socket) {
             $socket->close();
         });
     }
 }
Example #6
0
 /**
  * Load the current loop config from the loop registry.
  * 
  * Will return a shared default config if used outside of a KoolKode loop implementations run method.
  * 
  * Do not keep a reference to the component as it can change depending on the active loop!
  */
 public static function currentConfig() : LoopConfig
 {
     $config = LoopRegistry::getState(LoopConfig::class);
     if ($config instanceof LoopConfig) {
         return $config;
     }
     if (self::$defaultConfig === null) {
         return self::$defaultConfig = new LoopConfig();
     }
     return self::$defaultConfig;
 }
Example #7
0
 /** @test */
 public function executeStackReturnsScopedDriver()
 {
     $driver1 = new DummyDriver();
     $driver2 = new DummyDriver();
     Loop::execute(function () use($driver1, $driver2) {
         $this->assertSame($driver1, Loop::get());
         Loop::execute(function () use($driver2) {
             $this->assertSame($driver2, Loop::get());
         }, $driver2);
         $this->assertSame($driver1, Loop::get());
     }, $driver1);
 }
Example #8
0
 /**
  * Calls each callback in the queue, passing the provided values to the function.
  *
  * @param \Throwable|null $exception
  * @param mixed $value
  */
 public function __invoke($exception, $value)
 {
     foreach ($this->queue as $callback) {
         try {
             $callback($exception, $value);
         } catch (\Throwable $exception) {
             Loop::defer(static function () use($exception) {
                 throw $exception;
             });
         }
     }
 }
Example #9
0
 /**
  * @codeCoverageIgnore
  */
 public function run()
 {
     \ob_start(function () {
     });
     \error_reporting(-1);
     \ini_set('display_errors', false);
     \set_error_handler(function ($severity, $message) {
         if ($severity & \error_reporting()) {
             throw new \Error($message, $severity);
         }
     });
     require_once $this->autoloadFile;
     Loop::execute(function () {
         new Coroutine($this->processWork());
     }, new NativeLoop());
 }
Example #10
0
 /**
  * {@inheritdoc}
  */
 public function cancel(\Throwable $e) : array
 {
     if ($this->state === self::PENDING) {
         Loop::cancel($this->watcher);
         try {
             if ($this->promise instanceof Awaitable) {
                 $this->state = self::FAILED;
                 try {
                     return $this->promise->cancel($e);
                 } finally {
                     $this->state = self::PENDING;
                 }
             }
         } finally {
             $this->fail($e);
         }
     }
     return [];
 }
Example #11
0
 /**
  * @param callable(callable(mixed $value): Promise $emit): \Generator $emitter
  *
  * @throws \Error Thrown if the callable does not return a Generator.
  */
 public function __construct(callable $emitter)
 {
     $result = $emitter($this->callableFromInstanceMethod("emit"));
     if (!$result instanceof \Generator) {
         throw new \Error("The callable did not return a Generator");
     }
     Loop::defer(function () use($result) {
         $coroutine = new Coroutine($result);
         $coroutine->when(function ($exception, $value) {
             if ($this->resolved) {
                 return;
             }
             if ($exception) {
                 $this->fail($exception);
                 return;
             }
             $this->resolve($value);
         });
     });
 }
Example #12
0
<?php

namespace Amp\Loop;

use Interop\Async\Loop;
Loop::setFactory(new LoopFactory());
Example #13
0
 /**
  * @codeCoverageIgnore
  */
 private static function shutdownPool()
 {
     Loop::execute(function () {
         $await = [];
         try {
             foreach (self::$handshakes as list($worker, $defer)) {
                 $defer->fail(new PoolShutdownException('Pool shut down during worker handshake'));
                 $await[] = $worker->dispose();
             }
         } catch (\Throwable $e) {
             fwrite(STDERR, "PROCESS POOL SHUTDOWN ERROR:\n{$e}");
         } finally {
             self::$handshakes = [];
         }
         try {
             foreach (self::$sharedWorkers as $worker) {
                 $await[] = $worker->dispose();
             }
         } catch (\Throwable $e) {
             fwrite(STDERR, "PROCESS POOL SHUTDOWN ERROR:\n{$e}");
         } finally {
             self::$sharedWorkers = null;
         }
         if (self::$serverAwait !== null) {
             try {
                 self::$serverAwait->cancel(new PoolShutdownException('Handshakes done'));
             } catch (\Throwable $e) {
                 fwrite(STDERR, "PROCESS POOL SHUTDOWN ERROR:\n{$e}");
             }
         }
         if (!empty($await)) {
             $await = new AwaitAll($await);
             $await->when(function ($e = null, array $val = null) {
                 if (self::$server !== null) {
                     try {
                         @\fclose(self::$server);
                     } finally {
                         self::$server = null;
                     }
                 }
             });
         }
     }, new NativeLoop());
 }
Example #14
0
 protected function advance($val)
 {
     while (($this->valid = $this->generator->valid()) && $val instanceof Promise) {
         $done = false;
         $val->when(function (\Throwable $e = null, $v = null) use(&$val, &$done) {
             if (!$this->valid || !self::$alive) {
                 return;
             }
             if ($e) {
                 $this->dispose($e);
             } elseif ($done) {
                 $this->running = true;
                 try {
                     $this->advance($this->generator->send($v));
                     $this->running = false;
                     if ($this->error) {
                         $this->dispose($this->error);
                     }
                 } catch (\Throwable $e) {
                     $this->running = false;
                     $this->dispose($e);
                 }
             } else {
                 $done = true;
                 $val = $v;
             }
         });
         if (!$done || !self::$alive) {
             $done = true;
             $this->promise = $val;
             return;
         }
         $this->running = true;
         try {
             $val = $this->generator->send($val);
             $this->running = false;
             if ($this->error) {
                 return $this->dispose($this->error);
             }
         } catch (\Throwable $e) {
             $this->running = false;
             return $this->dispose($e);
         }
     }
     if ($this->valid) {
         return Loop::defer(function ($id, $val) {
             if (!$this->valid || !self::$alive) {
                 return;
             }
             $this->running = true;
             try {
                 $this->advance($this->generator->send($val));
                 $this->running = false;
                 if ($this->error) {
                     $this->dispose($this->error);
                 }
             } catch (\Throwable $e) {
                 $this->running = false;
                 $this->dispose($e);
             }
         }, $val);
     }
     if ($this->error) {
         return $this->dispose($this->error);
     }
     if ($this->state === self::PENDING) {
         $this->resolve($this->generator->getReturn());
     }
 }
Example #15
0
 /**
  * Run test method as a coroutine.
  */
 public final function runTestCaseWithinLoop(...$args)
 {
     $this->setName($this->testMethodNameBackup);
     $result = $this->{$this->testMethodNameBackup}(...$args);
     if ($result instanceof \Generator) {
         $generator = $result;
         $loop = $this->getLoop();
         $e = null;
         try {
             Loop::execute(function () use(&$result, &$e) {
                 $coroutine = new Coroutine($result);
                 $coroutine->when(function ($error, $val = null) use(&$result, &$e) {
                     if ($error) {
                         $e = $error;
                     } else {
                         $result = $val;
                     }
                     $this->disposeTest();
                 });
             }, $loop);
         } finally {
             if ($this->loggers) {
                 try {
                     foreach ($this->loggers as $handler) {
                         $this->getLoopConfig()->getLogger()->removeHandler($handler);
                     }
                 } finally {
                     $this->loggers = [];
                 }
             }
             if ($loop instanceof KoolLoop) {
                 $loop->reset();
             }
         }
         if ($e !== null) {
             throw $e;
         }
         if ($generator->valid()) {
             throw new \RuntimeException('Async test case did not finish!');
         }
     }
     return $result;
 }
Example #16
0
 /**
  * Fail the awaitable with the given error.
  * 
  * @param \Throwable $error
  * 
  * @throws \RuntimeException If the awaitable has already been resolved or failed.
  */
 protected function fail(\Throwable $error)
 {
     if ($this->state !== Awaitable::PENDING) {
         return;
     }
     $this->state = Awaitable::FAILED;
     $this->result = $error;
     if ($this->callbacks) {
         try {
             foreach ($this->callbacks as $callback) {
                 try {
                     $callback($error);
                 } catch (\Throwable $ex) {
                     Loop::defer(function () use($ex) {
                         throw $ex;
                     });
                 }
             }
         } finally {
             $this->callbacks = null;
         }
     }
 }
Example #17
0
                list($type, $payload) = (yield from $transmitter->receive());
                if ($type === SocketTransmitter::TYPE_EXIT) {
                    yield from $transmitter->send($payload, $type);
                    $socket->close();
                    $exitCode = 0;
                    break;
                }
                try {
                    if (isset($payload['func'])) {
                        if (\substr($payload['func'], 0, 1) === '@') {
                            $result = \substr($payload['func'], 1)(...$payload['args'] ?? []);
                        } else {
                            $result = $payload['func'](...$payload['args'] ?? []);
                        }
                    } elseif (isset($payload['class']) && isset($payload['method'])) {
                        $result = $payload['class']::{$payload['method']}(...$payload['args'] ?? []);
                    } else {
                        throw new \RuntimeException('No callable target passed to threaded worker');
                    }
                } catch (\Throwable $e) {
                    yield from $transmitter->sendError($e);
                    continue;
                }
                yield from $transmitter->send($result, SocketTransmitter::TYPE_DATA);
            }
        } finally {
            Loop::stop();
        }
    });
}, new NativeLoop());
exit($exitCode);
Example #18
0
 /**
  * Check if a loop implementation is available
  *
  * @return bool
  */
 protected function hasActiveLoop()
 {
     try {
         Loop::get();
         return true;
     } catch (\LogicException $exception) {
         return false;
     }
 }
Example #19
0
 /**
  * Write bytes to the stream, register a writable watcher if not all bytes could be written.
  * 
  * @param resource $stream Resource to be written to.
  * @param string $bytes Bytes to be written.
  * @param int $chunkSize Write data chunk size.
  */
 protected function writeBytesToStream($stream, string $bytes, int $chunkSize)
 {
     $len = 0;
     try {
         if ($this->writeBytes($stream, $len, $bytes, $chunkSize)) {
             return $this->resolve($len);
         }
     } catch (\Throwable $e) {
         return $this->fail($e);
     }
     $retry = false;
     $this->watcher = Loop::onWritable($stream, function ($watcherId) use(&$len, &$bytes, &$retry, $stream, $chunkSize) {
         try {
             $length = $len;
             if ($this->writeBytes($stream, $len, $bytes, $chunkSize)) {
                 $this->watcher = null;
                 Loop::cancel($watcherId);
                 $retry = false;
                 return $this->resolve($len);
             }
             if ($retry && $length === $len) {
                 throw new StreamClosedException('Write failed after retry, assuming broken pipe');
             }
         } catch (\Throwable $e) {
             $this->watcher = null;
             Loop::cancel($watcherId);
             return $this->fail($e);
         }
         if ($length === $len) {
             $retry = true;
         }
     });
 }
Example #20
0
 protected function dispose(\Throwable $e)
 {
     if ($this->promise) {
         $promise = $this->promise;
         $this->promise = null;
         Loop::defer(function () use($promise, $e) {
             $promise->cancel($e);
         });
     }
     $this->error = null;
     while ($this->valid = $this->generator->valid() && self::$alive) {
         $this->running = true;
         try {
             return $this->advance($this->generator->throw($e));
         } catch (\Throwable $e) {
             // Continue loop switching to the outer exception.
         } finally {
             $this->running = false;
         }
     }
     if (!self::$alive || !$this->valid && $this->state === Awaitable::PENDING) {
         $this->fail($e);
     }
 }
Example #21
0
 public function run(Container $container, callable $action)
 {
     $logger = LoopConfig::getLogger();
     if ($this->running === 0) {
         foreach ($this->loggers as $handler) {
             $logger->addHandler($handler);
         }
     }
     $this->running++;
     try {
         $buffer = '';
         if ($this->ipc) {
             ob_start(function (string $data, int $phase) use(&$buffer) {
                 $buffer .= $data;
                 return '';
             }, 1, \PHP_OUTPUT_HANDLER_FLUSHABLE);
         }
         Loop::execute(function () use($container, $action, $logger, &$buffer) {
             Loop::setErrorHandler(function (\Throwable $e) use($logger) {
                 $logger->critical('', ['exception' => $e]);
             });
             $watcher = Loop::repeat(500, function () use(&$buffer, $logger) {
                 if ($buffer !== '') {
                     try {
                         $this->ipc->sendOutput($buffer);
                     } finally {
                         $buffer = '';
                     }
                 }
             });
             Loop::unreference($watcher);
             if ($this->contextName === 'development') {
                 $locator = $container->get(ResourceLocator::class);
                 $watcher = Loop::repeat(5000, function () use($locator) {
                     $locator->syncFiles();
                 });
                 Loop::unreference($watcher);
             }
             if ($this->ipc) {
                 $this->ipc->run();
             }
             $signal = function () {
                 if ($this->ipc) {
                     $this->ipc->stop();
                 }
                 Loop::stop();
             };
             // Shutdown on SIGTERM.
             try {
                 Loop::onSignal(15, $signal);
             } catch (UnsupportedFeatureException $e) {
                 // signal handling is not available...
             }
             $action();
         });
     } finally {
         if ($this->ipc) {
             \ob_end_clean();
         }
         $this->running--;
         if ($this->running === 0) {
             foreach ($this->loggers as $handler) {
                 $logger->removeHandler($handler);
             }
         }
     }
 }
Example #22
0
 protected function createWriteWatcher() : callable
 {
     return function ($id) {
         while (!$this->pendingWrites->isEmpty()) {
             $write = $this->pendingWrites->bottom();
             if ($write->disabled) {
                 $this->pendingWrites->dequeue();
                 continue;
             }
             try {
                 $write->bytes = $this->writeBytes($write->bytes);
             } catch (\Throwable $e) {
                 $this->writerEnabled = false;
                 Loop::disable($id);
                 while (!$this->pendingWrites->isEmpty()) {
                     $this->pendingWrites->dequeue()->fail($e);
                 }
                 return;
             }
             if ($write->bytes === '') {
                 $this->pendingWrites->dequeue()->resolve($write->length);
             } else {
                 return;
             }
         }
         $this->writerEnabled = false;
         Loop::disable($id);
     };
 }
Example #23
0
 public function handleCallback(string $func, ...$args) : Awaitable
 {
     $request = null;
     $defer = new Deferred(function () use(&$request) {
         if (\is_resource($request)) {
             \eio_cancel($request);
         }
     });
     $defer->when(function () {
         $this->pending--;
         if (!$this->pending) {
             Loop::disable($this->watcherId);
         }
     });
     for ($len = \count($args), $i = 0; $i < $len; $i++) {
         if ($args[$i] instanceof \Closure) {
             $callback = $args[$i];
             $args[$i] = function (...$args) use($defer, $callback) {
                 try {
                     $result = $callback(...$args);
                 } catch (\Throwable $e) {
                     return $defer->fail($e);
                 }
                 if ($result instanceof \Generator) {
                     $defer->resolve(new Coroutine($result));
                 } else {
                     $defer->resolve($result);
                 }
             };
             break;
         }
     }
     if (!isset($callback)) {
         throw new \InvalidArgumentException('Missing callback argument');
     }
     if (!$this->pending) {
         if ($this->watcherId === null) {
             $this->watcherId = Loop::onReadable(self::$eio, self::$callback);
         } else {
             Loop::enable($this->watcherId);
         }
     }
     $this->pending++;
     try {
         $request = $func(...$args);
     } catch (\Throwable $e) {
         $this->pending--;
         if (!$this->pending) {
             Loop::disable($this->watcherId);
         }
         return new Failure($e);
     }
     return $defer;
 }
Example #24
0
 /**
  * @codeCoverageIgnore
  */
 private static function shutdownPool()
 {
     Loop::execute(function () {
         $awaitables = [];
         try {
             foreach (self::$sharedWorkers as $worker) {
                 try {
                     $awaitables[] = $worker->dispose();
                 } catch (\Throwable $e) {
                     fwrite(STDERR, "THREAD POOL SHUTDOWN ERROR:\n{$e}");
                 }
             }
         } finally {
             self::$sharedWorkers = null;
         }
         $await = new AwaitAll($awaitables);
         $await->when(function ($e = null, array $threads = null) {
             foreach ((array) $threads as $thread) {
                 $thread->join();
             }
         });
     }, new NativeLoop());
 }
Example #25
0
 protected function createReadWatcher() : callable
 {
     return function ($id) {
         while (!$this->pendingReads->isEmpty()) {
             $read = $this->pendingReads->bottom();
             if ($read->disabled) {
                 $this->pendingReads->dequeue();
                 continue;
             }
             $len = \strlen($this->readBuffer);
             try {
                 $chunk = $this->fillReadBuffer($len, $read->length);
             } catch (\Throwable $e) {
                 $this->readerEnabled = false;
                 Loop::disable($id);
                 while (!$this->pendingReads->isEmpty()) {
                     $this->pendingReads->dequeue()->fail($e);
                 }
                 return;
             }
             if ($chunk === $this) {
                 return;
             }
             if ($chunk === null) {
                 $this->readerEnabled = false;
                 Loop::disable($id);
                 while (!$this->pendingReads->isEmpty()) {
                     $this->pendingReads->dequeue()->resolve(null);
                 }
                 return;
             }
             $read = $this->pendingReads->dequeue();
             if ($len > $read->length) {
                 $this->readBuffer = \substr($chunk, $read->length);
                 $read->resolve(\substr($chunk, 0, $read->length));
             } else {
                 $this->readBuffer = '';
                 $read->resolve($chunk);
             }
         }
         $this->readerEnabled = false;
         Loop::disable($id);
     };
 }
Example #26
0
 protected function listenForExit() : \Generator
 {
     try {
         yield from $this->transmitter->receive();
     } finally {
         $this->stop();
         $this->socket->close();
         Loop::stop();
     }
 }