Skip to content

Commit 57b4a47

Browse files
authored
attributes: fix #[instrument] skipping code when returning pinned futures (#1297)
Fixes #1296. I had forgotten to use all the input statements in #1228, so we would delete nearly all the statement of sync functions that return a boxed future :/
1 parent ba57971 commit 57b4a47

File tree

2 files changed

+109
-38
lines changed

2 files changed

+109
-38
lines changed

tracing-attributes/src/lib.rs

Lines changed: 43 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -264,39 +264,45 @@ pub fn instrument(
264264
// the future instead of the wrapper
265265
if let Some(internal_fun) = get_async_trait_info(&input.block, input.sig.asyncness.is_some()) {
266266
// let's rewrite some statements!
267-
let mut out_stmts = Vec::with_capacity(input.block.stmts.len());
268-
for stmt in &input.block.stmts {
269-
if stmt == internal_fun.source_stmt {
270-
match internal_fun.kind {
271-
// async-trait <= 0.1.43
272-
AsyncTraitKind::Function(fun) => {
273-
out_stmts.push(gen_function(
274-
fun,
275-
args,
276-
instrumented_function_name,
277-
internal_fun.self_type,
278-
));
279-
}
280-
// async-trait >= 0.1.44
281-
AsyncTraitKind::Async(async_expr) => {
282-
// fallback if we couldn't find the '__async_trait' binding, might be
283-
// useful for crates exhibiting the same behaviors as async-trait
284-
let instrumented_block = gen_block(
285-
&async_expr.block,
286-
&input.sig.inputs,
287-
true,
288-
args,
289-
instrumented_function_name,
290-
None,
291-
);
292-
let async_attrs = &async_expr.attrs;
293-
out_stmts.push(quote! {
294-
Box::pin(#(#async_attrs) * async move { #instrumented_block })
295-
});
267+
let mut out_stmts: Vec<TokenStream> = input
268+
.block
269+
.stmts
270+
.iter()
271+
.map(|stmt| stmt.to_token_stream())
272+
.collect();
273+
274+
if let Some((iter, _stmt)) = input
275+
.block
276+
.stmts
277+
.iter()
278+
.enumerate()
279+
.find(|(_iter, stmt)| *stmt == internal_fun.source_stmt)
280+
{
281+
// instrument the future by rewriting the corresponding statement
282+
out_stmts[iter] = match internal_fun.kind {
283+
// async-trait <= 0.1.43
284+
AsyncTraitKind::Function(fun) => gen_function(
285+
fun,
286+
args,
287+
instrumented_function_name.as_str(),
288+
internal_fun.self_type.as_ref(),
289+
),
290+
// async-trait >= 0.1.44
291+
AsyncTraitKind::Async(async_expr) => {
292+
let instrumented_block = gen_block(
293+
&async_expr.block,
294+
&input.sig.inputs,
295+
true,
296+
args,
297+
instrumented_function_name.as_str(),
298+
None,
299+
);
300+
let async_attrs = &async_expr.attrs;
301+
quote! {
302+
Box::pin(#(#async_attrs) * async move { #instrumented_block })
296303
}
297304
}
298-
break;
299-
}
305+
};
300306
}
301307

302308
let vis = &input.vis;
@@ -310,16 +316,16 @@ pub fn instrument(
310316
)
311317
.into()
312318
} else {
313-
gen_function(&input, args, instrumented_function_name, None).into()
319+
gen_function(&input, args, instrumented_function_name.as_str(), None).into()
314320
}
315321
}
316322

317323
/// Given an existing function, generate an instrumented version of that function
318324
fn gen_function(
319325
input: &ItemFn,
320326
args: InstrumentArgs,
321-
instrumented_function_name: String,
322-
self_type: Option<syn::TypePath>,
327+
instrumented_function_name: &str,
328+
self_type: Option<&syn::TypePath>,
323329
) -> proc_macro2::TokenStream {
324330
// these are needed ahead of time, as ItemFn contains the function body _and_
325331
// isn't representable inside a quote!/quote_spanned! macro
@@ -377,8 +383,8 @@ fn gen_block(
377383
params: &Punctuated<FnArg, Token![,]>,
378384
async_context: bool,
379385
mut args: InstrumentArgs,
380-
instrumented_function_name: String,
381-
self_type: Option<syn::TypePath>,
386+
instrumented_function_name: &str,
387+
self_type: Option<&syn::TypePath>,
382388
) -> proc_macro2::TokenStream {
383389
let err = args.err;
384390

@@ -465,7 +471,7 @@ fn gen_block(
465471
// when async-trait <=0.1.43 is in use, replace instances
466472
// of the "Self" type inside the fields values
467473
if let Some(self_type) = self_type {
468-
replacer.types.push(("Self", self_type));
474+
replacer.types.push(("Self", self_type.clone()));
469475
}
470476

471477
for e in fields.iter_mut().filter_map(|f| f.value.as_mut()) {

tracing-attributes/tests/async_fn.rs

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
mod support;
55
use support::*;
66

7+
use std::{future::Future, pin::Pin, sync::Arc};
78
use tracing::collect::with_default;
89
use tracing_attributes::instrument;
910

@@ -214,14 +215,28 @@ fn async_fn_with_async_trait_and_fields_expressions_with_generic_parameter() {
214215
#[derive(Clone, Debug)]
215216
struct TestImpl;
216217

218+
// we also test sync functions that return futures, as they should be handled just like
219+
// async-trait (>= 0.1.44) functions
220+
impl TestImpl {
221+
#[instrument(fields(Self=std::any::type_name::<Self>()))]
222+
fn sync_fun(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
223+
let val = self.clone();
224+
Box::pin(async move {
225+
let _ = val;
226+
})
227+
}
228+
}
229+
217230
#[async_trait]
218231
impl Test for TestImpl {
219232
// instrumenting this is currently not possible, see https://github.com/tokio-rs/tracing/issues/864#issuecomment-667508801
220233
//#[instrument(fields(Self=std::any::type_name::<Self>()))]
221234
async fn call() {}
222235

223236
#[instrument(fields(Self=std::any::type_name::<Self>()))]
224-
async fn call_with_self(&self) {}
237+
async fn call_with_self(&self) {
238+
self.sync_fun().await;
239+
}
225240

226241
#[instrument(fields(Self=std::any::type_name::<Self>()))]
227242
async fn call_with_mut_self(&mut self) {}
@@ -230,6 +245,7 @@ fn async_fn_with_async_trait_and_fields_expressions_with_generic_parameter() {
230245
//let span = span::mock().named("call");
231246
let span2 = span::mock().named("call_with_self");
232247
let span3 = span::mock().named("call_with_mut_self");
248+
let span4 = span::mock().named("sync_fun");
233249
let (collector, handle) = collector::mock()
234250
/*.new_span(span.clone()
235251
.with_field(
@@ -243,6 +259,13 @@ fn async_fn_with_async_trait_and_fields_expressions_with_generic_parameter() {
243259
.with_field(field::mock("Self").with_value(&std::any::type_name::<TestImpl>())),
244260
)
245261
.enter(span2.clone())
262+
.new_span(
263+
span4
264+
.clone()
265+
.with_field(field::mock("Self").with_value(&std::any::type_name::<TestImpl>())),
266+
)
267+
.enter(span4.clone())
268+
.exit(span4)
246269
.exit(span2.clone())
247270
.drop_span(span2)
248271
.new_span(
@@ -266,3 +289,45 @@ fn async_fn_with_async_trait_and_fields_expressions_with_generic_parameter() {
266289

267290
handle.assert_finished();
268291
}
292+
293+
#[test]
294+
fn out_of_scope_fields() {
295+
// Reproduces tokio-rs/tracing#1296
296+
297+
struct Thing {
298+
metrics: Arc<()>,
299+
}
300+
301+
impl Thing {
302+
#[instrument(skip(self, _req), fields(app_id))]
303+
fn call(&mut self, _req: ()) -> Pin<Box<dyn Future<Output = Arc<()>> + Send + Sync>> {
304+
// ...
305+
let metrics = self.metrics.clone();
306+
// ...
307+
Box::pin(async move {
308+
// ...
309+
metrics // cannot find value `metrics` in this scope
310+
})
311+
}
312+
}
313+
314+
let span = span::mock().named("call");
315+
let (collector, handle) = collector::mock()
316+
.new_span(span.clone())
317+
.enter(span.clone())
318+
.exit(span.clone())
319+
.drop_span(span)
320+
.done()
321+
.run_with_handle();
322+
323+
with_default(collector, || {
324+
block_on_future(async {
325+
let mut my_thing = Thing {
326+
metrics: Arc::new(()),
327+
};
328+
my_thing.call(()).await;
329+
});
330+
});
331+
332+
handle.assert_finished();
333+
}

0 commit comments

Comments
 (0)