Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Object Pool Laravel Package

mix/object-pool

Object pooling for PHP and Laravel: reuse expensive-to-create objects instead of allocating new ones, cutting allocation and garbage-collection overhead in hot paths. Configurable pool sizing with framework-friendly integration.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is small enough to read end-to-end. Start with composer require mix/object-pool, then open src/ and read the pool class itself (ObjectPool.php / Pool.php depending on version) before touching your business code — there is no extensive docs, and the source is the spec. Pay attention to the constructor signature (max size + factory callable) and the get() / put() contract.

Requirements: PHP 7.2+ and the Swoole extension with coroutine support enabled. The pool is built on Swoole\Coroutine\Channel, so it only behaves correctly inside a coroutine context. Outside one, you'll get either a hard error or a silently blocking call — neither is what you want in a Laravel CLI/Web process.

Minimal first use — wrap an expensive client (PDO, Redis, HTTP client, anything you'd otherwise new per request):

use Mix\Pool\ObjectPool; // verify exact namespace in src/

$pool = new ObjectPool(10, function () {
    return new \PDO('mysql:host=127.0.0.1;dbname=app', 'user', 'pass', [
        \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
    ]);
});

Swoole\Coroutine\run(function () use ($pool) {
    $db = $pool->get();
    try {
        $db->query('SELECT 1');
    } finally {
        $pool->put($db);
    }
});

That smoke test — one coroutine, one get, one put, no exceptions — is your baseline. If it hangs, you're outside a coroutine or the factory threw.

Implementation Patterns

Acquire / release with try/finally. This is the pattern you'll write every day. Never let an exception escape between get() and put():

$client = $pool->get();
try {
    return $client->call($someCommand);
} finally {
    $pool->put($client);
}

Wrap this in a small repository or "manager" class so callers never touch get()/put() directly. In Laravel terms, bind the manager to the container and inject it, rather than injecting the raw pool.

Lazy factory with reconnect logic baked in. The factory runs whenever the pool needs to grow. Put your connect logic there, plus a fast health check if the resource is a network connection:

$factory = function () {
    $redis = new \Redis();
    $redis->connect('127.0.0.1', 6379, 1.0);
    $redis->select(0);
    return $redis;
};

If you have flaky connections, additionally probe on get() in your manager wrapper (cheap PING, SELECT 1) and drop/replace the resource if the probe fails — don't push that into the pool itself unless you're ready to subclass it.

Per-worker pools, sized against concurrency. Swoole forks workers; each worker gets its own pool instance. Size the pool to max_coroutines_per_worker (not total workers). A common starting point is 10–50 per worker, tuned by observing how often get() blocks. Prefill at worker start with a loop of get()/put() cycles so the first user request isn't paying connection costs.

Layered design with sibling packages. For real projects, prefer mix/database or mix/redis (which are themselves built on this package) over rolling your own pool. Reach for mix/object-pool directly when you have a bespoke resource: an internal gRPC/Thrift client, an SSH handle, an Imagick or wkhtmltopdf process wrapper, a heavy SDK object with a handshake.

Warm-up and drain lifecycle. Fill the pool on WorkerStart, and on WorkerStop/WorkerExit iterate the pool and explicitly close resources (PDO, sockets). __destruct ordering under Swoole shutdown is not something to rely on.

Timeouts as a first-class concern. Every get() should have a bounded wait. If the pool's get() accepts a timeout argument or the underlying Channel::pop() has one, use it — a bare get() on an exhausted pool will hang the coroutine indefinitely, and in a request context that's indistinguishable from a deadlock.

Gotchas and Tips

  • Non-coroutine context is a silent footgun. A pool constructed at bootstrap and used in an HTTP-framework callback outside Coroutine\run will either throw or block the whole worker. Guard with Swoole\Coroutine::getCid() > 0 in your manager, or only instantiate pools inside coroutine-aware entry points.
  • A missing put() drains the pool permanently. The channel capacity equals the max size; each leaked resource is a slot gone until the worker recycles. Log get()/put() pairs in dev, or add a counter around your manager to surface imbalances before they become an outage.
  • put() can block too. If you're using a bounded channel and try to return more objects than capacity (e.g., a finally running twice due to a retry bug), the put() will park. Treat "returned the same object twice" as a bug worth a guard flag.
  • Forked connections die silently. If the pool is built before WorkerStart (parent process), children inherit handles that the server may already have dropped. Always build pools in WorkerStart, or ensure the factory reconnects lazily.
  • Don't pool coroutine-local state. Any object holding a reference to the current coroutine's context (a request-scoped logger, a context object, a transaction handle) must not go back into the pool with that state. Reset state in your manager's release step, or don't pool it at all.
  • Transactions and pooled PDO are a classic trap. A connection returned mid-transaction comes back dirty. Your repository must rollBack() in the finally if a transaction is still open, and reset session state (SET NAMES, SET SESSION, temp tables) if you've touched it.
  • Timeout exceptions need handling. get() timeouts usually surface as RuntimeException or a Swoole channel timeout. Catch them at the manager boundary and translate to something your app understands (503 / retryable error). Don't retry in a tight loop — that just starves the pool further.
  • Debugging tip. Log count($pool) (or $channel->length()) on each get/put in a staging environment. A pool that never grows past 2 under load usually means a slow factory, not a slow app. A pool pinned at max with high get wait times means the size is too small or a leak is shrinking it.
  • Extension points to lean on. The factory closure is the natural place for metrics (connection time, creation count). If the base class allows it, subclass and override get/put (or the create callback) to add health checks and stats rather than monkey-patching call sites.
  • Config quirks. Set max size and timeout before the first coroutine starts — mutating pool config later is racy. Avoid -1/infinite timeouts in production; pick something like 500 ms–2 s and alert on timeouts, because they're your earliest signal of pool starvation.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo