| //! 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 | |
| } | |
| } |