Mode worker
Le mode worker fait tourner des processus PHP persistants qui s'initialisent une seule fois puis traitent de nombreuses requêtes, si bien que le coût de démarrage de PHP n'est payé qu'une seule fois au lieu de l'être à chaque requête. Plutôt que de détruire et de reconstruire l'état de PHP à chaque requête, votre application charge son autoloader, sa configuration et ses connexions à la base de données une seule fois, et les réutilise pendant toute la durée de vie du worker.
Fonctionnement
- Activez le mode worker. Définissez
WORKER_MODE_ENABLED=trueet faites pointerENTRY_FILEvers votre script d'amorçage. Cela active le mode worker pour tous les workers PHP du pool. - Initialisation unique. PHP démarre et exécute la portée externe une seule fois. L'enregistrement de l'autoloader, le chargement de la configuration, les connexions à la base de données et tout autre code d'initialisation ne s'exécutent qu'une seule fois.
- Entrez dans la boucle de requêtes. Appelez
oxphp_worker(callback). OxPHP commence à distribuer les requêtes HTTP entrantes à votre callback. - Réinitialisation entre les requêtes. Les superglobales (
$_GET,$_POST,$_SERVER,$_COOKIE,$_FILES,php://input), les tampons de sortie et les en-têtes de réponse sont réinitialisés automatiquement. Une réinitialisation douce nettoie l'état propre à la requête tout en préservant les ressources initialisées dans la portée externe. - La portée externe persiste. Les variables définies avant
oxphp_worker(), les propriétés statiques, les connexions à la base de données et les autoloaders restent disponibles pour toutes les requêtes traitées par ce worker.
Le mode worker modifie le comportement du routage. Toutes les requêtes qui ne correspondent pas à un fichier statique sur le disque sont transmises au worker au lieu de renvoyer une 404. Voir Routage pour plus de détails.
Configuration
| Variable | Valeur par défaut | Description |
|---|---|---|
WORKER_MODE_ENABLED |
false |
Active le mode worker persistant. Accepte true, 1, yes. Nécessite que ENTRY_FILE pointe vers un script .php |
ENTRY_FILE |
(non défini) | Chemin vers le script d'amorçage du worker. Résolu par rapport à DOCUMENT_ROOT lorsqu'il est relatif ; les segments .. et les chemins absolus sont autorisés (les scripts d'amorçage de worker situés en dehors de la racine documentaire publique constituent une disposition prise en charge) |
WORKER_MAX_MEMORY_MIB |
0 |
Mémoire PHP maximale par worker en Mio avant recyclage. 0 = illimité |
L'ancienne variable WORKER_FILE est toujours analysée (avec un WARN au démarrage) et se comporte comme WORKER_MODE_ENABLED=true ENTRY_FILE=$WORKER_FILE. Les nouveaux déploiements devraient utiliser la paire explicite ; l'ancienne forme sera supprimée dans une prochaine version.
Pour un recyclage piloté par l'application, appelez OxPHP\Server\Worker::scheduleExit() depuis l'intérieur d'un gestionnaire de requête. Le worker s'arrête proprement une fois la requête courante terminée.
Écrire un script de worker
Un script de worker comporte deux parties : la portée externe qui s'exécute une seule fois au démarrage, et le callback passé à oxphp_worker() qui s'exécute à chaque requête.
<?php
// Outer scope: runs once at startup
require __DIR__ . '/../vendor/autoload.php';
$config = parse_ini_file(__DIR__ . '/../config/app.ini');
$db = new PDO($config['dsn'], $config['user'], $config['pass'], [
PDO::ATTR_PERSISTENT => true,
]);
$app = new MyApp\Application($config, $db);
// Request loop: runs for every request
oxphp_worker(function () use ($app) {
$app->handle();
});
// Shutdown: runs when the worker exits
$app->terminate();Ce qui est réinitialisé et ce qui persiste
OxPHP effectue une réinitialisation douce entre les requêtes. L'état propre à la requête est nettoyé automatiquement, tandis que tout ce qui a été initialisé dans la portée externe survit pendant toute la durée de vie du worker.
- Superglobales —
$_GET,$_POST,$_SERVER,$_COOKIE,$_FILESetphp://inputsont repeuplées avec les données de la nouvelle requête - Tampons de sortie — tous les tampons de sortie sont vidés et nettoyés
- En-têtes de réponse — le code de statut HTTP et les en-têtes sont réinitialisés à leurs valeurs par défaut
- État d'erreur — les informations sur la dernière erreur (message, fichier, ligne, type) et le statut de connexion sont effacés. Les gestionnaires d'erreurs enregistrés par l'utilisateur (
set_error_handler()), les gestionnaires d'exceptions (set_exception_handler()) et le niveauerror_reporting()persistent d'une requête à l'autre
- Variables dans la portée externe — tout ce qui est défini avant
oxphp_worker()et capturé viause - Propriétés statiques — les propriétés statiques de classe conservent leurs valeurs
- Connexions à la base de données — les connexions PDO, MySQLi et autres connexions persistantes restent ouvertes
- Autoloaders — les autoloaders enregistrés (Composer, personnalisés) restent actifs
- Classes et fonctions chargées — toutes les classes, interfaces, traits et fonctions précédemment chargés
Recyclage
Les workers sont automatiquement recyclés (redémarrés avec un nouveau processus PHP) lorsque l'une des conditions suivantes est remplie :
- Mémoire maximale dépassée — l'utilisation de la mémoire PHP du worker dépasse
WORKER_MAX_MEMORY_MIBMio - Arrêt demandé par l'application — le gestionnaire a appelé
Worker::scheduleExit(). Utile pour un rechargement à chaud piloté par l'application, un rechargement basé sur le mtime des fichiers, ou une réexécution de l'amorçage à chaque requête - Erreurs consécutives — le worker rencontre 3 échecs consécutifs du gestionnaire (erreurs fatales, délais d'expiration ou exceptions non gérées). Notez que les appels
exit()/die()ne sont pas comptés comme des échecs
Lorsqu'un worker est recyclé, le processus PHP se termine et un nouveau démarre, réexécutant la portée externe du script de worker. Pour un arrêt fondé sur la mémoire ou programmé, la requête courante se termine normalement avant que le worker ne s'arrête. Pour un recyclage fondé sur une erreur, le worker s'arrête après la requête ayant échoué.
Rechargement en développement
Le mode worker conserve l'état d'amorçage (autoloader, conteneur d'injection de dépendances, connexions à la base de données) en mémoire, si bien que opcache.validate_timestamps=1 seul ne suffit pas à prendre en compte les modifications du code exécuté pendant la portée externe. Pour les boucles de développement, il existe deux options :
- Recyclez à chaque requête. Appelez
OxPHP\Server\Worker::current()->scheduleExit()à la fin de chaque invocation du gestionnaire (conditionné par un indicateur d'environnementOXPHP_DEV, par exemple). La requête courante se termine normalement, puis le worker s'arrête et est relancé, réexécutant la portée externe. Cela échange le gain de performance du mode worker contre une sémantique de rechargement à la manière de FPM. C'est l'approche la plus simple et la plus fiable pour le développement actif. - Gardez le worker à chaud, rechargez les gestionnaires de requête. Renoncez complètement à
scheduleExit(), activezopcache.validate_timestamps=1et gardez votre amorçage minimal. Le code chargé à l'intérieur du callback de requête sera rafraîchi par OPcache lors de la requête suivante ; le code chargé une seule fois dans la portée externe ne le sera pas. Voir OPcache et JIT → Réglages de développement pour la liste complète des réserves.
Dépannage
Les requêtes se bloquent et ne se terminent jamais
Si oxphp_worker() n'est jamais appelé dans le script d'amorçage, aucune requête n'est distribuée et chaque requête attend indéfiniment. Vérifiez que votre script appelle oxphp_worker() inconditionnellement dans le chemin d'exécution normal.
Fuite d'état entre les requêtes
Les variables définies à l'intérieur du callback oxphp_worker() sont nettoyées par le ramasse-miettes de PHP, mais les propriétés statiques et les variables globales définies dans la portée externe persistent. Si vous voyez des données d'une requête apparaître dans une autre, recherchez les propriétés statiques ou les variables globales qui accumulent de l'état d'un appel à l'autre.
Correctif : Réinitialisez explicitement l'état statique au début de chaque callback de requête, ou évitez de stocker un état propre à la requête dans des variables statiques.
Le worker se recycle immédiatement (limite de mémoire)
La limite de mémoire du worker est vérifiée après chaque requête à partir de l'utilisation mémoire rapportée par PHP. Si votre phase d'amorçage alloue une grande quantité de mémoire (par exemple en chargeant un cache volumineux), l'empreinte mémoire initiale peut déjà être proche de la limite.
Correctif : Augmentez WORKER_MAX_MEMORY_MIB ou reportez les allocations importantes à la première requête.
Le worker se recycle immédiatement (limite d'erreurs)
Trois échecs consécutifs du gestionnaire déclenchent un recyclage. Examinez les logs de votre application à la recherche d'exceptions ou d'erreurs fatales survenant dans le callback de requête.
À vérifier : Cherchez les erreurs dans le log d'accès ou dans la sortie de logs structurés :
docker logs <container> 2>&1 | grep '"level":"error"'La connexion à la base de données se coupe après une période d'inactivité
Si votre serveur de base de données ferme les connexions inactives, les tentatives de reconnexion lors de la requête suivante peuvent échouer. Utilisez un pool de connexions qui gère la reconnexion, ou interceptez l'exception et reconnectez-vous manuellement.
Exemple Docker
services:
app:
image: ghcr.io/oxphp/oxphp:0.10.0
ports:
- "8080:80"
volumes:
- ./src:/var/www/html
environment:
- DOCUMENT_ROOT=/var/www/html/public
- WORKER_MODE_ENABLED=true
- ENTRY_FILE=/var/www/html/worker.php
- WORKER_MAX_MEMORY_MIB=128API PHP
L'introspection du worker et le point d'entrée du worker sont exposés via la classe OxPHP\Server\Worker.
<?php
$worker = OxPHP\Server\Worker::current();
$worker->serve(function () {
handleRequest();
});Les fonctions libres héritées (oxphp_is_worker, oxphp_worker_id, oxphp_worker) restent disponibles et passent par le même état interne. Le nouveau code devrait privilégier l'API de classe.
La classe expose également une introspection à l'exécution utile pour l'auto-recyclage gracieux, l'observabilité et les contrôles de santé :
| Méthode | Renvoie |
|---|---|
Worker::isWorkerMode(): bool |
Si le serveur s'exécute en mode worker |
$worker->id(): int |
ID de worker stable par thread |
$worker->startTime(): float |
Horodatage Unix du démarrage de ce worker |
$worker->requestCount(): int |
Nombre de requêtes que ce worker a traitées |
$worker->memoryUsage(): int |
Valeur actuelle de memory_get_usage(true) pour ce worker |
$worker->rss(): int |
Taille actuelle du resident set en octets (Linux/macOS) |
$worker->maxMemoryBytes(): int |
Seuil de recyclage — WORKER_MAX_MEMORY_MIB × 1 Mio, ou 0 si illimité |
$worker->isExitScheduled(): bool |
Si scheduleExit() a été appelé |
$worker->exitReason(): ?string |
null pendant l'exécution ; "scheduled", "max_memory" ou "error" une fois que le worker s'arrête |
Voir OxPHP\Server\Worker pour les signatures complètes et des exemples détaillés.
Exemples PHP
Détecter le mode worker
Utilisez OxPHP\Server\Worker::isWorkerMode() pour vérifier si le processus courant s'exécute en mode worker. C'est utile pour écrire du code qui fonctionne à la fois en mode traditionnel et en mode worker.
<?php
if (OxPHP\Server\Worker::isWorkerMode()) {
// Reuse a persistent connection
$redis = new Redis();
$redis->pconnect('redis', 6379);
} else {
// Traditional mode: connect per request
$redis = new Redis();
$redis->connect('redis', 6379);
}Script de worker Symfony
<?php
use App\Kernel;
require __DIR__ . '/../vendor/autoload.php';
$kernel = new Kernel('prod', false);
$kernel->boot();
oxphp_worker(function () use ($kernel) {
$request = Symfony\Component\HttpFoundation\Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
});
$kernel->shutdown();Bonnes pratiques
- Définissez
WORKER_MAX_MEMORY_MIB(par exemple128) pour qu'un worker qui fuit se recycle automatiquement au lieu de consommer les ressources de l'hôte. Combinez-le avecWorker::scheduleExit()pour un recyclage piloté par l'application par-dessus. - Évitez de stocker un état propre à la requête dans des propriétés statiques ou des variables globales. Comme celles-ci persistent d'une requête à l'autre, l'état résiduel d'une requête peut fuir dans une autre.
- Validez tôt la réinitialisation douce. Ajoutez
Worker::current()->scheduleExit()à votre gestionnaire sous un indicateur de développement et exercez l'application de bout en bout. Cela permet de détecter les bugs de fuite d'état avant de vous engager sur des workers à longue durée de vie. - Gérez les délais d'expiration d'inactivité de la base de données. Si votre pilote de base de données se déconnecte après une période d'inactivité, interceptez l'exception et reconnectez-vous, ou utilisez un pool de connexions qui gère la reconnexion automatiquement.
- Gardez la portée externe minimale. N'initialisez que ce qui doit vraiment persister : autoloaders, configuration et services partagés. Reportez la configuration propre à la requête dans le callback.
Voir aussi
- Routage — comment le mode worker s'intègre au routage des URL
- Réponse anticipée — envoyer la réponse immédiatement et poursuivre le traitement en arrière-plan
- Fonctions PHP — référence complète pour
oxphp_worker(),oxphp_is_worker()et les autres fonctions intégrées - Référence de configuration — liste complète des variables d'environnement