TL;DR
Tokio không cho phép lồng runtime. Gọi block_on() từ bên trong một hàm async đang chạy trên các thread của Tokio sẽ gây panic ngay lập tức. Cách sửa phù hợp tùy thuộc vào ai sở hữu đoạn code có vấn đề:
- Bạn sở hữu code async? Thay
block_on(future)bằngfuture.await. Xong. - Đang gọi một thư viện sync có block_on bên trong? Chuyển lời gọi vào
tokio::task::spawn_blockinghoặctokio::task::block_in_place. - Cần cô lập runtime hoàn toàn? Tạo một OS thread thực sự và cấp cho nó
Runtime::new()riêng.
Điều Gì Đã Thực Sự Xảy Ra
Các thread trong thread pool của Tokio thuộc sở hữu của runtime. Gọi Handle::block_on() hoặc Runtime::block_on() từ một trong những thread đó và Tokio sẽ panic có chủ đích — chặn một thread đang poll future sẽ khiến executor bị deadlock ngay lập tức.
Ba tình huống gây ra lỗi này nhiều hơn bất cứ điều gì khác:
- Một thư viện bên thứ ba gọi
block_onbên trong. SDK wrapper bao quanh các async HTTP client thường là thủ phạm. - Một helper sync xây dựng
Runtimecục bộ và gọiblock_on, sau đó bạn gọi helper đó từ bên trong mộtasync fn. - Bạn đặt
#[tokio::main]trênmain(), rồi cũng thửtokio::runtime::Builder::new_current_thread().block_on()ở đâu đó trong phần thân hàm.
// Panic — block_on bên trong ngữ cảnh #[tokio::main]
#[tokio::main]
async fn main() {
let rt = tokio::runtime::Runtime::new().unwrap();
let result = rt.block_on(some_async_fn()); // PANIC tại đây
println!("{:?}", result);
}
Cách Sửa 1: Dùng .await (Lựa Chọn Mặc Định Đúng Đắn)
Bạn kiểm soát code? Bỏ hoàn toàn block_on. Để .await làm nhiệm vụ của nó.
// Trước (bị panic)
fn fetch_data() -> String {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async_fetch()) // panic bên trong ngữ cảnh async
}
// Sau (đúng)
async fn fetch_data() -> String {
async_fetch().await
}
Khi đó nơi gọi sẽ trở thành:
#[tokio::main]
async fn main() {
let data = fetch_data().await;
println!("{}", data);
}
Đây hầu như luôn là câu trả lời đúng. Chỉ dùng các cách sửa khác khi một thư viện bắt buộc bạn phải làm vậy.
Cách Sửa 2: spawn_blocking cho Công Việc Sync hoặc Tốn CPU
Có một hàm sync buộc phải block — một SDK cũ, một tính toán nặng CPU, I/O file đồng bộ? Chuyển nó sang thread pool blocking chuyên dụng của Tokio bằng spawn_blocking. Những thread này được quản lý tách biệt khỏi async executor. Không panic, không deadlock.
#[tokio::main]
async fn main() {
let result = tokio::task::spawn_blocking(|| {
// An toàn để block tại đây — chạy trên một blocking thread chuyên dụng
legacy_sync_client::fetch()
})
.await
.unwrap();
println!("{:?}", result);
}
Tokio giới hạn blocking thread ở mức 512 theo mặc định. Con số đó khá rộng rãi cho các lần gọi thỉnh thoảng. Nếu bạn spawn hàng trăm cái dưới tải liên tục, hãy tinh chỉnh giới hạn bằng Builder::max_blocking_threads() trước khi nó ảnh hưởng đến môi trường production.
Cách Sửa 3: block_in_place cho Block Nội Tuyến
Thay vì chuyển sang thread khác, block_in_place di chuyển các task khác ra khỏi thread hiện tại để bạn có thể block ngay tại chỗ. Dễ kết nối hơn spawn_blocking — nhưng chỉ hoạt động trên runtime multi-thread.
#[tokio::main] // mặc định = multi_thread
async fn main() {
let result = tokio::task::block_in_place(|| {
// Lời gọi blocking nội tuyến — runtime di chuyển các task khác ra khỏi thread này
some_blocking_sdk::run()
});
println!("{:?}", result);
}
Đang chạy trên current_thread? Dùng spawn_blocking thay thế. block_in_place sẽ panic trên runtime single-thread vì không có thread nào khác để di chuyển task sang.
Cách Sửa 4: OS Thread Riêng Biệt với Runtime Của Chính Nó
Một số thư viện gọi block_on nội bộ và bạn thực sự không thể thay đổi chúng. Cách giải quyết: đừng chạy chúng trên thread của Tokio. Spawn một OS thread thực sự — hoàn toàn bên ngoài executor — và xây dựng một runtime mới ở đó.
#[tokio::main]
async fn main() {
// Một OS thread thực sự, không phải Tokio task
let handle = std::thread::spawn(|| {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
// An toàn — runtime mới trên thread mà Tokio không sở hữu
some_library_with_internal_block_on()
})
});
let result = handle.join().unwrap();
println!("{:?}", result);
}
Hơi cồng kềnh? Một chút. Nhưng khi một dependency hard-code block_on và bạn không thể chạm vào nó, đây là cách thoát đáng tin cậy nhất.
Chẩn Đoán Cách Sửa Bạn Cần
Kiểm tra stack trace của panic. Nó chỉ thẳng vào thủ phạm:
thread 'tokio-runtime-worker' panicked at 'Cannot start a runtime from within a runtime.
...
stack backtrace:
0: tokio::runtime::scheduler::block_on
1: some_sdk::client::Client::send <-- thủ phạm
2: my_crate::handler::process
3: tokio::runtime::task::harness
Code thư viện ở frame 1? Bạn cần Cách Sửa 2 hoặc 4. Code của bạn ở frame 1? Cách Sửa 1 hoặc 3.
Đang viết một thư viện phải xử lý cả caller sync lẫn async? Dùng Handle::try_current() để phát hiện tình huống lúc runtime:
pub fn do_work() {
match tokio::runtime::Handle::try_current() {
Ok(_handle) => {
// Đang ở trong Tokio — caller phải dùng do_work_async().await thay thế
panic!("do_work() không thể gọi từ ngữ cảnh async; hãy dùng do_work_async().await");
}
Err(_) => {
// Không có runtime — an toàn để tạo mới
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(internal_async_work());
}
}
}
Xác Nhận Đã Sửa Xong
Chạy binary và xác nhận panic đã biến mất:
cargo run 2>&1 | grep -i "cannot start a runtime"
# Không có output = đã sửa xong
Thêm integration test để phát hiện regression sớm:
#[tokio::test]
async fn test_no_nested_runtime_panic() {
// Không được panic
let result = fetch_data().await;
assert!(!result.is_empty());
}
Dùng spawn_blocking dưới tải thực tế? Hãy theo dõi số lượng thread. Giới hạn mặc định 512 thread nghe có vẻ rộng rãi, nhưng các lời gọi blocking liên tục tích lũy nhanh. Kiểm tra mức sử dụng với Builder::max_blocking_threads() trước khi bạn chạm trần trong môi trường production.
Tham Khảo Nhanh
.await— lựa chọn đầu tiên khi bạn sở hữu codespawn_blocking— code sync trên blocking thread; hoạt động trên cả runtime multi-thread lẫn current_threadblock_in_place— block nội tuyến; chỉ dành cho runtime multi-threadstd::thread::spawn+ Runtime mới — khi dependency bắt buộc dùngblock_onvà không thể sửa đổi

