Visão geral
A peça que hoje normalmente vem de um Hangfire, Quartz ou Sidekiq, só que pequena o suficiente pra entender de ponta a ponta e adaptar. Fila com prioridade, pool de workers, retry com backoff exponencial, dead-letter queue, jobs recorrentes e shutdown gracioso de verdade — sem nenhuma dependência de terceiros.
Como funciona
A PriorityJobQueue usa dois semáforos clássicos (capacidade e itens disponíveis) em vez de
polling: um job Critical nunca fica preso atrás de mil jobs Low, porque o dequeue sempre drena
o bucket de prioridade mais alta primeiro. Job que falha espera um backoff exponencial com
jitter (a fórmula da AWS) antes de voltar pra fila — sem jitter, todo mundo tentaria de novo no
mesmo instante quando um serviço externo cai e volta. Job que esgota as tentativas vira
dead-letter, registrado com o motivo do erro em vez de simplesmente desaparecer.
O shutdown (StopAsync) é em duas fases: primeiro para de aceitar job novo e dá um prazo pros
que já estão rodando terminarem sozinhos; só cancela à força se o prazo estourar.
Testes
18 testes automatizados: ordem estrita por prioridade, backpressure real (EnqueueAsync
bloqueia com a fila cheia até um dequeue abrir espaço), job que falha e se recupera, job que
esgota tentativas e cai na dead-letter, persistência em arquivo sobrevivendo a reabertura do
processo, e dois testes de shutdown — um provando que um job em andamento termina normalmente
dentro do prazo, outro provando que é cancelado à força quando o prazo estoura.
Decisões assumidas
Cancelamento é cooperativo, não preemptivo — como em toda Task-based concurrency do .NET, um
job que ignora o CancellationToken recebido não pode ser interrompido à força. O IJobStore
grava o estado de cada job (dead-letter, auditoria), não o payload executável — reiniciar o
processo não retoma automaticamente um job que estava em andamento.
Stack
C# 12, .NET 8, xUnit. Zero dependências de terceiros.