# TrueAsync in Laravel: Architecture for True Concurrency Without Swoole
Traditional solutions for asynchrony in PHP, such as Swoole or RoadRunner, require deep code modifications and the introduction of colored functions. TrueAsync offers a fundamentally different approach—modifying the Zend core for transparent asynchrony. We'll break down how to adapt Laravel to this model, overcoming issues with stateful services and enabling concurrent handling of I/O-bound operations.
TrueAsync Basics: How Transparent Asynchrony Works
TrueAsync is an alternative PHP core that modifies the Zend Engine, I/O libraries, and database operations. Its key advantage is eliminating function coloring problems. Unlike Node.js or Python asyncio, standard PHP functions (PDO::query(), file_get_contents()) automatically yield control to the scheduler during I/O waits. Coroutines suspend at the C-function level, allowing hundreds of requests to be handled in a single thread.
Important to understand: this isn't multithreading. The model relies on cooperative multitasking—context switching while awaiting external resources. It's inefficient for CPU-bound tasks (heavy computations) but ideal for typical web scenarios involving database and API calls. Here's a basic usage example:
use function Async\spawn;
use function Async\await;
use function Async\delay;
$a = spawn(function() {
delay(100);
return 'result A';
});
$b = spawn(function() {
delay(100);
return 'result B';
});
// Obschee time vypolneniya ~100ms
echo await($a);
echo await($b);
The Problem of Stateful Services in Laravel
Laravel was originally designed for the FPM model, where each request is handled in an isolated process. Stateful services (AuthManager, Session, Router) store state in object properties, which are destroyed after request processing. In async mode, the process lives indefinitely, leading to state leaks:
// Coroutine A: request from user_1
Auth::loginUsingId(1);
delay(200);
// Coroutine B: request from user_2
Auth::loginUsingId(2);
// Return in korutinu A
echo Auth::id(); // Vozvraschaet 2 vmesto 1
Static properties (Model::$dispatcher, Facade::$resolvedInstance) and services in the IoC container become shared across all requests. Octane addresses this by cloning the container, but that introduces overhead and doesn't cover static properties.
Contextual Isolation: The Solution Architecture
TrueAsync provides current_context()—a data store tied to the request lifecycle. When a coroutine completes, the context is automatically cleared. The architecture divides Laravel into two zones:
- Static core: router, config, IoC container. Loaded once, shared across all requests.
- Per-request services: Auth, Session, Cookie. Created in each coroutine's context.
The key mechanism is intercepting service resolution via AsyncApplication::resolve():
protected function resolve($abstract, ...)
{
if ($this->asyncMode && $this->isScoped($abstract)) {
$ctx = current_context();
if ($instance = $ctx->find($abstract)) {
return $instance;
}
$instance = $this->buildFresh($abstract);
$ctx->set($abstract, $instance);
return $instance;
}
return parent::resolve($abstract, ...);
}
Contextual Keys via Enum and the Facade Solution
To avoid collisions and ensure safe access, enum keys are used:
enum ScopedService: string
{
case REQUEST = 'request';
case SESSION = 'session';
case AUTH = 'auth';
case AUTH_DRIVER = 'auth.driver';
case COOKIE = 'cookie';
}
This provides:
- Access isolation (only the enum owner can read/write data)
- Collision-free guarantees (even with identical string values)
- Static analysis support (PHPStan/IDE track usage)
Facades required a separate solution due to caching resolvedInstance in a static array. Enter ScopedServiceProxy:
class ScopedServiceProxy
{
public function __call($method, $args)
{
return ($this->resolver)()->$method(...$args);
}
}
The proxy forwards calls to the current coroutine's context while maintaining cache at the proxy level.
Critical Adaptations and PDO Pool
After basic isolation, services storing per-request state internally were identified, requiring specialized async versions:
- AsyncRouter (current route isolation)
- AsyncDispatcher (deferred per-coroutine events)
- AsyncTranslator (translation locale)
- AsyncViewFactory (View::share())
- AsyncConfig (runtime config changes)
- AsyncDatabaseSessionHandler (atomic session writes)
- Async*Connection (transaction counters)
Special attention went to database handling. Without a connection pool, concurrent coroutine queries break the PostgreSQL protocol. TrueAsync solves this with a built-in PDO Pool:
$pdo = new PDO($dsn, $user, $password, [
PDO::ATTR_POOL_ENABLED => true,
PDO::ATTR_POOL_MIN => 0,
PDO::ATTR_POOL_MAX => 10,
PDO::ATTR_POOL_HEALTHCHECK_INTERVAL => 30,
]);
The pool dynamically allocates connections: after a SELECT result, the connection returns to the pool, serving more coroutines than the pool's max size. For transactions, the connection is locked until commit/rollback.
Key Takeaways
- TrueAsync eliminates function coloring via PHP core modifications
- Laravel's stateful services require contextual isolation via current_context()
- PDO Pool is essential for correct database operation in concurrent environments
- Enum keys ensure safety and traceability of contextual data
- The solution doesn't replace multithreading for CPU-bound tasks
— Editorial Team
No comments yet.