Le chef d’orchestre qui démarre, surveille et arrête une application

Dans une application Socle V005, aucun worker ne démarre seul, ne se planifie seul, ni ne s’arrête seul. Un composant s’en charge : le MOP, pour Main Orchestrator Process. Il tient une machine à six états, démarre les workers par priorité, les appelle au rythme qu’ils demandent, surveille leur santé et les arrête dans l’ordre. Voici comment, d’après le code de la version 5.8.6.

Le problème

Un programme qui tourne des mois a trois moments délicats : le démarrage, les pannes et l’arrêt. Au démarrage, une brique qui accepte des requêtes avant que ses dépendances soient prêtes renvoie des erreurs. En cas de panne, une brique morte doit être relancée sans relancer tout le reste. À l’arrêt, une requête en cours ne doit pas être coupée net. Le MOP règle ces trois moments à un seul endroit.

Six états

De Vers Quand
INITIALIZING STARTING Spring a fini de démarrer (ApplicationReadyEvent)
STARTING RUNNING tous les workers ont démarré, l’API d’administration est prête
STARTING ERROR un worker a échoué à démarrer
RUNNING DRAINING arrêt demandé : signal du système, message admin.shutdown_request, ou Supervisor en état critique
DRAINING SHUTDOWN tout est arrêté dans l’ordre
RUNNING ERROR cinq erreurs de suite dans la boucle de surveillance

Le passage de INITIALIZING à STARTING est une comparaison atomique : un second appel de démarrage ne fait rien.

Le démarrage, dans l’ordre

Le MOP initialise d’abord ses propres briques : les métriques partagées, le KvBus, le Supervisor s’il est activé. Puis il trie les workers par getStartPriority() croissant, et pour chacun appelle initialize(), puis start(), puis planifie son doWork().

List<Worker> sortedWorkers = workers.stream()
    .sorted(Comparator.comparingInt(Worker::getStartPriority))
    .toList();

for (Worker worker : sortedWorkers) {
    try {
        // …
        worker.initialize();
        worker.start();

        if (!worker.isEnabled()) {
            // …
        } else {
            scheduleWorkerDoWork(worker);
            // …
        }
    } catch (Exception e) {
        // …
        throw new RuntimeException("Impossible de démarrer le worker: " + worker.getName(), e);
    }
}

Deux conséquences :

  • Il n’y a pas de mode dégradé au démarrage. Un worker qui lève une exception dans initialize() ou start() fait échouer toute l’application. C’est voulu : mieux vaut un programme qui refuse de démarrer qu’un programme qui démarre à moitié.
  • L’ordre est une affaire de nombres. La valeur par défaut est 100. Le serveur HTTP de l’application a 1 000 : il démarre en dernier, et n’accepte de requêtes que lorsque tout est prêt.

Dans la passerelle web de TheSocle, un proxy de sortie, trois workers se suivent ainsi. Celui qui capture le trafic reçoit la passerelle dans son constructeur ; il démarre donc après elle.

// ProxyGatewayWorker
@Override public int getStartPriority() { return 40; }

// PolicyActionWorker
@Override public int getStartPriority() { return 55; }

// CaptureActionWorker
@Override public int getStartPriority() { return 56; }

Le rythme de chaque worker

Le worker dit quand il veut travailler ; le MOP l’appelle.

Un intervalle

getCycleIntervalMs() : toutes les N millisecondes. Cinq secondes par défaut.

Une expression cron

getSchedule() : "* * * * *" pour chaque minute, "0 8 * * 1-5" pour 8 h en semaine.

Passif

getSchedule() rend "PASSIVE", ou l’intervalle vaut zéro : le MOP n’appelle jamais doWork(). Le worker répond à des événements ou à des actions.

Une exception dans doWork() est journalisée et comptée (mop.worker.dowork_error). Le cycle suivant a lieu normalement.

La boucle de surveillance

Une fois l’application en RUNNING, une boucle tourne au rythme du battement du Supervisor. À chaque tour, elle transmet l’état de chaque worker sain, synchronise les métriques partagées dans le KvBus, puis interroge isHealthy().

Un worker applicatif qui répond « pas sain » trois fois de suite est redémarré : annulation de sa planification, stop(), initialize(), start(), nouvelle planification. Les workers fournis par le framework — serveur HTTP, tableau de bord, maintenance — en sont exclus : leur redémarrage manuel exige force=true. Un worker désactivé par la configuration (isEnabled() faux) n’est ni planifié, ni surveillé, mais ses actions restent visibles.

On peut aussi demander un redémarrage de l’extérieur, en publiant sur le sujet worker.restart du KvBus.

L’arrêt, dans l’ordre inverse

L’arrêt passe par DRAINING. Le MOP arrête d’abord la planification des doWork(), puis arrête les workers par getStopPriority() croissant. Le serveur HTTP a la priorité 0 : il s’arrête en premier, en laissant finir les requêtes en cours, pour qu’aucune n’arrive pendant qu’on démonte le reste. Viennent ensuite le Supervisor, l’API d’administration, les métriques, le KvBus, et enfin le registre des données partagées. Une exception dans un stop() est journalisée et n’empêche pas les autres de s’arrêter.

Deux pièges vus en production

Une expression cron à six champs

Le MOP attend cinq champs, comme le cron Unix. Une expression à six, avec les secondes comme chez Spring, est refusée, et le worker se rabat sur un intervalle — cinq secondes par défaut — avec pour seul signe une ligne dans le journal. D’où le commentaire, dans le moteur de TheDiffuseur : « Cinq champs, pas six. »

Un worker passif qui se dit malade

Un worker passif dont isHealthy() rend faux est redémarré, sans effet possible, puisqu’il n’a rien à relancer. Le 2026-09-11, sur la Veille, quatre workers dans ce cas totalisaient environ 440 redémarrages par heure. Le traitement n’en souffrait pas ; les journaux devenaient illisibles. La règle depuis : la santé d’un worker porte sur lui, pas sur le service qu’il appelle ; et un worker non configuré le dit par isEnabled().

Ce que ça donne en vrai

Chaque application sert l’état de ses workers sur /dashboard, sur son propre port, et sa sonde de santé sur /actuator/health. Le Hub thesocle.net, lui-même une application Socle, comptait 61 workers le 2026-09-23 : tous démarrent, sont surveillés et s’arrêtent par ce même MOP.

Pour un décideur, ce que ça évite

Une application qui démarre à moitié et répond n’importe quoi. Une panne d’une brique qui oblige à tout relancer à la main. Une mise à jour qui coupe les utilisateurs au milieu d’une opération. Ces trois risques sont traités une fois, dans le framework, et non application par application.

À retenir

  • Démarrer par priorité croissante, arrêter par priorité croissante d’arrêt : HTTP en dernier, puis en premier.
  • Trois isHealthy() faux de suite : redémarrage automatique.
  • Pas de mode dégradé au démarrage ; un cron à cinq champs.

Un programme à construire sur TheSocle ?

Retour en haut

Mentions légales · Confidentialité · Contact

© 2026 LMVI Conseil — SARL, SIREN 949 417 620 · [email protected]