Быстрый старт
Три шага: написать адаптер, создать пул, брать и возвращать соединения. Ниже — на примере PDO, но ровно то же работает с любым драйвером.
Шаг 1 — адаптер
Единственное место, знающее про драйвер. Три метода:
<?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; // мёртвое → пул выселит и откроет новое
}
}
public function close(object $connection): void
{
// PDO закрывается при разыменовании; другим драйверам нужен явный вызов
}
}Шаг 2 — пул
use Flytachi\Winter\CPool\{ConnectionPool, PoolPolicy};
$pool = new ConnectionPool(
new PdoFactory('sqlite::memory:'),
new PoolPolicy(maximumPoolSize: 10),
);Создание пула ничего не открывает — соединения появляются по требованию.
Шаг 3 — взять и вернуть
$entry = $pool->borrow(); // ждёт свободное, максимум connectionTimeout
try {
/** @var PDO $pdo */
$pdo = $entry->resource;
$pdo->query('SELECT 1');
} finally {
$pool->release($entry); // возвращать обязательно
}borrow() отдаёт PoolEntry, само соединение лежит в $entry->resource.
Возврат — обязанность вызывающего
Пул не умеет отследить заёмщика, который не вернул соединение: каждая такая утечка
навсегда уменьшает пул на единицу, пока он не перестанет обслуживать кого-либо.
Поэтому finally, а лучше — фасад, возвращающий соединение автоматически в конце
запроса (во фреймворке это делает defer корутины).
Что происходит под нагрузкой
Пул не открывает соединений сверх потолка. Когда все заняты, заёмщик ждёт; если
свободное не появилось за connectionTimeout — исключение:
use Flytachi\Winter\CPool\PoolException;
$pool = new ConnectionPool($factory, new PoolPolicy(
maximumPoolSize: 1,
connectionTimeout: 0.2,
));
$held = $pool->borrow();
try {
$pool->borrow(); // свободных нет
} catch (PoolException $e) {
echo $e->getMessage();
// ConnectionPool: no free connection within 0.2s — raise maximumPoolSize or connectionTimeout.
}Это защита, а не сбой: очередь в приложении лучше, чем too many connections на
сервере базы, который положит и соседние приложения.
Проверить занятость можно в любой момент:
$pool->stats(); // ['total' => 2, 'idle' => 2, 'active' => 0, 'maximum' => 10]Без Swoole
Пулу нечего распределять, когда процесс обслуживает одну единицу работы за раз. Для
таких рантаймов — SingleConnection, тот же адаптер, тот же смысл:
use Flytachi\Winter\CPool\SingleConnection;
$single = new SingleConnection(new PdoFactory('sqlite::memory:'));
$pdo = $single->get(); // открывает при первом вызове
$pdo = $single->get(); // тот же объект; проверяется, если долго простоялВыбор делается один раз, при инициализации, и прикладной код о нём не знает:
$connection = extension_loaded('swoole') && \Swoole\Coroutine::getCid() > 0
? $pool->borrow()->resource // не забыть release()
: $single->get();Дальше
- Свой адаптер — что должен и чего не должен делать
validate() - Политика — потолок, сроки жизни, уборка
- Справочник API — все методы и исключения