/** * Runs the thread code and the initialized function. * * @codeCoverageIgnore Only executed in thread. */ public function run() { /* First thing we need to do is re-initialize the class autoloader. If * we don't do this first, any object of a class that was loaded after * the thread started will just be garbage data and unserializable * values (like resources) will be lost. This happens even with * thread-safe objects. */ foreach (get_declared_classes() as $className) { if (strpos($className, 'ComposerAutoloaderInit') === 0) { // Calling getLoader() will register the class loader for us $className::getLoader(); break; } } Loop\loop($loop = Loop\create(false)); // Disable signals in thread. // At this point, the thread environment has been prepared so begin using the thread. try { $channel = new ChannelledStream(new DuplexPipe($this->socket, false)); } catch (\Throwable $exception) { return; // Parent has destroyed Thread object, so just exit. } $coroutine = new Coroutine($this->execute($channel)); $coroutine->done(); $timer = $loop->timer(self::KILL_CHECK_FREQUENCY, true, function () use($loop) { if ($this->killed) { $loop->stop(); } }); $timer->unreference(); $loop->run(); }
/** * Accepts incoming connections as they are made. */ private function accept(SocketServer $server) : Generator { yield from log()->log(Log::INFO, 'NNTP server listening on %s:%d', $server->getAddress(), $server->getPort()); while ($server->isOpen()) { // Wait for a client to connect. $socket = (yield from $server->accept()); // Handle the client in a separate coroutine. $coroutine = new Coroutine($this->handleClient($socket)); $coroutine->done(); } }
public function testExecuteWithJsonBody() { $method = $this->createMock(AbstractMethodWithBodyStub::class); $method->expects($this->once())->method('getMethodName')->willReturn('/someMethod'); $method->expects($this->once())->method('getHttpMethod')->willReturn('POST'); $method->expects($this->once())->method('getParams')->willReturn(['param1' => 'value1', 'param2' => 'value2']); $method->expects($this->once())->method('buildResult')->willReturn('result'); $method->expects($this->once())->method('jsonSerialize')->willReturn(['jsonParam1' => 'jsonValue1', 'jsonParam2' => 'jsonValue2']); $responseData = json_encode(['ok' => true, 'result' => 'result'], JSON_UNESCAPED_UNICODE); $this->setUpHttpClient($responseData); $api = new Api($this->telegramToken, $this->httpClient); $coroutine = new Coroutine($api->execute($method)); $result = $coroutine->wait(); $this->assertEquals('result', $result); }
/** * {@inheritdoc} */ public function dispose(Throwable $exception = null) { if (null === $exception) { $exception = new DisposedException('Observable disposed.'); } $this->emitter = null; $this->queue->fail($exception); if (null !== $this->coroutine) { $this->coroutine->cancel($exception); } }
/** * Executes the emitter coroutine. */ private function start() { Loop\queue(function () { // Asynchronously start the observable. if (null === $this->emitter) { return; } /** * Emits a value from the observable. * * @coroutine * * @param mixed $value If $value is an instance of \Icicle\Awaitable\Awaitable, the fulfillment value is * used as the value to emit or the rejection reason is thrown from this coroutine. * * @return \Generator * * @resolve mixed The emitted value (the resolution value of $value) * * @throws \Icicle\Observable\Exception\CompletedError If the observable has been completed. */ $emit = function ($value = null) : \Generator { return $this->queue->push($value); }; try { $generator = ($this->emitter)($emit); if (!$generator instanceof Generator) { throw new UnexpectedTypeError('Generator', $generator); } $this->coroutine = new Coroutine($generator); $this->coroutine->done([$this->queue, 'complete'], [$this->queue, 'fail']); } catch (Throwable $exception) { $this->queue->fail(new InvalidEmitterError($this->emitter, $exception)); } $this->emitter = null; }); }
/** * Processing loop */ public function pump() { try { while ($this->isOpen()) { (yield $this->next()); } } catch (\Exception $e) { if ($this->outputHeartbeatCoroutine) { $this->outputHeartbeatCoroutine->cancel(); $this->outputHeartbeatCoroutine = null; } if ($this->inputHeartbeatCoroutine) { $this->inputHeartbeatCoroutine->cancel(); $this->inputHeartbeatCoroutine = null; } (yield $this->client->close()); } }
/** * @coroutine * * @param \Icicle\Http\Message\Message $message * @param float|int $timeout * * @return \Generator * * @resolve \Icicle\Http\Message\Message * * @throws \Icicle\Http\Exception\MessageException */ private function buildIncomingStream(Message $message, float $timeout = 0) : \Generator { $body = $message->getBody(); if ($body instanceof SeekableStream && $body->isOpen()) { yield from $body->seek(0); } if (!$body->isReadable()) { return $message; } if (strtolower($message->getHeader('Transfer-Encoding') === 'chunked')) { $stream = new ChunkedDecoder($this->hwm); $coroutine = new Coroutine(Stream\pipe($body, $stream, true, 0, null, $timeout)); $coroutine->done(null, [$stream, 'close']); $message = $message->withBody($stream); } elseif ($message->hasHeader('Content-Length')) { $length = (int) $message->getHeader('Content-Length'); if (0 > $length) { throw new MessageException(Response::BAD_REQUEST, 'Content-Length header invalid.'); } $stream = new MemoryStream($this->hwm); if (0 === $length) { yield from $stream->end(); } else { $coroutine = new Coroutine(Stream\pipe($body, $stream, true, $length, null, $timeout)); $coroutine->done(null, [$stream, 'close']); } $message = $message->withBody($stream); } elseif ($message instanceof Request) { switch ($message->getMethod()) { case 'POST': case 'PUT': // Post and put messages must have content length or be transfer encoded. throw new MessageException(Response::LENGTH_REQUIRED, 'Content-Length header required.'); default: // Assume 0 length body. $stream = new MemoryStream(); yield from $stream->end(); // Creates empty request body. return $message->withBody($stream); } } elseif (strtolower($message->getHeader('Connection')) !== 'close') { throw new MessageException(Response::LENGTH_REQUIRED, 'Content-Length header required.'); } $contentEncoding = strtolower($message->getHeader('Content-Encoding')); switch ($contentEncoding) { case 'deflate': $stream = new ZlibDecoder(ZlibDecoder::DEFLATE, $this->hwm); break; case 'gzip': $stream = new ZlibDecoder(ZlibDecoder::GZIP, $this->hwm); break; case '': // No content encoding. return $message; default: throw new MessageException(Response::BAD_REQUEST, sprintf('Unsupported content encoding received: %s', $contentEncoding)); } $coroutine = new Coroutine(Stream\pipe($message->getBody(), $stream, true, 0, null, $timeout)); $coroutine->done(null, [$stream, 'close']); return $message->withBody($stream); }
/** * {@inheritdoc} */ public function close() { if ($this->open && $this->worker->isRunning()) { $coroutine = new Coroutine($this->worker->enqueue(new Internal\FileTask('fclose', [], $this->id))); $coroutine->done(null, [$this->worker, 'kill']); } if (!$this->queue->isEmpty()) { $exception = new FileException('The file was closed.'); do { $this->queue->shift()->cancel($exception); } while (!$this->queue->isEmpty()); } $this->open = false; $this->writable = false; }
/** * Converts a function accepting a callback ($emitter) that invokes the callback when an event is emitted into an * observable that emits the arguments passed to the callback function each time the callback function would be * invoked. * * @param callable(mixed ...$args) $emitter Function accepting a callback that periodically emits events. * @param callable(callable $callback, \Exception $exception) $onDisposed Called if the observable is disposed. * The callback passed to this function is the callable provided to the $emitter callable given to this * function. * @param int $index Position of callback function in emitter function argument list. * @param mixed ...$args Other arguments to pass to emitter function. * * @return \Icicle\Observable\Observable */ function observe(callable $emitter, callable $onDisposed = null, int $index = 0, ...$args) : Observable { $emitter = function (callable $emit) use(&$callback, $emitter, $index, $args) : \Generator { $delayed = new Delayed(); $reject = [$delayed, 'reject']; $callback = function (...$args) use($emit, $reject) { $coroutine = new Coroutine($emit($args)); $coroutine->done(null, $reject); }; if (count($args) < $index) { throw new InvalidArgumentError('Too few arguments given to function.'); } array_splice($args, $index, 0, [$callback]); $emitter(...$args); return (yield $delayed); }; if (null !== $onDisposed) { $onDisposed = function (\Throwable $exception) use(&$callback, $onDisposed) { $onDisposed($callback, $exception); }; } return new Emitter($emitter, $onDisposed); }
use Icicle\Socket\Server\Server; use Icicle\Socket\Socket; $server = (new DefaultServerFactory())->create('localhost', 8080); $generator = function (Server $server) { printf("Server listening on %s:%d\n", $server->getAddress(), $server->getPort()); $generator = function (Socket $socket) { $request = ''; do { $request .= (yield $socket->read(0, "\n")); } while (substr($request, -4) !== "\r\n\r\n"); $message = sprintf("Received the following request:\r\n\r\n%s", $request); $data = "HTTP/1.1 200 OK\r\n"; $data .= "Content-Type: text/plain\r\n"; $data .= sprintf("Content-Length: %d\r\n", strlen($message)); $data .= "Connection: close\r\n"; $data .= "\r\n"; $data .= $message; (yield $socket->write($data)); $socket->close(); }; while ($server->isOpen()) { // Handle client in a separate coroutine so this coroutine is not blocked. $coroutine = new Coroutine($generator((yield $server->accept()))); $coroutine->done(null, function (Loop\Exception\Error $exception) { printf("Client error: %s\n", $exception->getMessage()); }); } }; $coroutine = new Coroutine($generator($server)); $coroutine->done(); Loop\run();
/** * Starts the context execution. * * @throws \Icicle\Concurrent\Exception\ForkException If forking fails. * @throws \Icicle\Stream\Exception\FailureException If creating a socket pair fails. */ public function start() { if (0 !== $this->oid) { throw new StatusError('The context has already been started.'); } list($parent, $child) = Stream\pair(); switch ($pid = pcntl_fork()) { case -1: // Failure throw new ForkException('Could not fork process!'); case 0: // Child // @codeCoverageIgnoreStart // Create a new event loop in the fork. Loop\loop($loop = Loop\create(false)); $channel = new ChannelledStream($pipe = new DuplexPipe($parent)); fclose($child); $coroutine = new Coroutine($this->execute($channel)); $coroutine->done(); try { $loop->run(); $code = 0; } catch (\Throwable $exception) { $code = 1; } $pipe->close(); exit($code); // @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd default: // Parent $this->pid = $pid; $this->oid = posix_getpid(); $this->channel = new ChannelledStream($this->pipe = new DuplexPipe($child)); fclose($parent); } }
/** * {@inheritdoc} */ public function seek($offset, $whence = SEEK_SET) { if (!$this->isSeekable()) { throw new RuntimeException('Stream is not seekable'); } $promise = new Coroutine($this->stream->seek($offset, $whence)); try { return $promise->wait(); } catch (\Exception $exception) { throw new RuntimeException('Error seeking stream', 0, $exception); } }
public function testQueueUnbindDelete() { $coroutine = new Coroutine\Coroutine($this->goTestQueueUnbindDelete()); $coroutine->done(); Loop\run(); }
/** * @coroutine * * @param \Icicle\Socket\Server\Server $server * @param int $cryptoMethod * @param float $timeout * @param bool $allowPersistent * * @return \Generator */ private function accept(SocketServer $server, int $cryptoMethod, float $timeout, bool $allowPersistent) : \Generator { yield from $this->log->log(Log::INFO, 'HTTP server listening on %s:%d', $server->getAddress(), $server->getPort()); while ($server->isOpen()) { try { $coroutine = new Coroutine($this->process(yield from $server->accept(), $cryptoMethod, $timeout, $allowPersistent)); $coroutine->done(null, $this->onError); } catch (Throwable $exception) { if ($this->isOpen()) { throw $exception; } } } }