Panic muncul ketika upload streaming file diterima Axum; log menunjukkan panic karena borrow overlap pada state async yang dibagikan. Artikel ini menjelaskan gejala, root cause, serta langkah debugging dan perbaikan agar upload tetap aman dan stabil.
1. Gejala Panic Dalam Streaming Upload
Ketika client mengirim file besar lewat multipart atau chunked upload, server Axum menerima data dengan Body streaming. Panic tidak terjadi saat upload awal, tetapi muncul setelah beberapa chunk masuk, disertai stack trace seperti berikut:
thread 'tokio-runtime-worker' panicked at 'already borrowed: BorrowError', /workspace/.cargo/registry/src/.../parking_lot/src/raw.rs:123:21
stack backtrace:
0: std::panicking::begin_panic
1: parking_lot_core::mutex::Mutex::lock
2: my_service::handlers::upload::handle_stream
3: axum::handler::Handler::call
...
Log juga menunjukkan request hampir selesai tapi handler tiba-tiba drop, lalu future yang memegang Arc ditimpa borrow kedua saat chunk terakhir ditangani.
2. Kenali Root Cause: Borrow Overlap pada Async State
Axum bekerja di atas Tokio, sehingga setiap request berjalan di future yang dapat ditunggangi (polled) beberapa kali. Panic terjadi karena handler memegang state.lock().await saat membaca stream, lalu memanggil fungsi async yang kembali mencoba meminjam state secara mutable lagi.
Contoh ringkas yang mencerminkan pola bermasalah:
async fn handle_stream(state: Arc>, mut body: Body) {
let mut guard = state.lock().await;
while let Some(chunk) = body.data().await {
process_payload(&mut guard, chunk?).await; // meminjam lagi ketika guard masih aktif
}
}
Masalahnya bukan hanya borrow token, tapi fakta bahwa guard tidak dilepas sebelum masuk ke await lain. Ketika process_payload membutuhkan state kembali, Mutex masih terkunci, memicu panic borrow error.
3. Strategi Perbaikan dan Penguatan Handler
Solusi utama adalah menghindari lock jangka panjang selama operasi async yang memerlukan borrow ulang. Pendekatan yang efektif:
- Rancang ulang state agar data yang diperlukan dipindahkan ke buffer sementara atau disalin sebelum
awaitpanjang. - Gunakan channel atau aggregator untuk menyalurkan chunk, sehingga mutex hanya dibuka sebentar untuk menyimpan metadata.
- Tambahkan context cancellation dengan timeout/token agar future abort jika client drop.
Contoh perbaikan minimal:
async fn handle_stream(state: Arc>, mut body: Body) {
let mut buffer = Vec::new();
while let Some(chunk) = body.data().await {
buffer.extend_from_slice(&chunk?);
flush_buffer_if_needed(&mut buffer, state.clone()).await;
}
}
async fn flush_buffer_if_needed(buffer: &mut Vec, state: Arc>) {
if buffer.len() >= CHUNK_LIMIT {
let data = buffer.split_off(0);
drop(buffer); // pastikan borrow selesai sebelum lock ulang
let mut guard = state.lock().await;
guard.append_data(data);
}
}
Perhatikan bahwa split_off memindahkan data dan drop(buffer) memastikan mutex tidak dikelola dua kali bersamaan.
4. Observability dan Verifikasi
Setelah mengganti implementasi, penting mengecek beberapa aspek:
- Observabilitas: Tambahkan tracing span atau log level debug untuk setiap chunk dan saat state lock diambil. Gunakan
tracinguntuk melihat durasilock().await. - Unit test: Isolasi
flush_buffer_if_neededdengan buffer besar, menguji bahwa mutex tidak dipegang lebih lama dari yang diperlukan. - Integration test: Gunakan
tokio-test::block_onataureqwestuntuk mensimulasikan upload multipart dengan chunk kecil dan observasi log panic.
Pemeriksaan manual dapat dilakukan dengan RUST_LOG=debug dan menambah assert! pada state untuk memastikan tidak ada borrow terduplikasi.
5. Catatan Tambahan
Trade-off: Buffer tambahan menambah memori sementara, namun menghindari panic dan mempermudah backpressure. Jaga agar state hanya berisi metadata kritikal, sementara payload besar disimpan di disk atau storage eksternal.
Kesalahan umum adalah tetap memegang MutexGuard di antara await tanpa menyadari polling ulang future. Di lingkungan async Rust, panggilannya bisa di-pause, lalu polled ulang di thread lain; karena itu release guard sebelum .await panjang sangat krusial.
Dengan pendekatan ini, upload streaming file di Axum berjalan tanpa panic, sekaligus menjaga observabilitas dan kemudahan debugging.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!