use std::cell::{Cell, RefCell};
use std::future::poll_fn;
use std::sync::atomic::Ordering;
use std::task::Poll;
use std::time::{Duration, Instant};
use std::{future::Future, io, marker::PhantomData, panic::Location};
use std::{pin::Pin, rc::Rc, sync::Arc};
use crate::driver::{Driver, Notify, PollResult};
use crate::handle::{JoinHandle, SpawnLocation};
use crate::metrics::Histogram;
#[cfg(not(ntex_loom))]
use std::thread;
mod queue;
pub(crate) mod task;
use self::queue::{CacheLine, RunnableQueue, suppress_cleanup_panic};
const LOG_TARGET: &str = module_path!();
pub(crate) const REPORTED: u32 = 1 << 31;
pub(crate) trait TaskStats: Copy {
fn location(self) -> SpawnLocation;
fn self_wakes(self) -> u32;
fn set_self_wakes(self, wakes: u32);
fn streak_start(self) -> u32;
fn set_streak_start(self, start: u32);
}
scoped_tls::scoped_thread_local!(static CURRENT_RUNTIME: Runtime);
#[cfg(not(ntex_loom))]
pub(crate) type ThreadId = thread::ThreadId;
#[cfg(ntex_loom)]
pub(crate) type ThreadId = loom::thread::ThreadId;
#[cfg(not(ntex_loom))]
thread_local!(static THREAD_ID: Cell<Option<ThreadId>> = const { Cell::new(None) });
#[cfg(not(ntex_loom))]
#[inline]
pub(crate) fn current_thread_id() -> ThreadId {
#[cold]
fn initialize(slot: &Cell<Option<ThreadId>>) -> ThreadId {
let id = thread::current().id();
slot.set(Some(id));
id
}
THREAD_ID.with(|slot| slot.get().unwrap_or_else(|| initialize(slot)))
}
#[cfg(ntex_loom)]
pub(crate) fn current_thread_id() -> ThreadId {
loom::thread::current().id()
}
#[derive(Debug)]
pub struct Runtime {
stop: Cell<bool>,
queue: Arc<RunnableQueue>,
_not_send: PhantomData<*const ()>,
}
impl Runtime {
pub fn new(handle: Box<dyn Notify>) -> Self {
Self::builder().build(handle)
}
pub fn builder() -> RuntimeBuilder {
RuntimeBuilder::new()
}
#[allow(clippy::arc_with_non_send_sync)]
fn with_builder(builder: &RuntimeBuilder, handle: Box<dyn Notify>) -> Self {
Self {
stop: Cell::new(false),
queue: Arc::new(RunnableQueue::new(builder, handle)),
_not_send: PhantomData,
}
}
pub fn with_current<T, F: FnOnce(&Self) -> T>(f: F) -> T {
#[cold]
fn not_in_neon_runtime() -> ! {
panic!("not in a neon runtime")
}
if CURRENT_RUNTIME.is_set() {
CURRENT_RUNTIME.with(f)
} else {
not_in_neon_runtime()
}
}
pub(crate) fn current_now() -> Instant {
if CURRENT_RUNTIME.is_set() {
CURRENT_RUNTIME.with(Runtime::now)
} else {
Instant::now()
}
}
#[inline]
pub fn handle(&self) -> Handle {
Handle {
queue: self.queue.clone(),
}
}
#[track_caller]
pub fn spawn<F: Future + 'static>(&self, future: F) -> JoinHandle<F::Output> {
unsafe { self.spawn_at(future, Location::caller()) }
}
#[track_caller]
pub unsafe fn spawn_unchecked<F: Future>(&self, future: F) -> JoinHandle<F::Output> {
unsafe { self.spawn_at(future, Location::caller()) }
}
pub(crate) unsafe fn spawn_at<F: Future>(
&self,
future: F,
location: SpawnLocation,
) -> JoinHandle<F::Output> {
unsafe { self.queue.spawn::<_, true>(future, location) }
}
#[track_caller]
pub fn spawn_detached<F: Future + 'static>(&self, future: F) {
unsafe { self.spawn_detached_at(future, Location::caller()) }
}
pub(crate) unsafe fn spawn_detached_at<F: Future>(&self, future: F, location: SpawnLocation) {
unsafe { self.queue.spawn_detached::<_, true>(future, location) }
}
#[inline]
pub fn awake(&self) {
if self.queue.idle.get() {
self.queue.start_busy();
}
}
pub fn poll(&self) -> PollResult {
let start = Instant::now();
if self.queue.idle.get() {
self.queue.busy.start(start);
}
let result = if self.stop.get() {
PollResult::Ready
} else if self.queue.run() {
self.queue.add_poll(true);
PollResult::PollAgain
} else {
self.queue.add_poll(false);
PollResult::Pending
};
let now = Instant::now();
if result != PollResult::Ready {
self.queue
.poll_histogram
.record(now.saturating_duration_since(start));
}
if result == PollResult::PollAgain {
self.queue.set_poll_start(now);
self.queue.busy.next_turn(now);
} else {
self.queue.idle.set(true);
self.queue.busy.stop(now);
}
result
}
#[inline]
pub fn now(&self) -> Instant {
if self.queue.idle.get() {
Instant::now()
} else {
self.queue.poll_start.get()
}
}
pub fn busy_duration(&self) -> Duration {
self.queue.busy.busy_duration()
}
pub fn current_turn(&self) -> Option<Duration> {
self.queue.busy.current_turn()
}
pub fn poll_count(&self) -> u64 {
self.queue.polls.load(Ordering::Relaxed)
}
pub fn poll_again_count(&self) -> u64 {
self.queue.polls_again.load(Ordering::Relaxed)
}
pub fn self_wake_count(&self) -> u64 {
self.queue.self_wakes.load(Ordering::Relaxed)
}
pub fn poll_histogram(&self) -> Histogram {
self.queue.poll_histogram.snapshot()
}
#[track_caller]
pub fn block_on<F: Future>(&self, future: F, driver: &dyn Driver) -> F::Output {
#[cold]
#[track_caller]
fn nested_block_on() -> ! {
panic!("Cannot start a runtime from within a runtime")
}
if CURRENT_RUNTIME.is_set() {
nested_block_on()
}
let location = Location::caller();
self.stop.set(false);
self.queue.set_poll_start(Instant::now());
CURRENT_RUNTIME.set(self, || {
let mut result = None;
let slot = BlockOnSlot(Rc::new(RefCell::new(Some(Box::pin(async {
result = Some(future.await);
self.stop.set(true);
self.queue.notify();
})))));
let task = slot.0.clone();
unsafe {
self.spawn_detached_at(
poll_fn(move |cx| {
let mut fut = task.borrow_mut();
if let Some(f) = fut.as_mut()
&& f.as_mut().poll(cx).is_pending()
{
return Poll::Pending;
}
*fut = None;
Poll::Ready(())
}),
location,
);
}
ntex_error::set_backtrace_start_alt("src/rt/task.rs", 0);
let res = driver.run(self);
drop(slot);
res.expect("Driver failed");
result.expect("Driver failed to poll")
})
}
}
struct BlockOnSlot<F>(Rc<RefCell<Option<Pin<Box<F>>>>>);
impl<F> Drop for BlockOnSlot<F> {
fn drop(&mut self) {
let fut = self.0.borrow_mut().take();
drop(fut);
}
}
impl Drop for Runtime {
fn drop(&mut self) {
CURRENT_RUNTIME.set(self, || {
self.queue.shutdown();
});
}
}
#[derive(Debug)]
pub struct Handle {
queue: Arc<RunnableQueue>,
}
impl Handle {
pub fn current() -> Handle {
Runtime::with_current(Runtime::handle)
}
pub fn notify(&self) -> io::Result<()> {
self.queue.handle.notify()
}
pub fn busy_duration(&self) -> Option<Duration> {
Some(self.queue.busy.busy_duration())
}
pub fn current_turn(&self) -> Option<Duration> {
self.queue.busy.current_turn()
}
pub fn poll_count(&self) -> Option<u64> {
Some(self.queue.polls.load(Ordering::Relaxed))
}
pub fn poll_again_count(&self) -> Option<u64> {
Some(self.queue.polls_again.load(Ordering::Relaxed))
}
pub fn self_wake_count(&self) -> Option<u64> {
Some(self.queue.self_wakes.load(Ordering::Relaxed))
}
pub fn current_task(&self) -> Option<&'static Location<'static>> {
let task = self.queue.current.load(Ordering::Relaxed);
unsafe { task.as_ref() }
}
pub fn poll_histogram(&self) -> Option<Histogram> {
Some(self.queue.poll_histogram.snapshot())
}
#[track_caller]
pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send,
{
unsafe { self.queue.spawn::<_, false>(future, Location::caller()) }
}
#[track_caller]
pub fn spawn_detached<F>(&self, future: F)
where
F: Future + Send + 'static,
{
unsafe {
self.queue
.spawn_detached::<_, false>(future, Location::caller());
}
}
}
impl Clone for Handle {
fn clone(&self) -> Self {
Self {
queue: self.queue.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct RuntimeBuilder {
event_interval: usize,
self_wake_limit: u32,
self_wake_time: Duration,
}
impl Default for RuntimeBuilder {
fn default() -> Self {
Self::new()
}
}
impl RuntimeBuilder {
pub fn new() -> Self {
Self {
event_interval: 61,
self_wake_limit: 10_000,
self_wake_time: Duration::from_secs(1),
}
}
pub fn event_interval(&mut self, val: usize) -> &mut Self {
self.event_interval = val.max(1);
self
}
pub fn self_wake_threshold(&mut self, count: u32, duration: Duration) -> &mut Self {
self.self_wake_limit = count.min(!REPORTED);
self.self_wake_time = duration;
self
}
pub fn build(&self, handle: Box<dyn Notify>) -> Runtime {
Runtime::with_builder(self, handle)
}
}
#[cfg(test)]
mod task_tests;
#[cfg(all(test, ntex_loom))]
mod task_loom;
#[cfg(test)]
mod tests;