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}