*/ abstract protected function prepare(Statement $statement): Promise; /** * @param ResultSet $resultSet * @param callable $release * * @return ResultSet */ abstract protected function createResultSet(ResultSet $resultSet, callable $release): ResultSet; /** * @param Pool $pool Pool used to re-create the statement if the original closes. * @param Statement $statement Original prepared statement returned from the Link. * @param callable $prepare Callable that returns a new prepared statement. */ public function __construct(Pool $pool, Statement $statement, callable $prepare) { $this->lastUsedAt = \time(); $this->statements = $statements = new \SplQueue; $this->pool = $pool; $this->prepare = $prepare; $this->sql = $statement->getQuery(); $this->statements->push($statement); $this->timeoutWatcher = Loop::repeat(1000, static function () use ($pool, $statements) { $now = \time(); $idleTimeout = ((int) ($pool->getIdleTimeout() / 10)) ?: 1; while (!$statements->isEmpty()) { $statement = $statements->bottom(); \assert($statement instanceof Statement); if ($statement->getLastUsedAt() + $idleTimeout > $now) { return; } $statements->shift(); } }); Loop::unreference($this->timeoutWatcher); } public function __destruct() { Loop::cancel($this->timeoutWatcher); } /** * {@inheritdoc} * * Unlike regular statements, as long as the pool is open this statement will not die. */ public function execute(array $params = []): Promise { $this->lastUsedAt = \time(); return call(function () use ($params) { $statement = yield from $this->pop(); \assert($statement instanceof Statement); try { $statement = yield $this->prepare($statement); \assert($statement instanceof Statement); $result = yield $statement->execute($params); } catch (\Throwable $exception) { $this->push($statement); throw $exception; } if ($result instanceof ResultSet) { $result = $this->createResultSet($result, function () use ($statement) { $this->push($statement); }); } else { $this->push($statement); } return $result; }); } /** * Only retains statements if less than 10% of the pool is consumed by this statement and the pool has * available connections. * * @param Statement $statement */ protected function push(Statement $statement) { $maxConnections = $this->pool->getConnectionLimit(); if ($this->statements->count() > ($maxConnections / 10)) { return; } if ($maxConnections === $this->pool->getConnectionCount() && $this->pool->getIdleConnectionCount() === 0) { return; } $this->statements->push($statement); } /** * Coroutine returning a Statement object from the pool or creating a new Statement. * * @return \Generator */ protected function pop(): \Generator { while (!$this->statements->isEmpty()) { $statement = $this->statements->shift(); \assert($statement instanceof Statement); if ($statement->isAlive()) { return $statement; } } $statement = yield ($this->prepare)($this->sql); \assert($statement instanceof Statement); return $statement; } /** {@inheritdoc} */ public function isAlive(): bool { return $this->pool->isAlive(); } /** {@inheritdoc} */ public function getQuery(): string { return $this->sql; } /** {@inheritdoc} */ public function getLastUsedAt(): int { return $this->lastUsedAt; } }