mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-08-04 10:50:15 +00:00
upstream stdx changes
This commit is contained in:
parent
2261e4e892
commit
428ee50540
11 changed files with 131 additions and 97 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -1083,9 +1083,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
|||
|
||||
[[package]]
|
||||
name = "jod-thread"
|
||||
version = "0.1.2"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b23360e99b8717f20aaa4598f5a6541efbe30630039fbc7706cf954a87947ae"
|
||||
checksum = "a037eddb7d28de1d0fc42411f501b53b75838d313908078d6698d064f3029b24"
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
|
|
|
@ -13,7 +13,7 @@ rust-version.workspace = true
|
|||
|
||||
[dependencies]
|
||||
backtrace = { version = "0.3.74", optional = true }
|
||||
jod-thread = "0.1.2"
|
||||
jod-thread = "1.0.0"
|
||||
crossbeam-channel.workspace = true
|
||||
itertools.workspace = true
|
||||
tracing.workspace = true
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
//! This file is a port of only the necessary features from <https://github.com/chris-morgan/anymap> version 1.0.0-beta.2 for use within rust-analyzer.
|
||||
//!
|
||||
//! Copyright © 2014–2022 Chris Morgan.
|
||||
//! COPYING: <https://github.com/chris-morgan/anymap/blob/master/COPYING>
|
||||
//! Note that the license is changed from Blue Oak Model 1.0.0 or MIT or Apache-2.0 to MIT OR Apache-2.0
|
||||
|
@ -20,14 +21,14 @@
|
|||
|
||||
use core::hash::Hasher;
|
||||
|
||||
/// A hasher designed to eke a little more speed out, given `TypeId`’s known characteristics.
|
||||
/// A hasher designed to eke a little more speed out, given `TypeId`'s known characteristics.
|
||||
///
|
||||
/// Specifically, this is a no-op hasher that expects to be fed a u64’s worth of
|
||||
/// Specifically, this is a no-op hasher that expects to be fed a u64's worth of
|
||||
/// randomly-distributed bits. It works well for `TypeId` (eliminating start-up time, so that my
|
||||
/// get_missing benchmark is ~30ns rather than ~900ns, and being a good deal faster after that, so
|
||||
/// that my insert_and_get_on_260_types benchmark is ~12μs instead of ~21.5μs), but will
|
||||
/// `get_missing` benchmark is ~30ns rather than ~900ns, and being a good deal faster after that, so
|
||||
/// that my `insert_and_get_on_260_types` benchmark is ~12μs instead of ~21.5μs), but will
|
||||
/// panic in debug mode and always emit zeros in release mode for any other sorts of inputs, so
|
||||
/// yeah, don’t use it! 😀
|
||||
/// yeah, don't use it! 😀
|
||||
#[derive(Default)]
|
||||
pub struct TypeIdHasher {
|
||||
value: u64,
|
||||
|
@ -36,9 +37,9 @@ pub struct TypeIdHasher {
|
|||
impl Hasher for TypeIdHasher {
|
||||
#[inline]
|
||||
fn write(&mut self, bytes: &[u8]) {
|
||||
// This expects to receive exactly one 64-bit value, and there’s no realistic chance of
|
||||
// that changing, but I don’t want to depend on something that isn’t expressly part of the
|
||||
// contract for safety. But I’m OK with release builds putting everything in one bucket
|
||||
// This expects to receive exactly one 64-bit value, and there's no realistic chance of
|
||||
// that changing, but I don't want to depend on something that isn't expressly part of the
|
||||
// contract for safety. But I'm OK with release builds putting everything in one bucket
|
||||
// if it *did* change (and debug builds panicking).
|
||||
debug_assert_eq!(bytes.len(), 8);
|
||||
let _ = bytes.try_into().map(|array| self.value = u64::from_ne_bytes(array));
|
||||
|
@ -59,7 +60,7 @@ use ::std::collections::hash_map;
|
|||
/// Raw access to the underlying `HashMap`.
|
||||
///
|
||||
/// This alias is provided for convenience because of the ugly third generic parameter.
|
||||
#[allow(clippy::disallowed_types)] // Uses a custom hasher
|
||||
#[expect(clippy::disallowed_types, reason = "Uses a custom hasher")]
|
||||
pub type RawMap<A> = hash_map::HashMap<TypeId, Box<A>, BuildHasherDefault<TypeIdHasher>>;
|
||||
|
||||
/// A collection containing zero or one values for any given type and allowing convenient,
|
||||
|
@ -73,19 +74,20 @@ pub type RawMap<A> = hash_map::HashMap<TypeId, Box<A>, BuildHasherDefault<TypeId
|
|||
///
|
||||
/// Cumulatively, there are thus six forms of map:
|
||||
///
|
||||
/// - <code>[Map]<dyn [core::any::Any]></code>,
|
||||
/// - `[Map]<dyn [core::any::Any]>`,
|
||||
/// also spelled [`AnyMap`] for convenience.
|
||||
/// - <code>[Map]<dyn [core::any::Any] + Send></code>
|
||||
/// - <code>[Map]<dyn [core::any::Any] + Send + Sync></code>
|
||||
/// - `[Map]<dyn [core::any::Any] + Send>`
|
||||
/// - `[Map]<dyn [core::any::Any] + Send + Sync>`
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// (Here using the [`AnyMap`] convenience alias; the first line could use
|
||||
/// <code>[anymap::Map][Map]::<[core::any::Any]>::new()</code> instead if desired.)
|
||||
/// (Here, the [`AnyMap`] convenience alias is used;
|
||||
/// the first line could use `[anymap::Map][Map]::<[core::any::Any]>::new()`
|
||||
/// instead if desired.)
|
||||
///
|
||||
/// ```
|
||||
/// # use stdx::anymap;
|
||||
#[doc = "let mut data = anymap::AnyMap::new();"]
|
||||
/// let mut data = anymap::AnyMap::new();
|
||||
/// assert_eq!(data.get(), None::<&i32>);
|
||||
/// ```
|
||||
///
|
||||
|
@ -95,11 +97,11 @@ pub struct Map<A: ?Sized + Downcast = dyn Any> {
|
|||
raw: RawMap<A>,
|
||||
}
|
||||
|
||||
/// The most common type of `Map`: just using `Any`; <code>[Map]<dyn [Any]></code>.
|
||||
/// The most common type of `Map`: just using `Any`; `[Map]<dyn [Any]>`.
|
||||
///
|
||||
/// Why is this a separate type alias rather than a default value for `Map<A>`?
|
||||
/// `Map::new()` doesn’t seem to be happy to infer that it should go with the default
|
||||
/// value. It’s a bit sad, really. Ah well, I guess this approach will do.
|
||||
/// `Map::new()` doesn't seem to be happy to infer that it should go with the default
|
||||
/// value. It's a bit sad, really. Ah well, I guess this approach will do.
|
||||
pub type AnyMap = Map<dyn Any>;
|
||||
|
||||
impl<A: ?Sized + Downcast> Default for Map<A> {
|
||||
|
@ -113,6 +115,7 @@ impl<A: ?Sized + Downcast> Map<A> {
|
|||
/// Returns a reference to the value stored in the collection for the type `T`,
|
||||
/// if it exists.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn get<T: IntoBox<A>>(&self) -> Option<&T> {
|
||||
self.raw.get(&TypeId::of::<T>()).map(|any| unsafe { any.downcast_ref_unchecked::<T>() })
|
||||
}
|
||||
|
@ -132,30 +135,30 @@ impl<A: ?Sized + Downcast> Map<A> {
|
|||
}
|
||||
|
||||
/// A view into a single occupied location in an `Map`.
|
||||
pub struct OccupiedEntry<'a, A: ?Sized + Downcast, V: 'a> {
|
||||
inner: hash_map::OccupiedEntry<'a, TypeId, Box<A>>,
|
||||
pub struct OccupiedEntry<'map, A: ?Sized + Downcast, V: 'map> {
|
||||
inner: hash_map::OccupiedEntry<'map, TypeId, Box<A>>,
|
||||
type_: PhantomData<V>,
|
||||
}
|
||||
|
||||
/// A view into a single empty location in an `Map`.
|
||||
pub struct VacantEntry<'a, A: ?Sized + Downcast, V: 'a> {
|
||||
inner: hash_map::VacantEntry<'a, TypeId, Box<A>>,
|
||||
pub struct VacantEntry<'map, A: ?Sized + Downcast, V: 'map> {
|
||||
inner: hash_map::VacantEntry<'map, TypeId, Box<A>>,
|
||||
type_: PhantomData<V>,
|
||||
}
|
||||
|
||||
/// A view into a single location in an `Map`, which may be vacant or occupied.
|
||||
pub enum Entry<'a, A: ?Sized + Downcast, V> {
|
||||
pub enum Entry<'map, A: ?Sized + Downcast, V> {
|
||||
/// An occupied Entry
|
||||
Occupied(OccupiedEntry<'a, A, V>),
|
||||
Occupied(OccupiedEntry<'map, A, V>),
|
||||
/// A vacant Entry
|
||||
Vacant(VacantEntry<'a, A, V>),
|
||||
Vacant(VacantEntry<'map, A, V>),
|
||||
}
|
||||
|
||||
impl<'a, A: ?Sized + Downcast, V: IntoBox<A>> Entry<'a, A, V> {
|
||||
impl<'map, A: ?Sized + Downcast, V: IntoBox<A>> Entry<'map, A, V> {
|
||||
/// Ensures a value is in the entry by inserting the result of the default function if
|
||||
/// empty, and returns a mutable reference to the value in the entry.
|
||||
#[inline]
|
||||
pub fn or_insert_with<F: FnOnce() -> V>(self, default: F) -> &'a mut V {
|
||||
pub fn or_insert_with<F: FnOnce() -> V>(self, default: F) -> &'map mut V {
|
||||
match self {
|
||||
Entry::Occupied(inner) => inner.into_mut(),
|
||||
Entry::Vacant(inner) => inner.insert(default()),
|
||||
|
@ -163,20 +166,21 @@ impl<'a, A: ?Sized + Downcast, V: IntoBox<A>> Entry<'a, A, V> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, A: ?Sized + Downcast, V: IntoBox<A>> OccupiedEntry<'a, A, V> {
|
||||
/// Converts the OccupiedEntry into a mutable reference to the value in the entry
|
||||
impl<'map, A: ?Sized + Downcast, V: IntoBox<A>> OccupiedEntry<'map, A, V> {
|
||||
/// Converts the `OccupiedEntry` into a mutable reference to the value in the entry
|
||||
/// with a lifetime bound to the collection itself
|
||||
#[inline]
|
||||
pub fn into_mut(self) -> &'a mut V {
|
||||
#[must_use]
|
||||
pub fn into_mut(self) -> &'map mut V {
|
||||
unsafe { self.inner.into_mut().downcast_mut_unchecked() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A: ?Sized + Downcast, V: IntoBox<A>> VacantEntry<'a, A, V> {
|
||||
/// Sets the value of the entry with the VacantEntry's key,
|
||||
impl<'map, A: ?Sized + Downcast, V: IntoBox<A>> VacantEntry<'map, A, V> {
|
||||
/// Sets the value of the entry with the `VacantEntry`'s key,
|
||||
/// and returns a mutable reference to it
|
||||
#[inline]
|
||||
pub fn insert(self, value: V) -> &'a mut V {
|
||||
pub fn insert(self, value: V) -> &'map mut V {
|
||||
unsafe { self.inner.insert(value.into_box()).downcast_mut_unchecked() }
|
||||
}
|
||||
}
|
||||
|
@ -201,14 +205,13 @@ mod tests {
|
|||
#[test]
|
||||
fn type_id_hasher() {
|
||||
use core::any::TypeId;
|
||||
use core::hash::Hash;
|
||||
use core::hash::Hash as _;
|
||||
fn verify_hashing_with(type_id: TypeId) {
|
||||
let mut hasher = TypeIdHasher::default();
|
||||
type_id.hash(&mut hasher);
|
||||
// SAFETY: u64 is valid for all bit patterns.
|
||||
let _ = hasher.finish();
|
||||
_ = hasher.finish();
|
||||
}
|
||||
// Pick a variety of types, just to demonstrate it’s all sane. Normal, zero-sized, unsized, &c.
|
||||
// Pick a variety of types, just to demonstrate it's all sane. Normal, zero-sized, unsized, &c.
|
||||
verify_hashing_with(TypeId::of::<usize>());
|
||||
verify_hashing_with(TypeId::of::<()>());
|
||||
verify_hashing_with(TypeId::of::<str>());
|
||||
|
@ -220,34 +223,34 @@ mod tests {
|
|||
/// Methods for downcasting from an `Any`-like trait object.
|
||||
///
|
||||
/// This should only be implemented on trait objects for subtraits of `Any`, though you can
|
||||
/// implement it for other types and it’ll work fine, so long as your implementation is correct.
|
||||
/// implement it for other types and it'll work fine, so long as your implementation is correct.
|
||||
pub trait Downcast {
|
||||
/// Gets the `TypeId` of `self`.
|
||||
fn type_id(&self) -> TypeId;
|
||||
|
||||
// Note the bound through these downcast methods is 'static, rather than the inexpressible
|
||||
// concept of Self-but-as-a-trait (where Self is `dyn Trait`). This is sufficient, exceeding
|
||||
// TypeId’s requirements. Sure, you *can* do CloneAny.downcast_unchecked::<NotClone>() and the
|
||||
// type system won’t protect you, but that doesn’t introduce any unsafety: the method is
|
||||
// TypeId's requirements. Sure, you *can* do CloneAny.downcast_unchecked::<NotClone>() and the
|
||||
// type system won't protect you, but that doesn't introduce any unsafety: the method is
|
||||
// already unsafe because you can specify the wrong type, and if this were exposing safe
|
||||
// downcasting, CloneAny.downcast::<NotClone>() would just return an error, which is just as
|
||||
// correct.
|
||||
//
|
||||
// Now in theory we could also add T: ?Sized, but that doesn’t play nicely with the common
|
||||
// implementation, so I’m doing without it.
|
||||
// Now in theory we could also add T: ?Sized, but that doesn't play nicely with the common
|
||||
// implementation, so I'm doing without it.
|
||||
|
||||
/// Downcast from `&Any` to `&T`, without checking the type matches.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that `T` matches the trait object, on pain of *undefined behaviour*.
|
||||
/// The caller must ensure that `T` matches the trait object, on pain of *undefined behavior*.
|
||||
unsafe fn downcast_ref_unchecked<T: 'static>(&self) -> &T;
|
||||
|
||||
/// Downcast from `&mut Any` to `&mut T`, without checking the type matches.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that `T` matches the trait object, on pain of *undefined behaviour*.
|
||||
/// The caller must ensure that `T` matches the trait object, on pain of *undefined behavior*.
|
||||
unsafe fn downcast_mut_unchecked<T: 'static>(&mut self) -> &mut T;
|
||||
}
|
||||
|
||||
|
@ -267,12 +270,12 @@ macro_rules! implement {
|
|||
|
||||
#[inline]
|
||||
unsafe fn downcast_ref_unchecked<T: 'static>(&self) -> &T {
|
||||
unsafe { &*(self as *const Self as *const T) }
|
||||
unsafe { &*std::ptr::from_ref::<Self>(self).cast::<T>() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn downcast_mut_unchecked<T: 'static>(&mut self) -> &mut T {
|
||||
unsafe { &mut *(self as *mut Self as *mut T) }
|
||||
unsafe { &mut *std::ptr::from_mut::<Self>(self).cast::<T>() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ pub mod thread;
|
|||
pub use itertools;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_ci() -> bool {
|
||||
pub const fn is_ci() -> bool {
|
||||
option_env!("CI").is_some()
|
||||
}
|
||||
|
||||
|
@ -26,14 +26,14 @@ pub fn hash_once<Hasher: std::hash::Hasher + Default>(thing: impl std::hash::Has
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
#[allow(clippy::print_stderr)]
|
||||
#[expect(clippy::print_stderr, reason = "only visible to developers")]
|
||||
pub fn timeit(label: &'static str) -> impl Drop {
|
||||
let start = Instant::now();
|
||||
defer(move || eprintln!("{}: {:.2?}", label, start.elapsed()))
|
||||
defer(move || eprintln!("{}: {:.2}", label, start.elapsed().as_nanos()))
|
||||
}
|
||||
|
||||
/// Prints backtrace to stderr, useful for debugging.
|
||||
#[allow(clippy::print_stderr)]
|
||||
#[expect(clippy::print_stderr, reason = "only visible to developers")]
|
||||
pub fn print_backtrace() {
|
||||
#[cfg(feature = "backtrace")]
|
||||
eprintln!("{:?}", backtrace::Backtrace::new());
|
||||
|
@ -126,6 +126,7 @@ where
|
|||
}
|
||||
|
||||
// Taken from rustc.
|
||||
#[must_use]
|
||||
pub fn to_camel_case(ident: &str) -> String {
|
||||
ident
|
||||
.trim_matches('_')
|
||||
|
@ -156,7 +157,7 @@ pub fn to_camel_case(ident: &str) -> String {
|
|||
|
||||
camel_cased_component
|
||||
})
|
||||
.fold((String::new(), None), |(acc, prev): (_, Option<String>), next| {
|
||||
.fold((String::new(), None), |(mut acc, prev): (_, Option<String>), next| {
|
||||
// separate two components with an underscore if their boundary cannot
|
||||
// be distinguished using an uppercase/lowercase case distinction
|
||||
let join = prev
|
||||
|
@ -166,16 +167,20 @@ pub fn to_camel_case(ident: &str) -> String {
|
|||
Some(!char_has_case(l) && !char_has_case(f))
|
||||
})
|
||||
.unwrap_or(false);
|
||||
(acc + if join { "_" } else { "" } + &next, Some(next))
|
||||
acc.push_str(if join { "_" } else { "" });
|
||||
acc.push_str(&next);
|
||||
(acc, Some(next))
|
||||
})
|
||||
.0
|
||||
}
|
||||
|
||||
// Taken from rustc.
|
||||
pub fn char_has_case(c: char) -> bool {
|
||||
#[must_use]
|
||||
pub const fn char_has_case(c: char) -> bool {
|
||||
c.is_lowercase() || c.is_uppercase()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_upper_snake_case(s: &str) -> bool {
|
||||
s.chars().all(|c| c.is_uppercase() || c == '_' || c.is_numeric())
|
||||
}
|
||||
|
@ -188,6 +193,7 @@ pub fn replace(buf: &mut String, from: char, to: &str) {
|
|||
*buf = buf.replace(from, to);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn trim_indent(mut text: &str) -> String {
|
||||
if text.starts_with('\n') {
|
||||
text = &text[1..];
|
||||
|
@ -249,8 +255,8 @@ impl ops::DerefMut for JodChild {
|
|||
|
||||
impl Drop for JodChild {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.0.kill();
|
||||
let _ = self.0.wait();
|
||||
_ = self.0.kill();
|
||||
_ = self.0.wait();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -259,12 +265,11 @@ impl JodChild {
|
|||
command.spawn().map(Self)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn into_inner(self) -> std::process::Child {
|
||||
if cfg!(target_arch = "wasm32") {
|
||||
panic!("no processes on wasm");
|
||||
}
|
||||
// SAFETY: repr transparent, except on WASM
|
||||
unsafe { std::mem::transmute::<JodChild, std::process::Child>(self) }
|
||||
unsafe { std::mem::transmute::<Self, std::process::Child>(self) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@ pub struct NonEmptyVec<T> {
|
|||
|
||||
impl<T> NonEmptyVec<T> {
|
||||
#[inline]
|
||||
pub fn new(first: T) -> Self {
|
||||
NonEmptyVec { first, rest: Vec::new() }
|
||||
pub const fn new(first: T) -> Self {
|
||||
Self { first, rest: Vec::new() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -24,7 +24,7 @@ impl<T> NonEmptyVec<T> {
|
|||
|
||||
#[inline]
|
||||
pub fn push(&mut self, value: T) {
|
||||
self.rest.push(value)
|
||||
self.rest.push(value);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -16,7 +16,7 @@ impl Drop for PanicContext {
|
|||
}
|
||||
|
||||
pub fn enter(frame: String) -> PanicContext {
|
||||
#[allow(clippy::print_stderr)]
|
||||
#[expect(clippy::print_stderr, reason = "already panicking anyway")]
|
||||
fn set_hook() {
|
||||
let default_hook = panic::take_hook();
|
||||
panic::set_hook(Box::new(move |panic_info| {
|
||||
|
|
|
@ -54,6 +54,9 @@ pub fn streaming_output(
|
|||
Ok((stdout, stderr))
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `cmd` is not configured to have `stdout` and `stderr` as `piped`.
|
||||
pub fn spawn_with_streaming_output(
|
||||
mut cmd: Command,
|
||||
on_stdout_line: &mut dyn FnMut(&str),
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
//! We don't use `rand`, as that's too many things for us.
|
||||
//! We don't use `rand` because that is too many things for us.
|
||||
//!
|
||||
//! We currently use oorandom instead, but it's missing these two utilities.
|
||||
//! Perhaps we should switch to `fastrand`, or our own small PRNG, it's not like
|
||||
//! we need anything more complicated than xor-shift.
|
||||
//! `oorandom` is used instead, but it's missing these two utilities.
|
||||
//! Switching to `fastrand` or our own small PRNG may be good because only xor-shift is needed.
|
||||
|
||||
pub fn shuffle<T>(slice: &mut [T], mut rand_index: impl FnMut(usize) -> usize) {
|
||||
let mut remaining = slice.len() - 1;
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
//! A utility module for working with threads that automatically joins threads upon drop
|
||||
//! and abstracts over 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.
|
||||
//! 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.
|
||||
//!
|
||||
|
@ -23,10 +23,12 @@ mod pool;
|
|||
pub use intent::ThreadIntent;
|
||||
pub use pool::Pool;
|
||||
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if failed to spawn the thread.
|
||||
pub fn spawn<F, T>(intent: ThreadIntent, f: F) -> JoinHandle<T>
|
||||
where
|
||||
F: FnOnce() -> T,
|
||||
F: Send + 'static,
|
||||
F: (FnOnce() -> T) + Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
Builder::new(intent).spawn(f).expect("failed to spawn thread")
|
||||
|
@ -39,26 +41,29 @@ pub struct Builder {
|
|||
}
|
||||
|
||||
impl Builder {
|
||||
pub fn new(intent: ThreadIntent) -> Builder {
|
||||
Builder { intent, inner: jod_thread::Builder::new(), allow_leak: false }
|
||||
#[must_use]
|
||||
pub fn new(intent: ThreadIntent) -> Self {
|
||||
Self { intent, inner: jod_thread::Builder::new(), allow_leak: false }
|
||||
}
|
||||
|
||||
pub fn name(self, name: String) -> Builder {
|
||||
Builder { inner: self.inner.name(name), ..self }
|
||||
#[must_use]
|
||||
pub fn name(self, name: String) -> Self {
|
||||
Self { inner: self.inner.name(name), ..self }
|
||||
}
|
||||
|
||||
pub fn stack_size(self, size: usize) -> Builder {
|
||||
Builder { inner: self.inner.stack_size(size), ..self }
|
||||
#[must_use]
|
||||
pub fn stack_size(self, size: usize) -> Self {
|
||||
Self { inner: self.inner.stack_size(size), ..self }
|
||||
}
|
||||
|
||||
pub fn allow_leak(self, b: bool) -> Builder {
|
||||
Builder { allow_leak: b, ..self }
|
||||
#[must_use]
|
||||
pub fn allow_leak(self, allow_leak: bool) -> Self {
|
||||
Self { allow_leak, ..self }
|
||||
}
|
||||
|
||||
pub fn spawn<F, T>(self, f: F) -> std::io::Result<JoinHandle<T>>
|
||||
where
|
||||
F: FnOnce() -> T,
|
||||
F: Send + 'static,
|
||||
F: (FnOnce() -> T) + Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
let inner_handle = self.inner.spawn(move || {
|
||||
|
@ -78,6 +83,10 @@ pub struct JoinHandle<T = ()> {
|
|||
}
|
||||
|
||||
impl<T> JoinHandle<T> {
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if there is no thread to join.
|
||||
#[must_use]
|
||||
pub fn join(mut self) -> T {
|
||||
self.inner.take().unwrap().join()
|
||||
}
|
||||
|
@ -95,6 +104,7 @@ impl<T> Drop for JoinHandle<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::min_ident_chars, reason = "trait impl")]
|
||||
impl<T> fmt::Debug for JoinHandle<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.pad("JoinHandle { .. }")
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
//! An opaque façade around platform-specific QoS APIs.
|
||||
//! 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
|
||||
/// Any thread which does work that isn't in the critical path of the user typing
|
||||
/// (e.g. processing Go To Definition).
|
||||
Worker,
|
||||
|
||||
|
@ -34,6 +34,7 @@ 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)
|
||||
}
|
||||
|
@ -63,7 +64,7 @@ mod imp {
|
|||
///
|
||||
/// * **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)
|
||||
/// (e.g. if the device's battery is in a critical state)
|
||||
///
|
||||
/// Examples:
|
||||
///
|
||||
|
@ -84,7 +85,7 @@ mod imp {
|
|||
/// All other work is prioritized over background tasks.
|
||||
Background,
|
||||
|
||||
/// TLDR: tasks that don’t block using your app
|
||||
/// TLDR: tasks that don't block using your app
|
||||
///
|
||||
/// Contract:
|
||||
///
|
||||
|
@ -110,7 +111,7 @@ mod imp {
|
|||
/// for tasks using this class.
|
||||
///
|
||||
/// This QoS class provides a balance between
|
||||
/// performance, responsiveness and efficiency.
|
||||
/// performance, responsiveness, and efficiency.
|
||||
Utility,
|
||||
|
||||
/// TLDR: tasks that block using your app
|
||||
|
@ -126,10 +127,10 @@ mod imp {
|
|||
/// * in a video editor:
|
||||
/// opening a saved project
|
||||
/// * in a browser:
|
||||
/// loading a list of the user’s bookmarks and top sites
|
||||
/// 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
|
||||
/// 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.
|
||||
|
@ -208,7 +209,7 @@ mod imp {
|
|||
}
|
||||
|
||||
_ => {
|
||||
// `pthread_set_qos_class_self_np`’s documentation
|
||||
// `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}")
|
||||
}
|
||||
|
@ -223,7 +224,7 @@ mod imp {
|
|||
};
|
||||
|
||||
if code != 0 {
|
||||
// `pthread_get_qos_class_np`’s documentation states that
|
||||
// `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.
|
||||
|
|
|
@ -38,7 +38,11 @@ struct Job {
|
|||
}
|
||||
|
||||
impl Pool {
|
||||
pub fn new(threads: usize) -> Pool {
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if job panics
|
||||
#[must_use]
|
||||
pub fn new(threads: usize) -> Self {
|
||||
const STACK_SIZE: usize = 8 * 1024 * 1024;
|
||||
const INITIAL_INTENT: ThreadIntent = ThreadIntent::Worker;
|
||||
|
||||
|
@ -63,7 +67,7 @@ impl Pool {
|
|||
}
|
||||
extant_tasks.fetch_add(1, Ordering::SeqCst);
|
||||
// discard the panic, we should've logged the backtrace already
|
||||
_ = panic::catch_unwind(job.f);
|
||||
drop(panic::catch_unwind(job.f));
|
||||
extant_tasks.fetch_sub(1, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
@ -73,9 +77,12 @@ impl Pool {
|
|||
handles.push(handle);
|
||||
}
|
||||
|
||||
Pool { _handles: handles.into_boxed_slice(), extant_tasks, job_sender }
|
||||
Self { _handles: handles.into_boxed_slice(), extant_tasks, job_sender }
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if job panics
|
||||
pub fn spawn<F>(&self, intent: ThreadIntent, f: F)
|
||||
where
|
||||
F: FnOnce() + Send + UnwindSafe + 'static,
|
||||
|
@ -84,14 +91,20 @@ impl Pool {
|
|||
if cfg!(debug_assertions) {
|
||||
intent.assert_is_used_on_current_thread();
|
||||
}
|
||||
f()
|
||||
f();
|
||||
});
|
||||
|
||||
let job = Job { requested_intent: intent, f };
|
||||
self.job_sender.send(job).unwrap();
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize {
|
||||
self.extant_tasks.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue