Download the PHP package yangusik/laravel-spawn without Composer
On this page you can find all versions of the php package yangusik/laravel-spawn. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download yangusik/laravel-spawn
More information about yangusik/laravel-spawn
Files in yangusik/laravel-spawn
Package laravel-spawn
Short Description Laravel adapter for PHP TrueAsync — async HTTP server with coroutine-per-request isolation
License MIT
Informations about the package laravel-spawn
Laravel adapter for PHP TrueAsync — a PHP fork with a native coroutine scheduler and async I/O. Think Laravel Octane, but instead of Swoole or RoadRunner the runtime is TrueAsync.
One worker. Many requests. Zero threads. Each HTTP request runs in its own coroutine with isolated state — no shared memory, no leaks between requests.
How it works
- Each request = a separate coroutine with its own
Scope - Request-scoped services (
auth,session,cookie) are isolated viacoroutine_context()andrequest_context()(if use True Async Server) - PDO Pool transparently gives each coroutine its own database connection and returns it when the coroutine ends
- No container cloning — isolation is handled at the coroutine level, not by copying the entire app
Requirements
- PHP TrueAsync fork 8.6+
- Laravel 12+
- For FrankenPHP mode:
trueasync/php-true-async:latest-frankenphpDocker image
Installation
Via git repository:
Via local path:
Then run composer update.
The service provider is auto-discovered by Laravel.
Replace the Application class in bootstrap/app.php:
This is required for per-coroutine isolation of auth, session, and request. Without it the service adapters register correctly but state isolation does not work.
Publish the config:
Servers
True Async Server
Production-ready adapter using True Async Server in async worker mode.
Check config/async.php!
Dev server
Simple TCP socket server for local development. Analogous to php artisan serve.
FrankenPHP
Production-ready adapter using FrankenPHP in async worker mode.
Requires the trueasync/php-true-async:latest-frankenphp Docker image.
A worker of your own
Any process that serves requests as coroutines calls one method to make the adapters per-request:
It applies the async.db_pool options and purges the connections bootstrap already opened,
configures the Redis pool, tells the adapters that boot is over and switches the application
into async mode — the three servers above do nothing else to start. TrueAsyncServer::initializeApp($app) is the
same call for a thread of an application that has its own server.
An adapter that is never told boot is over is a no-op that reports nothing: AsyncViewFactory
keeps answering from its process-wide state, so two coroutines rendering Blade share
@section and @push. Under php-fpm that is the correct behaviour, since one process serves
one request; under coroutines it is state shared between requests.
Docker quick start
TrueAsyncServer (better)
Dev server
FrankenPHP
Per-request services
A singleton lives as long as the worker and is shared by every request the worker is serving at that moment, so a service holding request state has to be declared per-request. The container gives every request its own instance, built on first resolve and kept in that request's coroutine context; its facade resolves per request as well.
Three registrations declare one, and the container treats them alike.
Laravel's own scoped() — the same call Octane packages already use:
The config list, for a package whose registration you do not control:
scopedSingleton(), when a shared binding has to stay in place: the factory here is
used for the per-request build only, and whatever bootstrap resolved through the original
binding stays reachable through scopedPrototype(). That is how this package scopes url
without losing URL::forceScheme() and the other setters a provider called at boot.
Per-request does not fit everything. A service the framework or a package captures —
kept in a static, taken in another singleton's constructor — must stay one object, or the
capture pins one request's copy for the life of the worker. The view factory is the case:
templates receive it as $__env, Component::$factory caches it, MailManager keeps it.
It stays shared, and its render state moves into the request instead.
Boot-time configuration does not follow on its own. A provider calling
Auth::extend() or Session::extend() configures the boot-time instance, and a fresh
per-request instance starts without it. Register a seeder to carry it across:
Turn on async.diagnostics to have the worker report, at start-up, every per-request
service that bootstrap configured and no seeder carries over — and every shared service
holding an object that belongs to one request.
Configuration
config/async.php:
If you use TrueAsyncServer, pls read docs: Configuration
Benchmarks (Obsolete use TrueAsyncServer)
Check results in HttpArena
Load: 840 req/s /hello + 360 req/s /test = 1 200 req/s total · constant-arrival-rate · 30s · 12 workers each · WSL2 (Linux 6.6 on Windows)
| Metric | PHP-FPM (12w) | Octane Swoole (12w) | TrueAsync-Franken (12w) |
|---|---|---|---|
| Target rate | 1 200 req/s | 1 200 req/s | 1 200 req/s |
| Actual throughput | ~200 req/s | ~752 req/s | ~1 118 req/s |
| Dropped iterations | ~28 000 | ~5 000 | 20 |
| Avg latency | ~4 000ms | ~880ms | 13ms |
| p95 latency | ~5 000ms | 2 320ms | 21ms |
| p95 < 200ms | ✗ | ✗ | ✓ |
| Failed requests | 0% | 0% | 0% |
| DB connections (peak) | — | — | 120 |
Why TrueAsync wins on DB-bound load
| PHP-FPM | Octane Swoole | TrueAsync-Franken | |
|---|---|---|---|
| Request model | Process per request | 1 process = 1 request at a time | 1 worker = N coroutines |
| DB I/O | Blocking (new conn each req) | Blocking (PDO synchronous) | Non-blocking (coroutine yield) |
| Memory model | Stateless | Long-lived process | Long-lived process + coroutine context isolation |
| App bootstrap | Every request | Once per worker | Once per worker |
Swoole keeps the app in memory (avoids bootstrap cost) but PDO is still synchronous — a worker blocked on a DB call cannot accept another request. TrueAsync yields the coroutine on every DB call, so one worker handles hundreds of concurrent DB-bound requests without blocking.
Notes
- Each adapter has its own PostgreSQL instance on a separate port to avoid interference
APP_DEBUG=falsein all setups for fair comparison- OPcache enabled in PHP-FPM
max_connections=500in all PostgreSQL instances- Absolute numbers will be higher on bare metal (benchmarks run on WSL2)
Full benchmark: ta_benchmark
Raw PHP — TrueAsync vs Swoole (no framework, no I/O)
On pure CPU-bound workloads both servers cap at the same throughput (~10k req/s). With optimal Swoole config (ZTS, 16 reactor threads) Swoole is ~1.6x faster on P95 latency due to FrankenPHP's Go↔PHP boundary overhead (futex synchronization). On I/O-bound workloads this overhead is negligible.
Metrics
The running server's counters — requests, responses by status class, live connections per
protocol, HTTP/2 and streaming traffic — are readable from the application under
async:serve:
The package publishes no endpoint of its own, so the URL and who may read it stay yours.
totals(), workers() and latency() return the same numbers as arrays. The counters are
always incremented; async.server.stats decides whether the aggregate can be read, and it
is on by default. See docs/METRICS.md.
Sessions
Database sessions (built-in fix)
The package automatically replaces Laravel's DatabaseSessionHandler with an async-safe version that uses upsert instead of INSERT + catch + UPDATE.
In a standard async server the HTTP response is sent before kernel->terminate() writes the session. If the client immediately sends the next request with the same cookie, two coroutines can race to INSERT the same session ID — causing duplicate-key warnings in the stock handler. The upsert is atomic, so this race is impossible regardless of concurrency.
No configuration needed. Works transparently when SESSION_DRIVER=database.
Redis sessions (recommended for production)
For high-concurrency workloads Redis sessions have lower overhead than database sessions and avoid any persistence race entirely:
Eloquent relations
Eloquent decides whether a relation adds its own where foreign_key = ? through
Relation::$constraints, a static property. Eager loading switches it off while it builds the
relation object and restores it afterwards from a captured value. A static property is one flag
per worker thread, shared by every coroutine of that worker, so under concurrent serving that
window belongs to whoever happens to be inside it: one request's relations come out unfiltered
because another request was eager loading at that moment, and two overlapping windows leave the
flag off for the rest of the worker's life. The queries stay valid and answer with the whole
table.
Nothing to do — the package puts its own copies of two Eloquent files in front of Laravel's,
Relations\Relation and Concerns\HasRelationships. The first keeps that window in the
coroutine that opened it; the second builds relation classes that read it. Every model is
covered, including the ones that come from other packages.
The copies live in overrides/laravel-13/, beside the one this package already keeps for
Telescope, and are frozen against the Laravel release they were taken from — 13.26.1, the floor of the
^13.26.1 that composer.json requires — so each carries the checksum of the file behind it. A release that touches either file leaves
the application on Laravel's own classes rather than on a copy that has fallen behind; the
worker writes the reason to stderr at start-up, and EloquentOverrides::status() answers it at
any time. SPAWN_ELOQUENT_OVERRIDES=0 switches the copies off.
License
MIT