use std::{
future::Future,
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
use pin_project_lite::pin_project;
use crate::{
observer::{Observer, with_observer_task_sync},
scope::{ScopeFrame, finish_scope_frame, pop_frame, push_frame},
};
pin_project! {
#[must_use = "Instrumented<F> is a future; await it to drive the inner future"]
pub struct Instrumented<F> {
#[pin]
inner: F,
scope_frame: Option<ScopeFrame>,
observer: Option<Arc<dyn Observer>>,
}
}
impl<F> std::fmt::Debug for Instrumented<F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Instrumented")
.field("has_scope", &self.scope_frame.is_some())
.field("has_observer", &self.observer.is_some())
.finish()
}
}
impl<F: Future> Future for Instrumented<F> {
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<F::Output> {
let this = self.project();
let mut scope_guard = PollScopeGuard::enter(this.scope_frame);
let result = match this.observer.as_ref() {
Some(o) => with_observer_task_sync(o.clone(), || this.inner.poll(cx)),
None => this.inner.poll(cx),
};
if result.is_ready() {
scope_guard.finish_on_drop();
}
result
}
}
struct PollScopeGuard<'a> {
slot: &'a mut Option<ScopeFrame>,
active: bool,
finished: bool,
}
impl<'a> PollScopeGuard<'a> {
fn enter(slot: &'a mut Option<ScopeFrame>) -> Self {
let Some(frame) = slot.take() else {
return Self {
slot,
active: false,
finished: false,
};
};
let _ = push_frame(frame);
Self {
slot,
active: true,
finished: false,
}
}
fn finish_on_drop(&mut self) {
self.finished = true;
}
}
impl Drop for PollScopeGuard<'_> {
fn drop(&mut self) {
if self.active
&& let Some(frame) = pop_frame()
{
if self.finished {
finish_scope_frame(frame);
} else {
*self.slot = Some(frame);
}
}
}
}
pub trait Instrument: Future + Sized {
fn instrument(self, scope: ScopeFrame) -> Instrumented<Self> {
Instrumented {
inner: self,
scope_frame: Some(scope),
observer: None,
}
}
}
impl<F: Future> Instrument for F {}
pub trait WithObserver: Future + Sized {
fn with_observer(self, observer: Arc<dyn Observer>) -> Instrumented<Self> {
Instrumented {
inner: self,
scope_frame: None,
observer: Some(observer),
}
}
}
impl<F: Future> WithObserver for F {}
impl<F: Future> Instrumented<F> {
pub fn instrument(mut self, scope: ScopeFrame) -> Self {
self.scope_frame = Some(scope);
self
}
pub fn with_observer(mut self, observer: Arc<dyn Observer>) -> Self {
self.observer = Some(observer);
self
}
}