Skip to main content

zerodds_rt_linux/
affinity.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! CPU affinity API. Platform routing by `target_os`.
5//!
6//! On Linux this module delegates to the internal `syscalls::pin_to_cpus`
7//! / `syscalls::get_cpus`. On other targets every
8//! function returns `Unsupported`.
9
10use std::io;
11
12/// Pins the **calling thread** to the given CPU indices.
13///
14/// The order in the slice is irrelevant — the kernel uses a
15/// bit-mask. Duplicate indices are deduplicated silently.
16///
17/// # Errors
18/// * `InvalidInput` if `cpus.is_empty()` or an index is `>=
19///   CPU_SETSIZE` (Linux: 1024).
20/// * Kernel errno from `sched_setaffinity(2)` — typically `EINVAL`
21///   if none of the CPUs is online.
22/// * `Unsupported` on non-Linux targets.
23///
24/// # Example
25/// ```no_run
26/// use zerodds_rt_linux::pin_current_thread_to_cpus;
27/// pin_current_thread_to_cpus(&[2, 3]).unwrap();
28/// ```
29pub fn pin_current_thread_to_cpus(cpus: &[usize]) -> io::Result<()> {
30    #[cfg(target_os = "linux")]
31    {
32        crate::syscalls::pin_to_cpus(cpus)
33    }
34    #[cfg(not(target_os = "linux"))]
35    {
36        let _ = cpus;
37        Err(io::Error::new(
38            io::ErrorKind::Unsupported,
39            "pin_current_thread_to_cpus requires Linux",
40        ))
41    }
42}
43
44/// Returns the CPUs on which the calling thread is allowed to run.
45///
46/// # Errors
47/// Kernel errno from `sched_getaffinity(2)`.
48/// `Unsupported` on non-Linux targets.
49pub fn current_thread_cpus() -> io::Result<std::vec::Vec<usize>> {
50    #[cfg(target_os = "linux")]
51    {
52        crate::syscalls::get_cpus()
53    }
54    #[cfg(not(target_os = "linux"))]
55    {
56        Err(io::Error::new(
57            io::ErrorKind::Unsupported,
58            "current_thread_cpus() requires Linux",
59        ))
60    }
61}
62
63#[cfg(test)]
64#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
65mod tests {
66    use super::*;
67
68    #[test]
69    #[cfg(not(target_os = "linux"))]
70    fn pin_returns_unsupported_off_linux() {
71        let err = pin_current_thread_to_cpus(&[0]).unwrap_err();
72        assert_eq!(err.kind(), io::ErrorKind::Unsupported);
73    }
74
75    #[test]
76    #[cfg(not(target_os = "linux"))]
77    fn cpus_returns_unsupported_off_linux() {
78        let err = current_thread_cpus().unwrap_err();
79        assert_eq!(err.kind(), io::ErrorKind::Unsupported);
80    }
81
82    #[test]
83    #[cfg(target_os = "linux")]
84    fn linux_round_trip_pin_then_read() {
85        let allowed = current_thread_cpus().expect("getaffinity");
86        let target = allowed[0];
87        pin_current_thread_to_cpus(&[target]).expect("setaffinity");
88        let after = current_thread_cpus().expect("getaffinity post");
89        assert_eq!(after, std::vec![target]);
90        // Restore.
91        pin_current_thread_to_cpus(&allowed).expect("restore");
92    }
93
94    #[test]
95    #[cfg(target_os = "linux")]
96    fn linux_empty_input_invalid() {
97        let err = pin_current_thread_to_cpus(&[]).unwrap_err();
98        assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
99    }
100}