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; }
/** * {@inheritdoc} */ public function cancel(\Throwable $e) : array { if ($this->state === self::PENDING) { Loop::cancel($this->watcher); $this->fail($e); } return []; }
/** * {@inheritdoc} */ public function when(callable $onResolved) { try { $onResolved($this->exception, null); } catch (\Throwable $exception) { Loop::defer(static function () use($exception) { throw $exception; }); } }
public function when(callable $onResolved) { try { $onResolved($this->error); } catch (\Throwable $e) { Loop::defer(function () use($e) { throw $e; }); } }
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(); }); } }
/** * 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; }
/** @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); }
/** * 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; }); } } }
/** * @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()); }
/** * {@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 []; }
/** * @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); }); }); }
<?php namespace Amp\Loop; use Interop\Async\Loop; Loop::setFactory(new LoopFactory());
/** * @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()); }
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()); } }
/** * 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; }
/** * 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; } } }
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);
/** * Check if a loop implementation is available * * @return bool */ protected function hasActiveLoop() { try { Loop::get(); return true; } catch (\LogicException $exception) { return false; } }
/** * 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; } }); }
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); } }
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); } } } }
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); }; }
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; }
/** * @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()); }
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); }; }
protected function listenForExit() : \Generator { try { yield from $this->transmitter->receive(); } finally { $this->stop(); $this->socket->close(); Loop::stop(); } }