Je asyncio-task kan halverwege het werk stoppen
Veel Python-services gebruiken dit patroon om achtergrondtaken uit te voeren:
async def create_order(order):
await save(order)
asyncio.create_task(send_webhook(order))
return {"status": "created"}
Het doel is om een webhook te verzenden zonder de client te laten wachten. Dit werkt tijdens de ontwikkeling. In productie mislukt het willekeurig.
Het probleem is garbage collection.
Wanneer je asyncio.create_task aanroept en het resultaat niet opslaat, komt de task in aanmerking voor garbage collection. De event loop houdt alleen zwakke referenties (weak references) naar tasks bij. Als niets anders naar die task verwijst, kan Python deze terugvorderen terwijl deze nog draait.
Je werk stopt gewoon. Er verschijnen geen foutmeldingen in je tracker. De code is perfect. De task verdwijnt simpelweg.
Je ziet mogelijk dit logbericht in je stderr:
Task was destroyed but it is pending!
Deze fout is moeilijk te vinden omdat deze vaak veel later verschijnt dan de eigenlijke fout.
Hoe je dit oplost:
Gebruik een set voor achtergrondtaken om referenties vast te houden.
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
De set houdt de task in leven. De callback verwijdert deze zodra het werk klaar is. Dit voorkomt geheugenlekken en zorgt ervoor dat de garbage collector je tasks niet beëindigt.
Voor Python 3.11 en nieuwer kun je TaskGroups gebruiken. Een TaskGroup houdt sterke referenties naar elke task binnenin vast. Let op: een TaskGroup wacht tot alle tasks zijn voltooid voordat het de block verlaat. Gebruik dit als het werk voltooid moet zijn voordat je een gebruiker antwoordt.
Gebruik voor zwaar achtergrondwerk een supervisor-patroon:
- Maak bij het opstarten één langdurige task aan.
- Gebruik een queue om werk naar deze task te sturen.
- De request handler voegt alleen items toe aan de queue.
Dit zorgt ervoor dat je werk blijft bestaan en stelt je in staat om later retries toe te voegen.
In Python moet je eigenaar zijn van je tasks. Als je geen referentie vasthoudt, zal de runtime ze niet beschermen.
Bron: https://dev.to/r9v/your-asyncio-task-can-be-garbage-collected-mid-flight-3kg1
