Skip to main content

photon_ring/
affinity.rs

1// Copyright 2026 Photon Ring Contributors
2// SPDX-License-Identifier: Apache-2.0
3
4//! CPU core affinity helpers for deterministic cross-core latency.
5//!
6//! Pinning publisher and subscriber threads to specific CPU cores
7//! eliminates OS scheduler jitter and ensures consistent cache-coherence
8//! transfer times.
9//!
10//! ## NUMA considerations
11//!
12//! On multi-socket systems, pin publisher and subscriber threads to
13//! cores on the **same** socket. Cross-socket communication (QPI/UPI)
14//! adds ~100-200 ns of additional latency per cache-line transfer
15//! compared to intra-socket L3 snoops (~40-55 ns on Intel Comet Lake).
16//!
17//! ## Example
18//!
19//! ```no_run
20//! use photon_ring::affinity;
21//!
22//! let cores = affinity::available_cores();
23//! assert!(cores.len() >= 2, "need at least 2 cores");
24//!
25//! // Pin to the first core
26//! assert!(affinity::pin_to_core(cores[0]));
27//! ```
28
29use alloc::vec::Vec;
30
31pub use core_affinity2::CoreId;
32
33/// Pin the current thread to a specific CPU core.
34///
35/// Returns `true` on success, `false` if the OS rejected the request
36/// (e.g., invalid core ID, insufficient permissions, or unsupported
37/// platform).
38///
39/// # Example
40///
41/// ```no_run
42/// use photon_ring::affinity;
43///
44/// let cores = affinity::available_cores();
45/// assert!(affinity::pin_to_core(cores[0]), "failed to pin");
46/// ```
47#[inline]
48pub fn pin_to_core(core_id: CoreId) -> bool {
49    core_id.set_affinity().is_ok()
50}
51
52/// Return the list of CPU cores available to this process.
53///
54/// The returned [`CoreId`]s can be passed directly to [`pin_to_core`].
55/// The list order matches the OS core numbering (logical CPUs including
56/// SMT siblings).
57pub fn available_cores() -> Vec<CoreId> {
58    core_affinity2::get_core_ids().unwrap_or_default()
59}
60
61/// Pin the current thread to a core by its numeric index.
62///
63/// Convenience wrapper around [`pin_to_core`] that looks up the core by
64/// index in [`available_cores`]. Returns `true` on success, `false` if
65/// the index is out of range or the OS rejects the request.
66///
67/// # Example
68///
69/// ```no_run
70/// use photon_ring::affinity;
71///
72/// assert!(affinity::pin_to_core_id(0), "failed to pin to core 0");
73/// ```
74pub fn pin_to_core_id(index: usize) -> bool {
75    let cores = available_cores();
76    match cores.get(index) {
77        Some(&core_id) => pin_to_core(core_id),
78        None => false,
79    }
80}
81
82/// Return the number of CPU cores available to this process.
83pub fn core_count() -> usize {
84    available_cores().len()
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn available_cores_is_nonempty() {
93        let cores = available_cores();
94        assert!(!cores.is_empty(), "expected at least one core");
95    }
96
97    #[test]
98    fn core_count_matches_available() {
99        assert_eq!(core_count(), available_cores().len());
100    }
101
102    #[test]
103    fn pin_to_first_core() {
104        let cores = available_cores();
105        assert!(pin_to_core(cores[0]), "failed to pin to first core");
106    }
107
108    #[test]
109    fn pin_to_core_id_valid() {
110        assert!(pin_to_core_id(0), "failed to pin to core index 0");
111    }
112
113    #[test]
114    fn pin_to_core_id_out_of_range() {
115        assert!(!pin_to_core_id(usize::MAX), "should fail for invalid index");
116    }
117}