#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "rayon")]
pub mod background_rayon;
#[cfg(feature = "rayon")]
pub use background_rayon::RayonBackgroundPool;
#[cfg(feature = "concurrency")]
pub mod concurrency;
#[cfg(feature = "alloc")]
pub mod ext;
#[cfg(feature = "alloc")]
pub mod primitives;
#[cfg(feature = "alloc")]
pub use primitives::{
JoinError, JoinSetLike, LocalMutexLike, LocalNotifyLike, MutexLike, NotifyLike,
};
#[cfg(all(feature = "alloc", feature = "std"))]
pub use primitives::{LocalRuntimeFactory, RuntimeFactory};
#[cfg(feature = "tokio")]
pub mod tokio;
use thiserror::Error;
#[cfg(feature = "alloc")]
use core::convert::Infallible;
#[cfg(feature = "alloc")]
use core::future::Future;
#[cfg(feature = "alloc")]
use core::pin::Pin;
#[cfg(feature = "alloc")]
use proxima_core::ProximaError;
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
#[cfg(feature = "alloc")]
pub enum SpawnRequest<Inline = Infallible> {
Send(Pin<Box<dyn Future<Output = ()> + Send + 'static>>),
Factory(Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + 'static>> + Send + 'static>),
SendInline(Inline),
Shutdown,
}
#[cfg(feature = "alloc")]
impl<Inline> core::fmt::Debug for SpawnRequest<Inline> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Send(..) => formatter.write_str("SpawnRequest::Send"),
Self::Factory(..) => formatter.write_str("SpawnRequest::Factory"),
Self::SendInline(..) => formatter.write_str("SpawnRequest::SendInline"),
Self::Shutdown => formatter.write_str("SpawnRequest::Shutdown"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CoreId(pub usize);
impl From<usize> for CoreId {
fn from(index: usize) -> Self {
CoreId(index)
}
}
impl CoreId {
#[must_use]
pub fn as_usize(&self) -> usize {
self.0
}
}
#[cfg(feature = "alloc")]
pub type BackgroundHandle<R> = Pin<Box<dyn Future<Output = Result<R, ProximaError>> + Send>>;
#[cfg(feature = "alloc")]
pub trait BackgroundPool: Send + Sync + 'static {
fn spawn(
&self,
work: Box<dyn FnOnce() -> Result<Box<dyn core::any::Any + Send>, ProximaError> + Send>,
) -> BackgroundHandle<Box<dyn core::any::Any + Send>>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
pub enum SpawnError {
#[error("inbox lane full; transient back-pressure")]
InboxFull,
#[error("target core disconnected")]
Disconnected,
}
#[cfg(feature = "alloc")]
pub fn spawn_on_core_blocking_with<F>(
runtime: &dyn Runtime,
core_id: CoreId,
mut factory: F,
) -> Result<(), SpawnError>
where
F: FnMut() -> Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
{
loop {
match runtime.spawn_on_core(core_id, factory()) {
Ok(()) => return Ok(()),
Err(SpawnError::InboxFull) => {
#[cfg(feature = "std")]
std::thread::yield_now();
#[cfg(not(feature = "std"))]
core::hint::spin_loop();
}
Err(SpawnError::Disconnected) => return Err(SpawnError::Disconnected),
}
}
}
#[cfg(all(feature = "alloc", feature = "std"))]
#[must_use = "block_on returns the future's output or the dispatch error"]
pub fn block_on<F>(runtime: &dyn Runtime, future: F) -> Result<F::Output, ProximaError>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
let (sender, receiver) = std::sync::mpsc::sync_channel::<F::Output>(1);
let task = async move {
let output = future.await;
let _ = sender.send(output);
};
match runtime.spawn_on_core(CoreId(0), Box::pin(task)) {
Ok(()) => {}
Err(SpawnError::InboxFull) => {
return Err(block_on_dispatch_error(
"runtime core 0 inbox full on block_on dispatch",
));
}
Err(SpawnError::Disconnected) => {
return Err(block_on_dispatch_error(
"runtime core 0 disconnected on block_on dispatch",
));
}
}
receiver.recv().map_err(|_| {
block_on_dispatch_error("runtime worker dropped the block_on completion channel")
})
}
#[cfg(all(feature = "alloc", feature = "std"))]
fn block_on_dispatch_error(message: &str) -> ProximaError {
ProximaError::Io(std::io::Error::other(message))
}
#[cfg(feature = "alloc")]
pub trait Runtime: Send + Sync + 'static {
fn spawn_on_current_core(&self, future: Pin<Box<dyn Future<Output = ()> + 'static>>);
fn spawn_on_core(
&self,
core_id: CoreId,
future: Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
) -> Result<(), SpawnError>;
fn spawn_factory_on_core(
&self,
core_id: CoreId,
factory: Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + 'static>> + Send + 'static>,
) -> Result<(), SpawnError>;
fn spawn_background_blocking(
&self,
work: Box<dyn FnOnce() -> Result<Box<dyn core::any::Any + Send>, ProximaError> + Send>,
) -> BackgroundHandle<Box<dyn core::any::Any + Send>>;
#[cfg(feature = "std")]
fn timer_at(&self, deadline: std::time::Instant)
-> Pin<Box<dyn Future<Output = ()> + 'static>>;
fn num_cores(&self) -> usize;
fn current_core(&self) -> CoreId;
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
#[test]
fn core_id_round_trips_as_usize() {
let id = CoreId(7);
assert_eq!(id.as_usize(), 7);
}
#[test]
fn spawn_error_inbox_full_display() {
let err = SpawnError::InboxFull;
assert_eq!(err.to_string(), "inbox lane full; transient back-pressure");
}
#[test]
fn spawn_error_disconnected_display() {
let err = SpawnError::Disconnected;
assert_eq!(err.to_string(), "target core disconnected");
}
#[test]
fn spawn_error_source_is_none() {
use core::error::Error;
assert!(SpawnError::InboxFull.source().is_none());
assert!(SpawnError::Disconnected.source().is_none());
}
#[cfg(feature = "alloc")]
mod alloc_tests {
use super::*;
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
struct MockRuntime {
send_dispatched: core::sync::atomic::AtomicBool,
factory_dispatched: core::sync::atomic::AtomicBool,
}
impl MockRuntime {
fn new() -> Self {
Self {
send_dispatched: core::sync::atomic::AtomicBool::new(false),
factory_dispatched: core::sync::atomic::AtomicBool::new(false),
}
}
}
impl Runtime for MockRuntime {
fn spawn_on_current_core(&self, _future: Pin<Box<dyn Future<Output = ()> + 'static>>) {
self.send_dispatched
.store(true, core::sync::atomic::Ordering::Relaxed);
}
fn spawn_on_core(
&self,
_core_id: CoreId,
_future: Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
) -> Result<(), SpawnError> {
self.send_dispatched
.store(true, core::sync::atomic::Ordering::Relaxed);
Ok(())
}
fn spawn_factory_on_core(
&self,
_core_id: CoreId,
_factory: Box<
dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + 'static>> + Send + 'static,
>,
) -> Result<(), SpawnError> {
self.factory_dispatched
.store(true, core::sync::atomic::Ordering::Relaxed);
Ok(())
}
fn spawn_background_blocking(
&self,
work: Box<
dyn FnOnce() -> Result<Box<dyn core::any::Any + Send>, ProximaError> + Send,
>,
) -> BackgroundHandle<Box<dyn core::any::Any + Send>> {
let result = work();
Box::pin(async move { result })
}
fn num_cores(&self) -> usize {
1
}
fn current_core(&self) -> CoreId {
CoreId(0)
}
#[cfg(feature = "std")]
fn timer_at(
&self,
_deadline: std::time::Instant,
) -> Pin<Box<dyn Future<Output = ()> + 'static>> {
Box::pin(async {})
}
}
fn noop_waker() -> Waker {
const VTABLE: RawWakerVTable =
RawWakerVTable::new(|ptr| RawWaker::new(ptr, &VTABLE), |_| {}, |_| {}, |_| {});
unsafe { Waker::from_raw(RawWaker::new(core::ptr::null(), &VTABLE)) }
}
#[test]
fn spawn_request_send_dispatches() {
let request: SpawnRequest = SpawnRequest::Send(Box::pin(async {}));
assert!(matches!(request, SpawnRequest::Send(..)));
}
#[test]
fn spawn_request_factory_dispatches() {
let request: SpawnRequest = SpawnRequest::Factory(Box::new(|| Box::pin(async {})));
assert!(matches!(request, SpawnRequest::Factory(..)));
}
#[test]
fn spawn_request_shutdown_dispatches() {
let request: SpawnRequest<core::convert::Infallible> = SpawnRequest::Shutdown;
assert!(matches!(request, SpawnRequest::Shutdown));
}
#[test]
fn background_handle_round_trip_with_payload() {
let rt = MockRuntime::new();
let payload = alloc::string::String::from("hello");
let mut handle = rt.spawn_background_blocking(Box::new(move || {
Ok(Box::new(payload) as Box<dyn core::any::Any + Send>)
}));
let waker = noop_waker();
let mut ctx = Context::from_waker(&waker);
let poll = Pin::new(&mut handle).poll(&mut ctx);
let result = match poll {
Poll::Ready(result) => result,
Poll::Pending => panic!("expected ready"),
};
let boxed = result.expect("ok result");
let value = boxed.downcast::<alloc::string::String>().expect("downcast");
assert_eq!(*value, "hello");
}
#[cfg(feature = "std")]
#[test]
fn timer_at_compiles_and_is_callable() {
let rt = MockRuntime::new();
let deadline = std::time::Instant::now();
let _future = rt.timer_at(deadline);
}
}
}