mod config;
mod convenience;
mod dyn_spawned;
mod objsafe;
mod spawn;
mod spawned;
mod task_local;
pub use convenience::DefaultFuture;
pub use dyn_spawned::{DynLocalSpawnedTask, DynSpawnedTask};
pub use objsafe::{
BoxedLocalFuture, BoxedLocalObserverNotifier, BoxedSendFuture, BoxedSendObserverNotifier,
ObjSafeLocalTask, ObjSafeStaticTask, ObjSafeTask,
};
pub use spawn::{
SpawnLocalObjSafeResult, SpawnLocalResult, SpawnObjSafeResult, SpawnResult,
SpawnStaticObjSafeResult, SpawnStaticResult,
};
pub use spawned::{SpawnedLocalTask, SpawnedStaticTask, SpawnedTask};
pub use task_local::{
IS_CANCELLED, InFlightTaskCancellation, TASK_EXECUTOR, TASK_ID, TASK_LABEL,
TASK_LOCAL_EXECUTOR, TASK_PRIORITY, TASK_STATIC_EXECUTOR,
};
use crate::Priority;
use crate::hint::Hint;
use std::convert::Infallible;
use std::fmt::Debug;
use std::future::Future;
use std::sync::atomic::AtomicU64;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TaskID(u64);
impl TaskID {
pub fn from_u64(id: u64) -> Self {
TaskID(id)
}
pub fn to_u64(self) -> u64 {
self.0
}
}
impl<F: Future, N> From<&Task<F, N>> for TaskID {
fn from(task: &Task<F, N>) -> Self {
task.task_id()
}
}
static TASK_IDS: AtomicU64 = AtomicU64::new(0);
#[derive(Debug)]
#[must_use]
pub struct Task<F, N>
where
F: Future,
{
future: F,
hint: Hint,
label: String,
poll_after: crate::sys::Instant,
notifier: Option<N>,
priority: Priority,
task_id: TaskID,
}
impl<F: Future, N> Task<F, N> {
pub fn with_notifications(
label: String,
configuration: Configuration,
notifier: Option<N>,
future: F,
) -> Self
where
F: Future,
{
let task_id = TaskID(TASK_IDS.fetch_add(1, std::sync::atomic::Ordering::Relaxed));
assert_ne!(task_id.0, u64::MAX, "TaskID overflow");
Task {
label,
future,
hint: configuration.hint(),
poll_after: configuration.poll_after(),
priority: configuration.priority(),
notifier,
task_id,
}
}
pub fn hint(&self) -> Hint {
self.hint
}
pub fn label(&self) -> &str {
self.label.as_ref()
}
pub fn priority(&self) -> priority::Priority {
self.priority
}
pub fn poll_after(&self) -> crate::sys::Instant {
self.poll_after
}
pub fn task_id(&self) -> TaskID {
self.task_id
}
pub fn into_future(self) -> F {
self.future
}
}
impl<F: Future> Task<F, Infallible> {
pub fn without_notifications(label: String, configuration: Configuration, future: F) -> Self {
Task::with_notifications(label, configuration, None, future)
}
}
pub use config::{Configuration, ConfigurationBuilder};
impl<F: Future, N> From<F> for Task<F, N> {
fn from(future: F) -> Self {
Task::with_notifications("".to_string(), Configuration::default(), None, future)
}
}
impl<F: Future, N> AsRef<F> for Task<F, N> {
fn as_ref(&self) -> &F {
&self.future
}
}
impl<F: Future, N> AsMut<F> for Task<F, N> {
fn as_mut(&mut self) -> &mut F {
&mut self.future
}
}
impl From<u64> for TaskID {
fn from(id: u64) -> Self {
TaskID::from_u64(id)
}
}
impl From<TaskID> for u64 {
fn from(id: TaskID) -> u64 {
id.to_u64()
}
}
impl AsRef<u64> for TaskID {
fn as_ref(&self) -> &u64 {
&self.0
}
}
#[cfg(test)]
mod tests {
use crate::observer::{FinishedObservation, Observer, ObserverNotified};
use crate::task::{DynLocalSpawnedTask, DynSpawnedTask, SpawnedTask, Task};
use crate::{SomeExecutor, SomeLocalExecutor, task_local};
use std::any::Any;
use std::convert::Infallible;
use std::future::Future;
use std::pin::Pin;
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
fn test_create_task() {
let task: Task<_, Infallible> =
Task::with_notifications("test".to_string(), Default::default(), None, async {});
assert_eq!(task.label(), "test");
}
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
fn test_create_no_notify() {
let t = Task::without_notifications("test".to_string(), Default::default(), async {});
assert_eq!(t.label(), "test");
}
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
fn test_send() {
task_local!(
static FOO: u32;
);
let scoped = FOO.scope(42, async {});
fn assert_send<T: Send>(_: T) {}
assert_send(scoped);
}
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
fn test_dyntask_objsafe() {
let _d: &dyn DynSpawnedTask<Infallible>;
}
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
fn test_send_task() {
#[allow(unused)]
fn task_check<F: Future + Send, N: Send>(task: Task<F, N>) {
fn assert_send<T: Send>(_: T) {}
assert_send(task);
}
#[allow(unused)]
fn task_check_sync<F: Future + Sync, N: Sync>(task: Task<F, N>) {
fn assert_sync<T: Sync>(_: T) {}
assert_sync(task);
}
#[allow(unused)]
fn task_check_unpin<F: Future + Unpin, N: Unpin>(task: Task<F, N>) {
fn assert_unpin<T: Unpin>(_: T) {}
assert_unpin(task);
}
#[allow(unused)]
fn spawn_check<F: Future + Send, E: SomeExecutor + Send>(
task: Task<F, Infallible>,
exec: &mut E,
) where
F::Output: Send,
{
let spawned: SpawnedTask<F, Infallible, E> = task.spawn(exec).0;
fn assert_send<T: Send>(_: T) {}
assert_send(spawned);
}
#[allow(unused)]
fn spawn_check_sync<F: Future + Sync, E: SomeExecutor>(
task: Task<F, Infallible>,
exec: &mut E,
) where
F::Output: Send,
E::ExecutorNotifier: Sync,
{
let spawned: SpawnedTask<F, Infallible, E> = task.spawn(exec).0;
fn assert_sync<T: Sync>(_: T) {}
assert_sync(spawned);
}
#[allow(unused)]
fn spawn_check_unpin<F: Future + Unpin, E: SomeExecutor + Unpin>(
task: Task<F, Infallible>,
exec: &mut E,
) {
let spawned: SpawnedTask<F, Infallible, E> = task.spawn(exec).0;
fn assert_unpin<T: Unpin>(_: T) {}
assert_unpin(spawned);
}
}
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
fn test_local_executor() {
#[allow(unused)]
struct ExLocalExecutor<'future>(
Vec<Pin<Box<dyn DynLocalSpawnedTask<ExLocalExecutor<'future>> + 'future>>>,
);
impl<'future> std::fmt::Debug for ExLocalExecutor<'future> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ExLocalExecutor")
.field("tasks", &format!("{} tasks", self.0.len()))
.finish()
}
}
impl<'existing_tasks, 'new_task> SomeLocalExecutor<'new_task> for ExLocalExecutor<'existing_tasks>
where
'new_task: 'existing_tasks,
{
type ExecutorNotifier = Infallible;
fn spawn_local<F: Future + 'new_task, Notifier: ObserverNotified<F::Output>>(
&mut self,
task: Task<F, Notifier>,
) -> impl Observer<Value = F::Output>
where
Self: Sized,
F::Output: 'static,
<F as Future>::Output: Unpin,
{
let (spawn, observer) = task.spawn_local(self);
let pinned_spawn = Box::pin(spawn);
self.0.push(pinned_spawn);
observer
}
async fn spawn_local_async<
F: Future + 'new_task,
Notifier: ObserverNotified<F::Output>,
>(
&mut self,
task: Task<F, Notifier>,
) -> impl Observer<Value = F::Output>
where
Self: Sized,
F::Output: 'static,
{
let (spawn, observer) = task.spawn_local(self);
let pinned_spawn = Box::pin(spawn);
self.0.push(pinned_spawn);
observer
}
fn spawn_local_objsafe(
&mut self,
task: Task<
Pin<Box<dyn Future<Output = Box<dyn Any>>>>,
Box<dyn ObserverNotified<dyn Any + 'static>>,
>,
) -> Box<dyn Observer<Value = Box<dyn Any>, Output = FinishedObservation<Box<dyn Any>>>>
{
let (spawn, observer) = task.spawn_local_objsafe(self);
let pinned_spawn = Box::pin(spawn);
self.0.push(pinned_spawn);
Box::new(observer)
}
fn spawn_local_objsafe_async<'s>(
&'s mut self,
task: Task<
Pin<Box<dyn Future<Output = Box<dyn Any>>>>,
Box<dyn ObserverNotified<dyn Any + 'static>>,
>,
) -> Box<
dyn Future<
Output = Box<
dyn Observer<
Value = Box<dyn Any>,
Output = FinishedObservation<Box<dyn Any>>,
>,
>,
> + 's,
> {
let (spawn, observer) = task.spawn_local_objsafe(self);
let pinned_spawn = Box::pin(spawn);
self.0.push(pinned_spawn);
#[allow(clippy::type_complexity)]
let boxed = Box::new(observer) as Box<dyn Observer<Value = _, Output = _>>;
Box::new(std::future::ready(boxed))
}
fn executor_notifier(&mut self) -> Option<Self::ExecutorNotifier> {
todo!()
}
}
}
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
fn test_into_objsafe_local_non_send() {
use crate::task::Configuration;
use std::rc::Rc;
let non_send_data = Rc::new(42);
let non_send_future = async move {
let _captured = non_send_data; "result"
};
let task = Task::without_notifications(
"non-send-task".to_string(),
Configuration::default(),
non_send_future,
);
let objsafe_task = task.into_objsafe_local();
assert_eq!(objsafe_task.label(), "non-send-task");
assert_eq!(objsafe_task.hint(), crate::hint::Hint::default());
assert_eq!(objsafe_task.priority(), crate::Priority::Unknown);
}
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
fn test_nested_spawn_local_current_no_panic() {
use crate::thread_executor::thread_local_executor;
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
let nested_call_worked = Arc::new(AtomicBool::new(false));
let nested_call_worked_clone = nested_call_worked.clone();
thread_local_executor(|_executor_rc1| {
thread_local_executor(|_executor_rc2| {
nested_call_worked_clone.store(true, Ordering::Relaxed);
});
});
assert!(
nested_call_worked.load(Ordering::Relaxed),
"Nested call should have succeeded"
);
}
}