/build/source/nativelink-worker/src/qos.rs
Line | Count | Source |
1 | | // Copyright 2024 The NativeLink Authors. All rights reserved. |
2 | | // |
3 | | // Licensed under the Functional Source License, Version 1.1, Apache 2.0 Future License (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // See LICENSE file for details |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | |
15 | | //! Darwin `QoS` (Quality of Service) helpers for worker scheduling. |
16 | | //! |
17 | | //! Apple Silicon (M-series) CPUs have a heterogeneous topology with |
18 | | //! performance ("P") and efficiency ("E") cores. XNU's scheduler routes |
19 | | //! threads to P or E cores in part based on the thread's `QoS` class. The |
20 | | //! default class assigned to long-running background daemons is typically |
21 | | //! `UTILITY` or `BACKGROUND`, both of which the scheduler may park on |
22 | | //! E-cores. |
23 | | //! |
24 | | //! Single-thread-bursty workloads such as `swift-frontend` and `clang` |
25 | | //! invocations (typical in iOS RBE builds) can run 2x–3x slower when |
26 | | //! pinned to an E-core. Tagging the worker process with |
27 | | //! `QOS_CLASS_USER_INITIATED` tells the scheduler to treat its threads |
28 | | //! as foreground-equivalent and bias placement toward P-cores. |
29 | | //! |
30 | | //! On Linux and Windows these helpers compile away to nothing — they are |
31 | | //! intentionally not behind a runtime branch so non-macOS builds never |
32 | | //! emit a call. |
33 | | |
34 | | /// Sets the calling thread's `QoS` class to `USER_INITIATED` on macOS. |
35 | | /// |
36 | | /// Returns `true` if the underlying `pthread_set_qos_class_self_np` |
37 | | /// call succeeded; returns `false` if it failed. |
38 | | /// |
39 | | /// Safe to call from any thread, including tokio runtime worker threads |
40 | | /// via `Builder::on_thread_start`. |
41 | | #[cfg(target_os = "macos")] |
42 | | #[inline] |
43 | | pub fn set_user_initiated() -> bool { |
44 | | // SAFETY: `pthread_set_qos_class_self_np` is a thread-local |
45 | | // setter with no preconditions on the caller; passing a valid |
46 | | // enum variant and relative priority 0 is always defined. |
47 | | let ret = unsafe { |
48 | | libc::pthread_set_qos_class_self_np(libc::qos_class_t::QOS_CLASS_USER_INITIATED, 0) |
49 | | }; |
50 | | ret == 0 |
51 | | } |
52 | | |
53 | | /// Compile-time no-op on non-macOS targets. |
54 | | /// |
55 | | /// Always returns `true`. The call site expands to nothing after |
56 | | /// inlining / dead-code elimination, so non-macOS builds never emit |
57 | | /// a runtime branch or a libc call. |
58 | | #[cfg(not(target_os = "macos"))] |
59 | | #[inline] |
60 | 1 | pub const fn set_user_initiated() -> bool { |
61 | 1 | true |
62 | 1 | } |
63 | | |
64 | | #[cfg(all(test, target_os = "macos"))] |
65 | | mod macos_tests { |
66 | | use super::set_user_initiated; |
67 | | |
68 | | /// Reads the current thread's `QoS` class via `pthread_get_qos_class_np`. |
69 | | /// Panics with a contextual message on failure (only called from tests). |
70 | | fn current_qos_class() -> libc::qos_class_t { |
71 | | let mut class: libc::qos_class_t = libc::qos_class_t::QOS_CLASS_UNSPECIFIED; |
72 | | let mut rel_prio: libc::c_int = 0; |
73 | | // SAFETY: out-pointers point to stack-allocated, properly sized |
74 | | // and aligned storage owned by this thread. |
75 | | let ret = unsafe { |
76 | | libc::pthread_get_qos_class_np( |
77 | | libc::pthread_self(), |
78 | | core::ptr::from_mut(&mut class), |
79 | | core::ptr::from_mut(&mut rel_prio), |
80 | | ) |
81 | | }; |
82 | | assert_eq!(ret, 0, "pthread_get_qos_class_np failed: {ret}"); |
83 | | class |
84 | | } |
85 | | |
86 | | /// Proves the `QoS` call is wired up on macOS and the underlying |
87 | | /// Darwin symbol resolves at link time. A failure here means the |
88 | | /// worker would silently keep running on E-cores. |
89 | | #[test] |
90 | | fn sets_user_initiated_on_current_thread() { |
91 | | assert!( |
92 | | set_user_initiated(), |
93 | | "pthread_set_qos_class_self_np(USER_INITIATED) returned non-zero", |
94 | | ); |
95 | | // `qos_class_t` is a `#[repr(u32)]` C enum that does not derive |
96 | | // `PartialEq` in libc, so compare the underlying discriminants. |
97 | | assert_eq!( |
98 | | current_qos_class() as u32, |
99 | | libc::qos_class_t::QOS_CLASS_USER_INITIATED as u32, |
100 | | "`QoS` class did not update; thread will be eligible for E-core scheduling", |
101 | | ); |
102 | | } |
103 | | |
104 | | /// Validates the load-bearing claim that tokio worker threads created |
105 | | /// with a `Builder::on_thread_start` hook calling `set_user_initiated` |
106 | | /// observe `QOS_CLASS_USER_INITIATED` from inside spawned tasks. This |
107 | | /// mirrors the wiring in `src/bin/nativelink.rs::main`. Without this |
108 | | /// test the entire `QoS` scheme is unverified at the integration level. |
109 | | /// |
110 | | /// This is the one place in the worker crate that must construct a |
111 | | /// fresh `tokio::runtime::Builder::new_multi_thread()` and drive it |
112 | | /// with `block_on` — the unit under test *is* the `on_thread_start` |
113 | | /// hook on a custom-built runtime, which `nativelink-util::task` and |
114 | | /// `#[nativelink_test]` do not expose. The `#[expect]` mirrors the |
115 | | /// same justified escape used in `src/bin/nativelink.rs::main`. |
116 | | #[test] |
117 | | #[expect( |
118 | | clippy::disallowed_methods, |
119 | | reason = "test exercises `Builder::on_thread_start` + `block_on`; \ |
120 | | no util wrapper exposes a custom-built runtime with a thread-start hook" |
121 | | )] |
122 | | fn tokio_worker_threads_inherit_user_initiated_via_on_thread_start() { |
123 | | // Deliberately build a fresh runtime in-test (do not reuse a |
124 | | // global one) so the hook is exercised on freshly-spawned |
125 | | // worker threads with whatever class they were born with. |
126 | | let rt = tokio::runtime::Builder::new_multi_thread() |
127 | | .worker_threads(2) |
128 | | .on_thread_start(|| { |
129 | | assert!(set_user_initiated(), "hook failed in worker thread"); |
130 | | }) |
131 | | .enable_all() |
132 | | .build() |
133 | | .expect("build tokio runtime"); |
134 | | |
135 | | let observed: u32 = rt.block_on(async { |
136 | | // Force execution on a worker thread (not the caller). |
137 | | tokio::spawn(async { current_qos_class() as u32 }) |
138 | | .await |
139 | | .expect("join spawned task") |
140 | | }); |
141 | | |
142 | | assert_eq!( |
143 | | observed, |
144 | | libc::qos_class_t::QOS_CLASS_USER_INITIATED as u32, |
145 | | "tokio worker thread did not inherit USER_INITIATED from on_thread_start", |
146 | | ); |
147 | | } |
148 | | } |
149 | | |
150 | | #[cfg(all(test, not(target_os = "macos")))] |
151 | | mod non_macos_tests { |
152 | | use super::set_user_initiated; |
153 | | |
154 | | /// On Linux/Windows the function must be a true no-op that always |
155 | | /// reports success — there is no runtime cost and no platform call. |
156 | | #[test] |
157 | 1 | fn is_a_noop_on_non_macos() { |
158 | 1 | assert!(set_user_initiated()); |
159 | 1 | } |
160 | | } |
161 | | |
162 | | /// Compile-time assertion: when `target_os` is not `macos`, this module |
163 | | /// must not reference any libc symbol. Reviewers can `grep "extern crate |
164 | | /// libc"` or inspect this constant to verify the no-op story. |
165 | | #[cfg(not(target_os = "macos"))] |
166 | | pub const NON_MACOS_IS_NOOP: () = (); |