Pertanyaan dan konteks
Sebuah API asinkron memuat data pengguna, pesanan, dan rekomendasi secara konkuren. Kegagalan pada tugas yang wajib harus membatalkan tugas-tugas saudara (siblings), sementara pemutusan koneksi klien atau timeout total tidak boleh meninggalkan coroutine yatim piatu. Rancang implementasinya dengan asyncio.TaskGroup dan jelaskan agregasi eksepsi, pembatalan, serta pembersihan sumber daya.
Dokumentasi Python mendeskripsikan TaskGroup sebagai konkurensi terstruktur: tugas-tugas selesai sebelum cakupan berakhir; eksepsi non-pembatalan membatalkan tugas-tugas yang tersisa dan dinaikkan sebagai ExceptionGroup. Wawancara ini menguji penalaran siklus hidup daripada sekadar tumpukan panggilan create_task().
Apa yang dinilai oleh pewawancara
Mencari pemahaman tentang hierarki pohon tugas induk-anak, pembatalan tugas saudara, CancelledError, ExceptionGroup, dan penggabungan async with. Kandidat harus meneruskan pembatalan ke database, HTTP, dan file, serta memindahkan pekerjaan yang harus bertahan setelah respons ke antrean persisten (durable queue).
Pertanyaan klarifikasi
- Apakah ketiga pembacaan tersebut wajib, atau apakah rekomendasi boleh terdegradasi?
- Tugas mana yang membuka koneksi, kursor, atau file sementara?
- Siapa yang membuat timeout total, dan bagaimana pembatalan pemanggil diterima?
- Error mana yang memerlukan klasifikasi, percobaan ulang (retry), atau respons yang aman bagi pengguna?
- Pekerjaan mana yang harus tetap berjalan setelah respons HTTP?
Jawaban 30 detik
“Buat tiga tugas di dalam async with TaskGroup() dan tunggu hingga cakupan selesai. Eksepsi non-pembatalan membatalkan tugas saudara dan menaikkan grup eksepsi; except* mengklasifikasikan error yang diketahui. Setiap tugas menutup sumber daya di dalam finally. Bungkus grup tersebut dalam satu asyncio.timeout() atau pembatalan pemanggil. Pekerjaan yang harus bertahan setelah permintaan dikirim ke antrean persisten.”
Solusi langkah demi langkah
Langkah 1: Tentukan hierarki tugas dan kontrak hasil
Deklarasikan setiap hasil dan tingkat kekritisannya. Pengguna dan pesanan mungkin wajib sementara rekomendasi bersifat opsional; pilihan tersebut menentukan apakah satu eksepsi akan membatalkan seluruh grup. Setiap tugas termasuk dalam cakupan permintaan.
async with asyncio.TaskGroup() as group:
user_task = group.create_task(load_user(user_id))
order_task = group.create_task(load_orders(user_id))
rec_task = group.create_task(load_recommendations(user_id))Keluar dari cakupan akan menggabungkan (join) tugas-tugas tersebut. Membaca tugas yang belum selesai di luar blok bukan merupakan pengganti untuk menggabungkannya.
Langkah 2: Tangani kegagalan dengan ExceptionGroup
Eksepsi selain CancelledError membatalkan tugas saudara, dan TaskGroup menaikkan ExceptionGroup setelah semua tugas selesai. Gunakan except* untuk error dependensi yang diharapkan dan pertahankan error yang tidak diketahui untuk penangan luar.
try:
async with asyncio.TaskGroup() as group:
user = group.create_task(load_user(user_id))
orders = group.create_task(load_orders(user_id))
except* RetryableDependencyError as errors:
record_dependency_failures(errors.exceptions)
raise ServiceUnavailable from errorsJangan menangkap BaseException dan menelan pembatalan secara diam-diam.
Langkah 3: Dorong pembatalan ke I/O nyata
TaskGroup membatalkan tugas Python; driver HTTP, database, dan file memerlukan dukungan pembatalan atau timeout. Setiap tugas menutup koneksi, melepaskan semaphore, menghapus file sementara, dan menghentikan konsumsi di dalam finally.
async def load_orders(user_id: str):
conn = await pool.acquire()
try:
return await conn.fetch("SELECT ...", user_id, timeout=1.5)
finally:
await pool.release(conn)Jika driver tidak dapat menginterupsi kueri, gunakan timeout statement, koneksi terisolasi, atau background job terikat alih-alih hanya mengandalkan pembatalan tugas.
Langkah 4: Tetapkan satu timeout total dan bedakan pembatalan eksternal
Bungkus seluruh grup di dalam asyncio.timeout() sehingga semua pekerjaan berbagi satu anggaran waktu.
try:
async with asyncio.timeout(2.0):
result = await aggregate(user_id)
except TimeoutError:
return degraded_response("deadline")
except asyncio.CancelledError:
raisePembatalan eksternal harus terus merambat ke atas, bukan menjadi respons yang berhasil. Catat timeout dan pembatalan pengguna sebagai penyebab yang berbeda.
Langkah 5: Pahami TaskGroup versus gather
asyncio.gather() biasanya mempropagasi eksepsi pertama ke penunggunya, tetapi tugas saudara belum tentu dibatalkan; langsung kembali dapat menjadikannya yatim. TaskGroup mengikat masa hidup tugas ke suatu cakupan dan membatalkan tugas saudara saat terjadi kegagalan.
gather(return_exceptions=True) berguna untuk kontrak kegagalan parsial yang eksplisit, tetapi setiap hasil harus diperiksa. Ini bukan pengganti pembersihan terstruktur, dan sebuah grup tidak boleh membuat tugas latar belakang tanpa batas.
Langkah 6: Uji urutan kegagalan dan pembersihan
Injeksi kegagalan pada pengguna terlebih dahulu, kegagalan pada rekomendasi terlebih dahulu, kegagalan simultan, pembatalan pemanggil, timeout total, timeout driver, dan finally yang gagal. Pastikan tugas saudara menerima pembatalan, sumber daya dikembalikan, tidak ada tugas yang tertunda, dan setiap eksepsi dapat dikaitkan dengan tugas anak.
Catat nama tugas, ID permintaan, durasi, penyebab pembatalan, tipe eksepsi, dan panggilan downstream tanpa payload privat. Ulangi pengujian I/O lambat untuk mengungkap race condition; pengujian yang semuanya berhasil tidaklah cukup.
Jawaban model
“Saya membuat tiga tugas di dalam TaskGroup, menjadikan pengguna dan pesanan wajib, serta mengizinkan rekomendasi untuk terdegradasi. Cakupan akan menggabungkan tugas; kegagalan non-pembatalan membatalkan tugas saudara dan menaikkan ExceptionGroup, yang diklasifikasikan oleh except*. Setiap tugas I/O meneruskan timeout dan melepaskan koneksi di dalam finally.”
“Timeout luar menyediakan satu anggaran bersama dan CancelledError dipropagasikan. Pekerjaan pasca-respons dikirim ke antrean persisten. Saya menguji kegagalan simultan, pembatalan, timeout, dan I/O lambat serta memverifikasi tidak ada tugas yatim atau kebocoran sumber daya.”
Kesalahan umum
- Mengembalikan nilai setelah
create_taskmentah → tugas menjadi yatim → pertahankan tugas dalam cakupan TaskGroup. - Menelan
CancelledError→ induk tidak dapat berhenti → bersihkan dan naikkan kembali (re-raise). - Memberikan timeout penuh untuk setiap tugas → latensi total membengkak → bagikan satu anggaran waktu.
- Memperlakukan
ExceptionGroupsebagai satu error → bukti paralel hilang → klasifikasikan denganexcept*. - Hanya membatalkan tugas Python → pekerjaan database atau HTTP terus berjalan → gunakan pembatalan driver atau timeout.
- Menggunakan gather untuk semua kasus → kegagalan parsial dan pembersihan menjadi ambigu → tentukan kontrak degradasi.
Pertanyaan lanjutan dan tanggapan
Pertanyaan lanjutan 1: Apakah TaskGroup secara instan menghentikan I/O yang mendasarinya?
Tidak. Driver harus mendukung pembatalan atau timeout statement; jika tidak, isolasi koneksi atau pindahkan pekerjaan ke background job yang terikat.
Pertanyaan lanjutan 2: Mengapa tidak membatalkan grup saat rekomendasi gagal?
Gunakan kontrak bisnis. Rekomendasi opsional dapat ditangkap dan diganti dengan hasil kosong; pekerjaan yang kritis terhadap keamanan atau penagihan harus membiarkan eksepsi lolos dan membatalkan tugas-tugas saudara.
Pertanyaan lanjutan 3: Bagaimana Anda memetakan ExceptionGroup ke respons API?
Petakan error dependensi yang diketahui ke 503 yang aman atau hasil terdegradasi dan catat error anak. Error yang tidak diketahui diteruskan ke global handler; jangan pernah mengembalikan internal stack trace ke klien.
Pertanyaan lanjutan 4: Pekerjaan mana yang berada di luar TaskGroup?
Email, pengindeksan, dan penulisan batch yang harus berlanjut setelah respons harus disimpan ke dalam antrean dan dicoba ulang oleh worker. Cakupan permintaan hanya boleh berisi pekerjaan seumur hidup permintaan.
Pertanyaan lanjutan 5: Bagaimana jika finally menaikkan eksepsi selama pembatalan?
Jaga pembersihan tetap kecil dan dapat diobservasi, lampirkan kegagalan pembersihan sebagai konteks, dan pertahankan pembatalan asli atau eksepsi bisnis agar akar penyebab masalah tidak tergantikan.