Task ของ asyncio ของคุณอาจหยุดทำงานกลางคันได้
บริการ Python หลายแห่งใช้รูปแบบนี้ในการทำงานเบื้องหลัง (background work):
async def create_order(order):
await save(order)
asyncio.create_task(send_webhook(order))
return {"status": "created"}
เป้าหมายคือการส่ง webhook โดยไม่ต้องทำให้ client ต้องรอ ซึ่งวิธีนี้ใช้งานได้ดีในขั้นตอนการพัฒนา แต่ในสภาพแวดล้อม production มันกลับทำงานล้มเหลวแบบสุ่ม
ปัญหาคือ garbage collection
เมื่อคุณเรียกใช้ asyncio.create_task และไม่ได้เก็บผลลัพธ์ไว้ Task นั้นจะเข้าข่ายที่จะถูก garbage collection ได้ เนื่องจาก event loop จะเก็บเพียง weak reference ไปยัง task เท่านั้น หากไม่มีส่วนอื่นที่ชี้ไปยัง task นั้น Python อาจดึงทรัพยากรคืน (reclaim) ในขณะที่มันยังทำงานอยู่
งานของคุณจะหยุดลงดื้อๆ โดยไม่มี error ปรากฏในระบบ tracker ของคุณเลย ทั้งที่โค้ดก็เขียนไว้สมบูรณ์แบบ แต่ task นั้นกลับหายไปเฉยๆ
คุณอาจเห็น log นี้ใน stderr:
Task was destroyed but it is pending!
error นี้หาได้ยากเพราะมันมักจะปรากฏขึ้นหลังจากเกิดความล้มเหลวไปนานแล้ว
วิธีแก้ไข:
ใช้ background task set เพื่อเก็บ reference ไว้
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 จะช่วยให้ task ยังคงมีชีวิตอยู่ ส่วน callback จะทำหน้าที่ลบ task ออกเมื่อทำงานเสร็จสิ้น วิธีนี้จะช่วยป้องกัน memory leak และหยุดไม่ให้ garbage collector มาฆ่า task ของคุณ
สำหรับ Python 3.11 และเวอร์ชันใหม่กว่า คุณสามารถใช้ TaskGroups ได้ โดย TaskGroup จะเก็บ strong reference ของทุก task ที่อยู่ภายใน ข้อควรระวังคือ TaskGroup จะรอให้ทุก task ทำงานเสร็จสิ้นก่อนที่จะออกจาก block ให้ใช้สิ่งนี้หากงานนั้นจำเป็นต้องเสร็จสมบูรณ์ก่อนที่คุณจะตอบกลับผู้ใช้
สำหรับการทำงานเบื้องหลังที่หนักหน่วง ให้ใช้ supervisor pattern:
- สร้างหนึ่ง long-lived task ไว้ตอนเริ่มต้นระบบ
- ใช้ queue ในการส่งงานไปให้ task นั้น
- ให้ request handler ทำหน้าที่เพียงแค่เพิ่ม item ลงใน queue เท่านั้น
วิธีนี้จะช่วยให้มั่นใจว่างานของคุณจะไม่หายไป และยังช่วยให้คุณสามารถเพิ่มระบบ retry ได้ในภายหลัง
ใน Python คุณต้องเป็นเจ้าของ task ของคุณเอง หากคุณไม่เก็บ reference ไว้ runtime จะไม่ช่วยปกป้องพวกมัน
Source: https://dev.to/r9v/your-asyncio-task-can-be-garbage-collected-mid-flight-3kg1
