Skip to content

rust.blocking_in_async

Performance High Causes Production Outages

Detects blocking operations like std::fs, std::thread::sleep, and synchronous network calls inside async functions.

Async runtimes like Tokio use a limited number of worker threads:

  • Thread starvation — Blocking one thread blocks all tasks on it
  • Performance collapse — Async benefits disappear
  • Deadlocks — Can deadlock if all workers are blocked
  • Hidden latency — Other tasks wait unexpectedly

The async executor can’t run other tasks while a thread is blocked.

// ❌ Before
async fn read_config() -> String {
std::fs::read_to_string("config.toml").unwrap() // Blocks!
}
async fn slow_operation() {
std::thread::sleep(Duration::from_secs(5)); // Blocks!
}
// ✅ After
async fn read_config() -> String {
tokio::fs::read_to_string("config.toml").await.unwrap()
}
async fn slow_operation() {
tokio::time::sleep(Duration::from_secs(5)).await;
}
  • std::fs::* in async functions
  • std::thread::sleep in async context
  • Synchronous HTTP clients (reqwest blocking)
  • std::net::* in async functions
  • Blocking mutex locks (std::sync::Mutex)

Unfault can convert blocking calls to their async equivalents when Tokio or async-std equivalents are available.

BlockingAsync
std::fs::read_to_stringtokio::fs::read_to_string
std::fs::writetokio::fs::write
std::thread::sleeptokio::time::sleep
std::sync::Mutextokio::sync::Mutex
std::net::TcpStreamtokio::net::TcpStream
// Use spawn_blocking for CPU-heavy work
let result = tokio::task::spawn_blocking(move || {
expensive_computation(data)
}).await?;
// Or use a dedicated thread pool
let pool = rayon::ThreadPoolBuilder::new()
.num_threads(4)
.build()?;
let result = pool.install(|| parallel_work(data));