Skip to main content

adze_concurrency_env_contract_core/
lib.rs

1//! Shared contracts for concurrency environment configuration.
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::env;
11
12pub use adze_concurrency_env_vars_core::{
13    DEFAULT_RAYON_NUM_THREADS, DEFAULT_TOKIO_WORKER_THREADS, RAYON_NUM_THREADS_ENV,
14    TOKIO_WORKER_THREADS_ENV,
15};
16pub use adze_concurrency_parse_core::parse_positive_usize_or_default;
17
18/// Snapshot of active concurrency cap values.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct ConcurrencyCaps {
21    /// Rayon global thread-pool thread count.
22    pub rayon_threads: usize,
23    /// Tokio worker thread count.
24    pub tokio_worker_threads: usize,
25}
26
27impl ConcurrencyCaps {
28    /// Read concurrency caps from process environment with stable defaults.
29    #[must_use]
30    pub fn from_env() -> Self {
31        Self::from_lookup(|name| env::var(name).ok())
32    }
33
34    /// Build caps from a lookup function returning optional raw environment values.
35    ///
36    /// This supports deterministic testing without mutating process-wide environment
37    /// state.
38    #[must_use]
39    pub fn from_lookup<F>(mut lookup: F) -> Self
40    where
41        F: FnMut(&str) -> Option<String>,
42    {
43        Self {
44            rayon_threads: parse_positive_usize_or_default(
45                lookup(RAYON_NUM_THREADS_ENV).as_deref(),
46                DEFAULT_RAYON_NUM_THREADS,
47            ),
48            tokio_worker_threads: parse_positive_usize_or_default(
49                lookup(TOKIO_WORKER_THREADS_ENV).as_deref(),
50                DEFAULT_TOKIO_WORKER_THREADS,
51            ),
52        }
53    }
54}
55
56impl Default for ConcurrencyCaps {
57    fn default() -> Self {
58        Self {
59            rayon_threads: DEFAULT_RAYON_NUM_THREADS,
60            tokio_worker_threads: DEFAULT_TOKIO_WORKER_THREADS,
61        }
62    }
63}
64
65/// Return the current caps resolved from environment values.
66#[must_use]
67pub fn current_caps() -> ConcurrencyCaps {
68    ConcurrencyCaps::from_env()
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn default_caps_use_expected_constants() {
77        let caps = ConcurrencyCaps::default();
78        assert_eq!(caps.rayon_threads, DEFAULT_RAYON_NUM_THREADS);
79        assert_eq!(caps.tokio_worker_threads, DEFAULT_TOKIO_WORKER_THREADS);
80    }
81
82    #[test]
83    fn from_lookup_returns_defaults_when_none() {
84        let caps = ConcurrencyCaps::from_lookup(|_| None);
85        assert_eq!(caps, ConcurrencyCaps::default());
86    }
87
88    #[test]
89    fn from_lookup_parses_valid_values() {
90        let caps = ConcurrencyCaps::from_lookup(|name| match name {
91            "RAYON_NUM_THREADS" => Some("16".to_string()),
92            "TOKIO_WORKER_THREADS" => Some("8".to_string()),
93            _ => None,
94        });
95        assert_eq!(caps.rayon_threads, 16);
96        assert_eq!(caps.tokio_worker_threads, 8);
97    }
98
99    #[test]
100    fn from_lookup_falls_back_on_zero() {
101        let caps = ConcurrencyCaps::from_lookup(|_| Some("0".to_string()));
102        assert_eq!(caps, ConcurrencyCaps::default());
103    }
104
105    #[test]
106    fn from_lookup_falls_back_on_invalid() {
107        let caps = ConcurrencyCaps::from_lookup(|_| Some("not_a_number".to_string()));
108        assert_eq!(caps, ConcurrencyCaps::default());
109    }
110
111    #[test]
112    fn clone_and_eq() {
113        let caps = ConcurrencyCaps::from_lookup(|_| Some("3".to_string()));
114        let cloned = caps;
115        assert_eq!(caps, cloned);
116    }
117
118    #[test]
119    fn debug_format_is_readable() {
120        let caps = ConcurrencyCaps::default();
121        let dbg = format!("{caps:?}");
122        assert!(dbg.contains("rayon_threads"));
123        assert!(dbg.contains("tokio_worker_threads"));
124    }
125
126    #[test]
127    fn env_var_constants_match_expected_names() {
128        assert_eq!(RAYON_NUM_THREADS_ENV, "RAYON_NUM_THREADS");
129        assert_eq!(TOKIO_WORKER_THREADS_ENV, "TOKIO_WORKER_THREADS");
130    }
131}