Tu tarea de asyncio puede morir a mitad de su ejecución
Muchos servicios de Python utilizan este patrón para ejecutar trabajos en segundo plano:
async def create_order(order):
await save(order)
asyncio.create_task(send_webhook(order))
return {"status": "created"}
El objetivo es enviar un webhook sin hacer esperar al cliente. Esto funciona en desarrollo. En producción, falla de forma aleatoria.
El problema es la recolección de basura (garbage collection).
Cuando llamas a asyncio.create_task y no guardas el resultado, la tarea se vuelve elegible para la recolección de basura. El bucle de eventos (event loop) solo mantiene referencias débiles a las tareas. Si nada más apunta a esa tarea, Python puede reclamarla mientras aún se está ejecutando.
Tu trabajo simplemente se detiene. No hay errores en tu rastreador. El código es perfecto. La tarea simplemente desaparece.
Podrías ver este registro en tu stderr:
Task was destroyed but it is pending!
Este error es difícil de encontrar porque a menudo aparece mucho después del fallo real.
Cómo solucionarlo:
Usa un conjunto (set) de tareas en segundo plano para mantener las referencias.
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
El conjunto mantiene la tarea viva. El callback la elimina una vez que el trabajo termina. Esto evita fugas de memoria (memory leaks) y evita que el recolector de basura elimine tus tareas.
Para Python 3.11 y versiones más recientes, puedes usar TaskGroups. Un TaskGroup mantiene referencias fuertes a cada tarea en su interior. Ten en cuenta que un TaskGroup esperará a que todas las tareas terminen antes de salir del bloque. Usa esto si el trabajo debe completarse antes de responder al usuario.
Para trabajos pesados en segundo plano, utiliza un patrón de supervisor:
- Crea una tarea de larga duración al iniciar.
- Usa una cola (queue) para enviarle trabajo.
- El manejador de solicitudes (request handler) solo añade elementos a la cola.
Esto garantiza que tu trabajo sobreviva y te permite añadir reintentos más adelante.
En Python, debes ser el dueño de tus tareas. Si no mantienes una referencia, el entorno de ejecución (runtime) no las protegerá.
Fuente: https://dev.to/r9v/your-asyncio-task-can-be-garbage-collected-mid-flight-3kg1
