use crate::runtime::{local_executor, Locality};
use crate::sync::{AsyncWaitGroup, WaitGroup};
use crate::{panic_if_local_in_future, yield_now};
use std::future::Future;
use std::pin::Pin;
use std::task::Poll;
pub struct Scope<'scope> {
wg: WaitGroup,
_scope: std::marker::PhantomData<&'scope ()>,
}
impl<'scope> Scope<'scope> {
#[inline(always)]
pub fn exec<F: Future<Output = ()> + Send>(&'scope self, future: F) {
self.wg.inc();
let handle = ScopedHandle {
scope: self,
fut: future,
};
let shared_task = crate::runtime::Task::from_future(handle, Locality::shared());
local_executor().exec_task(shared_task);
}
#[inline(always)]
pub fn spawn<F: Future<Output = ()> + Send>(&'scope self, future: F) {
self.wg.inc();
let handle = ScopedHandle {
scope: self,
fut: future,
};
local_executor().spawn_shared(handle);
}
}
unsafe impl Send for Scope<'_> {}
unsafe impl Sync for Scope<'_> {}
pub(crate) struct ScopedHandle<'scope, Fut: Future<Output = ()> + Send> {
scope: &'scope Scope<'scope>,
fut: Fut,
}
impl<Fut: Future<Output = ()> + Send> Future for ScopedHandle<'_, Fut> {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
panic_if_local_in_future!(cx, "Scope");
let this = unsafe { self.get_unchecked_mut() };
let mut pinned_future = unsafe { Pin::new_unchecked(&mut this.fut) };
match pinned_future.as_mut().poll(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(()) => {
this.scope.wg.done();
Poll::Ready(())
}
}
}
}
unsafe impl<F: Future<Output = ()> + Send> Send for ScopedHandle<'_, F> {}
unsafe impl<F: Future<Output = ()> + Send> Sync for ScopedHandle<'_, F> {}
#[inline(always)]
#[allow(
clippy::future_not_send,
reason = "It is not `Send` only when F is not `Send`, it is fine"
)]
pub async fn shared_scope<'scope, Fut, F>(f: F)
where
Fut: Future<Output = ()> + Send,
F: FnOnce(&'scope Scope<'scope>) -> Fut,
{
let scope = Scope {
wg: WaitGroup::new(),
_scope: std::marker::PhantomData,
};
let static_scope = unsafe { std::mem::transmute::<&_, &'static Scope<'static>>(&scope) };
f(static_scope).await;
scope.wg.wait().await;
yield_now().await; }
#[allow(dead_code, reason = "It is used only in compile tests")]
fn test_compile_shared_scope() {}
#[cfg(test)]
mod tests {
use super::*;
use crate as orengine;
use crate::yield_now;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering::{Relaxed, SeqCst};
#[orengine::test::test_shared]
fn test_shared_scope_exec() {
let a = AtomicUsize::new(0);
let wg = WaitGroup::new();
shared_scope(|scope| async {
scope.exec(async {
assert_eq!(a.load(SeqCst), 0);
a.fetch_add(1, SeqCst);
yield_now().await;
assert_eq!(a.load(SeqCst), 2);
a.fetch_add(1, SeqCst);
wg.done();
});
scope.exec(async {
assert_eq!(a.load(SeqCst), 1);
a.fetch_add(1, SeqCst);
wg.inc();
wg.wait().await;
assert_eq!(a.load(SeqCst), 3);
a.fetch_add(1, SeqCst);
});
})
.await;
yield_now().await;
assert_eq!(a.load(SeqCst), 4);
}
#[orengine::test::test_shared]
fn test_shared_scope_exec_with_main_future() {
let a = AtomicUsize::new(0);
let wg = WaitGroup::new();
shared_scope(|scope| async {
scope.exec(async {
assert_eq!(a.load(SeqCst), 0);
a.fetch_add(1, SeqCst);
yield_now().await;
assert_eq!(a.load(SeqCst), 3);
a.fetch_add(1, SeqCst);
wg.done();
});
scope.exec(async {
assert_eq!(a.load(SeqCst), 1);
a.fetch_add(1, SeqCst);
wg.inc();
wg.wait().await;
assert_eq!(a.load(SeqCst), 4);
a.fetch_add(1, SeqCst);
});
assert_eq!(a.load(SeqCst), 2);
a.fetch_add(1, SeqCst);
})
.await;
yield_now().await;
assert_eq!(a.load(SeqCst), 5);
}
#[orengine::test::test_shared]
fn test_shared_scope_spawn() {
let a = AtomicUsize::new(0);
let wg = WaitGroup::new();
wg.inc();
shared_scope(|scope| async {
scope.spawn(async {
assert_eq!(a.load(SeqCst), 1);
a.fetch_add(1, SeqCst);
yield_now().await;
assert_eq!(a.load(SeqCst), 2);
a.fetch_add(1, SeqCst);
wg.done();
});
scope.spawn(async {
assert_eq!(a.load(SeqCst), 0);
a.fetch_add(1, SeqCst);
wg.wait().await;
assert_eq!(a.load(SeqCst), 3);
a.fetch_add(1, SeqCst);
});
})
.await;
yield_now().await;
assert_eq!(a.load(SeqCst), 4);
}
#[orengine::test::test_shared]
fn test_shared_scope_spawn_with_main_future() {
let a = AtomicUsize::new(0);
let wg = WaitGroup::new();
shared_scope(|scope| async {
wg.add(1);
scope.spawn(async {
assert_eq!(a.load(SeqCst), 2);
a.fetch_add(1, SeqCst);
yield_now().await;
assert_eq!(a.load(SeqCst), 3);
a.fetch_add(1, SeqCst);
wg.done();
});
scope.spawn(async {
assert_eq!(a.load(SeqCst), 1);
a.fetch_add(1, SeqCst);
wg.wait().await;
assert_eq!(a.load(SeqCst), 4);
a.fetch_add(1, SeqCst);
});
assert_eq!(a.load(SeqCst), 0);
a.fetch_add(1, SeqCst);
})
.await;
yield_now().await;
assert_eq!(a.load(SeqCst), 5);
}
#[orengine::test::test_shared]
fn test_many_shared_scope_in_the_same_task() {
static ROUND: AtomicUsize = AtomicUsize::new(0);
#[allow(clippy::future_not_send, reason = "It is local test")]
async fn work_with_scope<'scope>(counter: &'scope AtomicUsize, scope: &Scope<'scope>) {
scope.spawn(async {
assert_eq!(counter.load(Relaxed), 2 + 6 * ROUND.load(Relaxed));
counter.fetch_add(1, Relaxed);
yield_now().await;
assert_eq!(counter.load(Relaxed), 5 + 6 * ROUND.load(Relaxed));
counter.fetch_add(1, Relaxed);
});
scope.exec(async {
assert_eq!(counter.load(Relaxed), 6 * ROUND.load(Relaxed));
counter.fetch_add(1, Relaxed);
yield_now().await;
assert_eq!(counter.load(Relaxed), 3 + 6 * ROUND.load(Relaxed));
counter.fetch_add(1, Relaxed);
});
assert_eq!(counter.load(Relaxed), 1 + 6 * ROUND.load(Relaxed));
counter.fetch_add(1, Relaxed);
yield_now().await;
assert_eq!(counter.load(Relaxed), 4 + 6 * ROUND.load(Relaxed));
counter.fetch_add(1, Relaxed);
}
let counter = AtomicUsize::new(0);
shared_scope(|scope| async {
work_with_scope(&counter, scope).await;
})
.await;
assert_eq!(counter.load(Relaxed), 6);
ROUND.store(1, Relaxed);
shared_scope(|scope| async {
work_with_scope(&counter, scope).await;
})
.await;
assert_eq!(counter.load(Relaxed), 12);
ROUND.store(2, Relaxed);
for i in 3..10 {
shared_scope(|scope| async {
work_with_scope(&counter, scope).await;
})
.await;
assert_eq!(counter.load(Relaxed), i * 6);
ROUND.store(i, Relaxed);
}
}
}