Prazo e cancelamento
Esperar para sempre é o defeito mais comum de código concorrente — e cancelar é mais difícil do que parece.
Uma espera sem prazo não dá erro: ela trava. E um travamento não tem linha, não tem mensagem e não aparece em log — só um processo que consome zero CPU e não responde.
adopt Arcane.Concurrent as C
// 'com_prazo' recebe SEGUNDOS, e LEVANTA quando o tempo acaba —
// um prazo que devolvesse void calado seria indistinguível de um
// trabalho que devolveu void.
action demorado():
sleep(300)
yield "pronto"
monitor:
C.com_prazo(demorado, 0.05)
assert no
handle Error as e:
out e.message
action rapido():
yield "pronto"
assert C.com_prazo(rapido, 2) is "pronto"
out "o prazo transforma um travamento numa decisão"O `receive` sem prazo NÃO espera#
É contrato da linguagem, e o contrário do que quase todo mundo supõe: sem argumento, ele devolve void na hora se a fila está vazia. Trocar o padrão não daria erro em programa nenhum — daria travamento:
adopt Arcane.Concurrent as C
canal := C.canal(4)
// vazio, e sem prazo: volta na hora
assert canal.tentar_receber() is void
canal.enviar("x")
assert canal.receber() is "x"
out "sem prazo, ele não espera — e é isso que evita o travamento calado"Cancelar é cooperativo#
Não há como matar uma thread por fora sem deixar o estado pela metade — um kill no meio de uma escrita é como desligar a máquina no meio de um write. O que existe é pedir, e o trabalho conferir:
adopt Arcane.Concurrent as C
blueprint Trabalho:
action setup():
self.cancelado := no
self.feitos := 0
action cancelar():
self.cancelado := yes
action rodar(quantos):
cycle i from 1 to quantos:
// A conferência é do TRABALHO, e ela acontece entre as
// unidades — nunca no meio de uma.
given self.cancelado:
yield "cancelado"
self.feitos := self.feitos + 1
yield "terminou"
t := spawn Trabalho()
t.cancelar()
assert t.rodar(1000) is "cancelado"
assert t.feitos is 0
out "cancelar é pedir, e o trabalho confere entre as unidades"Os quatro pontos onde conferir o cancelamento#
- Entre itens de um laço — o mais comum, e o mais barato.
- Antes de começar uma unidade cara: começar para cancelar depois é desperdício puro.
- Depois de uma espera — quem esperou dez segundos pode ter sido cancelado no meio deles.
- Nunca no meio de uma escrita — uma transação pela metade é pior que um trabalho a mais.
O prazo de quem espera, e o prazo de quem faz#
São dois prazos diferentes, e confundi-los custa caro: quem espera desiste e segue a vida; quem faz continua fazendo. Sem cancelamento, um prazo de cliente só troca um travamento por um vazamento — o trabalho continua, ninguém lê o resultado, e o recurso fica preso.
adopt Arcane.Concurrent as C
estado := {"terminou": no}
action trabalho():
sleep(120)
estado["terminou"] := yes
yield "pronto"
// Quem espera desiste em 30 ms…
monitor:
C.com_prazo(trabalho, 0.03)
handle Error:
out "desisti de esperar"
// …e o trabalho CONTINUA rodando: o prazo é de quem espera.
sleep(250)
assert estado["terminou"] is yes
out "o prazo não cancela o trabalho — quem cancela é o cancelamento"