Skip to main content

adze_concurrency_init_rayon_core/
lib.rs

1//! Idempotent Rayon global thread-pool initialization primitives.
2
3#![forbid(unsafe_op_in_unsafe_fn)]
4#![deny(missing_docs)]
5#![cfg_attr(feature = "strict_api", deny(unreachable_pub))]
6#![cfg_attr(not(feature = "strict_api"), warn(unreachable_pub))]
7#![cfg_attr(feature = "strict_docs", deny(missing_docs))]
8#![cfg_attr(not(feature = "strict_docs"), allow(missing_docs))]
9
10use std::sync::OnceLock;
11
12pub use adze_concurrency_init_classifier_core::is_already_initialized_error;
13
14/// Initialize Rayon global thread-pool once for the current process.
15///
16/// The first invocation determines the process-wide result; all subsequent
17/// calls return that same result.
18///
19/// A `num_threads` value of `0` is normalized to `1`.
20pub fn init_rayon_global_once(num_threads: usize) -> Result<(), String> {
21    RAYON_INIT_RESULT
22        .get_or_init(|| init_rayon_global(num_threads.max(1)))
23        .clone()
24}
25
26static RAYON_INIT_RESULT: OnceLock<Result<(), String>> = OnceLock::new();
27
28fn init_rayon_global(num_threads: usize) -> Result<(), String> {
29    match rayon::ThreadPoolBuilder::new()
30        .num_threads(num_threads)
31        .build_global()
32    {
33        Ok(()) => Ok(()),
34        Err(error) => {
35            let message = error.to_string();
36            if is_already_initialized_error(&message) {
37                Ok(())
38            } else {
39                Err(message)
40            }
41        }
42    }
43}
44
45#[cfg(test)]
46mod tests {
47    use super::{init_rayon_global_once, is_already_initialized_error};
48
49    #[test]
50    fn init_is_idempotent() {
51        assert!(init_rayon_global_once(1).is_ok());
52        assert!(init_rayon_global_once(8).is_ok());
53    }
54
55    #[test]
56    fn already_initialized_error_classifier_is_case_insensitive() {
57        assert!(is_already_initialized_error(
58            "The GlObAl thread pool has AlReAdY been initialized"
59        ));
60    }
61
62    #[test]
63    fn already_initialized_error_classifier_requires_both_tokens() {
64        assert!(!is_already_initialized_error(
65            "thread pool already initialized"
66        ));
67        assert!(!is_already_initialized_error(
68            "global thread pool initialized"
69        ));
70    }
71
72    #[test]
73    fn init_with_zero_threads_normalizes_and_succeeds() {
74        assert!(init_rayon_global_once(0).is_ok());
75    }
76
77    #[test]
78    fn init_result_is_stable_across_different_thread_counts() {
79        let first = init_rayon_global_once(2);
80        let second = init_rayon_global_once(64);
81        assert_eq!(first, second);
82    }
83
84    #[test]
85    fn init_with_one_thread_succeeds() {
86        assert!(init_rayon_global_once(1).is_ok());
87    }
88
89    #[test]
90    fn init_with_large_thread_count_succeeds() {
91        assert!(init_rayon_global_once(1024).is_ok());
92    }
93}