Há algum tempo, escrevi sobre a migração bem-sucedida do IBM BPM para Camunda e agora nossa vida é cheia de felicidade e impressões agradáveis. Camunda não decepcionou e continuamos nossa amizade com esse mecanismo de BPM.
Mas, infelizmente, Camunda também pode apresentar surpresas desagradáveis, devido às quais, às vezes, não são obtidos os resultados mais óbvios. Este artigo considerará um caso que, apesar de simples, se mostrou interessante e um pouco mais complexo do que parecia à primeira vista.
Nós treinamos em gatos
Para descrever o problema, considere um exemplo sintético. Suponhamos que decidamos expandir nossa base de clientes e precisamos atender gatos e gatos. Todo cliente em potencial deve ser verificado e, talvez, oferecido imediatamente algo.
Verificaremos a confiabilidade do candidato e os possíveis serviços que podemos oferecer a ele. A verificação de confiabilidade e os possíveis serviços não estão conectados de forma alguma - essas ações podem ser executadas em paralelo. Esquematicamente em um diagrama bpmn, ele será assim:
Diagrama 1. Processo esquemático de manutenção flexível
O diagrama mostra esquematicamente as principais etapas, bifurcação e coleta (junção) de gateways.
Este ícone representa um gateway paralelo. O Gateway Paralelo é o gateway mais fácil para criar uma parte paralela de um processo.
Existem dois tipos de gateways paralelos:
- fork - cria uma execução separada para cada ramificação;
- join - aguarda todas as execuções recebidas.
Execução - representa um 'caminho de execução' em uma instância do processo (da documentação). Ou seja, é o fio de execução do processo.
Agora vamos complicar um pouco a tarefa. Iremos verificar e procurar serviços da seguinte maneira: primeiro, verificamos o estado do cliente, depois examinamos quais serviços podem ser adequados a ele e executamos alguns pré-processamento. Além disso, vários serviços podem atender um cliente de uma só vez, portanto, devemos oferecer todos eles ao cliente.
Como trabalhamos com clientes peludos, os serviços serão adequados: valeriana, garra, travesseiro de mestre e outras coisas úteis.
Gráfico 2. Gráfico de Serviço ao Cliente Fofo Refinado A
nova versão do processo é semelhante a esta. O processo será paralelo à verificação de confiabilidade e à busca de possíveis sugestões. A pesquisa também é paralela. Nesse caso, os ramos nos quais as condições correspondentes são atendidas serão executados.
Para paralelização com condições, o Gateway Inclusivo é usado, indicado pelo seguinte ícone:
O Gateway Inclusivo é um gateway paralelo com condições de ramificação. Os ramos nos quais as condições são verdadeiras serão executados.
Existem dois tipos de gateways:
- fork - para cada ramificação com uma condição atendida, é criada a execução, que é executada em paralelo da mesma maneira que a execução no Parallel Gateway;
- O join, diferentemente do Parallel Gateway, não espera que as execuções executem todas as ramificações, mas apenas aquelas nas quais a condição é verdadeira.
Pode acontecer que as verificações realizadas sejam insuficientes e o cliente precise ser verificado novamente. Para fazer isso, adicione uma condição no final de todas as verificações, que pode ser enviada para verificação novamente no início:
Diagrama 3. A versão final do processo que deve funcionar
Era complicado, mas o processo resolve o problema.
O que? O que aconteceu?
Aqui coisas estranhas começam a acontecer. O ramo de verificação de confiabilidade atende e alcança o gateway paralelo de coleta. Até agora, tudo está indo bem.
A segunda ramificação verifica a condição do material e, dependendo dos resultados, as tarefas correspondentes são executadas. Em seguida, o processo para no gateway que coleta o Inclusive Gateway e não avança. Se você olhar para o Coockpit (painel de administração do Kamunda), as execuções ficarão travadas no Gateway Inclusivo e no Gateway Paralelo.
Diagrama 4. Processo de manutenção
suspensa. Podemos dizer que tivemos um impasse no processo em Camunda. Nesse caso, não está diretamente relacionado a deadlocks da teoria da programação paralela e deadlocks.
Em busca da resposta ̶̶̶̶̶̶̶̶̶̶̶̶
Como eu não tinha compreensão suficiente do que aconteceu e por que o processo parou, o problema teve que ser resolvido empiricamente.
Talvez você precise de uma ramificação padrão para o Gateway Inclusivo e, sem ela, o processo não pode ser executado normalmente?
Estranho, é claro, mas tentando adicionar o ramo padrão. A presença de uma ramificação padrão é uma boa prática, pois, caso contrário, nenhuma condição poderá ser atendida e, em seguida, obteremos um erro.
Diagrama 5. Processo de serviço com ramificação padrão
Inicie e obtenha o mesmo resultado - o processo permanece travado no Gateway Inclusivo de coleta.
Em seguida, vem a enumeração de todos os tipos de parâmetros, lendo a documentação, e isso se prolonga por meio dia. Em outra tentativa, o processo passa inesperadamente pelo infeliz destino. A ramificação inferior com o Inclusive Gateway funcionou em uma situação em que, durante o processo de pesquisa e depuração, a ramificação superior foi excluída com uma verificação de confiabilidade do cliente. Ou seja, quando o processo degenerou apenas na ramificação inferior com o Gateway Inclusivo, o processo foi encerrado.
Diagrama 6. Processo degenerado
Acontece que o Parallel Gateway de alguma forma influencia o Inclusive Gateway. Isso é estranho, ilógico e não deveria ser.
Como isso é possível? Talvez você deva reler a teoria sobre como o Gateway Paralelo e Inclusivo funciona. O que deve acontecer para o gateway de junção reunir todos e o processo continuar? Na Internet, eles escrevem que todos os que coletam um Gateway Inclusivo (ingresso) aguardam a entrada de tantas pessoas quanto saíram da bifurcação. De repente, surge outra pergunta: como esse contador funciona?
O que você é? Como você trabalha?
Esse problema é digno de quebra-cabeças e programas de TV inteligentes. Somente nos programas de TV eles podem ligar para um amigo. Por outro lado, também posso pedir ajuda. Chamaremos Denis de arquiteto de processos de negócios.
- Denis, olá! Você pode me dizer como a pista de coleta determina quando é hora do processo seguir em frente? Em todos os lugares eles escrevem: "Quanto saiu - muito deve entrar". Mas como exatamente ele pensa isso?
- Muito simples. Camunda conta o número de execuções ativas.
- Muito obrigado. Por enquanto,
considere o que aconteceu. Para fazer isso, mais uma vez, lembre-se do esquema inicial, que acabou:
Diagrama 7. Processo suspenso com uma ramificação padrão
Para simplificar, consideramos o caso em que todas as condições são atendidas. O que temos no momento em que três tarefas após o cumprimento dessas condições?
Quantas execuções ativas? Três no ramo inferior e um no superior, onde verificamos a confiabilidade do cliente. Camunda não se importa com o fato de que esses ramos sejam paralelos diferentes. Somente o número de execuções ativas é interessante, das quais há quatro, e o gateway inclusivo recebido recebeu apenas três.
Fixação
Para corrigir a situação, o Gateway de coleta deve coletar todas as execuções de uma só vez e, em teoria, o processo continuará. Vamos tentar deixar um em vez de dois gateways de junção:
Diagrama 8. Versão corrigida do processo
Infelizmente, após as alterações, o processo começou a parecer, na minha opinião, menos óbvio. Mas funcionou como planejado originalmente. Nesse ponto, a missão terminou em segurança, eu fui capaz de fazer as mudanças e ir para casa.
Interessante está apenas começando
Quando me sentei para escrever este artigo e criei um exemplo de processo no qual eu poderia descrever esse caso, fiquei desapontado: o processo funcionou como deveria e não havia impasse.
No começo, eu assumi que a versão Camunda no exemplo é maior que no projeto, e na nova versão esse problema já foi corrigido. Mas desvalorizar Camunda não fez nada. A propósito, em todos os exemplos, a versão 7.8.0 é usada - está longe de ser a mais recente, mas não importa. O problema também foi verificado e reproduzido na versão mais recente no momento - 7.13.
Por tentativa e erro, o problema foi corrigido. O exemplo falso original não tinha um ramo inverso, ao contrário do processo que eu estava desenvolvendo no local de trabalho.
Acontece que, se existe um ramo reverso, o problema é reproduzido e nos encontramos em uma espécie de impasse, e sem um ramo reverso, tudo funciona como deveria.
O caso exigia compreensão e análise. Para fazer isso, eu tive que olhar o código fonte do Camunda BPM. Como o problema estava no Gateway Inclusivo, parecia lógico procurar a resposta na classe responsável pelo comportamento desse elemento - InclusiveGatewayActivityBehavior . Executando algumas vezes a depuração nas duas versões do processo, percebi como ele funciona.
Se não estiver claro - consulte as fontes!
Para não fazer uma história chata, a descrição do trabalho do InclusiveGateway com base no código fonte será incompleta. A lógica de interesse para nós está concentrada no método execute , onde o método activatesGateway é o mais valioso para este caso . Pelo que entendi, ele verifica se é possível passar o InclusiveGateway. O método execute é chamado para cada execução (para cada ramo em execução). No nosso caso, existem três ramificações - isso significa que esse método será chamado três vezes.
Vamos ver como o método activatesGateway funciona. Para uma melhor compreensão, vamos dar nomes a todos os ramos executáveis.
Diagrama 9. Diagrama de processo com execuções
Pelo que entendi, a lógica do método é a seguinte:é feita uma comparação entre o número de execuções que chegaram a este getty e o número de flechas incluídas nesta fuga . Essa verificação foi feita no caso da situação mais simples, quando todas as ramificações do Gateway Inclusivo são executadas, e a lógica de verificar o gateway de coleta é aguardar até que o número de execuções inseridas seja igual ao número de setas recebidas. Ou seja, no caso mais simples, o método execute é chamado quantas vezes houver ramificações no gateway de coleta e, em seguida, o processo continua.
No nosso caso, esse método é chamado três vezes, porque o número de execuções recebidas aumentará de 1 para 3. Na última chamada, o número de execuções recebidas e enviadas será 3 e 4, respectivamente, e seguiremos o ramo falso.
Se a condição não for atendida, as execuções restantes serão verificadas como pertencentes ao Gateway Inclusivo. Ou seja, a capacidade das execuções ativas de acessar o Gateway Inclusivo associado é verificada.
Aqui você tem que ser paciente, expirar e ler. O desenlace está próximo!
Na ramificação falsa do método activatesGateway, a cada chamada, as que ainda não chegaram nas execuções de junção inclusiva são verificadas quanto à possibilidade de alcançar essa junção. Se pelo menos uma execução pode levar a um Gateway Inclusivo, você deve levar em consideração e aguardar que ele também entre nessa associação. Se não houver execuções que possam resultar em associação, o método retornará true.
A parte mais interessante está chegando. À primeira vista, a última execução (execução 1 no diagrama) não pode levar ao Gateway Inclusivo. Mas vale a pena examinar a implementação do método canReachActivity , que lida com essa verificação, e o motivo desse comportamento desse elemento ficará claro.
Se descartarmos todos os detalhes do código, dentro desse método, o método isReachable é chamado recursivamente, o qual, passo a passo, verifica se essa execução pode entrar na coleção InclusiveGateway. O ramo reverso apenas oferece essa oportunidade e, infelizmente, isso é levado em consideração, embora não deva, pois voltaremos depois de todas as junções.
Como resultado, o Inclusive Gateway está aguardando outra execução, que nunca ocorrerá. Assim, temos um tipo de impasse. Em princípio, se descartamos as convenções, obtemos um impasse clássico: a junção no Parallel aguarda a execução da ramificação com Inclusive e, inversamente, a ramificação com Inclusive espera a execução paralela.
O diagrama abaixo mostra uma direção aproximada de verificar a disponibilidade da junção do Inclusive Gateway a partir da execução, que veio ingressar no Prallel Gateway por meio de uma ramificação paralela.
Diagrama 10. Caminho possível da junção paralela à junção inclusiva
O diagrama mostra que, de fato, a junção do Inclusive Gateway está disponível a partir da junção do Parallel Gateway e, de acordo com a lógica do Camunda BPM, não importa que já esteja "à frente do círculo".
Depois de descobrir os motivos, surgiu a pergunta involuntariamente: isso é um bug ou um recurso? Na minha opinião, isso é um bug. Agora estou coletando informações e casos para enviar um relatório à equipe Camunda.
É bom que o problema esteja localizado. Mas e agora?
Na verdade, agora - as conclusões:
- O aviso antecipado está previsto. Devemos construir nossos processos, levando em consideração esse comportamento da Camunda.
- , . parallel join.
- Inclusive Gateway , , executions .
- , . , Parallel Gateway .
Parecer simplicidade e clareza às vezes engana. Isso só pode ser combatido através da acumulação e replicação de conhecimento. Infelizmente, na hora de resolver esse problema, eu não tinha um conhecimento profundo da lógica da junção inclusiva, então tive que mexer. Adquiri esse conhecimento por tentativa, erro, ligando para um amigo e depurando as fontes.
De tudo isso segue uma conclusão óbvia e longe de ser nova de que você precisa entender como a ferramenta que você está usando funciona. Quanto melhor você entender, menos serão esses problemas.
A segunda conclusão também é bastante óbvia: você precisa decompor não apenas o código, mas também os processos.
Links que foram úteis para analisar este caso e escrever um artigo: