Pular para o conteúdo

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çaO que ela exercita
vault por tarefatentativas, próximo horário e último erro
recuo exponencialo serviço que caiu não é martelado
carta mortaa tarefa impossível sai do caminho sem sumir
monitor / handlea 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 0

O 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ãoSem ela
o erro fica na tarefaa carta morta não diz por que morreu
recuo exponencialmil tarefas repetindo a cada segundo derrubam de novo o serviço que acabou de voltar
teto de tentativasa tarefa impossível roda para sempre e atrasa todas as outras
handle Error, não RuntimeErrorum trigger escapa do handle e derruba o laço inteiro

Para ir além#

Volte para todos os tipos de projeto.