modelslab/octane-coroutine
Adds coroutine support to Laravel Octane, letting you run concurrent PHP tasks inside Octane workers for faster I/O-bound requests. Integrates with Swoole coroutines, offers simple helpers for parallel execution, and helps reduce response times without changing your application structure.
This package provides request-scoped application state for long-running workers (Octane/Swoole), plus optional worker-level resource pooling. Install/pin v0.8.37 or later — do not rely on v0.8.36, since Packagist indexed that version while its tag pointed at the v0.8.35 commit and still serves that mapping:
composer require <vendor>/<package>:^0.8.37
First places to look:
config/octane.php or a package-specific config file). Confirm the new block exists:
'swoole' => [
'redis_pool' => [
'enabled' => false, // opt-in, default off
'max_idle' => 16,
'max_idle_time' => 60,
'ping_after_idle' => 5,
'max_lifetime' => 300,
],
],
refresh(), rebinding(), and terminating() are intercepted. If you have custom bootstrappers, check they use the request's application instance, not app() captured at worker boot.First use case: run your app under the worker with a single worker and a handful of requests, and confirm nothing accumulates. Before v0.8.37, each request leaked a terminating/rebinding callback onto the worker, so a simple loop of 40 requests was enough to see the drift (session/remember-me behaving oddly, terminate() firing for old requests, throughput falling off as the worker aged). After upgrading, that loop should be flat.
Second use case: enable the Redis pool on a staging worker and watch new-connection counts. The release notes report 2.0 → 0.004 new connections per request in production, with Cannot assign requested address errors disappearing under load.
Treat the request application as the only application. Anything you register during a request — event listeners, rebinding callbacks, terminating callbacks, macros — must land on the request-scoped instance. The v0.8.37 fix makes the package forward refresh()/rebinding()/terminating() to the per-request app rather than the base app. In your own service providers and middleware, prefer the injected $app/container over a statically captured app() from worker boot:
public function register(Application $app): void
{
$app->rebinding('request', function ($app, $request) {
// scoped to this request only
});
}
After-response work. dispatch(...)->afterResponse() and batches are now request-scoped via the bus dispatcher. Pattern: use them freely in controllers, but never resolve the dispatcher once at worker boot and stash it in a singleton — that reintroduces the leak the fix removes. If you have a long-lived service that dispatches, inject the dispatcher (or resolve it per call) so it comes from the current request's container.
Enable the Redis pool deliberately, per environment. Start with defaults, measure, then tune:
'redis_pool' => [
'enabled' => env('OCTANE_REDIS_POOL', false),
'max_idle' => 16,
'max_idle_time' => 60,
'ping_after_idle' => 5,
'max_lifetime' => 300,
],
Rules of thumb: max_idle should cover your peak concurrent Redis users per worker (queue listeners, cache, session all count); max_idle_time shorter than your Redis server's timeout; ping_after_idle below any NAT/firewall idle drop; max_lifetime as a backstop against half-dead sockets. If you use multiple Redis connections (cache, session, queue, Horizon), expect the pool to be sized per connection, not globally.
Verification workflow. Add a CI job (or local script) that boots the worker, issues N requests, and asserts stable behaviour:
terminate() side effect registered on request 1 does not fire on request 40,afterResponse() dispatch produces exactly one execution (pre-fix: 1 dispatch + 40 requests → 41 runs),INFO stats / CLIENT LIST on Redis shows a flat connection count.The release was verified against PHP 8.3/8.4 and Laravel 11/12/13 with a Redis service in the matrix — mirror that combination in your own CI before rolling to production.
composer require could silently give you v0.8.35's code under a v0.8.36 label. Require ^0.8.37, and after upgrading run composer show <vendor>/<package> and confirm the installed version and reference. If you already have v0.8.36 installed, force a reinstall (composer update <vendor>/<package>) rather than assuming you have the fixes.Auth::basic() start reading an empty request after an upgrade or a custom bootstrapper change, you're back on the base app's boot request. Check that refresh() is reaching the request-scoped instance.Coroutine::sleep. It survives the request boundary and will trip the lease guard. Grab connections at the point of use.queue:work process inside the same worker competes for the same idle sockets. Size max_idle for the sum, not the web path alone.ping_after_idle that's too long. If you see sporadic Connection reset by peer after enabling, lower ping_after_idle and max_idle_time.memory_get_usage() and the container's binding count per request (should be flat), redis-cli CLIENT LIST | wc -l sampled over time, and a two-request integration test asserting callback counts. Log request IDs through terminate() to catch cross-request bleed.How can I help you explore Laravel packages today?