നിങ്ങളുടെ asyncio ടാസ്ക് പകുതി വഴിയിൽ വെച്ച് നിലച്ചുപോയേക്കാം

പശ്ചാത്തല ജോലികൾ (background work) നടത്തുന്നതിനായി പല Python സർവീസുകളും ഈ രീതിയാണ് ഉപയോഗിക്കുന്നത്:

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

ക്ലയന്റിനെ കാത്തുനിൽപ്പിച്ചു നിർത്താതെ തന്നെ ഒരു webhook അയക്കുക എന്നതാണ് ഇതിന്റെ ലക്ഷ്യം. ഡെവലപ്‌മെന്റ് സമയത്ത് ഇത് കൃത്യമായി പ്രവർത്തിക്കും. എന്നാൽ പ്രൊഡക്ഷനിൽ ഇത് ഇടയ്ക്കിടെ പരാജയപ്പെട്ടേക്കാം.

ഇതിന്റെ പ്രശ്നം garbage collection ആണ്.

നിങ്ങൾ asyncio.create_task വിളിക്കുകയും അതിന്റെ റിസൾട്ട് സേവ് ചെയ്യാതിരിക്കുകയും ചെയ്യുമ്പോൾ, ആ ടാസ്ക് garbage collection-ന് വിധേയമാക്കാൻ പാകത്തിലാകുന്നു. Event loop ടാസ്ക്കുകളിലേക്ക് weak references മാത്രമേ സൂക്ഷിക്കുന്നുള്ളൂ. ആ ടാസ്ക്കിലേക്ക് മറ്റ് റഫറൻസുകൾ ഒന്നുമില്ലെങ്കിൽ, അത് പ്രവർത്തിച്ചുകൊണ്ടിരിക്കുമ്പോൾ തന്നെ Python അത് തിരിച്ചുപിടിച്ചേക്കാം (reclaim).

നിങ്ങളുടെ ജോലി പെട്ടെന്ന് നിലയ്ക്കും. നിങ്ങളുടെ ട്രാക്കറിൽ യാതൊരു പിശകും (error) കാണിക്കില്ല. കോഡ് തികച്ചും കൃത്യമായിരിക്കും. എന്നാൽ ആ ടാസ്ക് വെറുതെ അപ്രത്യക്ഷമാകും.

നിങ്ങളുടെ stderr-ൽ ഇങ്ങനെയൊരു ലോഗ് കാണാൻ സാധ്യതയുണ്ട്: Task was destroyed but it is pending!

യഥാർത്ഥ പരാജയം സംഭവിച്ചതിന് വളരെ വൈകിയാണ് ഈ എറർ കാണപ്പെടുന്നത് എന്നതുകൊണ്ട് ഇത് കണ്ടെത്തുക പ്രയാസമാണ്.

ഇത് എങ്ങനെ പരിഹരിക്കാം:

റഫറൻസുകൾ നിലനിർത്താൻ ഒരു background task 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

ഈ set ടാസ്കിനെ സജീവമായി നിലനിർത്തുന്നു. ജോലി പൂർത്തിയായുകഴിഞ്ഞാൽ callback അത് നീക്കം ചെയ്യുന്നു. ഇത് memory leaks തടയുകയും garbage collector നിങ്ങളുടെ ടാസ്ക്കുകളെ ഇല്ലാതാക്കുന്നത് ഒഴിവാക്കുകയും ചെയ്യുന്നു.

Python 3.11-നും അതിനുശേഷമുള്ള പതിപ്പുകൾക്കും നിങ്ങൾക്ക് TaskGroups ഉപയോഗിക്കാം. ഒരു TaskGroup അതിനുള്ളിലെ ഓരോ ടാസ്ക്കിനും strong references നൽകുന്നു. ഒരു TaskGroup ബ്ലോക്ക് അവസാനിക്കുന്നതിന് മുമ്പ് അതിനുള്ളിലെ എല്ലാ ടാസ്ക്കുകളും പൂർത്തിയാകുന്നത് വരെ അത് കാത്തുനിൽക്കുമെന്ന കാര്യം ശ്രദ്ധിക്കുക. ഉപയോക്താവിന് മറുപടി നൽകുന്നതിന് മുമ്പ് ജോലി പൂർത്തിയാകേണ്ടതുണ്ടെങ്കിൽ ഇത് ഉപയോഗിക്കുക.

കനത്ത പശ്ചാത്തല ജോലികൾക്കായി (heavy background work), ഒരു supervisor pattern ഉപയോഗിക്കുക:

  • സ്റ്റാർട്ടപ്പിൽ തന്നെ ദീർഘകാലം നിലനിൽക്കുന്ന ഒരു ടാസ്ക് നിർമ്മിക്കുക.
  • ജോലികൾ അതിലേക്ക് അയക്കാൻ ഒരു queue ഉപയോഗിക്കുക.
  • Request handler ക്യൂവിൽ ഐറ്റങ്ങൾ ചേർക്കുക മാത്രം ചെയ്യുന്നു.

ഇത് നിങ്ങളുടെ ജോലി സുരക്ഷിതമാണെന്ന് ഉറപ്പാക്കുകയും പിന്നീട് retries ചേർക്കാൻ അനുവദിക്കുകയും ചെയ്യുന്നു.

Python-ൽ, നിങ്ങൾ നിങ്ങളുടെ ടാസ്ക്കുകളെ നിയന്ത്രിക്കണം (own). നിങ്ങൾ അവയുടെ റഫറൻസ് സൂക്ഷിക്കുന്നില്ലെങ്കിൽ, runtime അവയെ സംരക്ഷിക്കില്ല.

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