Skip to main content

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}