diff --git a/app/composer.json b/app/composer.json index 077e018..b9492f9 100644 --- a/app/composer.json +++ b/app/composer.json @@ -15,7 +15,8 @@ "dotenv-org/phpdotenv-vault": "^0.2.4", "react/react": "^1.4", "robmorgan/phinx": "^0.16.1", - "react/mysql": "^0.7dev" + "react/mysql": "^0.7dev", + "react/async": "^4.3" }, "require-dev": { "phpunit/phpunit": "^11.1", diff --git a/app/composer.lock b/app/composer.lock index 1c6e482..0e90dad 100644 --- a/app/composer.lock +++ b/app/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "525ed6c2ef7a52e8447108c1427f1b7d", + "content-hash": "df12c4f8e3bfd8ecbaa4864a9d702c27", "packages": [ { "name": "cakephp/chronos", diff --git a/app/server/server.php b/app/server/server.php index d79e07e..b9b3c80 100644 --- a/app/server/server.php +++ b/app/server/server.php @@ -32,6 +32,3 @@ try { fprintf(STDERR, $e->getMessage() . "\n"); return -1; } - - - diff --git a/app/src/Infrastructure/Api/Database/ConnectionPoolInterface.php b/app/src/Infrastructure/Api/Database/ConnectionPoolInterface.php index 4b614d8..744886a 100644 --- a/app/src/Infrastructure/Api/Database/ConnectionPoolInterface.php +++ b/app/src/Infrastructure/Api/Database/ConnectionPoolInterface.php @@ -15,7 +15,13 @@ interface ConnectionPoolInterface * @return bool */ public function hasIdleConnection(): bool; - public function setWaitTimeout(int $seconds): void; + + /** + * Sets the new wait timeout for acquiring a connection. + * + * @param int $ms The amount of time in seconds. + */ + public function setWaitTimeout(int $s): void; public function getWaitTimeout(): int; public function getConnectionLimit(): int; diff --git a/app/src/Infrastructure/Database/ConnectionPool.php b/app/src/Infrastructure/Database/ConnectionPool.php index f059b13..efe83eb 100644 --- a/app/src/Infrastructure/Database/ConnectionPool.php +++ b/app/src/Infrastructure/Database/ConnectionPool.php @@ -2,65 +2,84 @@ namespace Slovocast\Infrastructure\Database; -use React\Mysql\MysqlClient; use Slovocast\Infrastructure\Api\Database\ConnectionPoolInterface; use Slovocast\Infrastructure\Api\Database\PooledConnectionInterface; +use SplObjectStorage; class ConnectionPool implements ConnectionPoolInterface { - /** - * Set a default wait timeout for acquiring a connection to 100ms - */ - const int DEFAULT_WAIT_TIMEOUT = 100; - private array $idleConnections; - private array $activeConnections; + private int $waitTimeout; + private SplObjectStorage $idleConnections; + private SplObjectStorage $activeConnections; private ConnectionPoolConfig $config; public function __construct(ConnectionPoolConfig $config) { - $this->config = $config; - for ($i = 0; $i < $config->getTotalConnections(); $i++) { - $this->idleConnections[] = new MysqlClient($this->config->getDsn()); + $this->idleConnections = new SplObjectStorage(); + $this->activeConnections = new SplObjectStorage(); + + for ($i = 0; $i < $this->config->getTotalConnections(); $i++) { + $pooledConnection = new PooledConnection($this->config->getDsnString()); + $this->idleConnections->attach($$pooledConnection); } + + $this->waitTimeout = $config->getPoolWaitTimeout(); } public function getTotalIdleConnections(): int { - // TODO: Implement getTotalIdleConnections() method. + return $this->idleConnections->count(); } public function getTotalActiveConnections(): int { - // TODO: Implement getTotalActiveConnections() method. + return $this->activeConnections->count(); } public function hasIdleConnection(): bool { - // TODO: Implement hasIdleConnection() method. + return $this->idleConnections->count() > 0; } - public function setWaitTimeout(int $seconds): void + public function setWaitTimeout(int $s): void { - // TODO: Implement setWaitTimeout() method. + $this->waitTimeout = $s; } public function getWaitTimeout(): int { - // TODO: Implement getWaitTimeout() method. + return $this->waitTimeout; } public function getConnectionLimit(): int { - // TODO: Implement getConnectionLimit() method. + return $this->config->getTotalConnections(); } + /** + * @TODO Throw an exception when a total timeout is exceeded. We do not want + * to get into an infinite loop. + * + * @return PooledConnectionInterface + */ public function getConnection(): PooledConnectionInterface { - // TODO: Implement getConnection() method. + if (!$this->hasIdleConnection()) { + \React\Async\delay((float) $this->getWaitTimeout()); + return this->getConnection(); + } + + $conn = $this->idleConnections->current(); + $this->idleConnections->detach($conn); + $this->activeConnections->attach($conn); + return $conn; } public function releaseConnection(PooledConnectionInterface $connection): void { - // TODO: Implement releaseConnection() method. + if ($this->activeConnections->contains($connection)) { + $this->activeConnections->detach($connection); + $this->idleConnections->attach($connection); + } } } diff --git a/app/src/Infrastructure/Database/ConnectionPoolConfig.php b/app/src/Infrastructure/Database/ConnectionPoolConfig.php index 2671822..861b83f 100644 --- a/app/src/Infrastructure/Database/ConnectionPoolConfig.php +++ b/app/src/Infrastructure/Database/ConnectionPoolConfig.php @@ -2,18 +2,19 @@ namespace Slovocast\Infrastructure\Database; -use React\Mysql\MysqlClient; - class ConnectionPoolConfig { + const DEFAULT_WAIT_TIMEOUT = 60; + const DEFAULT_TOTAL_CONNECTIONS = 10; + public function __construct( public readonly string $username, public readonly string $password, public readonly string $database, public readonly string $host, protected int $port = 3306, - protected int $poolWaitTimeout = ConnectionPool::DEFAULT_WAIT_TIMEOUT, - protected int $totalConnections = 10, + protected int $poolWaitTimeout = self::DEFAULT_WAIT_TIMEOUT, + protected int $totalConnections = self::DEFAULT_TOTAL_CONNECTIONS ) { } public function getPort(): int @@ -31,7 +32,7 @@ class ConnectionPoolConfig return $this->totalConnections; } - public function getDsn(): string + public function getDsnString(): string { return sprintf( "%s:%s@%s/%s",