Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/stdx/src/thread.rs')
| -rw-r--r-- | crates/stdx/src/thread.rs | 264 |
1 files changed, 20 insertions, 244 deletions
diff --git a/crates/stdx/src/thread.rs b/crates/stdx/src/thread.rs index 5042f00143..e577eb4313 100644 --- a/crates/stdx/src/thread.rs +++ b/crates/stdx/src/thread.rs @@ -1,36 +1,46 @@ //! A utility module for working with threads that automatically joins threads upon drop -//! and provides functionality for interfacing with operating system quality of service (QoS) APIs. +//! and abstracts over operating system quality of service (QoS) APIs +//! through the concept of a “thread intent”. +//! +//! The intent of a thread is frozen at thread creation time, +//! i.e. there is no API to change the intent of a thread once it has been spawned. //! //! As a system, rust-analyzer should have the property that //! old manual scheduling APIs are replaced entirely by QoS. //! To maintain this invariant, we panic when it is clear that //! old scheduling APIs have been used. //! -//! Moreover, we also want to ensure that every thread has a QoS set explicitly +//! Moreover, we also want to ensure that every thread has an intent set explicitly //! to force a decision about its importance to the system. -//! Thus, [`QoSClass`] has no default value -//! and every entry point to creating a thread requires a [`QoSClass`] upfront. +//! Thus, [`ThreadIntent`] has no default value +//! and every entry point to creating a thread requires a [`ThreadIntent`] upfront. use std::fmt; -pub fn spawn<F, T>(qos_class: QoSClass, f: F) -> JoinHandle<T> +mod intent; +mod pool; + +pub use intent::ThreadIntent; +pub use pool::Pool; + +pub fn spawn<F, T>(intent: ThreadIntent, f: F) -> JoinHandle<T> where F: FnOnce() -> T, F: Send + 'static, T: Send + 'static, { - Builder::new(qos_class).spawn(f).expect("failed to spawn thread") + Builder::new(intent).spawn(f).expect("failed to spawn thread") } pub struct Builder { - qos_class: QoSClass, + intent: ThreadIntent, inner: jod_thread::Builder, allow_leak: bool, } impl Builder { - pub fn new(qos_class: QoSClass) -> Builder { - Builder { qos_class, inner: jod_thread::Builder::new(), allow_leak: false } + pub fn new(intent: ThreadIntent) -> Builder { + Builder { intent, inner: jod_thread::Builder::new(), allow_leak: false } } pub fn name(self, name: String) -> Builder { @@ -52,7 +62,7 @@ impl Builder { T: Send + 'static, { let inner_handle = self.inner.spawn(move || { - set_current_thread_qos_class(self.qos_class); + self.intent.apply_to_current_thread(); f() })?; @@ -90,237 +100,3 @@ impl<T> fmt::Debug for JoinHandle<T> { f.pad("JoinHandle { .. }") } } - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -// Please maintain order from least to most priority for the derived `Ord` impl. -pub 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 const IS_QOS_AVAILABLE: bool = imp::IS_QOS_AVAILABLE; - -pub fn set_current_thread_qos_class(class: QoSClass) { - imp::set_current_thread_qos_class(class) -} - -pub fn get_current_thread_qos_class() -> Option<QoSClass> { - imp::get_current_thread_qos_class() -} - -// All Apple platforms use XNU as their kernel -// and thus have the concept of QoS. -#[cfg(target_vendor = "apple")] -mod imp { - use super::QoSClass; - - 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") - } - } - } -} - -// FIXME: Windows has QoS APIs, we should use them! -#[cfg(not(target_vendor = "apple"))] -mod imp { - use super::QoSClass; - - 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 - } -} |