Skip to main content

cubecl_environment/stream/
policy.rs

1use crate::sync::{AtomicU8, Ordering};
2
3/// How the current stream is derived when no explicit override is active.
4///
5/// Loaded from the `[streaming]` section of `cubecl.toml` by `cubecl-runtime`,
6/// or set programmatically with [`set_policy`]. An explicit [`set_policy`] call
7/// always wins over the configuration file.
8#[non_exhaustive]
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
10#[serde(rename_all = "kebab-case")]
11pub enum StreamPolicy {
12    /// One stream per OS thread (the historical behavior and the default).
13    ///
14    /// Correct for applications that submit work from plain threads. Under an
15    /// async executor with work stealing, a logical task hopping between
16    /// worker threads changes streams, causing spurious synchronization.
17    #[default]
18    PerThread,
19    /// One stream per logical task.
20    ///
21    /// With the `tokio` feature enabled, a task keeps a stable stream across
22    /// `.await` points even when the executor moves it between worker
23    /// threads. Outside a task, or without the `tokio` feature, this behaves
24    /// like [`PerThread`](StreamPolicy::PerThread).
25    PerTask,
26    /// Everything runs on stream `0`.
27    ///
28    /// The effective behavior on wasm and no-std targets today.
29    Single,
30}
31
32const SET_BY_USER: u8 = 0b1000_0000;
33
34static POLICY: AtomicU8 = AtomicU8::new(0);
35
36fn encode(policy: StreamPolicy) -> u8 {
37    match policy {
38        StreamPolicy::PerThread => 0,
39        StreamPolicy::PerTask => 1,
40        StreamPolicy::Single => 2,
41    }
42}
43
44fn decode(bits: u8) -> StreamPolicy {
45    match bits & !SET_BY_USER {
46        1 => StreamPolicy::PerTask,
47        2 => StreamPolicy::Single,
48        _ => StreamPolicy::PerThread,
49    }
50}
51
52/// Sets the active stream policy.
53///
54/// Takes precedence over any policy loaded from configuration files, no matter
55/// the call order. Should be called before the first kernel submissions:
56/// already-resolved stream ids are not revisited.
57pub fn set_policy(policy: StreamPolicy) {
58    POLICY.store(encode(policy) | SET_BY_USER, Ordering::Relaxed);
59}
60
61/// Returns the active stream policy.
62pub fn policy() -> StreamPolicy {
63    decode(POLICY.load(Ordering::Relaxed))
64}
65
66/// Sets the stream policy from a configuration file.
67///
68/// A no-op if [`set_policy`] was already called: the user's explicit choice
69/// wins over configuration.
70#[doc(hidden)]
71pub fn set_policy_from_config(policy: StreamPolicy) {
72    let _ = POLICY.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
73        if current & SET_BY_USER != 0 {
74            None
75        } else {
76            Some(encode(policy))
77        }
78    });
79}
80
81/// Resets the process-global policy so policy-mutating tests don't leak state.
82///
83/// Gated like `tests_policy_lock`: its only callers are the tests that take
84/// that lock, which need std.
85#[cfg(all(test, feature = "std"))]
86pub(crate) fn tests_reset_policy() {
87    POLICY.store(0, Ordering::Relaxed);
88}
89
90/// Serializes tests that read or mutate the process-global policy.
91#[cfg(all(test, feature = "std"))]
92pub(crate) fn tests_policy_lock() -> std::sync::MutexGuard<'static, ()> {
93    static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
94    LOCK.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
95}
96
97// Gated like `tests_policy_lock`, which these tests use to serialize.
98#[cfg(all(test, feature = "std"))]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn explicit_policy_wins_over_config() {
104        let _guard = tests_policy_lock();
105
106        set_policy_from_config(StreamPolicy::Single);
107        assert_eq!(policy(), StreamPolicy::Single);
108
109        set_policy(StreamPolicy::PerTask);
110        set_policy_from_config(StreamPolicy::Single);
111        assert_eq!(policy(), StreamPolicy::PerTask);
112
113        tests_reset_policy();
114    }
115}