1use std::future::Future;
16use std::pin::Pin;
17use std::task::Poll;
18
19use indextree::NodeId;
20use pin_project::{pin_project, pinned_drop};
21
22use crate::context::ContextId;
23use crate::root::current_context;
24use crate::Span;
25
26enum State {
27 Initial(Span),
28 Polled {
29 this_node: NodeId,
30 this_context_id: ContextId,
31 },
32 Ready,
33 Disabled,
35}
36
37#[pin_project(PinnedDrop)]
41pub struct Instrumented<F: Future, const VERBOSE: bool> {
42 #[pin]
43 inner: F,
44 state: State,
45}
46
47impl<F: Future, const VERBOSE: bool> Instrumented<F, VERBOSE> {
48 pub(crate) fn new(inner: F, span: Span) -> Self {
49 Self {
50 inner,
51 state: State::Initial(span),
52 }
53 }
54}
55
56impl<F: Future, const VERBOSE: bool> Future for Instrumented<F, VERBOSE> {
57 type Output = F::Output;
58
59 fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
60 let this = self.project();
61 let context = current_context();
62
63 let (context, this_node) = match this.state {
64 State::Initial(span) => {
65 match context {
66 Some(c) => {
67 if !c.verbose() && VERBOSE {
68 *this.state = State::Disabled;
71 return this.inner.poll(cx);
72 }
73 let node = c.tree().push(std::mem::take(span));
75 *this.state = State::Polled {
76 this_node: node,
77 this_context_id: c.id(),
78 };
79 (c, node)
80 }
81 None => return this.inner.poll(cx),
83 }
84 }
85 State::Polled {
86 this_node,
87 this_context_id: this_context,
88 } => {
89 match context {
90 Some(c) if c.id() == *this_context => {
92 c.tree().step_in(*this_node);
94 (c, *this_node)
95 }
96 Some(_) => {
98 tracing::warn!(
99 "future polled in a different context as it was first polled"
100 );
101 return this.inner.poll(cx);
102 }
103 None => {
105 tracing::warn!(
106 "future polled not in a context, while it was when first polled"
107 );
108 return this.inner.poll(cx);
109 }
110 }
111 }
112 State::Ready => unreachable!("the instrumented future should always be fused"),
113 State::Disabled => return this.inner.poll(cx),
114 };
115
116 debug_assert_eq!(this_node, context.tree().current());
118
119 match this.inner.poll(cx) {
120 Poll::Ready(output) => {
122 context.tree().pop();
123 *this.state = State::Ready;
124 Poll::Ready(output)
125 }
126 Poll::Pending => {
128 context.tree().step_out();
129 Poll::Pending
130 }
131 }
132 }
133}
134
135#[pinned_drop]
136impl<F: Future, const VERBOSE: bool> PinnedDrop for Instrumented<F, VERBOSE> {
137 fn drop(self: Pin<&mut Self>) {
138 let this = self.project();
139
140 match this.state {
141 State::Polled {
142 this_node,
143 this_context_id,
144 } => match current_context() {
145 Some(c) if c.id() == *this_context_id => {
147 c.tree().remove_and_detach(*this_node);
148 }
149 Some(_) => {
151 tracing::warn!("future is dropped in a different context as it was first polled, cannot clean up!");
152 }
153 None => {
155 tracing::warn!("future is not in a context, while it was when first polled, cannot clean up!");
156 }
157 },
158 State::Initial(_) | State::Ready | State::Disabled => {}
159 }
160 }
161}