Flesh out the connection pool class.

This commit is contained in:
Dave Smith-Hayes 2024-10-06 21:30:00 -04:00
parent 07b73e7f7f
commit f5abbcb8c9
6 changed files with 54 additions and 30 deletions

View File

@ -15,7 +15,8 @@
"dotenv-org/phpdotenv-vault": "^0.2.4", "dotenv-org/phpdotenv-vault": "^0.2.4",
"react/react": "^1.4", "react/react": "^1.4",
"robmorgan/phinx": "^0.16.1", "robmorgan/phinx": "^0.16.1",
"react/mysql": "^0.7dev" "react/mysql": "^0.7dev",
"react/async": "^4.3"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^11.1", "phpunit/phpunit": "^11.1",

2
app/composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "525ed6c2ef7a52e8447108c1427f1b7d", "content-hash": "df12c4f8e3bfd8ecbaa4864a9d702c27",
"packages": [ "packages": [
{ {
"name": "cakephp/chronos", "name": "cakephp/chronos",

View File

@ -32,6 +32,3 @@ try {
fprintf(STDERR, $e->getMessage() . "\n"); fprintf(STDERR, $e->getMessage() . "\n");
return -1; return -1;
} }

View File

@ -15,7 +15,13 @@ interface ConnectionPoolInterface
* @return bool * @return bool
*/ */
public function hasIdleConnection(): 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 getWaitTimeout(): int;
public function getConnectionLimit(): int; public function getConnectionLimit(): int;

View File

@ -2,65 +2,84 @@
namespace Slovocast\Infrastructure\Database; namespace Slovocast\Infrastructure\Database;
use React\Mysql\MysqlClient;
use Slovocast\Infrastructure\Api\Database\ConnectionPoolInterface; use Slovocast\Infrastructure\Api\Database\ConnectionPoolInterface;
use Slovocast\Infrastructure\Api\Database\PooledConnectionInterface; use Slovocast\Infrastructure\Api\Database\PooledConnectionInterface;
use SplObjectStorage;
class ConnectionPool implements ConnectionPoolInterface class ConnectionPool implements ConnectionPoolInterface
{ {
/** private int $waitTimeout;
* Set a default wait timeout for acquiring a connection to 100ms private SplObjectStorage $idleConnections;
*/ private SplObjectStorage $activeConnections;
const int DEFAULT_WAIT_TIMEOUT = 100;
private array $idleConnections;
private array $activeConnections;
private ConnectionPoolConfig $config; private ConnectionPoolConfig $config;
public function __construct(ConnectionPoolConfig $config) public function __construct(ConnectionPoolConfig $config)
{ {
$this->config = $config; $this->idleConnections = new SplObjectStorage();
for ($i = 0; $i < $config->getTotalConnections(); $i++) { $this->activeConnections = new SplObjectStorage();
$this->idleConnections[] = new MysqlClient($this->config->getDsn());
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 public function getTotalIdleConnections(): int
{ {
// TODO: Implement getTotalIdleConnections() method. return $this->idleConnections->count();
} }
public function getTotalActiveConnections(): int public function getTotalActiveConnections(): int
{ {
// TODO: Implement getTotalActiveConnections() method. return $this->activeConnections->count();
} }
public function hasIdleConnection(): bool 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 public function getWaitTimeout(): int
{ {
// TODO: Implement getWaitTimeout() method. return $this->waitTimeout;
} }
public function getConnectionLimit(): int 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 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 public function releaseConnection(PooledConnectionInterface $connection): void
{ {
// TODO: Implement releaseConnection() method. if ($this->activeConnections->contains($connection)) {
$this->activeConnections->detach($connection);
$this->idleConnections->attach($connection);
}
} }
} }

View File

@ -2,18 +2,19 @@
namespace Slovocast\Infrastructure\Database; namespace Slovocast\Infrastructure\Database;
use React\Mysql\MysqlClient;
class ConnectionPoolConfig class ConnectionPoolConfig
{ {
const DEFAULT_WAIT_TIMEOUT = 60;
const DEFAULT_TOTAL_CONNECTIONS = 10;
public function __construct( public function __construct(
public readonly string $username, public readonly string $username,
public readonly string $password, public readonly string $password,
public readonly string $database, public readonly string $database,
public readonly string $host, public readonly string $host,
protected int $port = 3306, protected int $port = 3306,
protected int $poolWaitTimeout = ConnectionPool::DEFAULT_WAIT_TIMEOUT, protected int $poolWaitTimeout = self::DEFAULT_WAIT_TIMEOUT,
protected int $totalConnections = 10, protected int $totalConnections = self::DEFAULT_TOTAL_CONNECTIONS
) { } ) { }
public function getPort(): int public function getPort(): int
@ -31,7 +32,7 @@ class ConnectionPoolConfig
return $this->totalConnections; return $this->totalConnections;
} }
public function getDsn(): string public function getDsnString(): string
{ {
return sprintf( return sprintf(
"%s:%s@%s/%s", "%s:%s@%s/%s",