## TrueAsync dans Laravel : Architecture pour une vraie concurrence sans Swoole
Les solutions traditionnelles pour l'asynchronie en PHP, comme Swoole ou RoadRunner, nécessitent des modifications profondes du code et l'introduction de fonctions colorées. TrueAsync propose une approche fondamentalement différente — modifier le noyau Zend pour une asynchronie transparente. Nous allons décomposer comment adapter Laravel à ce modèle, en surmontant les problèmes liés aux services avec état et en activant la gestion concurrente des opérations liées à l'E/S.
Les bases de TrueAsync : Comment fonctionne l'asynchronie transparente
TrueAsync est un noyau PHP alternatif qui modifie le moteur Zend, les bibliothèques E/S et les opérations de base de données. Son principal avantage est l'élimination des problèmes de coloration des fonctions. Contrairement à Node.js ou Python asyncio, les fonctions PHP standard (PDO::query(), file_get_contents()) cèdent automatiquement le contrôle au planificateur pendant les attentes E/S. Les coroutines se suspendent au niveau des fonctions C, permettant de gérer des centaines de requêtes dans un seul thread.
Important à comprendre : ce n'est pas du multithreading. Le modèle repose sur le multitâche coopératif — commutation de contexte en attendant des ressources externes. C'est inefficace pour les tâches liées au CPU (calculs lourds) mais idéal pour les scénarios web typiques impliquant des appels à base de données et des API. Voici un exemple d'utilisation basique :
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);
Le problème des services avec état dans Laravel
Laravel a été conçu initialement pour le modèle FPM, où chaque requête est traitée dans un processus isolé. Les services avec état (AuthManager, Session, Router) stockent l'état dans les propriétés des objets, qui sont détruites après le traitement de la requête. En mode asynchrone, le processus vit indéfiniment, entraînant des fuites d'état :
// 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
Les propriétés statiques (Model::$dispatcher, Facade::$resolvedInstance) et les services dans le conteneur IoC deviennent partagés entre toutes les requêtes. Octane résout cela en clonant le conteneur, mais cela introduit une surcharge et ne couvre pas les propriétés statiques.
Isolation contextuelle : L'architecture de la solution
TrueAsync fournit current_context() — un magasin de données lié au cycle de vie de la requête. Lorsqu'une coroutine se termine, le contexte est automatiquement effacé. L'architecture divise Laravel en deux zones :
- Noyau statique : routeur, config, conteneur IoC. Chargé une seule fois, partagé entre toutes les requêtes.
- Services par requête : Auth, Session, Cookie. Créés dans le contexte de chaque coroutine.
Le mécanisme clé est l'interception de la résolution des services 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, ...);
}
Clés contextuelles via Enum et la solution Facade
Pour éviter les collisions et assurer un accès sûr, des clés enum sont utilisées :
enum ScopedService: string
{
case REQUEST = 'request';
case SESSION = 'session';
case AUTH = 'auth';
case AUTH_DRIVER = 'auth.driver';
case COOKIE = 'cookie';
}
Cela fournit :
- Isolation d'accès (seul le propriétaire de l'enum peut lire/écrire les données)
- Garanties sans collision (même avec des valeurs de chaîne identiques)
- Support de l'analyse statique (PHPStan/IDE suivent l'utilisation)
Les Facades nécessitaient une solution séparée en raison du cache de resolvedInstance dans un tableau statique. Voici ScopedServiceProxy :
class ScopedServiceProxy
{
public function __call($method, $args)
{
return ($this->resolver)()->$method(...$args);
}
}
Le proxy transfère les appels vers le contexte de la coroutine courante tout en maintenant le cache au niveau du proxy.
Adaptations critiques et Pool PDO
Après l'isolation de base, les services stockant un état par requête en interne ont été identifiés, nécessitant des versions asynchrones spécialisées :
- AsyncRouter (isolation de la route courante)
- AsyncDispatcher (événements différés par coroutine)
- AsyncTranslator (locale de traduction)
- AsyncViewFactory (View::share())
- AsyncConfig (changements de config à l'exécution)
- AsyncDatabaseSessionHandler (écritures de session atomiques)
- Async*Connection (compteurs de transactions)
Une attention particulière a été portée à la gestion de la base de données. Sans un pool de connexions, les requêtes concurrentes des coroutines cassent le protocole PostgreSQL. TrueAsync résout cela avec un Pool PDO intégré :
$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,
]);
Le pool alloue dynamiquement les connexions : après un résultat SELECT, la connexion retourne au pool, servant plus de coroutines que la taille max du pool. Pour les transactions, la connexion est verrouillée jusqu'au commit/rollback.
Points clés à retenir
- TrueAsync élimine la coloration des fonctions via des modifications du noyau PHP
- Les services avec état de Laravel nécessitent une isolation contextuelle via current_context()
- Le Pool PDO est essentiel pour un fonctionnement correct de la base de données en environnements concurrents
- Les clés enum assurent la sécurité et la traçabilité des données contextuelles
- La solution ne remplace pas le multithreading pour les tâches liées au CPU
— Editorial Team
Aucun commentaire pour le moment.