loom_rs/lib.rs
1//! # loom-rs
2//!
3//! **Weaving multiple threads together**
4//!
5//! A bespoke thread pool runtime combining tokio and rayon with CPU pinning capabilities.
6//!
7//! ## Features
8//!
9//! - **Hybrid Runtime**: Combines tokio for async I/O with rayon for CPU-bound parallel work
10//! - **CPU Pinning**: Automatically pins threads to specific CPUs for consistent performance
11//! - **Zero Allocation**: `spawn_compute()` uses per-type pools for zero allocation after warmup
12//! - **Flexible Configuration**: Configure via files (TOML/YAML/JSON), environment variables, or code
13//! - **CLI Integration**: Built-in clap support for command-line overrides
14//! - **CUDA NUMA Awareness**: Optional feature for selecting CPUs local to a GPU (Linux only)
15//!
16//! ## Quick Start
17//!
18//! ```ignore
19//! use loom_rs::LoomBuilder;
20//!
21//! fn main() -> Result<(), Box<dyn std::error::Error>> {
22//! let runtime = LoomBuilder::new()
23//! .prefix("myapp")
24//! .tokio_threads(2)
25//! .rayon_threads(6)
26//! .build()?;
27//!
28//! runtime.block_on(async {
29//! // Spawn tracked async I/O task
30//! let io_handle = runtime.spawn_async(async {
31//! // Async I/O work
32//! 42
33//! });
34//!
35//! // Spawn tracked compute task and await result (zero alloc after warmup)
36//! let result = runtime.spawn_compute(|| {
37//! // CPU-bound work on rayon
38//! (0..1000000).sum::<i64>()
39//! }).await;
40//! println!("Compute result: {}", result);
41//!
42//! // Zero-overhead parallel iterators
43//! let _sum = runtime.install(|| {
44//! use rayon::prelude::*;
45//! (0..1000).into_par_iter().sum::<i64>()
46//! });
47//!
48//! // Wait for async task
49//! let io_result = io_handle.await.unwrap();
50//! println!("I/O result: {}", io_result);
51//! });
52//!
53//! // Graceful shutdown
54//! runtime.block_until_idle();
55//!
56//! Ok(())
57//! }
58//! ```
59//!
60//! ## Ergonomic Access
61//!
62//! Use `current_runtime()` or `spawn_compute()` from anywhere in the runtime:
63//!
64//! ```ignore
65//! use loom_rs::LoomBuilder;
66//!
67//! let runtime = LoomBuilder::new().build()?;
68//!
69//! runtime.block_on(async {
70//! // No need to pass &runtime around
71//! let result = loom_rs::spawn_compute(|| expensive_work()).await;
72//!
73//! // Or get the runtime explicitly
74//! let rt = loom_rs::current_runtime().unwrap();
75//! rt.spawn_async(async { /* ... */ });
76//! });
77//! ```
78//!
79//! ## Configuration
80//!
81//! Configuration sources are merged in order (later sources override earlier):
82//!
83//! 1. Default values
84//! 2. Config files (via `.file()`)
85//! 3. Environment variables (via `.env_prefix()`)
86//! 4. Programmatic overrides
87//! 5. CLI arguments (via `.with_cli_args()`)
88//!
89//! ### Config File Example (TOML)
90//!
91//! ```toml
92//! prefix = "myapp"
93//! cpuset = "0-7,16-23"
94//! tokio_threads = 2
95//! rayon_threads = 14
96//! compute_pool_size = 64
97//! ```
98//!
99//! ### Environment Variables
100//!
101//! With `.env_prefix("LOOM")`:
102//! - `LOOM_PREFIX=myapp`
103//! - `LOOM_CPUSET=0-7`
104//! - `LOOM_TOKIO_THREADS=2`
105//! - `LOOM_RAYON_THREADS=6`
106//!
107//! ### CLI Arguments
108//!
109//! ```ignore
110//! use clap::Parser;
111//! use loom_rs::{LoomBuilder, LoomArgs};
112//!
113//! #[derive(Parser)]
114//! struct MyArgs {
115//! #[command(flatten)]
116//! loom: LoomArgs,
117//! }
118//!
119//! let args = MyArgs::parse();
120//! let runtime = LoomBuilder::new()
121//! .file("config.toml")
122//! .env_prefix("LOOM")
123//! .with_cli_args(&args.loom)
124//! .build()?;
125//! ```
126//!
127//! ## CPU Set Format
128//!
129//! The `cpuset` option accepts a string in Linux taskset/numactl format:
130//! - Single CPUs: `"0"`, `"5"`
131//! - Ranges: `"0-7"`, `"16-23"`
132//! - Mixed: `"0-3,8-11"`, `"0,2,4,6-8"`
133//!
134//! ## CUDA Support
135//!
136//! With the `cuda` feature enabled (Linux only), you can configure the runtime
137//! to use CPUs local to a specific CUDA GPU:
138//!
139//! ```ignore
140//! let runtime = LoomBuilder::new()
141//! .cuda_device_id(0) // Use CPUs near GPU 0
142//! .build()?;
143//! ```
144//!
145//! ## Thread Naming
146//!
147//! Threads are named with the configured prefix:
148//! - Tokio threads: `{prefix}-tokio-0000`, `{prefix}-tokio-0001`, ...
149//! - Rayon threads: `{prefix}-rayon-0000`, `{prefix}-rayon-0001`, ...
150
151pub(crate) mod affinity;
152pub(crate) mod bridge;
153pub mod builder;
154pub mod config;
155pub(crate) mod context;
156pub mod cpuset;
157pub mod error;
158pub(crate) mod pool;
159pub mod runtime;
160pub mod stream;
161
162#[cfg(feature = "cuda")]
163pub mod cuda;
164
165pub use builder::{LoomArgs, LoomBuilder};
166pub use config::LoomConfig;
167pub use context::current_runtime;
168pub use error::{LoomError, Result};
169pub use runtime::{LoomRuntime, LoomRuntimeInner};
170pub use stream::ComputeStreamExt;
171
172/// Spawn compute work using the current runtime.
173///
174/// This is a convenience function for `loom_rs::current_runtime().unwrap().spawn_compute(f)`.
175/// It allows spawning compute work from anywhere within a loom runtime without
176/// explicitly passing the runtime reference.
177///
178/// # Panics
179///
180/// Panics if called outside a loom runtime context (i.e., not within `block_on`,
181/// a tokio worker thread, or a rayon worker thread managed by the runtime).
182///
183/// # Performance
184///
185/// Same as `LoomRuntime::spawn_compute()`:
186/// - 0 bytes allocation after warmup (pool hit)
187/// - ~100-500ns overhead
188///
189/// # Example
190///
191/// ```ignore
192/// use loom_rs::LoomBuilder;
193///
194/// let runtime = LoomBuilder::new().build()?;
195///
196/// runtime.block_on(async {
197/// // No need to pass &runtime around
198/// let result = loom_rs::spawn_compute(|| {
199/// expensive_work()
200/// }).await;
201/// });
202/// ```
203pub async fn spawn_compute<F, R>(f: F) -> R
204where
205 F: FnOnce() -> R + Send + 'static,
206 R: Send + 'static,
207{
208 current_runtime()
209 .expect("spawn_compute called outside loom runtime")
210 .spawn_compute(f)
211 .await
212}
213
214/// Try to spawn compute work using the current runtime.
215///
216/// Like `spawn_compute()`, but returns `None` if not in a runtime context
217/// instead of panicking.
218///
219/// # Example
220///
221/// ```ignore
222/// if let Some(future) = loom_rs::try_spawn_compute(|| work()) {
223/// let result = future.await;
224/// }
225/// ```
226pub fn try_spawn_compute<F, R>(f: F) -> Option<impl std::future::Future<Output = R>>
227where
228 F: FnOnce() -> R + Send + 'static,
229 R: Send + 'static,
230{
231 current_runtime().map(|rt| {
232 let rt = rt;
233 async move { rt.spawn_compute(f).await }
234 })
235}