O que é um Deadlock? Quando tudo espera para sempre
Um deadlock é um impasse em que duas ou mais tarefas detêm cada uma um recurso de que a outra precisa e esperam uma pela outra para sempre, então nenhuma delas consegue progredir.
A concorrência permite que tarefas compartilhem recursos, mas compartilhar exige coordenação. Um deadlock é o que acontece quando a coordenação dá errado de forma circular: o thread A detém o lock 1 e quer o lock 2, enquanto o thread B detém o lock 2 e quer o lock 1. Nenhum vai liberar, nenhum consegue avançar, e o programa trava.
O deadlock clássico
Dois threads, dois locks, adquiridos em ordens opostas. Cada um pega um lock e depois bloqueia esperando pelo outro, que o par está detendo. Sem intervenção, eles ficam congelados indefinidamente. O programa não trava; ele simplesmente para de responder.
As condições para deadlock
- Mutual exclusion: um recurso só pode ser detido por uma tarefa de cada vez.
- Hold and wait: uma tarefa detém um recurso enquanto espera por outro.
- No preemption: recursos não podem ser tomados à força.
- Circular wait: um ciclo de tarefas em que cada uma espera pela próxima.
Como evitar deadlocks
Quebre uma das quatro condições. A tática mais comum é sempre adquirir locks em uma ordem global consistente, o que previne circular waits. Timeouts, estruturas de dados lock-free e reduzir estado compartilhado também ajudam.
Deadlock vs livelock vs starvation
Em um deadlock as tarefas ficam presas esperando. Em um livelock elas continuam reagindo umas às outras mas não fazem progresso. Em starvation uma tarefa nunca recebe um recurso de que precisa porque outras continuam tomando. Todos os três travam o trabalho, mas de formas diferentes.
Um exemplo rápido
Uma transação de banco de dados detendo um lock de linha enquanto espera por uma segunda linha que outra transação travou (e vice-versa) entra em deadlock. Muitos bancos detectam isso e abortam uma transação para quebrar o ciclo.
Deadlocks no CI
Um teste em deadlock não falha rápido; ele trava até o job atingir seu limite de tempo, desperdiçando minutos e dinheiro. A correção está no código, não em um retry. A Latchkey impõe timeouts para que um job travado seja cortado prontamente, e dá retry apenas em falhas que parecem genuinamente transitórias.
Principais conclusões
- Um deadlock é uma espera circular em que as tarefas detêm cada uma o que as outras precisam.
- Ele requer mutual exclusion, hold-and-wait, no preemption e circular wait.
- Jobs de CI em deadlock travam até o timeout, então corrija o locking em vez de dar retry.