Example #1
0
 /**
  * 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);
 }
Example #2
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();
             }
         }
     }
 }
Example #3
0
 /**
  * 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();
 }
Example #4
0
 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);
         }
     }
 }
Example #5
0
 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];
     });
 }
Example #6
0
 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;
 }
Example #7
0
 /**
  * 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);
 }
Example #8
0
 /**
  * {@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);
     });
 }
Example #9
0
 /**
  * {@inheritdoc}
  */
 public function getReadableStream() : Awaitable
 {
     return LoopConfig::currentFilesystem()->readStream($this->file);
 }
Example #10
0
 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];
 }
Example #11
0
 /**
  * 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)));
     });
 }
Example #12
0
 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();
     }
 }
Example #13
0
 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());
 }
Example #14
0
 /**
  * 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;
 }
Example #15
0
 /**
  * {@inheritdoc}
  */
 public static function getMaxSize() : int
 {
     return LoopConfig::getCpuCount() * 2;
 }
Example #16
0
 protected function start() : array
 {
     $count = \max(1, $this->workerCount ?? LoopConfig::getCpuCount());
     $starting = [];
     for ($i = 1; $i <= $count; $i++) {
         $starting[] = $this->spawnWorker($i);
     }
     return $starting;
 }
Example #17
0
 public function syncFiles() : Awaitable
 {
     return new Coroutine(function () {
         $this->watchedResources = (yield LoopConfig::currentPool()->invokeStaticMethod(static::class, 'collectAppResources', $this->paths['app']));
     });
 }