Blog ·
Une pause qui maintenait la commande ouverte
Une attente dans un flux maintenait la requête de commande ouverte tout du long. La correction est un seuil, pas une case à cocher, et le nombre a été mesuré.
Par Provibr
Un flux peut contenir une attente. Vous provisionnez une machine, vous laissez un instant à l’agent, puis vous envoyez la commande suivante. Jusqu’à cette semaine, cette attente était une mise en sommeil à l’intérieur de la requête qui avait lancé le flux, ce qui voulait dire qu’un client en train de passer commande restait à regarder un indicateur d’attente pendant toute la durée de la pause.
L’ancienne borne supérieure était de cinq minutes. Quelqu’un pouvait donc construire un flux d’apparence parfaitement raisonnable dont l’effet était un bon de commande de cinq minutes.
Maintenant qu’une exécution peut s’arrêter et être reprise par un ouvrier, la pause n’a plus à se produire à l’intérieur de la requête. La borne supérieure est passée à trente jours. La question intéressante n’est pourtant pas le plafond. C’est de savoir où passe la ligne entre une pause que la requête peut absorber et une pause qui doit suspendre l’exécution.
Le seuil est mesuré, pas choisi
Trente secondes.
La question que nous avons posée n’était pas « quelle durée une pause a-t-elle le droit d’avoir », qui n’a pas de réponse, mais « combien de pause la requête dans laquelle ce flux s’exécute tolère-t-elle déjà », qui en a une. Cette requête, c’est la commande.
Mesuré par rapport à ce qui s’y produit déjà : un nœud d’action attend une commande pendant soixante secondes par défaut et peut être configuré jusqu’à trois cents, et une notification par courriel coûte environ une demi-seconde par destinataire. Une pause qui ajoute encore une demi-minute là-dessus est défendable. Une pause qui ajoute une minute entière ne l’est pas.
Il y a là un piège dans lequel nous sommes délibérément entrés au lieu de le contourner. Le choix confortable aurait été de fixer le seuil à l’ancienne borne supérieure de trois cents secondes, car alors chaque graphe existant se comporte exactement comme avant et personne n’est surpris. C’est également faux : cela conserve toute la classe de problème qui a motivé le changement. Les graphes dont la pause se situe entre trente et une et trois cents secondes se comportent bel et bien différemment désormais. Leur commande se termine plus tôt et le reste du flux suit plus tard. C’est l’amélioration, pas son coût.
Le plafond de trente jours est une règle de produit et non une limite technique. La mécanique d’attente tiendrait volontiers une exécution pendant un an. Ce qui ne tient pas un an, c’est une organisation où plus personne ne se souvient pourquoi une exécution est ouverte.
Pourquoi c’est un seuil et non une case à cocher
L’autre solution évidente est un interrupteur « attendre durablement » sur le nœud. Nous ne l’avons pas construit, et la raison tient à une question à laquelle on ne peut pas répondre.
S’il y avait une case à cocher, quand voudriez-vous la décocher ? La réponse honnête est jamais. L’attente durable est un détail d’implémentation : celui qui écrit un flux exprime « faire une pause ici pendant ce temps » et n’a aucun avis sur le fait que cela coûte ou non une ligne de base de données. Un réglage dont la valeur correcte est toujours la même n’est pas un réglage : c’est une chose que vous avez oublié de trancher.
Celui qui écrit tape donc un nombre de secondes et le seuil décide. Sous la ligne, le parcours se met simplement en sommeil, à l’intérieur de la requête, sans rien coûter. Au-dessus, l’exécution écrit sa continuation dans la file d’attente avec une heure de réveil et l’appelant rend la main immédiatement.
L’étape qui finit par apparaître dans le journal d’exécution porte un drapeau de durabilité et le temps écoulé réellement mesuré plutôt que celui qui a été demandé, car ce ne sont pas les mêmes nombres et l’écart est le seul moyen de voir que la file d’attente prend du retard.
Des flux qui appellent des flux, et deux limites vérifiées deux fois chacune
La seconde moitié de ce travail est un nœud de sous-flux : un flux qui exécute un autre flux et l’attend.
Cela crée deux façons de construire quelque chose qui ne se termine jamais, et elles se ressemblent assez pour qu’il soit tentant de les vérifier ensemble. Ce n’est pas la même chose. Un cycle est infini. Une chaîne trop profonde est finie mais trop longue. Si vous répondez aux deux avec une seule fonction, un cycle est signalé comme « trop profond », et celui qui écrit part à la recherche d’une longue chaîne alors que la sienne fait deux flux.
Il y a donc deux questions, et chacune est posée deux fois.
Elles sont posées une fois à l’enregistrement d’un flux, statiquement, contre les autres flux de l’organisation. Cette vérification doit être là, car c’est le seul endroit où un bon message d’erreur est possible : il peut nommer le flux qui referme la boucle.
Elles sont posées à nouveau pendant l’exécution. Cette vérification doit être là, car les flux sont modifiés indépendamment. Le flux A est enregistré et tout va bien. Quelqu’un modifie ensuite le flux C, qui pointe désormais vers A. Rien n’a revalidé A. La vérification statique est une politesse envers celui qui écrit ; la dynamique est la garantie véritable.
Deux détails d’implémentation méritent d’être nommés. Demander « ce flux peut-il s’atteindre lui-même en au moins une étape » répond d’un seul coup au cas direct et au cas indirect, et le « au moins une » est toute la définition : un chemin de zéro étape ferait que chaque flux s’atteint lui-même. Et le parcours en profondeur garde son propre chemin et renvoie l’infini quand il rencontre un flux déjà présent dessus, si bien qu’il ne se bloque pas précisément sur l’entrée pour laquelle la vérification de cycle existe. Cela compte, car les deux vérifications peuvent alors être exécutées dans n’importe quel ordre, ce qui veut dire que personne n’a de règle d’ordre à retenir.
La profondeur est stockée comme colonne sur l’exécution enfant, ce qui va contre notre règle habituelle selon laquelle une valeur dérivable n’est pas une colonne. La clé étrangère vers le parent est mise à nul quand le parent est supprimé : après une suppression, la chaîne ne peut donc plus être remontée pour la recalculer. Une valeur dérivée dont la source peut disparaître n’est pas dérivable.
L’horloge de repli est d’un jour, pas de cinq minutes
Quand une exécution enfant se termine, elle met à maintenant l’heure de réveil sur la ligne d’attente du parent, et le parent est repris dans les dix secondes. C’est le mécanisme.
Il y a aussi une horloge de repli, pour le cas où cela n’arrive pas : un conteneur tombé à mi-chemin de la fin de l’enfant, une ligne touchée à la main. Elle est de vingt-quatre heures.
Cinq minutes serait le choix instinctif et c’est le mauvais. Un enfant posé sur une approbation peut légitimement attendre trente jours. Avec une horloge courte, le parent se réveillerait des milliers de fois sur cette période 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. Un jour borne à une journée le dégât d’un signal manqué et coûte au plus trente réveils inutiles sur la plus longue attente que le système autorise.
Une approbation dont personne n’est averti est une exécution à l’arrêt
Une demande d’approbation peut désormais envoyer un courriel, et la catégorie sous laquelle il part lui est propre plutôt que d’être la catégorie générale des flux. C’était un écart délibéré par rapport au plan.
La catégorie générale des flux part vers tous ceux qui ont le module d’automatisation. Une demande d’approbation doit partir vers les personnes qui peuvent réellement la trancher, ce qui est un ensemble différent et plus petit. Il y a donc un second tamis sur la catégorie : la notification n’est remise qu’aux membres qui détiennent le droit de décider.
Mesuré sur notre organisation de test : la catégorie générale des flux s’est résolue en trois destinataires et la catégorie d’approbation en deux. Deux nombres qui diffèrent, c’est toute la preuve que le tamis fait quelque chose.
Un clic qui n’a rien sélectionné
La leçon de mesure de cette ronde porte sur le test navigateur plutôt que sur le produit.
Notre assistant navigateur a une méthode de clic qui émet un appui de souris et un relâchement de souris. Sur un bouton ordinaire, cela fonctionne. Sur un nœud de la toile des flux, cela ne fait absolument rien : le panneau de propriétés est resté sur le nœud déclencheur et la liste déroulante que nous voulions changer n’existait pas, ce qui se lit exactement comme une fonctionnalité manquante.
La toile démarre un glissement à l’appui du pointeur et appelle setPointerCapture sur le nœud. Sans événement de déplacement de souris précédent, et sans le champ buttons posé sur l’appui, le navigateur ne produit pas de clic que le chemin de capture reconnaisse. Ajouter ces deux éléments rend la sélection immédiate.
Il y a un second détail dans le même assistant. Ne cliquez pas au milieu d’un nœud. Le milieu d’un nœud à ports se trouve entre les pastilles de port, et celles-ci ont leur propre gestionnaire de pointeur pour tirer un fil vers l’extérieur. Cliquez plutôt en haut à gauche de l’en-tête.
Ces deux détails ressemblent à des bogues du produit quand on les rencontre, et aucun des deux n’en est un. C’est la forme générale du problème : une simulation d’entrée presque juste échoue d’une manière que l’on ne peut pas distinguer d’une défaillance de la chose que l’on essayait de tester.
Ce que nous noterions
- Un seuil vaut mieux qu’une case à cocher quand la valeur correcte de la case est toujours la même. Demandez quand quelqu’un la décocherait ; s’il n’y a pas de réponse, ne construisez pas l’interrupteur.
- Mesurez le seuil par rapport au budget dans lequel il doit tenir, et non par rapport à la fonctionnalité que vous limitez.
- Ne fixez pas le nouveau seuil à l’ancienne limite au seul motif que rien ne change alors. Que rien ne change est le problème que vous étiez en train de résoudre.
- Un cycle et une chaîne trop longue sont deux erreurs différentes. Un seul message pour les deux envoie les gens au mauvais endroit.
- Validez à l’enregistrement pour le message et à l’exécution pour la garantie. Les enregistrements voisins changent sans que le vôtre soit revalidé.
- Une recherche qui se termine sur son propre chemin peut être exécutée dans n’importe quel ordre par rapport à la vérification qu’elle soutient, ce qui supprime une règle d’ordre dont personne ne se souviendrait.
- Une valeur dérivée dont la source peut être supprimée n’est pas dérivée. Stockez-la.
- Dimensionnez une horloge de repli par rapport à la plus longue attente légitime, et non par rapport à la vitesse à laquelle vous aimeriez vous en apercevoir.
- Acheminez une notification selon qui peut agir dessus, et non selon le module auquel elle appartient.
- Une entrée simulée presque juste échoue comme une fonctionnalité cassée. Vérifiez comment le composant reçoit réellement les événements avant de croire le résultat.
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, les vérifications de 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, et le test navigateur 31 verts avec un vrai ouvrier de passerelle, dans lequel une pause de quatre-vingt-dix secondes a repris après quatre-vingt-quinze secondes, une exécution parente et une exécution enfant ont été liées, exactement un courriel d’approbation a été envoyé, et une chaîne de quatre ainsi qu’une référence récursive ont toutes deux été refusées avec la chaîne fautive nommée dans le message. Le test de l’éditeur a donné 9 verts et le balayage d’accessibilité n’a rien trouvé sur six pages. Non prouvé : les branches d’épuisement des tentatives et de revendication périmée, l’horloge de repli de vingt-quatre heures, un sous-flux dans une boucle, une exécution enfant disparue, le courriel vers un non-propriétaire par un envoi réel, et rien du tout contre un véritable hyperviseur.
Issu du travail de plateforme sur Provibr. Comment le parcours a appris à s’arrêter en premier lieu est dans Chaque appel récursif était en position terminale, et la forme du graphe lui-même est dans Un flux est un graphe, et il doit être fini.