| //! An opaque façade around platform-specific `QoS` APIs. |
| |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] |
| // Please maintain order from least to most priority for the derived `Ord` impl. |
| pub enum ThreadIntent { |
| /// Any thread which does work that isn't in the critical path of the user typing |
| /// (e.g. processing Go To Definition). |
| Worker, |
| |
| /// Any thread which does work caused by the user typing |
| /// (e.g. processing syntax highlighting). |
| LatencySensitive, |
| } |
| |
| impl ThreadIntent { |
| // These APIs must remain private; |
| // we only want consumers to set thread intent |
| // either during thread creation or using our pool impl. |
| |
| pub(super) fn apply_to_current_thread(self) { |
| let class = thread_intent_to_qos_class(self); |
| set_current_thread_qos_class(class); |
| } |
| |
| pub(super) fn assert_is_used_on_current_thread(self) { |
| if IS_QOS_AVAILABLE { |
| let class = thread_intent_to_qos_class(self); |
| assert_eq!(get_current_thread_qos_class(), Some(class)); |
| } |
| } |
| } |
| |
| use imp::QoSClass; |
| |
| const IS_QOS_AVAILABLE: bool = imp::IS_QOS_AVAILABLE; |
| |
| #[expect(clippy::semicolon_if_nothing_returned, reason = "thin wrapper")] |
| fn set_current_thread_qos_class(class: QoSClass) { |
| imp::set_current_thread_qos_class(class) |
| } |
| |
| fn get_current_thread_qos_class() -> Option<QoSClass> { |
| imp::get_current_thread_qos_class() |
| } |
| |
| fn thread_intent_to_qos_class(intent: ThreadIntent) -> QoSClass { |
| imp::thread_intent_to_qos_class(intent) |
| } |
| |
| // All Apple platforms use XNU as their kernel |
| // and thus have the concept of QoS. |
| #[cfg(target_vendor = "apple")] |
| mod imp { |
| use super::ThreadIntent; |
| |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] |
| // Please maintain order from least to most priority for the derived `Ord` impl. |
| pub(super) enum QoSClass { |
| // Documentation adapted from https://github.com/apple-oss-distributions/libpthread/blob/67e155c94093be9a204b69637d198eceff2c7c46/include/sys/qos.h#L55 |
| // |
| /// TLDR: invisible maintenance tasks |
| /// |
| /// Contract: |
| /// |
| /// * **You do not care about how long it takes for work to finish.** |
| /// * **You do not care about work being deferred temporarily.** |
| /// (e.g. if the device's battery is in a critical state) |
| /// |
| /// Examples: |
| /// |
| /// * in a video editor: |
| /// creating periodic backups of project files |
| /// * in a browser: |
| /// cleaning up cached sites which have not been accessed in a long time |
| /// * in a collaborative word processor: |
| /// creating a searchable index of all documents |
| /// |
| /// Use this QoS class for background tasks |
| /// which the user did not initiate themselves |
| /// and which are invisible to the user. |
| /// It is expected that this work will take significant time to complete: |
| /// minutes or even hours. |
| /// |
| /// This QoS class provides the most energy and thermally-efficient execution possible. |
| /// All other work is prioritized over background tasks. |
| Background, |
| |
| /// TLDR: tasks that don't block using your app |
| /// |
| /// Contract: |
| /// |
| /// * **Your app remains useful even as the task is executing.** |
| /// |
| /// Examples: |
| /// |
| /// * in a video editor: |
| /// exporting a video to disk – |
| /// the user can still work on the timeline |
| /// * in a browser: |
| /// automatically extracting a downloaded zip file – |
| /// the user can still switch tabs |
| /// * in a collaborative word processor: |
| /// downloading images embedded in a document – |
| /// the user can still make edits |
| /// |
| /// Use this QoS class for tasks which |
| /// may or may not be initiated by the user, |
| /// but whose result is visible. |
| /// It is expected that this work will take a few seconds to a few minutes. |
| /// Typically your app will include a progress bar |
| /// for tasks using this class. |
| /// |
| /// This QoS class provides a balance between |
| /// performance, responsiveness, and efficiency. |
| Utility, |
| |
| /// TLDR: tasks that block using your app |
| /// |
| /// Contract: |
| /// |
| /// * **You need this work to complete |
| /// before the user can keep interacting with your app.** |
| /// * **Your work will not take more than a few seconds to complete.** |
| /// |
| /// Examples: |
| /// |
| /// * in a video editor: |
| /// opening a saved project |
| /// * in a browser: |
| /// loading a list of the user's bookmarks and top sites |
| /// when a new tab is created |
| /// * in a collaborative word processor: |
| /// running a search on the document's content |
| /// |
| /// Use this QoS class for tasks which were initiated by the user |
| /// and block the usage of your app while they are in progress. |
| /// It is expected that this work will take a few seconds or less to complete; |
| /// not long enough to cause the user to switch to something else. |
| /// Your app will likely indicate progress on these tasks |
| /// through the display of placeholder content or modals. |
| /// |
| /// This QoS class is not energy-efficient. |
| /// Rather, it provides responsiveness |
| /// by prioritizing work above other tasks on the system |
| /// except for critical user-interactive work. |
| UserInitiated, |
| |
| /// TLDR: render loops and nothing else |
| /// |
| /// Contract: |
| /// |
| /// * **You absolutely need this work to complete immediately |
| /// or your app will appear to freeze.** |
| /// * **Your work will always complete virtually instantaneously.** |
| /// |
| /// Examples: |
| /// |
| /// * the main thread in a GUI application |
| /// * the update & render loop in a game |
| /// * a secondary thread which progresses an animation |
| /// |
| /// Use this QoS class for any work which, if delayed, |
| /// will make your user interface unresponsive. |
| /// It is expected that this work will be virtually instantaneous. |
| /// |
| /// This QoS class is not energy-efficient. |
| /// Specifying this class is a request to run with |
| /// nearly all available system CPU and I/O bandwidth even under contention. |
| UserInteractive, |
| } |
| |
| pub(super) const IS_QOS_AVAILABLE: bool = true; |
| |
| pub(super) fn set_current_thread_qos_class(class: QoSClass) { |
| let c = match class { |
| QoSClass::UserInteractive => libc::qos_class_t::QOS_CLASS_USER_INTERACTIVE, |
| QoSClass::UserInitiated => libc::qos_class_t::QOS_CLASS_USER_INITIATED, |
| QoSClass::Utility => libc::qos_class_t::QOS_CLASS_UTILITY, |
| QoSClass::Background => libc::qos_class_t::QOS_CLASS_BACKGROUND, |
| }; |
| |
| let code = unsafe { libc::pthread_set_qos_class_self_np(c, 0) }; |
| |
| if code == 0 { |
| return; |
| } |
| |
| let errno = unsafe { *libc::__error() }; |
| |
| match errno { |
| libc::EPERM => { |
| // This thread has been excluded from the QoS system |
| // due to a previous call to a function such as `pthread_setschedparam` |
| // which is incompatible with QoS. |
| // |
| // Panic instead of returning an error |
| // to maintain the invariant that we only use QoS APIs. |
| panic!("tried to set QoS of thread which has opted out of QoS (os error {errno})") |
| } |
| |
| libc::EINVAL => { |
| // This is returned if we pass something other than a qos_class_t |
| // to `pthread_set_qos_class_self_np`. |
| // |
| // This is impossible, so again panic. |
| unreachable!( |
| "invalid qos_class_t value was passed to pthread_set_qos_class_self_np" |
| ) |
| } |
| |
| _ => { |
| // `pthread_set_qos_class_self_np`'s documentation |
| // does not mention any other errors. |
| unreachable!("`pthread_set_qos_class_self_np` returned unexpected error {errno}") |
| } |
| } |
| } |
| |
| pub(super) fn get_current_thread_qos_class() -> Option<QoSClass> { |
| let current_thread = unsafe { libc::pthread_self() }; |
| let mut qos_class_raw = libc::qos_class_t::QOS_CLASS_UNSPECIFIED; |
| let code = unsafe { |
| libc::pthread_get_qos_class_np(current_thread, &mut qos_class_raw, std::ptr::null_mut()) |
| }; |
| |
| if code != 0 { |
| // `pthread_get_qos_class_np`'s documentation states that |
| // an error value is placed into errno if the return code is not zero. |
| // However, it never states what errors are possible. |
| // Inspecting the source[0] shows that, as of this writing, it always returns zero. |
| // |
| // Whatever errors the function could report in future are likely to be |
| // ones which we cannot handle anyway |
| // |
| // 0: https://github.com/apple-oss-distributions/libpthread/blob/67e155c94093be9a204b69637d198eceff2c7c46/src/qos.c#L171-L177 |
| let errno = unsafe { *libc::__error() }; |
| unreachable!("`pthread_get_qos_class_np` failed unexpectedly (os error {errno})"); |
| } |
| |
| match qos_class_raw { |
| libc::qos_class_t::QOS_CLASS_USER_INTERACTIVE => Some(QoSClass::UserInteractive), |
| libc::qos_class_t::QOS_CLASS_USER_INITIATED => Some(QoSClass::UserInitiated), |
| libc::qos_class_t::QOS_CLASS_DEFAULT => None, // QoS has never been set |
| libc::qos_class_t::QOS_CLASS_UTILITY => Some(QoSClass::Utility), |
| libc::qos_class_t::QOS_CLASS_BACKGROUND => Some(QoSClass::Background), |
| |
| libc::qos_class_t::QOS_CLASS_UNSPECIFIED => { |
| // Using manual scheduling APIs causes threads to “opt out” of QoS. |
| // At this point they become incompatible with QoS, |
| // and as such have the “unspecified” QoS class. |
| // |
| // Panic instead of returning an error |
| // to maintain the invariant that we only use QoS APIs. |
| panic!("tried to get QoS of thread which has opted out of QoS") |
| } |
| } |
| } |
| |
| pub(super) fn thread_intent_to_qos_class(intent: ThreadIntent) -> QoSClass { |
| match intent { |
| ThreadIntent::Worker => QoSClass::Utility, |
| ThreadIntent::LatencySensitive => QoSClass::UserInitiated, |
| } |
| } |
| } |
| |
| // FIXME: Windows has QoS APIs, we should use them! |
| #[cfg(not(target_vendor = "apple"))] |
| mod imp { |
| use super::ThreadIntent; |
| |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] |
| pub(super) enum QoSClass { |
| Default, |
| } |
| |
| pub(super) const IS_QOS_AVAILABLE: bool = false; |
| |
| pub(super) fn set_current_thread_qos_class(_: QoSClass) {} |
| |
| pub(super) fn get_current_thread_qos_class() -> Option<QoSClass> { |
| None |
| } |
| |
| pub(super) fn thread_intent_to_qos_class(_: ThreadIntent) -> QoSClass { |
| QoSClass::Default |
| } |
| } |