/** * Dispose of all watchers and reset all resources allocated by the loop. * * This method will be called after each async test run in order to free resources. */ public function reset() { $this->config->getPool()->shutdown(); foreach ($this->watchers as $watcher) { switch ($watcher->type) { case Watcher::TYPE_DEFER: unset($this->deferred[$watcher->id]); break; case Watcher::TYPE_DELAY: $this->disableDelayWatcher($watcher, true); break; case Watcher::TYPE_REPEAT: $this->disableRepeatWatcher($watcher, true); break; case Watcher::TYPE_READ: $this->disableReadWatcher($watcher, true); break; case Watcher::TYPE_WRITE: $this->disableWriteWatcher($watcher, true); break; case Watcher::TYPE_SIGNAL: $this->disableSignalWatcher($watcher, true); break; } } $this->running = 0; $this->watchers = []; $this->enable = []; $this->deferred = []; $this->info = \array_fill(0, 12, 0); }
/** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { $file = \rtrim($this->directory ?? \getcwd(), '/\\') . '/' . $input->getArgument('app'); if (!\is_file($file)) { throw new \RuntimeException(\sprintf('App file not found: "%s"', $file)); } if (!$this->boot->isWorker() && $input->getOption('dev')) { $this->executeDev($input, $output); } else { $peer = \sprintf('%s:%u', $input->getOption('address'), $input->getOption('port')); $http = $this->factory->createHttpEndpoint($peer); if (!$this->boot->isWorker()) { $this->console->disableLogger(); $logger = new ProcessLogHandler('php://stdout', $this->console->computeLogThreshold($output)); LoopConfig::getLogger()->addHandler($logger); } try { $this->k1->run(require $file, $http); } finally { if (!$this->boot->isWorker()) { LoopConfig::getLogger()->removeHandler($logger); $this->console->enableLogger(); } } } }
/** * Get the loop config of the current KoolKode loop. * * Will fall back to a shared global config when no KoolKode loop is used. * * @return LoopConfig */ protected function getLoopConfig() : LoopConfig { $loop = $this->getLoop(); if ($loop instanceof KoolLoop) { return $loop->getConfig(); } return LoopConfig::currentConfig(); }
public function doRun(InputInterface $input, OutputInterface $output) { if (!$this->boot->isWorker()) { $this->logger = new ConsoleLogHandler($output, $this->computeLogThreshold($output)); LoopConfig::getLogger()->addHandler($this->logger); } try { return parent::doRun($input, $output); } finally { if (!$this->boot->isWorker()) { LoopConfig::getLogger()->removeHandler($this->logger); } } }
public function compile(string $path) : Awaitable { return new Coroutine(function () use($path) { $args = [$path, $this->locator->getPaths(), $this->minify ? Crunched::class : Expanded::class, $this->lineComments]; list($css, $info) = (yield LoopConfig::currentPool()->invokeStaticMethod(static::class, 'compileFile', ...$args)); $files = []; foreach ($info as $file => $mtime) { $files[\str_replace('\\', '/', $file)] = $mtime; } if ($this->logger) { $data = ''; foreach (\array_keys($files) as $file) { $data .= "\n" . $file; } $this->logger->debug("Compiled SCSS file <{file}> from sources:" . $data, ['file' => \basename($path)]); } return [$css, $files]; }); }
protected function resolveAddress() : \Generator { if ($this->protocol == 'unix') { return [$this->peer]; } // Do a substring split here because IPv6 addresses may contain colons. $index = \strrpos($this->peer, ':'); if ($index !== false) { $parts = [\substr($this->peer, 0, $index), \substr($this->peer, $index + 1)]; $port = \array_pop($parts); } if (empty($parts)) { throw new SocketException(\sprintf('Missing port in peer "%s"', $this->peer)); } $address = (yield ($this->resolver ?? LoopConfig::currentHostResolver())->resolve($parts[0])); $resolved = []; foreach ($address as $addr) { $resolved[] = \sprintf('%s:%u', $addr, $port); } return $resolved; }
/** * Check if the HTTP request matches a public file and server it as needed. * * @param HttpRequest $request * @param NextMiddleware $next * @return HttpResponse */ public function __invoke(HttpRequest $request, NextMiddleware $next) : \Generator { static $methods = [Http::HEAD, Http::GET]; if (!\in_array($request->getMethod(), $methods, true)) { return yield from $next($request); } $path = '/' . \trim($request->getRequestTarget(), '/'); if ($this->basePath !== '/') { if (0 !== \strpos($path, $this->basePath)) { return yield from $next($request); } $path = \substr($path, \strlen($this->basePath) - 1); } $file = Filesystem::normalizePath($this->directory . \substr($path, 1)); if (0 !== \strpos($file, $this->directory)) { return yield from $next($request); } if (!(yield LoopConfig::currentFilesystem()->isFile($file))) { return yield from $next($request); } return $this->createResponse($request, $file); }
/** * {@inheritdoc} */ public function getReadableStream() : Awaitable { if ($this->temp) { $this->temp->rewind(); return new Success(new BufferedBodyStream($this->temp, $this->stream, $this->bufferSize, $this)); } if ($this->size !== null) { return new Success(new ReadableMemoryStream($this->buffer)); } return new Coroutine(function () { $buffer = (yield $this->stream->readBuffer($this->bufferSize)); $len = \strlen($buffer); if ($len < $this->bufferSize) { $this->buffer = $buffer; $this->size = $len; return new ReadableMemoryStream($buffer); } $this->temp = (yield LoopConfig::currentFilesystem()->tempStream()); $this->offset += (yield $this->temp->write($buffer)); return new BufferedBodyStream($this->temp, $this->stream, $this->bufferSize, $this); }); }
/** * {@inheritdoc} */ public function getReadableStream() : Awaitable { return LoopConfig::currentFilesystem()->readStream($this->file); }
protected function createContainer() : array { $builder = new ContainerBuilder(); $builder->bindInstance(clone $this); $builder->bindInstance(new Environment($this->env)); $builder->share(LoggerInterface::class)->instance(LoopConfig::getLogger()); $locator = new ResourceLocator($this->providers, $this->resourcePaths, $this->publicResourcePaths); $builder->bindInstance($locator); $builder->bindInstance(new Assets($this->env['K1_RESOURCE_PATH'], $locator)); foreach ($this->providers as $provider) { $builder->bindInstance($provider); } $configs = new \SplPriorityQueue(); $bindings = new \SplPriorityQueue(); $paths = [\dirname(__DIR__) . '/config']; $this->collectPhpFiles($paths, $configs, 300); $this->collectPhpFiles($paths, $configs, 250, $this->contextName); $paths = [\dirname(__DIR__) . '/bind']; $this->collectPhpFiles($paths, $bindings, 300); $this->collectPhpFiles($paths, $bindings, 250, $this->contextName); foreach ($this->providers as $provider) { $this->collectPhpFiles($provider->getConfigPaths(), $configs, 200); $this->collectPhpFiles($provider->getConfigPaths(), $configs, 150, $this->contextName); $this->collectPhpFiles($provider->getBindingPaths(), $bindings, 200); $this->collectPhpFiles($provider->getBindingPaths(), $bindings, 150, $this->contextName); } $this->collectPhpFiles($this->configPaths, $configs, 100); $this->collectPhpFiles($this->configPaths, $configs, 50, $this->contextName); $this->collectPhpFiles($this->bindingPaths, $bindings, 100); $this->collectPhpFiles($this->bindingPaths, $bindings, 50, $this->contextName); $settings = new Settings(); while (!$configs->isEmpty()) { $settings = $settings->mergeWith($this->loadSettings($configs->extract())); } while (!$bindings->isEmpty()) { $this->bind($bindings->extract(), $builder); } return [$builder, $settings]; }
/** * Open the given resource for streaming reads. * * @param string $path K1 resource path (k1:// URL scheme is optional). * @return ReadableStream */ public function readStream(string $path) : Awaitable { return new Coroutine(function () use($path) { return (yield LoopConfig::currentFilesystem()->readStream($this->locator->locateFile($path))); }); }
protected function processLogs() : \Generator { // Store reference to the correct channel as the property will be re-initialized when calling stop(). $channel = $this->channel; LoopConfig::getLogger()->addHandler($logger = new ChannelLogHandler($this->channel)); try { while (null !== ($message = (yield $channel->receive()))) { yield from $this->transmitter->send($message, self::TYPE_LOG); } } finally { LoopConfig::getLogger()->removeHandler($logger); Loop::stop(); } }
protected function sendRequest(SocketStream $socket, HttpRequest $request, int &$sent) : \Generator { $request = $this->normalizeRequest($request); $body = $request->getBody(); $size = (yield $body->getSize()); $sendfile = false; if ($body instanceof FileBody && $socket->isSendfileSupported()) { $sendfile = true; $chunk = $size ? '' : null; } else { if ($request->getProtocolVersion() == '1.0' && $size === null) { if (!$body->isCached()) { $body = new BufferedBody((yield $body->getReadableStream())); } (yield $body->discard()); $size = (yield $body->getSize()); } $bodyStream = (yield $body->getReadableStream()); $clen = $size === null ? 4089 : 4096; $chunk = (yield $bodyStream->readBuffer($clen)); $len = \strlen($chunk ?? ''); if ($chunk === null) { $size = 0; } elseif ($len < $clen) { $size = $len; } } $buffer = $this->serializeHeaders($request, $size); $expect = false; if ($this->expectContinue && $chunk !== null && $request->getProtocolVersion() == '1.1') { $expect = true; $buffer .= "Expect: 100-continue\r\n"; } (yield $socket->write($buffer . "\r\n")); (yield $socket->flush()); if ($expect) { if (!\preg_match("'^HTTP/1\\.1\\s+100(?:\$|\\s)'i", $line = (yield $socket->readLine()))) { try { return $line; } finally { if (isset($bodyStream)) { $bodyStream->close(); } } } } if ($sendfile) { if ($size) { $sent += (yield LoopConfig::currentFilesystem()->sendfile($body->getFile(), $socket->getSocket(), $size)); } } elseif ($size === null) { $sent += (yield $socket->write(\dechex($len) . "\r\n" . $chunk . "\r\n")); if ($len === $clen) { // Align each chunk with length and line breaks to fit into 4 KB payload. $sent += (yield new CopyBytes($bodyStream, $socket, true, null, 4089, function (string $chunk) { return \dechex(\strlen($chunk)) . "\r\n" . $chunk . "\r\n"; })); } (yield $socket->write("0\r\n\r\n")); } elseif ($size > 0) { $sent += (yield $socket->write($chunk)); if ($len === $clen) { $sent += (yield new CopyBytes($bodyStream, $socket, true, $size - $len)); } } (yield $socket->flush()); }
/** * Coroutine that sends the given HTTP response to the connected client. */ protected function sendResponse(SocketStream $socket, HttpRequest $request, HttpResponse $response, bool $close) : \Generator { // Discard request body in another coroutine. $request->getBody()->discard(); $response = $this->normalizeResponse($request, $response); $http11 = $response->getProtocolVersion() == '1.1'; $head = $request->getMethod() === Http::HEAD; $nobody = $head || Http::isResponseWithoutBody($response->getStatusCode()); $sendfile = false; $body = $response->getBody(); if ($body instanceof DeferredBody) { return yield from $this->sendDeferredResponse($socket, $request, $response, $nobody, $close); } $size = (yield $body->getSize()); if (!$nobody) { if ($body instanceof FileBody && $socket->isSendfileSupported()) { $sendfile = true; } else { $bodyStream = (yield $body->getReadableStream()); if ($nobody || $size === 0) { $chunk = null; $size = 0; $len = 0; } else { $clen = $size === null ? 4089 : 4096; $chunk = (yield $bodyStream->readBuffer($clen)); $len = \strlen($chunk ?? ''); } if ($chunk === null) { $size = 0; } elseif ($len < $clen) { $size = $len; } } } (yield $socket->write($this->serializeHeaders($response, $close, $size, $nobody) . "\r\n")); (yield $socket->flush()); $sent = 0; try { if (!$nobody) { if ($sendfile) { if ($size) { $sent += (yield LoopConfig::currentFilesystem()->sendfile($body->getFile(), $socket->getSocket(), $size)); } } elseif ($http11 && $size === null) { $sent += (yield $socket->write(\dechex($len) . "\r\n" . $chunk . "\r\n")); if ($len === $clen) { $sent += (yield new CopyBytes($bodyStream, $socket, false, null, 4089, function (string $chunk) { return \dechex(\strlen($chunk)) . "\r\n" . $chunk . "\r\n"; })); } $sent += (yield $socket->write("0\r\n\r\n")); } elseif ($chunk !== null) { $sent += (yield $socket->write($chunk)); if ($len === $clen) { $sent += (yield new CopyBytes($bodyStream, $socket, false, $size === null ? null : $size - $len)); } } (yield $socket->flush()); } } finally { if (isset($bodyStream)) { $bodyStream->close(); } } 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' => $sent ?: '-']); } return !$close; }
/** * {@inheritdoc} */ public static function getMaxSize() : int { return LoopConfig::getCpuCount() * 2; }
protected function start() : array { $count = \max(1, $this->workerCount ?? LoopConfig::getCpuCount()); $starting = []; for ($i = 1; $i <= $count; $i++) { $starting[] = $this->spawnWorker($i); } return $starting; }
public function syncFiles() : Awaitable { return new Coroutine(function () { $this->watchedResources = (yield LoopConfig::currentPool()->invokeStaticMethod(static::class, 'collectAppResources', $this->paths['app'])); }); }