private function createPoll() : Io { return Loop\poll(\eio_get_event_stream(), function () { while (\eio_npending()) { \eio_poll(); } }, true); }
/** * @coroutine * * @param string $connectionString * @param float|int $timeout * * @return \Generator * * @resolve \Icicle\Postgres\Connection * * @throws \Icicle\Postgres\Exception\FailureException */ function connect(string $connectionString, float $timeout = 0) : \Generator { if (!($connection = @\pg_connect($connectionString, \PGSQL_CONNECT_ASYNC | \PGSQL_CONNECT_FORCE_NEW))) { throw new FailureException('Failed to create connection resource'); } if (\pg_connection_status($connection) === \PGSQL_CONNECTION_BAD) { throw new FailureException(\pg_last_error($connection)); } if (!($socket = \pg_socket($connection))) { throw new FailureException('Failed to access connection socket'); } $delayed = new Delayed(); $callback = function ($resource, bool $expired) use(&$poll, &$await, $connection, $delayed, $timeout) { try { if ($expired) { throw new TimeoutException('Connection attempt timed out.'); } switch (\pg_connect_poll($connection)) { case \PGSQL_POLLING_READING: return; // Connection not ready, poll again. // Connection not ready, poll again. case \PGSQL_POLLING_WRITING: $await->listen($timeout); return; // Still writing... // Still writing... case \PGSQL_POLLING_FAILED: throw new FailureException('Could not connect to PostgreSQL server'); case \PGSQL_POLLING_OK: $poll->free(); $await->free(); $delayed->resolve(new BasicConnection($connection, $resource)); return; } } catch (\Throwable $exception) { $poll->free(); $await->free(); \pg_close($connection); $delayed->reject($exception); } }; $poll = Loop\poll($socket, $callback, true); $await = Loop\await($socket, $callback); $poll->listen($timeout); $await->listen($timeout); return (yield $delayed); }
/** * Connection constructor. * * @param resource $handle PostgreSQL connection handle. * @param resource $socket PostgreSQL connection stream socket. */ public function __construct($handle, $socket) { $this->handle = $handle; $this->poll = Loop\poll($socket, static function ($resource, bool $expired, Io $poll) use($handle) { /** @var \Icicle\Awaitable\Delayed $delayed */ $delayed = $poll->getData(); if (!\pg_consume_input($handle)) { $delayed->reject(new FailureException(\pg_last_error($handle))); return; } if (!\pg_connection_busy($handle)) { $delayed->resolve(\pg_get_result($handle)); return; } $poll->listen(); // Reading not done, listen again. }); $this->await = Loop\await($socket, static function ($resource, bool $expired, Io $await) use($handle) { $flush = \pg_flush($handle); if (0 === $flush) { $await->listen(); // Not finished sending data, listen again. return; } if (false === $flush) { /** @var \Icicle\Awaitable\Delayed $delayed */ $delayed = $await->getData(); $delayed->reject(new FailureException(\pg_last_error($handle))); } }); $this->onCancelled = static function () use($handle) { \pg_cancel_query($handle); }; $this->executeCallback = function (string $name, array $params) : \Generator { return $this->createResult(yield from $this->send('pg_send_execute', $name, $params)); }; }
/** * @param resource $resource * @param \SplQueue $readQueue * * @return \Icicle\Loop\Watcher\Io */ private function createPoll($resource, \SplQueue $readQueue) : Io { $length =& $this->length; return Loop\poll($resource, static function ($resource, bool $expired) use(&$length, $readQueue) { /** @var \Icicle\Awaitable\Delayed $delayed */ $delayed = $readQueue->shift(); try { if ($expired) { throw new TimeoutException('The datagram timed out.'); } $data = stream_socket_recvfrom($resource, $length, 0, $peer); // Having difficulty finding a test to cover this scenario, but the check seems appropriate. if (false === $data) { // Reading failed, so close datagram. $message = 'Failed to read from datagram.'; if ($error = error_get_last()) { $message .= sprintf(' Errno: %d; %s', $error['type'], $error['message']); } throw new FailureException($message); } list($address, $port) = Socket\parseName($peer); $result = [$address, $port, $data]; $delayed->resolve($result); } catch (Throwable $exception) { $delayed->reject($exception); } }); }
/** * @throws \Icicle\Concurrent\Exception\StatusError If the process is already running. */ public function start() { if (null !== $this->promise) { throw new StatusError('The process has already been started.'); } $fd = [ ['pipe', 'r'], // stdin ['pipe', 'w'], // stdout ['pipe', 'a'], // stderr ['pipe', 'w'], // exit code pipe ]; $nd = 0 === strncasecmp(PHP_OS, 'WIN', 3) ? 'NUL' : '/dev/null'; $command = sprintf('(%s) 3>%s; code=$?; echo $code >&3; exit $code', $this->command, $nd); $this->process = proc_open($command, $fd, $pipes, $this->cwd ?: null, $this->env ?: null, $this->options); if (!is_resource($this->process)) { throw new ProcessException('Could not start process.'); } $this->oid = getmypid(); $status = proc_get_status($this->process); if (!$status) { proc_close($this->process); $this->process = null; throw new ProcessException('Could not get process status.'); } $this->pid = $status['pid']; $this->stdin = new WritablePipe($pipes[0]); $this->stdout = new ReadablePipe($pipes[1]); $this->stderr = new ReadablePipe($pipes[2]); $stream = $pipes[3]; stream_set_blocking($stream, 0); $this->promise = new Promise(function (callable $resolve, callable $reject) use ($stream) { $this->poll = Loop\poll($stream, function ($resource) use ($resolve, $reject) { if (feof($resource)) { $reject(new ProcessException('Process ended unexpectedly.')); } else { $code = fread($resource, 1); if (!strlen($code) || !is_numeric($code)) { $reject(new ProcessException('Process ended without providing a status code.')); } else { $resolve((int) $code); } } fclose($resource); if (is_resource($this->process)) { proc_close($this->process); $this->process = null; } $this->stdin->close(); $this->poll->free(); }); $this->poll->unreference(); $this->poll->listen(); }); }
/** * @param resource $resource * @param \SplQueue $queue * * @return \Icicle\Loop\Watcher\Io */ private function createPoll($resource, \SplQueue $queue) : Io { return Loop\poll($resource, static function ($resource, bool $expired) use($queue) { /** @var \Icicle\Awaitable\Delayed $delayed */ $delayed = $queue->shift(); if ($expired) { $delayed->reject(new TimeoutException('The connection timed out.')); return; } $delayed->resolve(); }); }
/** * @param resource $resource * @param \SplQueue $queue * * @return \Icicle\Loop\Watcher\Io */ private function createPoll($resource, \SplQueue $queue) : Io { return Loop\poll($resource, static function ($resource, bool $expired, Io $poll) use($queue) { // Error reporting suppressed since stream_socket_accept() emits E_WARNING on client accept failure. $socket = @stream_socket_accept($resource, 0); // Timeout of 0 to be non-blocking. // Having difficulty finding a test to cover this scenario, but it has been seen in production. if (!$socket) { $poll->listen(); // Accept failed, let's go around again. return; } /** @var \Icicle\Awaitable\Delayed $delayed */ $delayed = $queue->shift(); $delayed->resolve($socket); }); }