Perintah dan konteks
Ketika Tokio select! menunggu batas waktu (timeout), cancellation token, dan I/O, bagaimana Anda menentukan apakah sebuah future aman dari pembatalan (cancellation-safe)? Bagaimana cara memulihkannya jika operasi yang selesai sebagian dibatalkan?
Ini cocok untuk peran Rust, backend, infrastruktur, dan layanan asinkron. Tokio mendeskripsikan pembatalan sebagai tindakan men-drop sebuah future; cabang select! yang menang akan berlanjut sementara cabang lainnya dapat di-drop pada .await apa pun. Perbedaan utamanya adalah menghentikan proses tunggu versus membatalkan efek samping eksternal, dengan state machine yang dapat dimulai ulang secara aman.
Hal yang diuji oleh pewawancara
- Memahami bahwa men-drop future tidak me-rollback I/O yang mendasarinya atau efek samping jarak jauh (remote).
- Mengidentifikasi progres parsial dalam buffer baca, antrean, lock, dan frame protokol.
- Menggunakan ownership, state machine, dan kunci idempotensi untuk mencegah kehilangan data atau pengiriman duplikat setelah restart.
- Mengetahui primitif Tokio mana yang mendokumentasikan cancellation safety dan mana yang memerlukan wrapper.
- Membedakan antara abort, timeout, graceful shutdown, dan pembatalan eksternal.
- Memvalidasi jalur pembatalan dengan model test, fault injection, dan metrik.
Kerangka jawaban 30 detik
“Saya mendefinisikan batas pembatalan terlebih dahulu: men-drop future akan menghentikan polling tugas tersebut tetapi tidak menjamin bahwa operasi jarak jauh telah dibatalkan. Sebuah future dinyatakan aman dari pembatalan (cancellation-safe) hanya jika men-drop-nya setelah await apa pun dan memanggilnya kembali dapat melanjutkan atau mencoba ulang operasi dengan benar. Saya menyimpan byte yang dikonsumsi, ID permintaan, buffer, dan kunci idempotensi dalam state machine yang dimiliki (owned); efek yang tidak dapat dibatalkan akan dipersistensikan atau ditunggu konfirmasinya. Pengujian menyuntikkan pembatalan di setiap titik await.”
Pembahasan mendalam langkah demi langkah
Langkah 1: Tandai titik-titik pembatalan
Tinjau setiap .await dalam fungsi asinkron. Apakah fungsi tersebut telah mengubah status lokal atau jarak jauh, dan field mana yang dapat dipulihkan jika future di-drop di titik tersebut? Perlakukan operasi baca dan tulis jaringan, penantian lock, penerimaan channel, dan sleep sebagai titik pembatalan, alih-alih hanya menguji titik masuk dan keluar.
Langkah 2: Pisahkan pembatalan dari pembatalan aksi (undo)
Membatalkan cabang select! biasanya men-drop future-nya; permintaan yang sudah dikirim ke layanan jarak jauh mungkin tetap berjalan. Handler timeout mencatat ID permintaan dan status hasil yang tidak diketahui (unknown-result) alih-alih langsung menandai kegagalan dan secara membabi buta mengirim ulang operasi yang tidak idempoten. Jika undo didukung, panggil operasi pembatalan protokol dan tunggu konfirmasinya.
Langkah 3: Rancang state machine yang dapat dipulihkan
Representasikan status persiapan (preparation), pengiriman (send), menunggu konfirmasi (await-confirmation), terkomit (committed), dan kompensasi (compensation). Pemilik eksplisit menyimpan status dan buffer; proses mulai ulang (restart) dapat memilih apakah akan melanjutkan pembacaan, mengirim ulang, menanyakan hasil, atau melakukan kompensasi. Untuk protokol streaming, catat batas frame dan offset yang dikonfirmasi agar pesan parsial tidak diinterpretasikan ulang dari tengah.
Langkah 4: Jaga konsistensi antrean dan lock
Membatalkan setelah menerima item channel atau stream tetapi sebelum pemrosesan yang tahan lama (durable) akan menghasilkan item yang terkonsumsi tetapi belum selesai. Gunakan pengakuan transaksional (transactional acknowledgement), repeatable reads, atau lease yang tahan lama. Jangan melakukan pop pada satu-satunya salinan ke dalam variabel sementara di dalam cabang select!. Guard lock melepaskan shared state saat di-drop, sementara proses pemulihan mencatat apakah operasi yang dilindungi telah terkomit.
Langkah 5: Kelola sumber daya dan penghentian tugas
JoinHandle::abort menghentikan tugas tetapi tidak menggantikan pembersihan logika bisnis. Gunakan guard RAII untuk socket, file, file sementara, dan izin semafor (semaphore permits). Pekerjaan yang harus terus berjalan ditempatkan pada tugas terpisah dengan handle yang tetap disimpan. Graceful shutdown berhenti menerima pekerjaan baru, menunggu operasi yang dapat diselesaikan, kemudian memberi sinyal dan mencatat pembatalan untuk sisanya.
Langkah 6: Verifikasi cancellation safety
Suntikkan pembatalan sebelum dan sesudah setiap await, periksa restart, duplikasi, kehilangan data, dan kebocoran sumber daya. Gunakan simulasi I/O terkontrol, model state machine, dan pengujian konkurensi untuk mencakup timeout, penutupan channel, pemutusan koneksi peer, dan abort tugas. Lacak permintaan in-flight, hit kunci duplikat, jumlah kompensasi, latensi pembatalan, dan izin yang bocor; tanpa metrik ini, status “dibatalkan” hanyalah sebuah asumsi.
Trade-off, batasan, dan perolehan informasi
Cancellation safety berarti status bisnis tetap dapat dijelaskan ketika alur kontrol asinkron terinterupsi. Future kecil tanpa efek samping eksternal mudah untuk dimulai ulang; operasi jaringan, database, dan antrean memerlukan idempotensi, konfirmasi, dan kompensasi. Membuat setiap tugas tidak dapat dibatalkan akan menyembunyikan bug sumber daya dan meningkatkan latensi shutdown, jadi lindungi hanya batas atomik yang eksplisit.
Contoh jawaban berkualitas tinggi
“Saya memperlakukan setiap .await sebagai titik pembatalan dan menganalisis efek samping apa yang terjadi sebelumnya serta bagaimana drop dapat dipulihkan. Tokio select! men-drop future yang kalah; ini tidak membatalkan permintaan yang telah dikirim. Oleh karena itu, timeout mempertahankan ID permintaan dan hanya mencoba ulang dengan kunci idempotensi atau setelah menanyakan hasilnya. State machine operasi membedakan status persiapan, pengiriman, menunggu konfirmasi, terkomit, dan kompensasi.
Penerimaan channel, penulisan file, dan commit database memerlukan pengakuan transaksional atau repeatable reads agar item yang terkonsumsi tidak hilang. Guard RAII melepaskan sumber daya, dan pekerjaan yang harus selesai dijalankan dalam tugas terpisah dengan handle yang dipantau. Graceful shutdown menghentikan penerimaan data baru sebelum membatalkan dan menunggu.
Saya menyuntikkan pembatalan pada setiap await, menguji restart, duplikasi, kehilangan data, dan kebocoran, serta memantau pekerjaan in-flight, kunci duplikat, kompensasi, latensi pembatalan, dan izin. Hal tersebut mendemonstrasikan cancellation safety alih-alih sekadar menunjukkan bahwa suatu tugas telah berhenti.”
Kesalahan umum
- Menganggap drop sebagai pembatalan jarak jauh → permintaan mungkin sudah berjalan → simpan ID-nya, lakukan query, atau gunakan API pembatalan eksplisit.
- Mempertimbangkan pembatalan setelah mengonsumsi pesan → satu-satunya salinan bisa hilang → gunakan pengakuan (acknowledgement), lease, atau repeatable reads.
- Mengirim ulang secara membabi buta setelah timeout → operasi tulis dapat menimbulkan efek duplikat → gunakan kunci idempotensi atau lakukan query status terlebih dahulu.
- Hanya menguji titik masuk fungsi → race condition terjadi di antara await → suntikkan pembatalan di setiap await.
- Menggunakan abort alih-alih pembersihan → file, lock, dan izin dapat bocor → gunakan guard dan protokol shutdown.
- Membuat setiap tugas tidak dapat dibatalkan → graceful shutdown dapat menunggu tanpa batas waktu → lindungi hanya batas atomik yang sebenarnya.
Pertanyaan lanjutan dan jawaban
Bagaimana Anda mengetahui apakah sebuah primitif Tokio aman dari pembatalan (cancellation-safe)?
Periksa dokumentasinya untuk memastikan adanya jaminan eksplisit bahwa membatalkan di dalam select! dan memanggilnya kembali tidak akan menghilangkan data. Tanpa jaminan tersebut, asumsikan bahwa progres parsial dapat hilang, simpan status, dan tulis pengujian pemulihan.
Bagaimana Anda mengetahui apakah penulisan database selesai setelah timeout?
Gunakan ID permintaan klien dan constraint unik untuk menanyakan hasilnya, atau sediakan status operasi yang dapat di-query. Timeout klien saja bukan izin untuk mengulang operasi penulisan yang tidak idempoten.
Bisakah Anda menghapus sumber daya segera setelah JoinHandle::abort?
Hanya setelah tugas benar-benar berhenti dan proses drop sumber dayanya selesai. Tunggu hasil join atau biarkan guard pemilik melakukan pembersihan; memanggil abort bukanlah penyelesaian yang sinkron.
Kapan pekerjaan harus dipindahkan ke tugas terpisah?
Pindahkan pekerjaan yang harus diselesaikan, memerlukan percobaan ulang lintas-permintaan, atau tidak dapat di-rollback pada batas pembatalan saat ini ke antrean yang tahan lama atau tugas independen. Pantau pekerjaan tersebut dengan handle, penyimpanan status, dan kunci idempotensi; jangan gunakan detached task hanya untuk menghindari seluruh semantik pembatalan.