Beispiel #1
0
 /**
  * @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);
 }
Beispiel #2
0
 /**
  * {@inheritdoc}
  */
 public function connect(string $ip, int $port = null, array $options = []) : \Generator
 {
     $protocol = (string) ($options['protocol'] ?? (null === $port ? 'unix' : 'tcp'));
     $autoClose = (bool) ($options['auto_close'] ?? true);
     $allowSelfSigned = (bool) ($options['allow_self_signed'] ?? self::DEFAULT_ALLOW_SELF_SIGNED);
     $timeout = (double) ($options['timeout'] ?? self::DEFAULT_CONNECT_TIMEOUT);
     $verifyDepth = (int) ($options['verify_depth'] ?? self::DEFAULT_VERIFY_DEPTH);
     $cafile = (string) ($options['cafile'] ?? '');
     $name = (string) ($options['name'] ?? '');
     $cn = (string) ($options['cn'] ?? $name);
     $context = [];
     $context['socket'] = ['connect' => Socket\makeName($ip, $port)];
     $context['ssl'] = ['capture_peer_cert' => true, 'capture_peer_chain' => true, 'capture_peer_cert_chain' => true, 'verify_peer' => true, 'verify_peer_name' => true, 'allow_self_signed' => $allowSelfSigned, 'verify_depth' => $verifyDepth, 'CN_match' => $cn, 'SNI_enabled' => true, 'SNI_server_name' => $name, 'peer_name' => $name, 'disable_compression' => true, 'honor_cipher_order' => true];
     if ('' !== $cafile) {
         if (!file_exists($cafile)) {
             throw new InvalidArgumentError('No file exists at path given for cafile.');
         }
         $context['ssl']['cafile'] = $cafile;
     }
     $context = stream_context_create($context);
     $uri = Socket\makeUri($protocol, $ip, $port);
     // Error reporting suppressed since stream_socket_client() emits an E_WARNING on failure (checked below).
     $socket = @stream_socket_client($uri, $errno, $errstr, null, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT, $context);
     if (!$socket || $errno) {
         throw new FailureException(sprintf('Could not connect to %s; Errno: %d; %s', $uri, $errno, $errstr));
     }
     $delayed = new Delayed();
     $await = Loop\await($socket, function ($resource, bool $expired, Io $await) use($delayed, $autoClose) {
         $await->free();
         if ($expired) {
             $delayed->reject(new TimeoutException('Connection attempt timed out.'));
             return;
         }
         $delayed->resolve(new NetworkSocket($resource, $autoClose));
     });
     $await->listen($timeout);
     try {
         return (yield $delayed);
     } finally {
         $await->free();
     }
 }
Beispiel #3
0
 /**
  * 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));
     };
 }
Beispiel #4
0
 /**
  * @param resource $resource
  * @param \SplQueue $writeQueue
  *
  * @return \Icicle\Loop\Watcher\Io
  */
 private function createAwait($resource, \SplQueue $writeQueue) : Io
 {
     return Loop\await($resource, static function ($resource, bool $expired, Io $await) use($writeQueue) {
         /** @var \Icicle\Awaitable\Delayed $delayed */
         list($data, $previous, $peer, $delayed) = $writeQueue->shift();
         $length = strlen($data);
         if (0 === $length) {
             $delayed->resolve($previous);
         } else {
             $written = stream_socket_sendto($resource, substr($data, 0, self::MAX_PACKET_SIZE), 0, $peer);
             // Having difficulty finding a test to cover this scenario, but the check seems appropriate.
             if (false === $written || -1 === $written || 0 === $written) {
                 $message = 'Failed to write to datagram.';
                 if ($error = error_get_last()) {
                     $message .= sprintf(' Errno: %d; %s', $error['type'], $error['message']);
                 }
                 $delayed->reject(new FailureException($message));
                 return;
             }
             if ($length <= $written) {
                 $delayed->resolve($written + $previous);
             } else {
                 $data = substr($data, $written);
                 $written += $previous;
                 $writeQueue->unshift([$data, $written, $peer, $delayed]);
             }
         }
         if (!$writeQueue->isEmpty()) {
             $await->listen();
         }
     });
 }
Beispiel #5
0
 /**
  * @param resource $resource
  * @param \SplQueue $writeQueue
  *
  * @return \Icicle\Loop\Watcher\Io
  */
 private function createAwait($resource, \SplQueue $writeQueue) : Io
 {
     return Loop\await($resource, static function ($resource, bool $expired, Io $await) use($writeQueue) {
         /** @var \Icicle\Awaitable\Delayed $delayed */
         list($data, $previous, $timeout, $delayed) = $writeQueue->shift();
         if ($expired) {
             $delayed->reject(new TimeoutException('Writing to the socket timed out.'));
             return;
         }
         $length = strlen($data);
         if (0 === $length) {
             $delayed->resolve($previous);
         } else {
             // Error reporting suppressed since fwrite() emits E_WARNING if the pipe is broken or the buffer is full.
             $written = @fwrite($resource, $data, self::CHUNK_SIZE);
             if (false === $written || 0 === $written) {
                 $message = 'Failed to write to stream.';
                 if ($error = error_get_last()) {
                     $message .= sprintf(' Errno: %d; %s', $error['type'], $error['message']);
                 }
                 $delayed->reject(new FailureException($message));
                 return;
             }
             if ($length <= $written) {
                 $delayed->resolve($written + $previous);
             } else {
                 $data = substr($data, $written);
                 $written += $previous;
                 $writeQueue->unshift([$data, $written, $timeout, $delayed]);
             }
         }
         if (!$writeQueue->isEmpty()) {
             list(, , $timeout) = $writeQueue->bottom();
             $await->listen($timeout);
         }
     });
 }