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