/** * Close the channel, this operation will terminate all receivers that cannot receive any data. * * Closing a closed channel has no effect and does not throw an error! * * @param \Throwable $e Error will be propagated to all receivers that cannot receive a buffered value anymore. */ public function close(\Throwable $e = null) { if ($this->closed) { return; } $this->closed = $e ?? true; if ($this->generator) { $this->generator->cancel($e ?? new \RuntimeException('Channel closed')); } // Dispose of queued receivers that cannot receive a value from the channel. $sources = ($this->senders ? $this->countActiveSubscriptions($this->senders) : 0) + ($this->buffer ? $this->buffer->count() : 0); $sinks = $this->receivers ? $this->countActiveSubscriptions($this->receivers) : 0; $fails = new \SplStack(); $fails->setIteratorMode(\SplStack::IT_MODE_LIFO | \SplStack::IT_MODE_DELETE); while ($sinks > $sources) { $receiver = $this->receivers->pop(); if ($receiver->active) { $sinks--; $fails->push($receiver); } } foreach ($fails as $subscription) { if ($e) { $subscription->fail($e); } else { $subscription->resolve($subscription->data); } } }
/** * Shut down connection. * * @return Awaitable */ public function shutdown() : Awaitable { if ($this->processor) { try { return new AwaitPending($this->processor->cancel(new \RuntimeException('WebSocket connection shutdown'))); } finally { $this->processor = null; } } return new Success(null); }
/** * 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; }
protected function sendDeferredResponse(SocketStream $socket, HttpRequest $request, HttpResponse $response, bool $nobody, bool $close) : \Generator { if ($this->logger) { $this->logger->info('{ip} "{method} {target} HTTP/{protocol}" {status} {size}', ['ip' => $request->getClientAddress(), 'method' => $request->getMethod(), 'target' => $request->getRequestTarget(), 'protocol' => $request->getProtocolVersion(), 'status' => $response->getStatusCode(), 'size' => '-']); } if (!$nobody) { $close = true; } (yield $socket->write($this->serializeHeaders($response, $close, null, $nobody, true) . "\r\n")); (yield $socket->flush()); if ($nobody) { $response->getBody()->close(false); return !$close; } $watcher = null; $task = new Coroutine(function () use(&$watcher, $socket, $request, $response) { $body = $response->getBody(); $body->start($request, $this->logger); $bodyStream = (yield $body->getReadableStream()); $e = null; try { while (null !== ($chunk = (yield $bodyStream->read()))) { (yield $socket->write($chunk)); } } catch (StreamClosedException $e) { // Client disconnected from server. } finally { try { $bodyStream->close(); } finally { $body->close($e ? true : false); } $watcher->cancel(new \RuntimeException()); } return false; }, true); $watcher = new Coroutine(function () use($socket, $task) { $socket = $socket->getSocket(); while (\is_resource($socket) && !\feof($socket)) { (yield new AwaitRead($socket)); } $task->cancel(new StreamClosedException('Client disconnected')); }); return (yield $task); }
/** * Shutdown the inbound record handler of the connection. */ public function shutdown() : Awaitable { $stop = []; if ($this->processor) { $stop = $this->processor->cancel(new \RuntimeException('Connection closed')); } return new AwaitPending($stop); }
/** * {@inheritdoc} */ public function resolve(string $host) : Awaitable { $host = \strtolower(\trim($host, '[]')); if ($host === 'localhost') { return new Success(new Address('::1', '127.0.0.1')); } if (Address::isResolved($host)) { return new Success(new Address($host)); } if (isset($this->hosts[$host])) { return new Success($this->hosts[$host]); } if (isset($this->cache[$host]) && \time() < $this->cache[$host]['expires']) { return new Success($this->cache[$host]['address']); } if (!isset($this->pending[$host])) { $this->pending[$host] = $resolver = new Coroutine($this->resolveHost($host)); $resolver->when(function (\Throwable $e = null, array $val = null) use($host) { unset($this->pending[$host]); if ($val) { $this->cache[$host] = $val; } }); } return new Task(new Transform($this->pending[$host], function (array $result) { return $result['address']; })); }