use std::{
mem,
panic::Location,
pin::Pin,
sync::{
Arc,
atomic::{AtomicU64, Ordering},
},
task::{Context, Poll, Wake, Waker},
};
use tracing::Span;
use super::WakerOp;
const TARGET: &str = "compio::task";
#[derive(Debug, Clone, Copy)]
enum TaskKind {
Task,
Blocking,
BlockOn,
}
impl TaskKind {
const fn as_str(self) -> &'static str {
match self {
Self::Task => "task",
Self::Blocking => "blocking",
Self::BlockOn => "block_on",
}
}
}
fn next_task_id() -> u64 {
static NEXT: AtomicU64 = AtomicU64::new(1);
NEXT.fetch_add(1, Ordering::Relaxed)
}
thread_local! {
static THREAD: String = {
let thread = std::thread::current();
match thread.name() {
Some(name) => name.to_owned(),
None => format!("{:?}", thread.id()),
}
};
}
fn spawn_span(
kind: TaskKind,
size: usize,
loc: &'static Location<'static>,
name: Option<&'static str>,
) -> Span {
fn build(
kind: TaskKind,
size: usize,
loc: &'static Location<'static>,
name: Option<&'static str>,
thread: &str,
) -> Span {
tracing::trace_span!(
target: TARGET,
parent: None,
"runtime.spawn",
kind = kind.as_str(),
task.id = next_task_id(),
task.name = name,
size.bytes = size,
thread = thread,
loc.file = loc.file(),
loc.line = loc.line(),
loc.col = loc.column(),
)
}
THREAD
.try_with(|thread| build(kind, size, loc, name, thread.as_str()))
.unwrap_or_else(|_| build(kind, size, loc, name, ""))
}
fn waker_op(id: u64, op: WakerOp) {
tracing::trace!(target: "runtime::waker", op = op.as_str(), task.id = id);
}
#[derive(Debug, Clone, Copy)]
pub struct SpawnMeta(Option<Reported>);
#[derive(Debug, Clone, Copy)]
struct Reported {
loc: &'static Location<'static>,
name: Option<&'static str>,
}
impl SpawnMeta {
#[inline]
#[track_caller]
pub fn capture() -> Self {
Self(Some(Reported {
loc: Location::caller(),
name: None,
}))
}
#[inline]
pub fn named(self, name: &'static str) -> Self {
Self(self.0.map(|it| Reported {
name: Some(name),
..it
}))
}
#[inline]
pub fn untracked() -> Self {
Self(None)
}
fn span(self, kind: TaskKind, size: usize) -> Span {
match self.0 {
Some(it) => spawn_span(kind, size, it.loc, it.name),
None => Span::none(),
}
}
}
pub(crate) type EnterGuard<'a> = tracing::span::Entered<'a>;
#[derive(Debug)]
pub(crate) struct TaskSpan(Span);
impl TaskSpan {
pub(crate) fn new<F>(meta: SpawnMeta) -> Self {
Self(meta.span(TaskKind::Task, mem::size_of::<F>()))
}
#[inline]
pub(crate) fn enter(&self) -> EnterGuard<'_> {
self.0.enter()
}
#[inline]
pub(crate) fn waker_op(&self, op: WakerOp) {
if let Some(id) = self.0.id() {
waker_op(id.into_u64(), op);
}
}
}
struct ShimWaker {
inner: Waker,
id: u64,
}
impl ShimWaker {
fn waker(inner: Waker, id: u64) -> Waker {
let this = Arc::new(Self { inner, id });
this.report(WakerOp::Clone);
Waker::from(this)
}
#[inline]
fn report(&self, op: WakerOp) {
waker_op(self.id, op);
}
}
impl Wake for ShimWaker {
fn wake(self: Arc<Self>) {
self.wake_by_ref();
}
fn wake_by_ref(self: &Arc<Self>) {
self.report(WakerOp::WakeByRef);
self.inner.wake_by_ref();
}
}
impl Drop for ShimWaker {
fn drop(&mut self) {
self.report(WakerOp::Drop);
}
}
struct BlockOn<F> {
span: Span,
id: Option<u64>,
waker: Option<(Waker, Waker)>,
fut: F,
}
impl<F: Future> Future for BlockOn<F> {
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = unsafe { self.get_unchecked_mut() };
let fut = unsafe { Pin::new_unchecked(&mut this.fut) };
let _entered = this.span.enter();
let Some(id) = this.id else {
return fut.poll(cx);
};
if !matches!(&this.waker, Some((given, _)) if given.will_wake(cx.waker())) {
let given = cx.waker().clone();
let shim = ShimWaker::waker(given.clone(), id);
this.waker = Some((given, shim));
}
let shim = &this.waker.as_ref().expect("waker was just set").1;
fut.poll(&mut Context::from_waker(shim))
}
}
#[doc(hidden)]
pub fn instrument_blocking<T, F: FnOnce() -> T>(meta: SpawnMeta, f: F) -> impl FnOnce() -> T {
let span = meta.span(TaskKind::Blocking, mem::size_of::<F>());
move || {
let _entered = span.enter();
f()
}
}
#[doc(hidden)]
pub fn instrument_execute<F: Future>(meta: SpawnMeta, fut: F) -> impl Future<Output = F::Output> {
instrument_block_on(meta, fut)
}
#[doc(hidden)]
pub fn instrument_block_on<F: Future>(meta: SpawnMeta, fut: F) -> impl Future<Output = F::Output> {
let span = meta.span(TaskKind::BlockOn, mem::size_of::<F>());
let id = span.id().map(|id| id.into_u64());
BlockOn {
span,
id,
waker: None,
fut,
}
}