Processador de fila
Tarefas com retentativa, recuo exponencial e carta morta — sem perder nenhuma.
Todo sistema acaba com trabalho que não cabe no pedido: mandar e-mail, gerar PDF, chamar um serviço lento. A fila resolve, e cria três perguntas novas: o que acontece quando a tarefa falha, quantas vezes se tenta, e onde fica a que nunca vai dar certo. Esta página responde as três com um laço que se entende em cinco minutos.
| Peça | O que ela exercita |
|---|---|
| vault por tarefa | tentativas, próximo horário e último erro |
| recuo exponencial | o serviço que caiu não é martelado |
| carta morta | a tarefa impossível sai do caminho sem sumir |
monitor / handle | a falha de uma não para as outras |
Estrutura#
text
processador/
src/
fila.df enfileirar, proxima, concluir, falhar
trabalhos.df o que cada tipo de tarefa faz
main.df o laco
tests/forge.toml
[project]
name = "processador"
version = "0.1.0"
description = "Fila de trabalho"
entry = "src/main.df"
dataforge = ">=1.1"
[dependencies]
[scripts]
start = "run src/main.df"
test = "test tests/"
O núcleo#
Este bloco roda sozinho — copie para um arquivo e rode dataforge run. Ele termina com assert, e é assim que esta página é conferida a cada build.
src/fila.df
steady MAXIMO := 4
fila := []
mortas := []
feitas := []
action enfileirar(tipo, dados):
fila.append({"tipo": tipo, "dados": dados, "tentativas": 0, "quando": 0, "erro": ""})
action recuo(tentativa):
yield 2 ** tentativa
action proxima(agora):
cycle i in range(0, len(fila)):
given fila[i]["quando"] smaller_eq agora:
yield fila.pop(i)
yield void
// Um servico que falha nas duas primeiras chamadas, e um que nunca volta.
chamadas := {"email": 0}
action trabalhar(t):
match t["tipo"]:
point "email":
chamadas["email"] += 1
given chamadas["email"] smaller_eq 2:
trigger "servidor de e-mail indisponivel"
yield $"enviado para {t['dados']}"
point "pdf":
yield $"gerado {t['dados']}.pdf"
default:
trigger $"ninguem sabe fazer '{t['tipo']}'"
action rodar_ate(fim):
agora := 0
persist agora smaller_eq fim:
t := proxima(agora)
given t is void:
agora += 1
skip
monitor:
feitas.append(trabalhar(t))
handle Error as e:
t["tentativas"] += 1
t["erro"] := e.message
given t["tentativas"] bigger_eq MAXIMO:
mortas.append(t)
otherwise:
t["quando"] := agora + recuo(t["tentativas"])
fila.append(t)
enfileirar("email", "ana@exemplo.com")
enfileirar("pdf", "relatorio-setembro")
enfileirar("fax", "1998")
rodar_ate(60)
out $"feitas: {feitas}"
out $"mortas: {mortas >> morph m: m['tipo'] + ' (' + m['erro'] + ')'}"
assert len(feitas) is 2
assert len(mortas) is 1 and mortas[0]["tipo"] is "fax"
assert mortas[0]["tentativas"] is MAXIMO
assert len(fila) is 0O teste#
No projeto, a regra mora em src/ e o teste a importa pelo caminho relativo — dataforge test tests/ descobre o arquivo sozinho.
tests/nucleo_test.df
adopt ../src/fila as F
crucible "recuo":
trial "o recuo dobra":
expect [F.recuo(1), F.recuo(2), F.recuo(3)] is [2, 4, 8]As decisões#
| Decisão | Sem ela |
|---|---|
| o erro fica na tarefa | a carta morta não diz por que morreu |
| recuo exponencial | mil tarefas repetindo a cada segundo derrubam de novo o serviço que acabou de voltar |
| teto de tentativas | a tarefa impossível roda para sempre e atrasa todas as outras |
handle Error, não RuntimeError | um trigger escapa do handle e derruba o laço inteiro |
Para ir além#
- Fila que sobrevive a reinício, com reserva e prazo:
Eventos.fila_persistente— Arcane.Eventos. - Acrescente jitter ao recuo — Arcane.Malha explica por quê.
- Vários operários: Concorrência.
Volte para todos os tipos de projeto.