Skip to main content

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}