Il y a quelque temps, j'ai écrit sur la migration réussie d'IBM BPM vers Camunda, et maintenant notre vie est pleine de bonheur et d'impressions agréables. Camunda n'a pas déçu et nous continuons notre amitié avec ce moteur BPM.
Mais, hélas, Camunda peut également présenter des surprises désagréables, grâce auxquelles les résultats les plus évidents ne sont parfois pas obtenus. Cet article examinera un cas qui, malgré sa simplicité, s'est avéré intéressant et un peu plus complexe qu'il n'y paraissait à première vue.
Nous nous entraînons sur les chats
Pour décrire le problème, prenons un exemple synthétique. Supposons que nous décidions d'élargir notre clientèle et que nous devions servir les chats et les chats. Chaque client potentiel devrait être vérifié et, peut-être, immédiatement offert quelque chose.
Nous vérifierons la fiabilité du candidat et les services possibles que nous pouvons lui offrir. La diligence raisonnable et les services possibles ne sont en aucun cas liés - ces actions peuvent être effectuées en parallèle. Schématiquement, dans un diagramme bpmn, cela ressemblera à ceci:
Diagramme 1. Processus schématique de maintenance duveteuse
Le diagramme montre schématiquement les principales étapes, forking et collecter (joindre) les passerelles.
Cette icône représente une passerelle parallèle. La passerelle parallèle est la passerelle la plus simple pour créer une partie parallèle d'un processus.
Il existe deux types de passerelles parallèles:
- fork - crée une exécution séparée pour chaque branche;
- join - attend toutes les exécutions entrantes.
Exécution - représente un «chemin d'exécution» dans une instance de processus (à partir de la documentation). Autrement dit, c'est le fil conducteur du processus.
Maintenant, compliquons un peu la tâche. Nous allons vérifier et rechercher des services de la manière suivante: d'abord nous vérifions l'état du client, puis nous regardons quels services peuvent lui convenir, et faisons un prétraitement. De plus, plusieurs services peuvent convenir à un client à la fois, nous devons donc être en mesure de les offrir tous au client.
Puisque nous travaillons avec des clients à fourrure, les services seront appropriés: valériane, râteau à griffes, oreiller de maître et autres choses utiles.
Graphique 2. Graphique du service client Fluffy raffiné La
nouvelle version du processus ressemble à ceci. Le processus est parallèle à la vérification de la fiabilité et à la recherche d'offres possibles. La recherche est également parallélisée. Dans ce cas, les branches sur lesquelles les conditions correspondantes sont remplies seront exécutées.
Pour la parallélisation avec des conditions, la passerelle inclusive est utilisée, ce qui est indiqué par l'icône suivante:
Inclusive Gateway est une passerelle parallèle avec des conditions de branche. Des branches seront exécutées dont les conditions sont vraies.
Il existe deux types de passerelles:
- fork - pour chaque branche avec une condition remplie, l'exécution est créée, qui est exécutée en parallèle de la même manière que l'exécution dans Parallel Gateway;
- join, contrairement à Parallel Gateway, ne s'attend pas à ce que les exécutions exécutent toutes les branches, mais uniquement celles sur lesquelles la condition est vraie.
Il peut arriver que les contrôles effectués soient insuffisants et que le client doive être revérifié. Pour ce faire, ajoutez une condition à la fin de tous les contrôles, qui peut être envoyée au tout début pour une nouvelle vérification:
Diagramme 3. La version finale du processus qui devrait fonctionner
Cela s'est avéré fastidieux, mais le processus résout le problème.
Quoi? Qu'est-il arrivé?
Ici, des choses étranges commencent à se produire. La branche de vérification de fiabilité remplit et atteint la passerelle parallèle de collecte. Jusqu'à présent, tout va bien.
La deuxième branche vérifie l'état du matériau et, en fonction des résultats, les tâches correspondantes sont exécutées. En outre, le processus s'arrête à la passerelle collectant la passerelle inclusive et ne va pas plus loin. Si vous regardez le Coockpit (panneau d'administration du Kamunda), les exécutions seront suspendues à la passerelle inclusive et à la passerelle parallèle collectrices.
Schéma 4. Processus de maintenance suspendu
. Terminé. Nous pouvons dire que nous avons eu une impasse dans le processus sur Camunda. Dans ce cas, il n'est pas directement lié aux blocages de la théorie de la programmation parallèle et des blocages.
A la recherche de la réponse ̶̶r̶i̶k̶l̶uch̶e̶n̶i̶y̶
Comme je n'avais pas une compréhension suffisante de ce qui s'était passé et pourquoi le processus s'était arrêté, j'ai dû résoudre le problème de manière empirique.
Peut-être avez-vous besoin d'une branche par défaut pour la passerelle inclusive et sans elle, le processus ne peut pas fonctionner normalement?
Étrange, bien sûr, mais essayez d'ajouter une branche par défaut. La présence d'une branche par défaut est une bonne pratique, car sinon aucune condition ne peut être remplie et nous obtiendrons alors une erreur.
Diagramme 5. Processus de service avec la branche par défaut
Lancez et obtenez le même résultat - le processus reste suspendu à la passerelle inclusive de collecte.
Vient ensuite l'énumération de toutes sortes de paramètres, la lecture de la documentation, et cela dure une demi-journée. Lors de la prochaine tentative, le processus passe inopinément par la passerelle malheureuse. La branche inférieure avec Inclusive Gateway a fonctionné dans une situation où la branche supérieure a été supprimée pendant le processus de recherche et de débogage, vérifiant la fiabilité du client. Autrement dit, lorsque le processus a dégénéré uniquement dans la branche inférieure avec la passerelle inclusive, le processus s'est terminé.
Diagramme 6. Processus dégénéré
Il s'avère que Parallel Gateway influence d'une manière ou d'une autre la passerelle inclusive. C'est bizarre, illogique et ça ne devrait pas l'être.
Comment est-ce possible? Il vaut probablement la peine de relire la théorie sur le fonctionnement de la passerelle parallèle et inclusive. Que doit-il se passer pour que la passerelle de jointure réunisse tout le monde et que le processus se poursuive? Sur Internet, ils écrivent que chaque passerelle inclusive (jointure) de collecte attend que le même numéro y entre en sortant de la «fourche». Puis une autre question se pose soudain: comment fonctionne ce compteur?
Qu'es-tu? Comment travailles-tu?
Ce problème est digne des puzzles et des émissions de télévision intelligentes. Seuls les programmes télévisés sont autorisés à appeler un ami. D'un autre côté, je peux aussi demander de l'aide. Nous appellerons notre architecte des processus d'affaires Denis.
- Denis, bonjour! Pouvez-vous me dire comment le mécanisme de collecte détermine le moment où le processus doit avancer? Partout, ils écrivent: "Combien est sorti - tant devrait rentrer." Mais comment le pense-t-il exactement?
- Très simple. Camunda compte le nombre d'exécutions actives.
- Merci beaucoup. Pour l'instant
Considérez ce qui s'est passé. Pour ce faire, rappelez à nouveau le schéma initial, qui s'est avéré:
Schéma 7. Processus de suspension avec une branche par défaut
Pour simplifier, considérons le cas où toutes les conditions sont remplies. Qu'avons-nous au moment où trois tâches après ces conditions sont remplies?
Combien d'exécutions actives? Trois sur la branche inférieure et une sur la branche supérieure, où nous avons vérifié la fiabilité du client. Camunda ne se soucie pas du fait que ce sont généralement des branches parallèles différentes. Je ne suis intéressé que par le nombre d'exécutions actives, dont il y en a quatre, et la passerelle inclusive entrante n'en a reçu que trois.
Fixation
Pour remédier à la situation, la passerelle de collecte doit collecter toutes les exécutions en même temps, puis, en théorie, le processus se poursuivra. Essayons de laisser une au lieu de deux passerelles de jointure:
Diagramme 8. Version corrigée du processus
Hélas, après l'édition, le processus a commencé à paraître, à mon avis, moins évident. Mais cela a fonctionné comme prévu au départ. À ce stade, la quête s'est terminée en toute sécurité, j'ai pu pousser les changements et rentrer chez moi.
Le plaisir ne fait que commencer
Lorsque je me suis assis pour écrire cet article et que j'ai proposé un exemple de processus sur lequel je pourrais décrire ce cas, j'ai été déçu: le processus a fonctionné comme il se doit et il n'y avait pas de blocage.
Au début, je supposais que la version de Camunda dans l'exemple est plus élevée que dans le projet, et ce problème a déjà été corrigé dans la nouvelle version. Mais déclasser Camunda n'a rien fait. Soit dit en passant, dans tous les exemples, la version 7.8.0 est utilisée - elle est loin d'être la plus récente, mais elle n'a pas d'importance en principe. Le problème a également été vérifié et reproduit pour le moment sur la dernière version - 7.13.
Par essais et erreurs, le problème a été résolu. Le faux exemple d'origine n'avait pas de branche inverse, contrairement au processus que je développais en milieu de travail.
Il s'avère qu'en présence d'une branche inverse, le problème se reproduit et on se retrouve dans une sorte d'impasse, et sans branche inverse, tout fonctionne comme il se doit.
Le cas nécessitait une compréhension et une analyse. Pour ce faire, j'ai dû regarder les sources de Camunda BPM. Étant donné que le problème était lié à la passerelle inclusive, il semblait logique de rechercher la réponse dans la classe responsable du comportement de cet élément - InclusiveGatewayActivityBehavior . Après avoir exécuté le débogage sur les deux versions du processus plusieurs fois, j'ai compris comment cela fonctionne.
Si ce n'est pas clair - voir les sources!
Afin de ne pas organiser une narration ennuyeuse, la description du travail d'InclusGateway basée sur le code source sera schématique. La logique qui nous intéresse est concentrée dans la méthode execute , où la méthode activatesGateway est la plus précieuse dans ce cas . Si je comprends bien, il vérifie s'il est possible de passer le InclusiveGateway. La méthode execute est appelée pour chaque exécution (pour chaque branche en cours d'exécution). Dans notre cas, il existe trois de ces branches, ce qui signifie que cette méthode sera appelée trois fois.
Voyons comment fonctionne la méthode activatesGateway. Pour une meilleure compréhension, nous donnerons des noms à toutes les branches exécutées.
Diagramme 9. Diagramme de processus avec exécutions
Si je comprends bien, la logique de la méthode est la suivante:une comparaison est faite entre le nombre d'exécutions qui sont venues à ce Getty et le nombre de flèches incluses dans cette escapade . Cette vérification a été effectuée dans le cas de la situation la plus simple, lorsque toutes les branches de la passerelle inclusive sont exécutées, et la logique de la vérification de la passerelle de collecte est d'attendre que le nombre d'exécutions saisies soit égal au nombre de flèches entrantes. Autrement dit, dans le cas le plus simple, la méthode d'exécution est appelée autant de fois qu'il y a de branches dans la passerelle de collecte, puis le processus se poursuit.
Dans notre cas, cette méthode est appelée trois fois, car le nombre d'exécutions entrantes passera de 1 à 3. Lors du dernier appel, le nombre d'exécutions entrantes et sortantes sera respectivement de 3 et 4, et nous suivrons la fausse branche.
Si les conditions ne sont pas remplies, les exécutions restantes sont vérifiées pour appartenir à la passerelle inclusive. À savoir, la capacité des exécutions actives à accéder à la passerelle inclusive de jointure est vérifiée.
Ici, vous avez besoin d'un peu de patience, expirez et lisez. Le dénouement est proche!
Dans la fausse branche de la méthode activatesGateway, à chaque appel, ceux qui ne sont pas encore arrivés dans les exécutions de jointure inclusive sont vérifiés pour la possibilité d'atteindre cette jointure. Si au moins une exécution peut conduire à une passerelle inclusive, vous devez la prendre en compte et attendre qu'elle arrive également à cette jointure. S'il n'y a aucune exécution qui peut conduire à la jointure, la méthode retournera true.
La partie la plus intéressante arrive. À première vue, la dernière exécution (dans le diagramme - exécution 1) ne peut pas conduire à la passerelle inclusive. Mais il vaut la peine d'examiner la mise en œuvre de la méthode canReachActivity , qui est engagée dans cette vérification, et la raison de ce comportement de cet élément deviendra claire.
Si nous supprimons tous les détails du code, la méthode isReachable est appelée récursivement à l'intérieur de cette méthode, qui vérifie pas à pas la possibilité que cette exécution entre dans le InclusiveGateway de collecte. La branche inverse donne juste une telle opportunité, et, hélas, cela est pris en compte, bien que cela ne devrait pas, car nous reviendrons après tout join'ov.
En conséquence - Inclusive Gateway attend une autre exécution qui ne viendra jamais. Ainsi, nous obtenons une sorte d'impasse. En principe, si nous ignorons les conventions, nous obtenons une impasse classique: join on Parallel attend que la branche avec Inclusive soit exécutée, et, inversement, la branche avec Inclusive attend que Parallel s'exécute.
Le diagramme ci-dessous montre la direction approximative de la vérification de l'accessibilité d'une jointure Inclusive Gateway à partir de l'exécution, qui est venue rejoindre Prallel Gateway via une branche parallèle.
Diagramme 10. Chemin possible de la jointure parallèle à la jointure inclusive
Le diagramme montre qu'en effet, à partir de la jointure Parallel Gateway, la jointure Inclusive Gateway est disponible et, selon la logique de Camunda BPM, cela ne signifie pas qu'il y a déjà un «cercle en avant».
Après avoir découvert les raisons, la question s'est posée sans le savoir: est-ce un bug ou une fonctionnalité? À mon avis, c'est un bug. Maintenant, je collecte des informations et des cas pour envoyer un rapport à l'équipe de Camunda.
C'est bien que le problème soit localisé. Mais qu'en est-il maintenant?
En fait, maintenant - les conclusions:
- Averti est prévenu. Nous devons construire nos processus en tenant compte de ce comportement de Camunda.
- , . parallel join.
- Inclusive Gateway , , executions .
- , . , Parallel Gateway .
La simplicité et la clarté apparentes sont parfois trompeuses. Cela ne peut être combattu que par l’accumulation et la reproduction des connaissances. Hélas, au moment de résoudre ce problème, je n'avais pas une connaissance approfondie de la logique de la jointure inclusive, j'ai donc dû bricoler. J'ai acquis ces connaissances par essai, erreur, appel d'un ami et source de débogage.
De tout cela découle une conclusion évidente et loin d'être nouvelle selon laquelle vous devez comprendre comment fonctionne l'outil que vous utilisez. Mieux vous comprendrez, moins de tels problèmes seront.
La deuxième conclusion est également assez évidente: vous devez décomposer non seulement le code, mais aussi les processus.
Liens utiles pour analyser ce cas et rédiger un article: