Blog ·
L’enfant réveille le parent
Un flux qui en appelle un autre pouvait interroger ou être averti. Nous avons bâti les deux, et l’horloge de repli est d’un jour, pas de cinq minutes.
Par Provibr
Un flux peut désormais en appeler un autre. L’exécution appelante s’arrête, l’exécution appelée démarre, et quand l’exécution appelée se termine, l’exécution appelante continue sur le fil ok ou sur le fil error.
Toute la question de conception dans cette phrase tient au mot quand. Quelque chose doit remarquer que l’enfant a terminé. Il y a deux manières d’organiser cela et nous avons finalement retenu les deux, dans un ordre précis, et la seconde contient un nombre qui a demandé de la discussion.
Le parent n’attend pas, on l’avertit
La manière naturelle d’écrire cela est un parent qui attend. Le parent sait quelle exécution il attend, il peut donc poser la question. Interroger toutes les dix secondes, voir si l’enfant a terminé, continuer quand c’est le cas.
C’est à l’envers, et la raison est que l’enfant le sait déjà. C’est lui qui passe à l’état terminé. Faire poser la question au parent signifie que l’information voyage de celui qui la détient vers celui qui en a besoin par une question répétée, et la fréquence de cette question est la latence de toute la fonctionnalité.
L’enfant annonce donc. Quand une exécution enfant atteint un état terminal, elle met à maintenant l’heure de réveil de la ligne d’attente qui pointe vers elle, et l’ouvrier ordinaire de la file d’attente la reprend dès son battement suivant. C’est exactement le mécanisme qu’emploie une approbation humaine : décider met l’heure de réveil à maintenant. Une seule colonne pilote la file d’attente, et il n’y a délibérément pas de seconde voie vers l’exécuteur.
Cette annonce tient en une seule instruction : mettre à jour la ligne d’attente dont l’identifiant d’exécution enfant correspond et dont le statut est encore en attente.
Pourquoi cet identifiant est une colonne et non une part du bloc d’état
La continuation d’un parcours en pause est stockée en JSON, et elle contient tout ce dont le parcours a besoin pour reprendre : la pile, les nœuds visités, les variables, les compteurs. Il aurait été cohérent d’y mettre aussi l’identifiant de l’enfant.
C’est une colonne à la place, et la raison est une question de direction. Tout le reste de ce bloc est lu par le parcours, depuis l’intérieur. Cette valeur-ci est lue depuis l’extérieur, par une exécution enfant qui cherche la ligne qui pointe vers elle. Dans le bloc, cela revient à parcourir toutes les attentes ouvertes du système, et l’index dont vous auriez besoin pour rendre cela peu coûteux est de toute façon cette colonne.
La clé étrangère est en SET NULL plutôt qu’en CASCADE. Si l’exécution enfant est supprimée, l’attente du parent ne doit pas disparaître avec elle, car le parent serait alors suspendu sans rien qui puisse jamais le réveiller. Perdre la référence est survivable ; perdre la ligne ne l’est pas.
L’horloge de repli est d’un jour, et le nombre est tout l’argument
L’annonce est le mécanisme. L’horloge est le filet de sécurité pour le cas où l’annonce n’arrive jamais : un conteneur tombé à mi-chemin de la fin de l’enfant, une ligne touchée à la main.
La valeur évidente pour un filet de sécurité est quelque chose de petit. Cinq minutes, cela paraît sérieux.
C’est faux, et la raison tient à ce qu’un enfant a le droit de faire. Une exécution enfant peut contenir une approbation humaine, et une approbation peut avoir une échéance à trente jours. Avec une horloge de cinq minutes, le parent de cet enfant se réveille des milliers de fois sur ces trente jours pour constater que rien n’a changé, et chacun de ces réveils est une requête HTTP de la passerelle vers le conteneur web.
Vingt-quatre heures coûtent au plus trente battements inutiles sur la plus longue attente que le produit autorise, et bornent à un jour le dégât d’une annonce réellement manquée. Voilà l’arbitrage, énoncé dans les unités qui comptent : combien de requêtes le filet de sécurité coûte-t-il quand tout va bien, contre combien de temps une véritable défaillance reste invisible.
La forme générale : un intervalle de réessai n’est pas « aussi court que nous pouvons nous le permettre ». C’est une fonction de la plus longue attente légitime, et si vous ne vous êtes pas demandé quelle est cette attente, l’intervalle est une supposition.
Une horloge que l’on ne peut pas raccourcir est une horloge que l’on ne peut pas prouver
Un filet de sécurité ne se déclenche que lorsque le mécanisme en dessous a échoué. Avec un intervalle fixé à un jour, démontrer qu’il fonctionne prend un jour, si bien que la ronde qui l’a construit a livré cette branche comme non prouvée, honnêtement étiquetée.
La tentation est de la prouver à la main : entrer dans la base de données, déplacer l’heure de réveil dans le passé, regarder l’exécution reprendre. Cela prouve la reprise. Cela ne prouve pas l’horloge, car l’horloge est précisément la chose que vous avez remplacée.
L’intervalle lit donc une variable d’environnement, et le point d’accroche vit dans les sources à côté de la constante plutôt que dans un fichier de test. Il est employé par un script de bout en bout qui le règle sur quelques secondes, laisse le filet de sécurité se déclencher réellement, et regarde un parent dont l’enfant n’a jamais rien annoncé se rétablir de lui-même.
Deux propriétés rendent ce point d’accroche sûr à livrer. Il ne peut que raccourcir l’intervalle : la valeur est bornée par la constante de production, si bien qu’un environnement mal configuré ne peut jamais faire attendre un parent plus d’un jour. Et tout ce qui n’est pas un nombre positif retombe sur la valeur de production.
Cette seconde propriété n’est pas de la programmation défensive pour elle-même. Copier notre fichier d’environnement d’exemple vous donne la variable présente et vide, et une chaîne vide n’est pas une valeur indéfinie. La convertir en nombre donne zéro. Sans le tamis, un fichier de configuration copié réglerait l’intervalle à zéro et mettrait chaque parent ayant un enfant en cours dans une boucle de réveil contre le conteneur web. La distinction entre chaîne vide et valeur indéfinie nous a déjà joué un tour, à un endroit où elle a coûté un 404 à une route de téléchargement.
Deux limites, chacune vérifiée deux fois
Un flux qui appelle un flux soulève la question d’un flux qui s’appelle lui-même. Il y a deux limites, la récursion et la profondeur, et chacune est vérifiée à deux endroits.
Statiquement, à l’enregistrement d’un flux : le graphe est parcouru, les flux qu’il appelle sont résolus, et l’on refuse un cycle ou une chaîne trop profonde, avec la chaîne nommée dans l’erreur. Dynamiquement, pendant une exécution : les deux mêmes limites à nouveau, avec leurs propres codes de refus.
Les deux sont nécessaires parce que les flux sont modifiés indépendamment. Deux flux corrects pris séparément peuvent devenir un cycle dès que quelqu’un ajoute un appel dans le second, et rien ne revalide le premier. La vérification statique est ce qui donne un message d’erreur utile au moment où quelqu’un peut encore le corriger ; la vérification dynamique est ce qui tient réellement.
La profondeur est une colonne sur l’exécution plutôt qu’une valeur calculée en remontant les parents. Cela va contre la ligne que nous tenons d’ordinaire, selon laquelle tout ce qui est dérivable doit être dérivé. Elle ne tient pas ici, car la référence au parent est en SET NULL : une fois une exécution parente supprimée, la chaîne ne peut plus être remontée et la profondeur d’un enfant en cours deviendrait impossible à connaître exactement au moment où elle compte.
Ce que la ronde suivante a mesuré
Les branches que cette conception a laissées ouvertes ont été prises dans une ronde ultérieure, et deux d’entre elles sont les plus intéressantes.
Une exécution enfant qui disparaît entièrement : l’attente garde sa ligne et perd sa référence, l’horloge de repli réveille le parent, et la reprise ne trouve aucun enfant et met ce nœud en échec avec un motif précis plutôt que de rester suspendue. Un graphe totalement illisible produit un autre motif, si bien que les deux situations sont distinguables dans le journal d’exécution.
Un sous-flux à l’intérieur d’une boucle, sous les deux formes qui se comportent différemment : un enfant qui se termine à l’intérieur de la requête d’origine, et un enfant qui se suspend lui-même. Le premier ne touche jamais à la file d’attente. Le second est toute la fonctionnalité.
Ce que nous noterions
- Celui qui sait doit dire, et non être interrogé. L’interrogation périodique transforme votre latence en valeur de configuration.
- Réutilisez le chemin de réveil dont vous disposez déjà. Une décision et un enfant terminé sont le même événement pour la file d’attente.
- Une valeur lue depuis l’extérieur du bloc d’état a sa place dans une colonne. C’est la direction qui en décide, pas la cohérence.
SET NULL, pasCASCADE, quand la ligne qui serait supprimée est le seul moyen pour quelqu’un d’être réveillé.- L’intervalle d’un filet de sécurité est une fonction de la plus longue attente légitime, et non du sentiment de réactivité que vous aimeriez donner.
- Énoncez l’arbitrage en requêtes et en heures. Trente battements perdus contre un jour d’invisibilité, c’est une décision ; « cinq minutes » est un réflexe.
- Une constante que vous ne pouvez pas faire varier est une branche que vous ne pouvez pas prouver. Le point d’accroche a sa place à côté de la constante.
- Faites que le point d’accroche ne puisse que rendre les choses plus sûres, et bornez-le par la valeur de production.
- Une variable d’environnement vide n’est pas une valeur indéfinie, et la convertir en nombre vous donne zéro.
- Vérifiez une limite là où elle peut être corrigée et là où elle doit tenir. Des graphes modifiés indépendamment ont besoin des deux.
Pour être clair : la vérification de fusion a exécuté 16 étapes sur 16 sans une seule ligne d’échec, la suite web a donné 4583 verts, les suites des paquets, de la passerelle et des outils 7286 verts, les tests de parcours 75 verts, la vérification statique des sous-flux 13 verts, l’exécution de bout en bout contre la base de données de développement 58 verts, la mesure des destinataires 4 verts, un test navigateur avec un vrai ouvrier de passerelle 31 verts, dans lequel une pause de quatre-vingt-dix secondes a repris après quatre-vingt-quinze secondes, le test de l’éditeur 9 verts et le balayage d’accessibilité n’a rien trouvé sur six pages. Non prouvé à l’époque : l’horloge de repli, un sous-flux dans une boucle, une exécution enfant disparue, et la branche d’épuisement des tentatives. Ces quatre-là ont été prises dans la ronde suivante.
Issu du travail de plateforme sur Provibr. Comment le parcours est devenu une chose que l’on peut écrire dans une ligne est dans Chaque appel récursif était en position terminale, et la ronde qui a fini par atteindre les branches que celle-ci a laissées ouvertes est dans Vert parce qu’il ne pouvait pas y parvenir.