Gesaan dan konteks
Permintaan tak segerak membaca metadata penghalaan sebelum ia mengetahui belanjawan untuk panggilan hiliran. Reka bentuk mesti menggunakan jam monotonik gelung peristiwa (event loop), mencipta had masa tamat tanpa tarikh akhir awal, menjadualkan semula tarikh akhir mutlak sebaik sahaja metadata tiba, dan mengekalkan perbezaan antara pembatalan pemanggil dengan had masa tamat.
Python mendokumenkan asyncio.timeout() sebagai pengurus konteks tak segerak yang boleh dijadualkan semula dan timeout_at() sebagai tarikh akhir mutlak berdasarkan jam gelung peristiwa. Pembatalan yang disebabkan oleh konteks ditukarkan kepada TimeoutError di luar konteks.
Perkara yang diuji oleh penemu duga
Mencari perbezaan yang jelas antara tempoh relatif dan tarikh akhir mutlak, had masa tamat perniagaan dan pembatalan luaran, serta penggunaan reschedule(), expired(), penyarangan, dan wait_for() yang betul. Jawapan yang kukuh menyebarkan satu belanjawan ke setiap I/O dan membersihkan sumber.
Soalan penjelasan untuk ditanya terlebih dahulu
- Komponen manakah yang memiliki belanjawan tersebut, dan berapakah yang telah dibelanjakan untuk membaca metadata?
- Adakah klien HTTP, pangkalan data, dan giliran menerima isyarat had masa tamat atau pembatalan?
- Patutkah panggilan anak berkongsi satu tarikh akhir mutlak atau mempunyai belanjawan bebas?
- Adakah had masa tamat merupakan respons terdegradasi, percubaan semula (retry), atau ralat?
- Apakah versi minimum Python dalam persekitaran pengeluaran?
Rangka kerja jawapan 30 saat
"Saya mengira tarikh akhir mutlak dengan loop.time(). Saya bermula dengan asyncio.timeout(None), kemudian memanggil cm.reschedule(deadline) selepas metadata tiba. Pembatalan yang dicipta oleh konteks menjadi TimeoutError apabila ia keluar, jadi saya menangkapnya di luar; CancelledError luaran kekal sebagai pembatalan selepas pembersihan. Setiap operasi hiliran menerima baki belanjawan, dan panggilan bersarang tidak boleh menetapkan semula had masa tamat penuh. Saya menggunakan cm.expired() untuk diagnosis dan menguji keadaan perlumbaan (race conditions)."
Perbincangan mendalam langkah demi langkah
Langkah 1: Nyatakan tarikh akhir dengan jam monotonik
Jangan kira baki masa daripada masa jam dinding (wall-clock) kerana pembetulan jam boleh melonjak. Gunakan loop.time() dan hantar satu tarikh akhir mutlak kepada setiap operasi hiliran.
loop = asyncio.get_running_loop()
deadline = loop.time() + 2.0
async with asyncio.timeout_at(deadline):
await call_dependency(deadline)Langkah 2: Mulakan konteks yang boleh dijadualkan semula
Apabila belanjawan tidak diketahui, gunakan asyncio.timeout(None) as cm. Selepas metadata tiba, kira tarikh akhir mutlak dan panggil cm.reschedule(deadline). Kekalkan satu konteks supaya kerja awal dan panggilan hiliran berkongsi satu sempadan.
Langkah 3: Sebarkan baki belanjawan
Setiap klien mengira deadline - loop.time() dan menganggap nilai bukan positif sebagai kegagalan serta-merta. Menghantar tarikh akhir yang sama menghalang lapisan penghalaan, pangkalan data, dan HTTP daripada masing-masing memberikan had masa tamat relatif yang penuh.
Langkah 4: Fahami penukaran pembatalan kepada had masa tamat
Konteks membatalkan Task semasa secara dalaman dan menukarkan pembatalan tersebut kepada TimeoutError semasa keluar. Oleh itu, TimeoutError harus ditangkap di luar async with, bukan di dalamnya.
try:
async with asyncio.timeout(1.0):
await slow_call()
except TimeoutError:
return degraded_result()Langkah 5: Kekalkan pembatalan luaran
Jika klien terputus sambungan atau induk membatalkan permintaan, CancelledError bukanlah had masa tamat perniagaan. Lepaskan sambungan, kunci, dan fail sementara, kemudian bangkitkan semula (re-raise) dan bukannya mengembalikan kejayaan yang terdegradasi.
Langkah 6: Bandingkan timeout, timeoutat, dan waitfor
timeout(delay) menggunakan kelewatan relatif dan boleh dijadualkan semula; timeout_at(when) mengambil tarikh akhir monotonik mutlak dan sesuai untuk penyebaran. wait_for(aw, timeout) menyasarkan satu awaitable, membatalkannya apabila had masa tamat, dan mungkin menunggu pembatalan selesai. Menggabungkan banyak panggilan wait_for boleh terlebih membelanjakan belanjawan permintaan.
Langkah 7: Kendalikan penyarangan dan tamat tempoh
Konteks had masa tamat boleh bersarang; tarikh akhir dalaman tidak boleh melebihi tarikh akhir luaran. Selepas keluar, cm.expired() memberitahu sama ada konteks benar-benar mencapai tarikh akhirnya. Pengecualian biasa, pembatalan luaran, dan hasil yang berjaya mesti kekal dapat dibezakan.
Langkah 8: Uji keadaan perlumbaan dan pembersihan
Uji metadata yang tertangguh, tarikh akhir yang telah tamat tempoh, had masa tamat bahagian dalam dahulu, pembatalan luaran dan had masa tamat yang berlaku serentak, operasi hiliran yang mengabaikan pembatalan, reschedule(None), penjadualan semula berulang, dan kegagalan pembersihan. Rekodkan tarikh akhir, baki belanjawan, sebab pembatalan, dan tempoh hiliran tanpa muatan sensitif.
Model jawapan berkualiti tinggi
"Saya mencipta satu tarikh akhir mutlak dengan loop.time() dan menghantarnya ke hiliran. Apabila belanjawan tidak diketahui, saya memasuki timeout(None) dan menjadualkan semula selepas metadata tiba. Konteks menukarkan pembatalan dalamannya kepada TimeoutError di luar blok; CancelledError luaran terus disebarkan. Panggilan bersarang menggunakan tarikh akhir paling awal, dan klien serta pangkalan data menerima baki belanjawan. Ujian perlumbaan mengesahkan tiada Task atau sumber yang bocor."
Kesilapan lazim
- Menggunakan
time.time()untuk tarikh akhir → pembetulan jam mengubah belanjawan → gunakanloop.time(). - Menangkap TimeoutError di dalam konteks → pengecualian yang diubah tidak berada di situ → tangkapnya di luar.
- Menganggap pembatalan luaran sebagai had masa tamat → pembatalan klien menjadi hasil terdegradasi palsu → pastikan jenis pengecualian kekal berbeza.
- Memberikan had masa tamat penuh pada setiap lapisan → jumlah kependaman melebihi kontrak → sebarkan satu tarikh akhir mutlak.
- Mengabaikan penungguan pembatalan wait_for → tempoh sebenar melebihi had masa tamat berangka → ukur penumpuan pembatalan.
- Hanya menguji kejayaan dan satu had masa tamat → laluan perlumbaan membocorkan sumber → uji penjadualan semula, pembatalan serentak, dan kegagalan pembersihan.
Soalan susulan dan respons yang kukuh
Soalan susulan 1: Mengapakah timeout_at lebih stabil daripada had masa tamat bagi setiap lapisan?
Setiap lapisan menyasarkan detik mutlak yang sama, jadi pembundaran masa relatif dan belanjawan berulang tidak boleh terkumpul melebihi kontrak permintaan.
Soalan susulan 2: Bagaimana jika reschedule menerima tarikh akhir yang telah berlalu?
Konteks akan tamat tempoh pada peluang gelung peristiwa yang seterusnya. Semak baki belanjawan sebelum menjadualkan semula dan elakkan memulakan I/O yang tidak boleh dibatalkan apabila ia sudah kehabisan.
Soalan susulan 3: Bagaimanakah pangkalan data mematuhi tarikh akhir?
Hantar baki saat kepada had masa tamat penyata (statement timeout) atau API pembatalan. Membatalkan Python Task sahaja tidak semestinya menghentikan pertanyaan di pihak pelayan.
Soalan susulan 4: Apakah yang anda rekodkan apabila pembatalan dan had masa tamat bersaing?
Kekalkan sebab pembatalan induk dan bilangan pembatalan, serta sebarkan pembatalan luaran. Simpan expired() sebagai medan diagnostik dan bukannya melabelkan setiap penamatan sebagai had masa tamat perniagaan.
Soalan susulan 5: Bilakah anda akan memilih wait_for?
Gunakan ia untuk satu awaitable dengan had relatif yang mudah. Utamakan timeout atau timeout_at apabila panggilan berkongsi tarikh akhir dinamik atau belanjawan merangkumi seluruh permintaan.