#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
#![allow(clippy::multiple_crate_versions)]
use std::{
cell::RefCell,
sync::{LazyLock, atomic::AtomicU64},
};
#[cfg(feature = "_any_backend")]
pub use futures;
#[cfg(feature = "macros")]
pub use switchy_async_macros::{inject_yields, inject_yields_mod};
#[cfg(all(feature = "macros", feature = "simulator"))]
#[doc(hidden)]
pub use switchy_async_macros::select_internal;
#[cfg(all(feature = "macros", feature = "simulator"))]
#[doc(hidden)]
pub use switchy_async_macros::join_internal;
#[cfg(all(feature = "macros", feature = "simulator"))]
#[doc(hidden)]
pub use switchy_async_macros::try_join_internal;
#[cfg(all(feature = "macros", feature = "simulator"))]
#[doc(hidden)]
pub use switchy_async_macros::test_internal;
#[cfg(all(feature = "macros", feature = "simulator"))]
#[doc(hidden)]
pub use switchy_async_macros::internal_test;
#[cfg(all(feature = "macros", feature = "simulator"))]
pub use switchy_async_macros::test;
#[cfg(all(feature = "macros", feature = "tokio", not(feature = "simulator")))]
pub use switchy_async_macros::tokio_test_wrapper as test;
#[cfg(all(feature = "macros", feature = "simulator"))]
#[doc(hidden)]
pub use switchy_async_macros::main_internal;
#[cfg(all(feature = "macros", feature = "simulator"))]
#[doc(hidden)]
pub use switchy_async_macros::internal_main;
#[cfg(all(feature = "macros", feature = "simulator"))]
pub use switchy_async_macros::main;
#[cfg(all(feature = "macros", feature = "tokio", not(feature = "simulator")))]
pub use switchy_async_macros::tokio_main_wrapper as main;
#[cfg(all(feature = "macros", feature = "tokio", not(feature = "simulator")))]
#[macro_export]
#[doc(hidden)]
macro_rules! select_internal {
($($tokens:tt)*) => {
::switchy_async::__private::tokio::select! { $($tokens)* }
};
}
#[cfg(all(feature = "macros", feature = "tokio", not(feature = "simulator")))]
#[macro_export]
#[doc(hidden)]
macro_rules! join_internal {
($($tokens:tt)*) => {
::switchy_async::__private::tokio::join! { $($tokens)* }
};
}
#[cfg(all(feature = "macros", feature = "tokio", not(feature = "simulator")))]
#[macro_export]
#[doc(hidden)]
macro_rules! try_join_internal {
($($tokens:tt)*) => {
::switchy_async::__private::tokio::try_join! { $($tokens)* }
};
}
#[cfg(all(feature = "macros", feature = "tokio", not(feature = "simulator")))]
pub use crate::tokio::test as test_internal;
#[cfg(all(feature = "macros", feature = "tokio", not(feature = "simulator")))]
pub use crate::tokio::main as main_internal;
#[cfg(feature = "tokio")]
pub mod tokio;
#[cfg(feature = "tokio")]
#[doc(hidden)]
pub mod __private {
pub use tokio;
}
#[cfg(feature = "simulator")]
pub mod simulator;
static THREAD_ID_COUNTER: LazyLock<AtomicU64> = LazyLock::new(|| AtomicU64::new(1));
thread_local! {
static THREAD_ID: RefCell<u64> = RefCell::new(THREAD_ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst));
}
#[must_use]
pub fn thread_id() -> u64 {
THREAD_ID.with_borrow(|x| *x)
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
IO(#[from] std::io::Error),
#[cfg(feature = "_any_backend")]
#[error(transparent)]
Join(#[from] task::JoinError),
}
pub trait GenericRuntime {
fn block_on<F: Future>(&self, future: F) -> F::Output;
fn wait(self) -> Result<(), Error>;
}
pub struct Builder {
#[cfg(feature = "rt-multi-thread")]
pub max_blocking_threads: Option<u16>,
}
impl Default for Builder {
fn default() -> Self {
Self::new()
}
}
impl Builder {
#[must_use]
pub const fn new() -> Self {
Self {
#[cfg(feature = "rt-multi-thread")]
max_blocking_threads: None,
}
}
#[cfg(feature = "rt-multi-thread")]
pub fn max_blocking_threads<T: Into<Option<u16>>>(
&mut self,
max_blocking_threads: T,
) -> &mut Self {
self.max_blocking_threads = max_blocking_threads.into();
self
}
}
#[allow(unused)]
macro_rules! impl_async {
($module:ident $(,)?) => {
pub use $module::task;
pub use $module::runtime;
#[cfg(feature = "io")]
pub use $module::io;
#[cfg(feature = "process")]
pub use $module::process;
#[cfg(feature = "sync")]
pub use $module::sync;
#[cfg(feature = "time")]
pub use $module::time;
#[cfg(feature = "util")]
pub use $module::util;
#[cfg(all(feature = "macros", not(feature = "simulator")))]
pub use $module::select;
#[cfg(all(feature = "macros", not(feature = "simulator")))]
pub use $module::join;
#[cfg(all(feature = "macros", not(feature = "simulator")))]
pub use $module::try_join;
impl $module::runtime::Runtime {
pub fn block_on<F: Future>(&self, f: F) -> F::Output {
<Self as GenericRuntime>::block_on(self, f)
}
pub fn wait(self) -> Result<(), Error> {
<Self as GenericRuntime>::wait(self)
}
}
impl Builder {
pub fn build(&self) -> Result<$module::runtime::Runtime, Error> {
$module::runtime::build_runtime(self)
}
}
};
}
#[cfg(feature = "simulator")]
impl_async!(simulator);
#[cfg(all(not(feature = "simulator"), feature = "tokio"))]
impl_async!(tokio);
#[cfg(test)]
mod tests {
use std::{
sync::{Arc, Mutex},
thread,
};
use super::thread_id;
#[cfg(feature = "_any_backend")]
use super::{Builder, Error};
#[test_log::test]
fn thread_id_is_unique_across_threads() {
let ids = Arc::new(Mutex::new(Vec::new()));
let mut handles = vec![];
for _ in 0..10 {
let ids_clone = Arc::clone(&ids);
handles.push(thread::spawn(move || {
let id = thread_id();
ids_clone.lock().unwrap().push(id);
}));
}
for handle in handles {
handle.join().unwrap();
}
let (sorted_len, ids_len) = {
let ids = ids.lock().unwrap();
let mut sorted = ids.clone();
sorted.sort_unstable();
sorted.dedup();
(sorted.len(), ids.len())
};
assert_eq!(sorted_len, ids_len);
}
#[test_log::test]
fn thread_id_is_consistent_within_thread() {
let id1 = thread_id();
let id2 = thread_id();
let id3 = thread_id();
assert_eq!(id1, id2);
assert_eq!(id2, id3);
}
#[test_log::test]
fn thread_id_is_monotonically_increasing() {
let main_id = thread_id();
let spawned_id = thread::spawn(thread_id).join().unwrap();
assert!(spawned_id > main_id);
}
#[cfg(feature = "_any_backend")]
#[test_log::test]
fn error_from_io_error() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "test error");
let err: Error = io_err.into();
assert!(matches!(err, Error::IO(_)));
}
#[cfg(feature = "_any_backend")]
#[test_log::test]
fn builder_default_is_same_as_new() {
let builder1 = Builder::default();
let builder2 = Builder::new();
#[cfg(feature = "rt-multi-thread")]
assert_eq!(builder1.max_blocking_threads, builder2.max_blocking_threads);
let _runtime1 = builder1.build().unwrap();
let _runtime2 = builder2.build().unwrap();
}
#[cfg(all(feature = "_any_backend", feature = "rt-multi-thread"))]
#[test_log::test]
fn builder_max_blocking_threads_configuration() {
let mut builder = Builder::new();
builder.max_blocking_threads(4);
assert_eq!(builder.max_blocking_threads, Some(4));
builder.max_blocking_threads(Some(8));
assert_eq!(builder.max_blocking_threads, Some(8));
builder.max_blocking_threads(None);
assert_eq!(builder.max_blocking_threads, None);
}
}