use std::{
cell::Cell,
collections::VecDeque,
future::Future,
io, mem,
pin::pin,
ptr::NonNull,
sync::{
Arc, Mutex, MutexGuard, PoisonError,
atomic::{AtomicBool, Ordering},
},
task::{Context, Poll, Wake, Waker},
thread,
time::Duration,
};
use crate::{
Mode, Shared, log::error, mode::sealed::Lock, poll::Poller, reactor::Reactor,
scheduler::Scheduler,
};
pub struct Core<M>
where
M: Mode,
{
pub(crate) scheduler: Scheduler<M>,
pub(crate) reactor: Reactor<M>,
pub(crate) remote: Arc<Remote>,
driven: M::Lock<bool>,
#[cfg(feature = "helper")]
pub(crate) seat: M::Seat,
}
impl<M> Core<M>
where
M: Mode,
{
pub(crate) fn new() -> io::Result<M::Ptr<Self>> {
Ok(M::new_ptr(Self::unshared()?))
}
pub(crate) fn block_on<F>(this: &M::Ptr<Self>, future: F) -> F::Output
where
F: Future,
{
let _driving = Driving::<M>::enter(this);
let mut future = pin!(future);
let signal = Arc::new(Signal {
woken: AtomicBool::new(false),
remote: this.remote.clone(),
});
let waker = Waker::from(signal.clone());
let mut cx = Context::from_waker(&waker);
let mut failed_waits = 0u32;
loop {
signal.woken.swap(false, Ordering::AcqRel);
if let Poll::Ready(output) = future.as_mut().poll(&mut cx) {
return output;
}
this.round(&signal.woken, &mut failed_waits);
}
}
pub(crate) fn round(&self, woken: &AtomicBool, failed_waits: &mut u32) {
self.run_batch();
let woken = woken.load(Ordering::Acquire);
self.wait(woken, failed_waits);
}
pub(crate) fn run_batch(&self) {
self.scheduler.run_batch(&mut [0; BATCH]);
}
pub(crate) fn wait(&self, at_once: bool, failed_waits: &mut u32) {
let at_most = (at_once || self.remote.has_ready()).then_some(Duration::ZERO);
match self.reactor.wait(at_most) {
Ok(()) => *failed_waits = 0,
Err(e) => {
*failed_waits += 1;
if *failed_waits == 1 {
error!("The runtime's wait failed: {}", e);
}
self.reactor.wake_everything();
thread::sleep(Duration::from_millis(1 << (*failed_waits).min(10)));
}
}
}
fn unshared() -> io::Result<Self> {
let remote = Arc::new(Remote::new()?);
Ok(Self {
scheduler: Scheduler::new(remote.clone()),
reactor: Reactor::new(remote.clone()),
remote,
driven: Lock::new(false),
#[cfg(feature = "helper")]
seat: Default::default(),
})
}
}
impl Core<Shared> {
#[cfg(feature = "helper")]
pub(crate) fn with_seat() -> io::Result<Arc<Self>> {
let mut core = Self::unshared()?;
core.seat = Some(Mutex::new(crate::driver::Seat::new()));
Ok(Arc::new(core))
}
pub(crate) fn ensure_progress(self: &Arc<Self>) {
#[cfg(feature = "helper")]
crate::driver::ensure_helper(self);
}
}
const BATCH: usize = 32;
pub(crate) struct Remote {
ready: Mutex<VecDeque<u64>>,
pub(crate) poller: Poller,
pub(crate) wake_pending: AtomicBool,
}
impl Remote {
pub(crate) fn schedule(&self, id: u64) {
lock(&self.ready).push_back(id);
self.notify();
}
pub(crate) fn take_ready(&self, ids: &mut [u64]) -> usize {
let mut ready = lock(&self.ready);
let taken = ready.len().min(ids.len());
for (slot, id) in ids.iter_mut().zip(ready.drain(..taken)) {
*slot = id;
}
taken
}
pub(crate) fn requeue(&self, ids: &[u64]) {
let mut ready = lock(&self.ready);
for &id in ids.iter().rev() {
ready.push_front(id);
}
}
#[cfg(test)]
pub(crate) fn next_ready(&self) -> Option<u64> {
lock(&self.ready).pop_front()
}
pub(crate) fn has_ready(&self) -> bool {
!lock(&self.ready).is_empty()
}
#[cfg(test)]
pub(crate) fn ready_len(&self) -> usize {
lock(&self.ready).len()
}
pub(crate) fn notify(&self) {
if drives(self) {
return;
}
if self.wake_pending.swap(true, Ordering::AcqRel) {
return;
}
if self.poller.notify().is_err() {
self.wake_pending.store(false, Ordering::Release);
}
}
#[cfg(test)]
pub(crate) fn wake_pending(&self) -> bool {
self.wake_pending.load(Ordering::Acquire)
}
fn new() -> io::Result<Self> {
Ok(Self {
ready: Mutex::new(VecDeque::new()),
poller: Poller::new()?,
wake_pending: AtomicBool::new(false),
})
}
}
struct Driving<'a, M>
where
M: Mode,
{
core: &'a M::Ptr<Core<M>>,
}
impl<'a, M> Driving<'a, M>
where
M: Mode,
{
fn enter(core: &'a M::Ptr<Core<M>>) -> Self {
assert!(
!drives_a_runtime(),
"block_on called from a task this runtime is running: the call would wait for the \
very thread it is on"
);
#[cfg(feature = "helper")]
assert!(
!crate::driver::in_block_on(),
"block_on called from inside the future of another block_on: the call would keep the \
thread from the runtime that one drives"
);
let claimed = !mem::replace(&mut *core.driven.lock(), true);
assert!(
claimed,
"block_on called on a runtime another thread is driving: a runtime is driven by one \
thread at a time"
);
set_driving::<M>(Some(core));
Self { core }
}
}
impl<M> Drop for Driving<'_, M>
where
M: Mode,
{
fn drop(&mut self) {
set_driving::<M>(None);
*self.core.driven.lock() = false;
}
}
struct Signal {
woken: AtomicBool,
remote: Arc<Remote>,
}
impl Wake for Signal {
fn wake(self: Arc<Self>) {
self.wake_by_ref();
}
fn wake_by_ref(self: &Arc<Self>) {
self.woken.swap(true, Ordering::AcqRel);
self.remote.notify();
}
}
pub(crate) fn set_driving<M>(core: Option<&M::Ptr<Core<M>>>)
where
M: Mode,
{
DRIVING.with(|driving| driving.set(core.map(|core| NonNull::from(&*core.remote))));
M::set_driven(core);
}
pub(crate) fn drives(remote: &Remote) -> bool {
DRIVING.with(Cell::get) == Some(NonNull::from(remote))
}
pub(crate) fn drives_a_runtime() -> bool {
DRIVING.with(Cell::get).is_some()
}
thread_local! {
static DRIVING: Cell<Option<NonNull<Remote>>> = const { Cell::new(None) };
}
pub(crate) fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
mutex.lock().unwrap_or_else(PoisonError::into_inner)
}