Blogue ·
Um fio que se arrasta, e exatamente um gatilho
Arrastar um fio é a parte fácil. O difícil era o que um botão de teste pode tocar, quem age quando ninguém está a ver, e quanto tempo uma pausa pode durar.
Por Provibr
Os nossos fluxos de automação começaram como uma lista de passos, tornaram-se um grafo, depois uma tela, e nesta ronda receberam finalmente o gesto que toda a gente imagina ao ouvir «editor de fluxos»: agarra-se o ponto à direita de um nó e puxa-se um fio até ao ponto à esquerda de outro.
Esse gesto são umas quantas linhas de código e um comentário longo a explicar porque é que essas linhas estão onde estão. O resto da ronda foram perguntas que nada têm a ver com arrastar. Quantos sítios podem arrancar um fluxo. O que é que um botão de teste pode tocar. Quem age quando ninguém está a ver. E quanto tempo um passo pode pausar.
A captura que come o clique
A tela tem uma única camada de transformação. Os nós ficam nela em posição absoluta e os fios são um SVG nessa mesma camada, portanto não existe um segundo sistema de coordenadas capaz de se desalinhar do primeiro. Essa parte é aborrecida de propósito.
O interessante é a captura do ponteiro. Ao carregar num ponto de saída, algo tem de seguir o ponteiro até se largar, mesmo fora da janela. A implementação óbvia captura no ponto carregado, e está errada de uma maneira que não produz erro nenhum.
A captura redireciona todos os eventos de ponteiro seguintes para o elemento que captura, pointerup incluído, e o navegador dispara depois click no antepassado comum do carregar e do largar. Se for o próprio ponto a capturar, um arrasto que termina noutro ponto nunca lá chega; volta ao sítio onde começou. É por isso a superfície que captura, e onde o dedo se levantou de facto é uma pergunta à parte, respondida por document.elementFromPoint().
Essa resposta tem três degraus. Um ponto de entrada sob o cursor. Se não, uma caixa de nó sob o cursor, que leva à sua primeira entrada. Se não, um íman: a entrada mais próxima dentro de dezasseis pixéis de ecrã, e é isso que torna o gesto tolerante sem obrigar ninguém a acertar num alvo de dez pixéis.
Dois achados menores, que só aparecem quando se constrói isto. O React liga wheel de forma passiva à raiz, portanto impedir a ação por omissão dentro do seu tratador não faz nada e a página desloca-se enquanto se tenta ampliar; esse ouvinte tem de ficar no próprio elemento, explicitamente não passivo. E o ponto médio de uma curva de Bézier não é o centro da sua caixa envolvente: acertar num fio significa pedir ao traçado um ponto a determinado comprimento e passá-lo pela matriz de transformação atual.
Clique e depois clique continua a funcionar. Não é o caminho menor: é o caminho que funciona com teclado, e por isso os pontos continuaram a ser botões a sério.
Um gatilho, e quatro coisas penduradas nele
Um grafo tem exatamente um nó de gatilho. Zero é um erro ao guardar e dois é um erro ao guardar. Parece arrumação. É portante, e quatro funções assentam nisso.
| O que dá | Porque é preciso um único gatilho |
|---|---|
| A alcançabilidade | Todo o nó executável tem de ser alcançável a partir de um único ponto de partida conhecido |
| A deteção de ciclos | Uma travessia em profundidade com marcação por cores exige uma raiz; duas raízes são duas travessias e duas respostas a fundir |
| O seletor de variáveis | Que variáveis existem depende do evento que disparou, e um segundo gatilho seriam duas respostas num mesmo editor |
| A poda do contexto | Um grupo de variáveis que este evento não consegue fornecer é removido: lê-lo é portanto um erro e não uma cadeia vazia |
Essa última linha paga-se sozinha. Há quatro eventos de gatilho: um servidor aprovisionado, reinstalado, eliminado ou arrancado à mão. Um servidor eliminado não tem identificador de sistema operativo, portanto todo o grupo do sistema operativo desaparece do contexto em vez de ficar num objeto vazio. Com variáveis estritas, lê-lo produz então um erro legível de «variável desconhecida» no momento em que se guarda. Esvaziá-lo, em vez disso, faz com que a condição que o lê fique falsa, em silêncio, para sempre, no ramo que interessava.
É também por isso que não existe um nó de injeção separado. Arrancar à mão é um evento de gatilho e não uma segunda entrada; acrescentar um partiria a invariante de que dependem as quatro funções, em troca de um nó que se desenha um pouco melhor.
Um ensaio não toca em nada, e percorre na mesma
O editor tem um botão Test run, e um grafo pode agora conter um nó que arranca, para, desliga, suspende ou retoma o servidor dessa execução. Estes dois factos no mesmo ecrã são uma armadilha: alguém vai carregar em testar para ver que ramo um grafo toma, e desliga uma máquina de um cliente.
Um ensaio nunca envia, por isso, um comando. O nó de ação regressa de imediato, marcado como ignorado, com um código de motivo apresentado na língua de quem lê. E o essencial: toma o ramo ok. Um ensaio que caísse no ramo de erro afirmaria que o grafo faz algo que não faz, e isso é pior do que não dizer nada. Agir a sério passa pela injeção, que é uma execução real e pede confirmação.
Quem age quando ninguém está a ver
Uma execução de fluxo não tem espectador. Numa encomenda, quem carregou em Encomendar é quando muito por acaso a pessoa a quem a ação deveria ser atribuída. Numa eliminação pode já ter saído. Mas encontrar um servidor exige um agente com um perfil de permissões, e esse perfil tem de vir de algum lado.
A escolha é que uma execução age como a pessoa dona da conta, por três razões por ordem de peso. O momento da autorização é a implantação e não a execução: guardar um fluxo exige o módulo de scripts, pendurá-lo num plano exige permissões de planos, e essa é a mesma forma de uma tarefa agendada ou do trabalhador de supervisão. Agir como quem carregou tornaria o grafo irreprodutível: o mesmo fluxo arrancaria uma máquina para um comprador e não para outro, por causa de uma caixa de permissões que nada tem a ver com o fluxo. E o limite verdadeiro continua duro de qualquer forma: a procura recebe o identificador de organização da execução e recusa toda a linha fora dele, e este nó só pode, por construção, endereçar o servidor desta execução.
Antes de nascer um comando, dois crivos têm de dizer que sim os dois: este fornecedor sabe fazer este comando, e este tipo de recurso consegue. No Proxmox, hoje, nenhuma das cinco ações é recusada num contentor, e é precisamente por isso que o ensaio de ponta a ponta pede também um alargamento de disco e exige que esse seja recusado. Um crivo que nunca diz que não é indistinguível de um crivo que não está ligado.
Cinco minutos, porque a pausa está dentro de um pedido
O nó de atraso aceita de cinco a trezentos segundos. A encomenda pedia seiscentos, e seiscentos está errado por uma razão mensurável: um fluxo despoletado por uma encomenda é esperado dentro do pedido da encomenda. Dez minutos de pausa são dez minutos em que um cliente que paga olha para um indicador de carregamento.
Não existe um estado de espera duradouro, e enquanto não existir uma pausa não pode sobreviver a um pedido. Essa costura tem um nome no código, para que «porque é que o meu fluxo não pode esperar uma hora» seja uma pergunta com resposta localizável. Exige uma fila capaz de guardar uma execução a meio e retomá-la mais tarde, e só quando existir se poderá pensar em ciclos.
O nó HTTP, e porque não é fetch
Um nó HTTP numa ferramenta de automação é uma máquina de falsificação de pedidos do lado do servidor se não se tiver cuidado, e ter cuidado exclui aqui o cliente padrão.
O fetch não sabe fazer a única coisa que esta política exige: ligar-se a um endereço escolhido por nós enquanto o nome de máquina fica em Host e em SNI. Resolve por sua conta, e essa segunda resolução é exatamente a janela onde vive a reassociação de DNS. Cria-se o crivo sobre um endereço público e meio segundo depois o cliente liga-se ao ciclo local porque a validade do registo era zero.
O cliente HTTP do próprio Node aceita uma resolução própria, e a nossa devolve exatamente um endereço: o que o crivo aprovou. Host e SNI continuam a derivar do nome de máquina, portanto o TLS é validado contra o nome. A validação de certificado está ligada sem interruptor para a desligar, e as variáveis de ambiente de intermediário não fazem nada, porque esse cliente não as lê.
Os reencaminhamentos são seguidos à mão, três no máximo, e cada salto volta a passar pelo crivo desde o início: esquema, informação de utilizador, porta, DNS, cada endereço. Um reencaminhamento aberto para um ponto de metadados de uma nuvem é recusado no segundo salto, mesmo que o primeiro fosse uma máquina pública respeitável. São admitidos sete nomes de cabeçalho, e dois deles só podem transportar uma referência a um segredo guardado, decifrado para o pedido, ocultado em tudo o que fica gravado e registado apenas pelo nome.
O que anotaríamos
- Capture no invólucro, não naquilo que agarrou. A captura do ponteiro redireciona tudo o que vem a seguir, e o sintoma é um gesto que termina em silêncio onde começou.
- Um gesto tolerante precisa de uma cadeia de recuos, não de um alvo maior. Elemento sob o cursor, depois o seu pai, depois um limiar de distância.
- Uma única entrada compra quatro funções. Alcançabilidade, deteção de ciclos, seletor de variáveis e poda do contexto exigem os quatro uma raiz conhecida. Conte o que perde antes de acrescentar uma segunda.
- Remova o que não pode existir; não o esvazie. Um grupo de variáveis ausente falha em voz alta ao guardar; um vazio falha em silêncio durante a execução.
- Um botão de teste capaz de tocar na produção não é um botão de teste. Ignorar e ainda assim tomar o ramo de sucesso é a combinação honesta.
- Uma pausa é limitada por quem a espera. Se quem chama é um pedido HTTP, a pausa conta-se em segundos.
Para ser claro sobre o que isto é: os fluxos são alcançáveis a partir do ecrã de scripts e a tela é agora o único editor. Três coisas não estão provadas. A espera por um comando realmente concluído não foi medida contra um hipervisor real, o arrasto foi conduzido com eventos simulados do protocolo do navegador e não por uma mão, e nada disto foi medido numa compilação de produção.
Do trabalho de plataforma da Provibr. Porque é que o grafo tem de ser finito à partida está em um fluxo é um grafo, e um grafo tem de ser finito, e como um ecrã de resumo lê tabelas alheias sem nunca escrever nelas está em projetar, nunca substituir.