تسک asyncio شما ممکن است در میانه راه از بین برود

بسیاری از سرویس‌های پایتون از این الگو برای اجرای کارهای پس‌زمینه استفاده می‌کنند:

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

هدف این است که یک webhook ارسال شود بدون اینکه کلاینت منتظر بماند. این روش در محیط توسعه (development) کار می‌کند، اما در محیط عملیاتی (production) به صورت تصادفی با شکست مواجه می‌شود.

مشکل، garbage collection است.

وقتی شما asyncio.create_task را فراخوانی می‌کنید و نتیجه را ذخیره نمی‌کنید، آن تسک واجد شرایط برای garbage collection می‌شود. حلقه رویداد (event loop) فقط ارجاعات ضعیف (weak references) به تسک‌ها نگه می‌دارد. اگر هیچ چیز دیگری به آن تسک اشاره نکند، پایتون ممکن است آن را در حالی که هنوز در حال اجراست، بازپس بگیرد (reclaim).

کار شما ناگهان متوقف می‌شود. هیچ خطایی در سیستم رهگیری (tracker) شما ثبت نمی‌شود. کد بی‌نقص است، اما تسک به سادگی ناپدید می‌شود.

ممکن است این لاگ را در stderr خود ببینید: Task was destroyed but it is pending!

پیدا کردن این خطا دشوار است، زیرا اغلب بسیار دیرتر از زمان وقوع واقعی خطا ظاهر می‌شود.

روش رفع مشکل:

از یک مجموعه (set) برای تسک‌های پس‌زمینه جهت نگه داشتن ارجاعات استفاده کنید.

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

این مجموعه، تسک را زنده نگه می‌دارد. تابع callback نیز پس از اتمام کار، آن را از مجموعه حذف می‌کند. این کار از نشت حافظه (memory leaks) جلوگیری کرده و مانع از آن می‌شود که garbage collector تسک‌های شما را از بین ببرد.

برای پایتون ۳.۱۱ و نسخه‌های جدیدتر، می‌توانید از TaskGroups استفاده کنید. یک TaskGroup ارجاعات قوی (strong references) به تمام تسک‌های داخل خود نگه می‌دارد. توجه داشته باشید که یک TaskGroup قبل از خروج از بلوک، منتظر می‌ماند تا تمام تسک‌ها تمام شوند. اگر کار باید قبل از پاسخ دادن به کاربر تکمیل شود، از این روش استفاده کنید.

برای کارهای سنگین پس‌زمینه، از الگوی ناظر (supervisor pattern) استفاده کنید:

  • یک تسک با طول عمر بالا در هنگام شروع برنامه ایجاد کنید.
  • از یک صف (queue) برای ارسال کارها به آن استفاده کنید.
  • هندلر درخواست (request handler) فقط آیتم‌ها را به صف اضافه می‌کند.

این کار تضمین می‌کند که کار شما با موفقیت انجام شود و امکان افزودن قابلیت تلاش مجدد (retries) را در آینده فراهم می‌کند.

در پایتون، شما باید مالک تسک‌های خود باشید. اگر ارجاعی به آن‌ها نگه ندارید، زمان اجرا (runtime) از آن‌ها محافظت نخواهد کرد.

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