stdx/thread/intent.rs
1//! An opaque façade around platform-specific `QoS` APIs.
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
4// Please maintain order from least to most priority for the derived `Ord` impl.
5pub enum ThreadIntent {
6 /// Any thread which does work that isn't in the critical path of the user typing
7 /// (e.g. processing Go To Definition).
8 Worker,
9
10 /// Any thread which does work caused by the user typing
11 /// (e.g. processing syntax highlighting).
12 LatencySensitive,
13}
14
15impl ThreadIntent {
16 // These APIs must remain private;
17 // we only want consumers to set thread intent
18 // either during thread creation or using our pool impl.
19
20 pub(super) fn apply_to_current_thread(self) {
21 let class = thread_intent_to_qos_class(self);
22 set_current_thread_qos_class(class);
23 }
24
25 pub(super) fn assert_is_used_on_current_thread(self) {
26 if IS_QOS_AVAILABLE {
27 let class = thread_intent_to_qos_class(self);
28 assert_eq!(get_current_thread_qos_class(), Some(class));
29 }
30 }
31}
32
33use imp::QoSClass;
34
35const IS_QOS_AVAILABLE: bool = imp::IS_QOS_AVAILABLE;
36
37#[expect(clippy::semicolon_if_nothing_returned, reason = "thin wrapper")]
38fn set_current_thread_qos_class(class: QoSClass) {
39 imp::set_current_thread_qos_class(class)
40}
41
42fn get_current_thread_qos_class() -> Option<QoSClass> {
43 imp::get_current_thread_qos_class()
44}
45
46fn thread_intent_to_qos_class(intent: ThreadIntent) -> QoSClass {
47 imp::thread_intent_to_qos_class(intent)
48}
49
50// All Apple platforms use XNU as their kernel
51// and thus have the concept of QoS.
52#[cfg(target_vendor = "apple")]
53mod imp {
54 use super::ThreadIntent;
55
56 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
57 // Please maintain order from least to most priority for the derived `Ord` impl.
58 pub(super) enum QoSClass {
59 // Documentation adapted from https://github.com/apple-oss-distributions/libpthread/blob/67e155c94093be9a204b69637d198eceff2c7c46/include/sys/qos.h#L55
60 //
61 /// TLDR: invisible maintenance tasks
62 ///
63 /// Contract:
64 ///
65 /// * **You do not care about how long it takes for work to finish.**
66 /// * **You do not care about work being deferred temporarily.**
67 /// (e.g. if the device's battery is in a critical state)
68 ///
69 /// Examples:
70 ///
71 /// * in a video editor:
72 /// creating periodic backups of project files
73 /// * in a browser:
74 /// cleaning up cached sites which have not been accessed in a long time
75 /// * in a collaborative word processor:
76 /// creating a searchable index of all documents
77 ///
78 /// Use this QoS class for background tasks
79 /// which the user did not initiate themselves
80 /// and which are invisible to the user.
81 /// It is expected that this work will take significant time to complete:
82 /// minutes or even hours.
83 ///
84 /// This QoS class provides the most energy and thermally-efficient execution possible.
85 /// All other work is prioritized over background tasks.
86 Background,
87
88 /// TLDR: tasks that don't block using your app
89 ///
90 /// Contract:
91 ///
92 /// * **Your app remains useful even as the task is executing.**
93 ///
94 /// Examples:
95 ///
96 /// * in a video editor:
97 /// exporting a video to disk –
98 /// the user can still work on the timeline
99 /// * in a browser:
100 /// automatically extracting a downloaded zip file –
101 /// the user can still switch tabs
102 /// * in a collaborative word processor:
103 /// downloading images embedded in a document –
104 /// the user can still make edits
105 ///
106 /// Use this QoS class for tasks which
107 /// may or may not be initiated by the user,
108 /// but whose result is visible.
109 /// It is expected that this work will take a few seconds to a few minutes.
110 /// Typically your app will include a progress bar
111 /// for tasks using this class.
112 ///
113 /// This QoS class provides a balance between
114 /// performance, responsiveness, and efficiency.
115 Utility,
116
117 /// TLDR: tasks that block using your app
118 ///
119 /// Contract:
120 ///
121 /// * **You need this work to complete
122 /// before the user can keep interacting with your app.**
123 /// * **Your work will not take more than a few seconds to complete.**
124 ///
125 /// Examples:
126 ///
127 /// * in a video editor:
128 /// opening a saved project
129 /// * in a browser:
130 /// loading a list of the user's bookmarks and top sites
131 /// when a new tab is created
132 /// * in a collaborative word processor:
133 /// running a search on the document's content
134 ///
135 /// Use this QoS class for tasks which were initiated by the user
136 /// and block the usage of your app while they are in progress.
137 /// It is expected that this work will take a few seconds or less to complete;
138 /// not long enough to cause the user to switch to something else.
139 /// Your app will likely indicate progress on these tasks
140 /// through the display of placeholder content or modals.
141 ///
142 /// This QoS class is not energy-efficient.
143 /// Rather, it provides responsiveness
144 /// by prioritizing work above other tasks on the system
145 /// except for critical user-interactive work.
146 UserInitiated,
147
148 /// TLDR: render loops and nothing else
149 ///
150 /// Contract:
151 ///
152 /// * **You absolutely need this work to complete immediately
153 /// or your app will appear to freeze.**
154 /// * **Your work will always complete virtually instantaneously.**
155 ///
156 /// Examples:
157 ///
158 /// * the main thread in a GUI application
159 /// * the update & render loop in a game
160 /// * a secondary thread which progresses an animation
161 ///
162 /// Use this QoS class for any work which, if delayed,
163 /// will make your user interface unresponsive.
164 /// It is expected that this work will be virtually instantaneous.
165 ///
166 /// This QoS class is not energy-efficient.
167 /// Specifying this class is a request to run with
168 /// nearly all available system CPU and I/O bandwidth even under contention.
169 UserInteractive,
170 }
171
172 pub(super) const IS_QOS_AVAILABLE: bool = true;
173
174 pub(super) fn set_current_thread_qos_class(class: QoSClass) {
175 let c = match class {
176 QoSClass::UserInteractive => libc::qos_class_t::QOS_CLASS_USER_INTERACTIVE,
177 QoSClass::UserInitiated => libc::qos_class_t::QOS_CLASS_USER_INITIATED,
178 QoSClass::Utility => libc::qos_class_t::QOS_CLASS_UTILITY,
179 QoSClass::Background => libc::qos_class_t::QOS_CLASS_BACKGROUND,
180 };
181
182 let code = unsafe { libc::pthread_set_qos_class_self_np(c, 0) };
183
184 if code == 0 {
185 return;
186 }
187
188 let errno = unsafe { *libc::__error() };
189
190 match errno {
191 libc::EPERM => {
192 // This thread has been excluded from the QoS system
193 // due to a previous call to a function such as `pthread_setschedparam`
194 // which is incompatible with QoS.
195 //
196 // Panic instead of returning an error
197 // to maintain the invariant that we only use QoS APIs.
198 panic!("tried to set QoS of thread which has opted out of QoS (os error {errno})")
199 }
200
201 libc::EINVAL => {
202 // This is returned if we pass something other than a qos_class_t
203 // to `pthread_set_qos_class_self_np`.
204 //
205 // This is impossible, so again panic.
206 unreachable!(
207 "invalid qos_class_t value was passed to pthread_set_qos_class_self_np"
208 )
209 }
210
211 _ => {
212 // `pthread_set_qos_class_self_np`'s documentation
213 // does not mention any other errors.
214 unreachable!("`pthread_set_qos_class_self_np` returned unexpected error {errno}")
215 }
216 }
217 }
218
219 pub(super) fn get_current_thread_qos_class() -> Option<QoSClass> {
220 let current_thread = unsafe { libc::pthread_self() };
221 let mut qos_class_raw = libc::qos_class_t::QOS_CLASS_UNSPECIFIED;
222 let code = unsafe {
223 libc::pthread_get_qos_class_np(current_thread, &mut qos_class_raw, std::ptr::null_mut())
224 };
225
226 if code != 0 {
227 // `pthread_get_qos_class_np`'s documentation states that
228 // an error value is placed into errno if the return code is not zero.
229 // However, it never states what errors are possible.
230 // Inspecting the source[0] shows that, as of this writing, it always returns zero.
231 //
232 // Whatever errors the function could report in future are likely to be
233 // ones which we cannot handle anyway
234 //
235 // 0: https://github.com/apple-oss-distributions/libpthread/blob/67e155c94093be9a204b69637d198eceff2c7c46/src/qos.c#L171-L177
236 let errno = unsafe { *libc::__error() };
237 unreachable!("`pthread_get_qos_class_np` failed unexpectedly (os error {errno})");
238 }
239
240 match qos_class_raw {
241 libc::qos_class_t::QOS_CLASS_USER_INTERACTIVE => Some(QoSClass::UserInteractive),
242 libc::qos_class_t::QOS_CLASS_USER_INITIATED => Some(QoSClass::UserInitiated),
243 libc::qos_class_t::QOS_CLASS_DEFAULT => None, // QoS has never been set
244 libc::qos_class_t::QOS_CLASS_UTILITY => Some(QoSClass::Utility),
245 libc::qos_class_t::QOS_CLASS_BACKGROUND => Some(QoSClass::Background),
246
247 libc::qos_class_t::QOS_CLASS_UNSPECIFIED => {
248 // Using manual scheduling APIs causes threads to “opt out” of QoS.
249 // At this point they become incompatible with QoS,
250 // and as such have the “unspecified” QoS class.
251 //
252 // Panic instead of returning an error
253 // to maintain the invariant that we only use QoS APIs.
254 panic!("tried to get QoS of thread which has opted out of QoS")
255 }
256 }
257 }
258
259 pub(super) fn thread_intent_to_qos_class(intent: ThreadIntent) -> QoSClass {
260 match intent {
261 ThreadIntent::Worker => QoSClass::Utility,
262 ThreadIntent::LatencySensitive => QoSClass::UserInitiated,
263 }
264 }
265}
266
267// FIXME: Windows has QoS APIs, we should use them!
268#[cfg(not(target_vendor = "apple"))]
269mod imp {
270 use super::ThreadIntent;
271
272 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
273 pub(super) enum QoSClass {
274 Default,
275 }
276
277 pub(super) const IS_QOS_AVAILABLE: bool = false;
278
279 pub(super) fn set_current_thread_qos_class(_: QoSClass) {}
280
281 pub(super) fn get_current_thread_qos_class() -> Option<QoSClass> {
282 None
283 }
284
285 pub(super) fn thread_intent_to_qos_class(_: ThreadIntent) -> QoSClass {
286 QoSClass::Default
287 }
288}