Quick start
Three steps: write an adapter, create the pool, borrow and release connections. The example below uses PDO, but exactly the same works with any driver.
Step 1 — the adapter
The only place that knows about the driver. Three methods:
<?php
use Flytachi\Winter\CPool\ConnectionFactory;
final readonly class PdoFactory implements ConnectionFactory
{
public function __construct(private string $dsn) {}
public function create(): object
{
return new PDO($this->dsn, options: [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
}
public function validate(object $connection): bool
{
try {
$connection->query('SELECT 1');
return true;
} catch (Throwable) {
return false; // dead → the pool evicts it and opens a new one
}
}
public function close(object $connection): void
{
// PDO closes when dereferenced; other drivers need an explicit call
}
}Step 2 — the pool
use Flytachi\Winter\CPool\{ConnectionPool, PoolPolicy};
$pool = new ConnectionPool(
new PdoFactory('sqlite::memory:'),
new PoolPolicy(maximumPoolSize: 10),
);Creating the pool opens nothing — connections appear on demand.
Step 3 — borrow and release
$entry = $pool->borrow(); // waits for a free one, up to connectionTimeout
try {
/** @var PDO $pdo */
$pdo = $entry->resource;
$pdo->query('SELECT 1');
} finally {
$pool->release($entry); // returning it is mandatory
}borrow() returns a PoolEntry; the connection itself is in $entry->resource.
Releasing is the caller's duty
The pool cannot track a borrower who never gives the connection back: every such leak
permanently shrinks the pool by one, until it serves nobody at all. Hence the finally
— or better, a facade that releases automatically at the end of the request (in the
framework a coroutine defer does that).
What happens under load
The pool never opens more connections than the cap. When all are busy the borrower
waits; if nothing frees up within connectionTimeout — an exception:
use Flytachi\Winter\CPool\PoolException;
$pool = new ConnectionPool($factory, new PoolPolicy(
maximumPoolSize: 1,
connectionTimeout: 0.2,
));
$held = $pool->borrow();
try {
$pool->borrow(); // none free
} catch (PoolException $e) {
echo $e->getMessage();
// ConnectionPool: no free connection within 0.2s — raise maximumPoolSize or connectionTimeout.
}That is a guard rail, not a failure: a queue inside the application beats
too many connections on the database server, which takes the neighbouring
applications down with it.
You can inspect the load at any moment:
$pool->stats(); // ['total' => 2, 'idle' => 2, 'active' => 0, 'maximum' => 10]Without Swoole
A pool has nothing to distribute when the process serves one unit of work at a time. For
such runtimes there is SingleConnection — same adapter, same meaning:
use Flytachi\Winter\CPool\SingleConnection;
$single = new SingleConnection(new PdoFactory('sqlite::memory:'));
$pdo = $single->get(); // opens on the first call
$pdo = $single->get(); // the same object; validated if it sat idle for a whileThe choice is made once, at initialisation, and the application code knows nothing of it:
$connection = extension_loaded('swoole') && \Swoole\Coroutine::getCid() > 0
? $pool->borrow()->resource // do not forget release()
: $single->get();Next
- Writing an adapter — what
validate()must and must not do - Policy — the cap, lifetimes, housekeeping
- API reference — every method and exception