use std::sync::{Arc, atomic::AtomicU64, atomic::Ordering};
use std::task::{Context, Poll, ready};
use std::time::{Duration, Instant};
use std::{cell::Cell, cell::RefCell, fmt, future::Future, pin::Pin};
use async_channel::Sender;
use crate::arbiter::{Arbiter, ArbiterCommand};
use crate::busy::BusyClock;
use crate::metrics::{AtomicHistogram, Histogram};
thread_local! {
static STOPPING: Cell<bool> = const { Cell::new(false) };
static STATS: RefCell<Arc<Stats>> = RefCell::new(Arc::default());
static POLL_START: Cell<Option<Instant>> = const { Cell::new(None) };
}
#[inline]
pub fn now() -> Instant {
POLL_START.with(Cell::get).unwrap_or_else(Instant::now)
}
#[doc(hidden)]
pub fn set_poll_start(start: Option<Instant>) {
POLL_START.with(|s| s.set(start));
}
#[derive(Debug)]
struct Stats {
busy: BusyClock,
polls: AtomicU64,
polls_again: AtomicU64,
poll_histogram: AtomicHistogram,
}
impl Default for Stats {
fn default() -> Self {
Self {
busy: BusyClock::new(),
polls: AtomicU64::new(0),
polls_again: AtomicU64::new(0),
poll_histogram: AtomicHistogram::default(),
}
}
}
fn incr(counter: &AtomicU64) {
let v = counter.load(Ordering::Relaxed);
counter.store(v.wrapping_add(1), Ordering::Relaxed);
}
#[doc(hidden)]
pub fn reset_busy() {
STATS.with(|s| *s.borrow_mut() = Arc::default());
}
#[doc(hidden)]
pub fn start_busy(start: Instant) {
STATS.with(|s| s.borrow().busy.start(start));
}
#[doc(hidden)]
pub fn stop_busy(end: Instant) {
STATS.with(|s| s.borrow().busy.stop(end));
}
#[doc(hidden)]
pub fn add_poll(busy: Duration, again: bool) {
STATS.with(|s| {
let s = s.borrow();
s.poll_histogram.record(busy);
incr(&s.polls);
if again {
incr(&s.polls_again);
}
});
}
#[doc(hidden)]
pub fn set_stopping(stopping: bool) {
STOPPING.with(|s| s.set(stopping));
}
#[inline]
pub fn spawn<F>(f: F) -> JoinHandle<F::Output>
where
F: Future + 'static,
{
if STOPPING.with(Cell::get) {
drop(f);
return JoinHandle { task: None };
}
let task = compio_runtime::spawn(crate::task::wrap(f));
JoinHandle {
task: Some(Either::Compio(task)),
}
}
#[inline]
pub fn spawn_detached<F>(f: F)
where
F: Future + 'static,
{
if STOPPING.with(Cell::get) {
drop(f);
return;
}
compio_runtime::spawn(crate::task::wrap(f)).detach();
}
#[derive(Debug, Copy, Clone)]
pub struct JoinError;
impl fmt::Display for JoinError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "JoinError")
}
}
impl std::error::Error for JoinError {}
#[derive(Debug)]
enum Either<T> {
Compio(compio_runtime::JoinHandle<T>),
Spawn(crate::task::RemoteJoin<T>),
}
#[derive(Debug)]
pub struct JoinHandle<T> {
task: Option<Either<T>>,
}
impl<T> JoinHandle<T> {
pub fn cancel(mut self) {
match self.task.take() {
Some(Either::Compio(fut)) => drop(fut.cancel()),
Some(Either::Spawn(fut)) => fut.cancel(),
None => (),
}
}
pub fn detach(mut self) {
if let Some(Either::Compio(fut)) = self.task.take() {
fut.detach();
}
}
pub fn is_finished(&self) -> bool {
match &self.task {
Some(Either::Compio(fut)) => fut.is_finished(),
Some(Either::Spawn(fut)) => fut.is_finished(),
None => true,
}
}
}
impl<T> Drop for JoinHandle<T> {
fn drop(&mut self) {
if let Some(Either::Compio(fut)) = self.task.take() {
fut.detach();
}
}
}
impl<T> Future for JoinHandle<T> {
type Output = Result<T, JoinError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(match self.task.as_mut() {
Some(Either::Compio(fut)) => {
let mut res = Pin::new(&mut *fut).poll(cx);
if res.is_pending() && fut.is_finished() {
res = Pin::new(fut).poll(cx);
}
ready!(res).map_err(|_| JoinError)
}
Some(Either::Spawn(fut)) => ready!(Pin::new(fut).poll(cx)).map_err(|_| JoinError),
None => Err(JoinError),
})
}
}
#[derive(Clone, Debug)]
pub struct Handle {
sender: Sender<ArbiterCommand>,
stats: Arc<Stats>,
}
impl Handle {
pub(crate) fn new(sender: Sender<ArbiterCommand>) -> Self {
let stats = STATS.with(|s| s.borrow().clone());
Self { sender, stats }
}
pub fn current() -> Self {
Arbiter::current().handle().clone()
}
pub fn notify(&self) {}
pub fn busy_duration(&self) -> Option<Duration> {
Some(self.stats.busy.busy_duration())
}
pub fn current_turn(&self) -> Option<Duration> {
self.stats.busy.current_turn()
}
pub fn poll_count(&self) -> Option<u64> {
Some(self.stats.polls.load(Ordering::Relaxed))
}
pub fn poll_again_count(&self) -> Option<u64> {
Some(self.stats.polls_again.load(Ordering::Relaxed))
}
pub fn self_wake_count(&self) -> Option<u64> {
None
}
pub fn current_task(&self) -> Option<&'static std::panic::Location<'static>> {
None
}
pub fn poll_histogram(&self) -> Option<Histogram> {
Some(self.stats.poll_histogram.snapshot())
}
pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
JoinHandle {
task: Some(Either::Spawn(crate::task::RemoteJoin::spawn(
&self.sender,
future,
))),
}
}
pub fn spawn_detached<F>(&self, future: F)
where
F: Future + Send + 'static,
{
let _ = self
.sender
.try_send(ArbiterCommand::Execute(Box::pin(async move {
future.await;
})));
}
}