6 releases
Uses new Rust 2024
new 0.2.1 | Jul 6, 2025 |
---|---|
0.2.0 | Jul 4, 2025 |
0.1.3 | Jul 3, 2025 |
#8 in Rendering
461 downloads per month
12KB
168 lines
DriveShaft
A minimal, high-performance thread pool for running synchronous tasks with per-thread context from async code.
NOTE: This crate is under heavy development. Expect breaking API changes.
What is DriveShaft?
DriveShaft is a lightweight thread pool designed to bridge the gap between async and sync Rust. It lets you spawn synchronous closures that execute on dedicated worker threads — each thread holding a long-lived, mutable context of your choosing (e.g. a RocksDB instance, a database connection, or some custom state).
This is especially useful when integrating blocking libraries (like RocksDB, SQLite, image processing, etc.) into an async application without blocking the runtime or when you need a simple way to spawn cpu intense operations from your async runtime.
Summary of Benchmarks
Generally, when the async runtime is under heavy load:
Task Type | spawn blocking | driveshaft | direct on runtime |
---|---|---|---|
CPU-heavy | ❌ a few times slower | ✅ Fastest | ⚠️ Poor scaling |
IO-heavy | ✅ Best | ⚠️ Slightly behind | ❌ Extremely slow |
As the load decreases, driveshaft and spawn_blocking tend to even out for CPU-heavy workloads.
As with any benchmarks, you should run them yourself using a workload similar to how you might use driveshaft. You can find some sample code to get you started here. The machine you use to benchmark should have a few more cores available than the number of threads you plan to benchmark with for a meaningful assessment.
Key Features
- Run sync closures from async code with
.run_with(...)
- Custom per-thread context (
&mut T
) — noArc<Mutex<_>>
needed - Work Stealing across threads
- Returns results via async
await
- Foundation for higher-level wrappers (TBD)
Example
use driveshaft::DriveShaftPool;
#[tokio::main]
async fn main() {
// Create 4 contexts — one for each worker
let ctxs: VecDeque<_> = (0..4)
.map(|_| create_context())
.collect();
// Spawn a pool with those contexts
let mut pool = DriveShaftPool::new(ctxs);
// Run a blocking call from async context
let result = pool
.run_with(|ctx| ctx.do_something())
.await;
println!("Value: {:?}", result);
}
Use Cases
Wrapping blocking libraries like:
-
RocksDB
-
SQLite
-
LMDB
-
Image/audio/video codecs
Maintaining one connection/context per thread
Avoiding lock contention from shared global state
Offloading CPU-heavy tasks while preserving &mut access
How It Works
Each thread owns its own T context.
Tasks are FnOnce(&mut T) closures.
Results are sent back by implementing Future.
A DriveShaftPool distributes jobs across threads using crossbeam::deque
.
Crate Roadmap
-
Per-thread context ownership
-
run_with async API
-
Bounded / unbounded channel modes
-
Graceful shutdown
-
Retry / backpressure handling
-
Metrics + tracing
-
Runtime-agnostic executor trait
Contributing
Feedback and contributions are welcome.
License
MIT or Apache-2.0 — your choice.
Dependencies
~2–11MB
~100K SLoC