Sua task asyncio pode morrer no meio do trabalho

Muitos serviços Python usam este padrão para executar trabalhos em segundo plano:

async def create_order(order):
    await save(order)
    asyncio.create_task(send_webhook(order))
    return {"status": "created"}

O objetivo é enviar um webhook sem fazer o cliente esperar. Isso funciona em desenvolvimento. Em produção, falha aleatoriamente.

O problema é o garbage collection.

Quando você chama asyncio.create_task e não salva o resultado, a task torna-se elegível para o garbage collection. O event loop mantém apenas referências fracas (weak references) para as tasks. Se nada mais apontar para essa task, o Python pode recuperá-la enquanto ela ainda está em execução.

Seu trabalho simplesmente para. Não há erros no seu rastreador. O código está perfeito. A task simplesmente desaparece.

Você pode ver este log no seu stderr: Task was destroyed but it is pending!

Este erro é difícil de encontrar porque muitas vezes aparece muito depois da falha real.

Como corrigir:

Use um set de tasks em segundo plano para manter as referências.

background_tasks = set()

def fire_and_forget(coro):
    task = asyncio.create_task(coro)
    background_tasks.add(task)
    task.add_done_callback(background_tasks.discard)
    return task

O set mantém a task viva. O callback a remove assim que o trabalho termina. Isso evita vazamentos de memória (memory leaks) e impede que o garbage collector mate suas tasks.

Para o Python 3.11 e versões mais recentes, você pode usar TaskGroups. Um TaskGroup mantém referências fortes para cada task dentro dele. Note que um TaskGroup esperará que todas as tasks terminem antes de sair do bloco. Use isso se o trabalho precisar ser concluído antes de você responder ao usuário.

Para trabalhos pesados em segundo plano, use um padrão de supervisor (supervisor pattern):

  • Crie uma task de longa duração na inicialização.
  • Use uma fila (queue) para enviar trabalho para ela.
  • O manipulador de requisições (request handler) apenas adiciona itens à fila.

Isso garante que seu trabalho sobreviva e permite que você adicione tentativas de reexecução (retries) mais tarde.

No Python, você deve ser o dono de suas tasks. Se você não mantiver uma referência, o runtime não as protegerá.

Fonte: https://dev.to/r9v/your-asyncio-task-can-be-garbage-collected-mid-flight-3kg1