PHPackages                             phpdot/pool - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. phpdot/pool

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

phpdot/pool
===========

Generic coroutine-safe connection pool for Swoole. Holds any object. Channel-based with idle cleanup, optional heartbeat, and leak prevention.

v1.1.0(today)019MITPHPPHP &gt;=8.4

Since Apr 5Pushed 4d agoCompare

[ Source](https://github.com/phpdot/pool)[ Packagist](https://packagist.org/packages/phpdot/pool)[ RSS](/packages/phpdot-pool/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (26)Versions (7)Used By (0)

phpdot/pool
===========

[](#phpdotpool)

Generic coroutine-safe connection pool for Swoole. Holds any object. Channel-based with idle cleanup, optional heartbeat, and leak prevention.

---

Table of Contents
-----------------

[](#table-of-contents)

- [Install](#install)
- [Architecture](#architecture)
    - [How It Works](#how-it-works)
    - [Package Structure](#package-structure)
- [ConnectorInterface](#connectorinterface)
- [Pool](#pool)
    - [Lifecycle](#lifecycle)
    - [Borrow](#borrow)
    - [Release](#release)
    - [Discard](#discard)
    - [Stats](#stats)
    - [Close](#close)
- [PoolConfig](#poolconfig)
- [Idle Cleanup](#idle-cleanup)
- [Heartbeat](#heartbeat)
- [Edge Cases](#edge-cases)
    - [Double Release](#double-release)
    - [Pool Exhaustion](#pool-exhaustion)
    - [Connect Failure During Init](#connect-failure-during-init)
    - [Borrow After Close](#borrow-after-close)
    - [Race Condition Prevention](#race-condition-prevention)
- [Framework Wiring](#framework-wiring)
- [API Reference](#api-reference)
    - [ConnectorInterface API](#connectorinterface-api)
    - [Pool API](#pool-api)
    - [PoolConfig API](#poolconfig-api)
    - [PoolStats API](#poolstats-api)
    - [Exceptions API](#exceptions-api)
- [License](#license)

---

Install
-------

[](#install)

```
composer require phpdot/pool
```

RequirementVersionPHP&gt;= 8.3ext-swoole&gt;= 6.0---

Architecture
------------

[](#architecture)

### How It Works

[](#how-it-works)

```
Swoole Worker Process (single OS process, 100+ coroutines)
    │
    Pool (Channel-based, per worker)
    │
    ├── init()     → pre-create minConnections, start timers
    │
    ├── borrow()   → pop from Channel (or create on-demand)
    │   ├── Channel has idle item → return immediately
    │   ├── Channel empty, below max → create new (slot reserved before I/O)
    │   └── Channel empty, at max → suspend coroutine, wait for release
    │
    ├── release()  → push back to Channel (with timestamp)
    │   └── Double release silently ignored (spl_object_id tracking)
    │
    ├── discard()  → close permanently, don't return to Channel
    │
    └── close()    → stop timers, close all idle connections

```

The pool is built on `Swoole\Coroutine\Channel` — a coroutine-safe bounded FIFO queue. `pop()` suspends only the calling coroutine (not the worker process). `push()` wakes the next waiting coroutine. Lock-free at the C level.

### Package Structure

[](#package-structure)

```
src/
├── ConnectorInterface.php      # How to create, check, close connections
├── Pool.php                    # Channel + Timers + borrow/release/discard
├── PoolConfig.php              # 6 readonly config properties
├── PoolStats.php               # 10 readonly monitoring counters
├── PooledItem.php              # Internal: connection + lastReleasedAt
└── Exception/
    ├── PoolException.php       # Base
    ├── BorrowTimeoutException.php
    └── PoolClosedException.php

```

8 files. One dependency (ext-swoole).

---

ConnectorInterface
------------------

[](#connectorinterface)

The pool doesn't know what it's pooling. The connector tells it how to create, check, and close objects:

```
interface ConnectorInterface
{
    public function connect(): object;
    public function isAlive(object $connection): bool;
    public function close(object $connection): void;
}
```

Implementations live in the framework kernel, not in this package:

```
// Example: MongoDB connector (lives in phpdot/dot)
final class MongoConnector implements ConnectorInterface
{
    public function connect(): object
    {
        $connection = new Connection($this->config);
        $connection->connect();
        return $connection;
    }

    public function isAlive(object $connection): bool
    {
        return $connection->isConnected(); // local check, no network
    }

    public function close(object $connection): void
    {
        $connection->close();
    }
}
```

---

Pool
----

[](#pool)

### Lifecycle

[](#lifecycle)

```
use PHPdot\Pool\Pool;
use PHPdot\Pool\PoolConfig;

// Created in onWorkerStart (after fork)
$pool = new Pool($connector, new PoolConfig(
    minConnections: 2,
    maxConnections: 10,
));

$pool->init(); // pre-creates minConnections, starts timers

// ... application runs, coroutines borrow and release ...

$pool->close(); // onWorkerStop — closes everything
```

### Borrow

[](#borrow)

```
$connection = $pool->borrow();
```

1. If the Channel has idle connections → returns one immediately
2. If empty but below `maxConnections` → creates a new one (slot reserved before I/O to prevent race)
3. If at capacity → suspends the coroutine until a connection is released
4. If timeout → throws `BorrowTimeoutException`
5. If pool closed → throws `PoolClosedException`

### Release

[](#release)

```
$pool->release($connection);
```

Returns the connection to the Channel. Double release is silently ignored (tracked via `spl_object_id`). If the pool was closed while the connection was borrowed, the connection is closed instead.

### Discard

[](#discard)

```
$pool->discard($connection);
```

Permanently closes the connection. Not returned to the Channel. Decrements the pool count, allowing a new connection to be created on the next borrow.

### Stats

[](#stats)

```
$stats = $pool->stats();

$stats->active;       // currently borrowed
$stats->idle;         // sitting in Channel
$stats->total;        // active + idle
$stats->borrowCount;  // total borrows since init
$stats->releaseCount;
$stats->discardCount;
$stats->createCount;
$stats->closeCount;
$stats->timeoutCount;
$stats->waitingCount; // coroutines waiting for a connection right now
```

### Close

[](#close)

```
$pool->close();
```

Stops timers, closes all idle connections. Borrowed connections are closed when released/discarded. Idempotent.

---

PoolConfig
----------

[](#poolconfig)

```
new PoolConfig(
    minConnections: 2,                  // pre-created, never shrink below
    maxConnections: 10,                 // hard limit per worker
    borrowTimeout: 3.0,                 // seconds to wait when exhausted
    maxIdleTime: 300.0,                 // seconds before idle cleanup (0.0 = disabled)
    idleCheckInterval: 30.0,            // seconds between cleanup runs
    heartbeatInterval: 0.0,             // seconds between heartbeat (0.0 = disabled)
    validateOnBorrowAfterIdle: 5.0,     // call isAlive() on borrow when idle ≥ this many seconds
                                        //   0.0 = always validate; negative = disabled
    validateOnReturn: false,            // call isAlive() on release; close (don't re-pool) dead
);
```

**Total database connections = workers x maxConnections.** 4 workers x 10 max = 40 max connections.

---

Idle Cleanup
------------

[](#idle-cleanup)

Purpose: shrink the pool after traffic spikes. Not for connection health.

When `maxIdleTime > 0.0`, a timer runs every `idleCheckInterval` seconds:

1. Skip if coroutines are waiting for connections
2. Pop idle items from Channel
3. Close items idle longer than `maxIdleTime` (only if above `minConnections`)
4. Push back the rest
5. Refill to `minConnections` if needed

```
new PoolConfig(
    minConnections: 2,
    maxConnections: 10,
    maxIdleTime: 300.0,       // close after 5 min idle
    idleCheckInterval: 30.0,  // check every 30s
);
```

Set `maxIdleTime: 0.0` to disable.

---

Heartbeat
---------

[](#heartbeat)

Purpose: safety net for dead connections. Disabled by default.

When `heartbeatInterval > 0.0`, a timer checks idle connections via `ConnectorInterface::isAlive()`:

1. Skip if coroutines are waiting
2. Pop all idle items
3. Close dead ones (`isAlive() === false`)
4. Push back alive ones
5. Refill to `minConnections`

```
new PoolConfig(heartbeatInterval: 60.0);
```

`isAlive()` should be lightweight. A single round-trip ping (e.g. `SELECT 1`) is acceptable and is the recommended implementation — for typical drivers, only a server-side check detects connections killed by `wait_timeout`, firewall idle drops, or server restarts.

---

Validate-on-Borrow
------------------

[](#validate-on-borrow)

Purpose: catch dead connections at the moment they're handed to a caller, before the caller spends a query learning the connection is broken. TTL-gated to avoid pinging on every hot-path borrow.

When `validateOnBorrowAfterIdle ≥ 0.0` and a popped connection has been idle ≥ that many seconds, the pool calls `isAlive()` before returning it. If dead, the connection is closed and the borrow loop continues — either popping another or creating a fresh one.

```
new PoolConfig(
    validateOnBorrowAfterIdle: 5.0,   // ping if idle ≥ 5s
    // 0.0  = always validate (every borrow)
    // -1.0 = disabled
);
```

Hot borrow/release cycles skip the check; only "stale" connections get pinged.

---

Validate-on-Return
------------------

[](#validate-on-return)

Purpose: belt-and-suspenders — catch connections that have died mid-use before they're put back in the pool.

When `validateOnReturn = true`, `release()` calls `isAlive()` on the returned connection. Dead connections are closed (not re-pooled), and the pool refills toward `minConnections`.

```
new PoolConfig(validateOnReturn: true);
```

Off by default — the connection just succeeded a query, so the check is usually redundant. Enable for "no tolerance" production stances.

---

Edge Cases
----------

[](#edge-cases)

### Double Release

[](#double-release)

Tracked via `spl_object_id`. Second `release()` on the same connection is silently ignored. Prevents Channel corruption.

### Pool Exhaustion

[](#pool-exhaustion)

All connections borrowed, new coroutine calls `borrow()`:

- Coroutine is **suspended** (not the worker process)
- Other coroutines continue running
- When any coroutine releases → waiting coroutine resumes
- If `borrowTimeout` expires → `BorrowTimeoutException`

### Connect Failure During Init

[](#connect-failure-during-init)

If `connector->connect()` throws during `init()`, the error is skipped. The pool starts with fewer connections. On-demand creation in `borrow()` fills the gap.

### Borrow After Close

[](#borrow-after-close)

Throws `PoolClosedException` immediately.

### Race Condition Prevention

[](#race-condition-prevention)

On-demand creation in `borrow()` increments `currentCount` **before** calling `connect()` (which yields on I/O). This prevents multiple coroutines from passing the `< maxConnections` check simultaneously — the slot is reserved before any yield point.

---

Framework Wiring
----------------

[](#framework-wiring)

The developer never touches the pool. The framework kernel wires it:

```
// Swoole mode (in onWorkerStart)
$pool = new Pool(new MongoConnector($config), new PoolConfig(...));
$pool->init();

$container->scoped(Connection::class, function () use ($pool) {
    $connection = $pool->borrow();
    Coroutine::defer(fn () => $pool->release($connection));
    return $connection;
});

// FPM mode — no pool, direct binding
$container->singleton(Connection::class, fn () => new Connection($config));
```

Developer code is identical in both modes:

```
public function __construct(private Connection $mongodb) {}
```

---

API Reference
-------------

[](#api-reference)

### ConnectorInterface API

[](#connectorinterface-api)

```
interface ConnectorInterface

connect(): object               // create new connection
isAlive(object $connection): bool  // local health check
close(object $connection): void    // destroy permanently

```

### Pool API

[](#pool-api)

```
final class Pool

__construct(ConnectorInterface $connector, PoolConfig $config = new PoolConfig())
init(): void                                    // pre-create + start timers
borrow(): object                                // get a connection
release(object $connection): void               // return to pool
discard(object $connection): void               // close permanently
stats(): PoolStats                              // monitoring snapshot
close(): void                                   // shutdown pool
isClosed(): bool

```

### PoolConfig API

[](#poolconfig-api)

```
final readonly class PoolConfig

__construct(
    public int   $minConnections    = 2,
    public int   $maxConnections    = 10,
    public float $borrowTimeout     = 3.0,
    public float $maxIdleTime       = 300.0,
    public float $idleCheckInterval = 30.0,
    public float $heartbeatInterval = 0.0,
)

```

### PoolStats API

[](#poolstats-api)

```
final readonly class PoolStats

public int $active        // currently borrowed
public int $idle          // in Channel
public int $total         // active + idle
public int $borrowCount   // lifetime borrows
public int $releaseCount  // lifetime releases
public int $discardCount  // lifetime discards
public int $createCount   // lifetime creates
public int $closeCount    // lifetime closes
public int $timeoutCount  // lifetime timeouts
public int $waitingCount  // coroutines waiting now

```

### Exceptions API

[](#exceptions-api)

```
PoolException (extends RuntimeException)
├── BorrowTimeoutException   // pool exhausted
└── PoolClosedException      // pool shut down

```

---

License
-------

[](#license)

MIT

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance99

Actively maintained with recent releases

Popularity8

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity55

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~17 days

Total

6

Last Release

0d ago

Major Versions

v0.0.1 → v1.0.02026-05-02

PHP version history (2 changes)v0.0.1PHP &gt;=8.3

v1.0.3PHP &gt;=8.4

### Community

Maintainers

![](https://www.gravatar.com/avatar/62e82421bda4b5d6ba9a47ba6d88caca060dcd0d1a2862f351f3a97657385db0?d=identicon)[phpdot](/maintainers/phpdot)

---

Top Contributors

[![phpdot](https://avatars.githubusercontent.com/u/252500?v=4)](https://github.com/phpdot "phpdot (6 commits)")

---

Tags

swoolecoroutinepoolchannelconnection-pool

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/phpdot-pool/health.svg)

```
[![Health](https://phpackages.com/badges/phpdot-pool/health.svg)](https://phpackages.com/packages/phpdot-pool)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
