Skip to content

Commit 73ccf73

Browse files
committed
telemetry: add apply_generic to telemetry context
This allows user to skip the boxing step and save a memory allocation. This is configure with `generic=true` in attribute. For example ``` #[span_fn("async_trait_span", generic = true)] ```
1 parent 38ca892 commit 73ccf73

File tree

3 files changed

+140
-2
lines changed

3 files changed

+140
-2
lines changed

foundations-macros/src/span_fn.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ struct Options {
4444

4545
#[darling(default = "Options::default_async_local")]
4646
async_local: bool,
47+
48+
#[darling(default = "Options::default_generic")]
49+
generic: bool,
4750
}
4851

4952
impl Options {
@@ -54,6 +57,10 @@ impl Options {
5457
fn default_async_local() -> bool {
5558
false
5659
}
60+
61+
fn default_generic() -> bool {
62+
false
63+
}
5764
}
5865

5966
struct Args {
@@ -176,6 +183,8 @@ fn try_async_trait_fn_rewrite(args: &Args, body: &Block) -> Option<TokenStream2>
176183
fn wrap_with_span(args: &Args, block: TokenStream2) -> TokenStream2 {
177184
let apply_fn = if args.options.async_local {
178185
quote!(apply_local)
186+
} else if args.options.generic {
187+
quote!(apply_generic)
179188
} else {
180189
quote!(apply)
181190
};
@@ -321,6 +330,38 @@ mod tests {
321330
assert_eq!(actual, expected);
322331
}
323332

333+
#[test]
334+
fn expand_async_fn_generic() {
335+
let args = parse_attr! {
336+
#[span_fn("async_span", generic = true)]
337+
};
338+
339+
let item_fn = parse_quote! {
340+
async fn do_async() -> io::Result<String> {
341+
do_something_else().await;
342+
343+
Ok("foo".into())
344+
}
345+
};
346+
347+
let actual = expand_from_parsed(args, item_fn).to_string();
348+
349+
let expected = code_str! {
350+
async fn do_async<>() -> io::Result<String> {
351+
::foundations::telemetry::tracing::span("async_span")
352+
.into_context()
353+
.apply_generic(async move {{
354+
do_something_else().await;
355+
356+
Ok("foo".into())
357+
}})
358+
.await
359+
}
360+
};
361+
362+
assert_eq!(actual, expected);
363+
}
364+
324365
#[test]
325366
fn expand_async_trait_fn() {
326367
let args = parse_attr! {
@@ -453,6 +494,72 @@ mod tests {
453494
assert_eq!(actual, expected);
454495
}
455496

497+
#[test]
498+
fn expand_async_trait_fn_generic() {
499+
let args = parse_attr! {
500+
#[span_fn("async_trait_span", generic = true)]
501+
};
502+
503+
let item_fn = parse_quote! {
504+
fn test<'life0, 'async_trait>(
505+
&'life0 self,
506+
) -> ::core::pin::Pin<
507+
Box<dyn ::core::future::Future<Output = String> + ::core::marker::Send + 'async_trait>
508+
>
509+
where
510+
'life0: 'async_trait,
511+
Self: 'async_trait,
512+
{
513+
Box::pin(async move {
514+
if let ::core::option::Option::Some(__ret) = ::core::option::Option::None::<String> {
515+
return __ret;
516+
}
517+
let __self = self;
518+
let __ret: String = {
519+
__self.do_something_else().await;
520+
"foo".into()
521+
};
522+
#[allow(unreachable_code)]
523+
__ret
524+
})
525+
}
526+
};
527+
528+
let actual = expand_from_parsed(args, item_fn).to_string();
529+
530+
let expected = code_str! {
531+
fn test<'life0, 'async_trait>(
532+
&'life0 self,
533+
) -> ::core::pin::Pin<
534+
Box<dyn ::core::future::Future<Output = String> + ::core::marker::Send + 'async_trait>
535+
>
536+
where
537+
'life0: 'async_trait,
538+
Self: 'async_trait,
539+
{
540+
Box::pin(async move {
541+
::foundations::telemetry::tracing::span("async_trait_span")
542+
.into_context()
543+
.apply_generic(async move {
544+
if let ::core::option::Option::Some(__ret) = ::core::option::Option::None::<String> {
545+
return __ret;
546+
}
547+
let __self = self;
548+
let __ret: String = {
549+
__self.do_something_else().await;
550+
"foo".into()
551+
};
552+
#[allow(unreachable_code)]
553+
__ret
554+
})
555+
.await
556+
})
557+
}
558+
};
559+
560+
assert_eq!(actual, expected);
561+
}
562+
456563
#[test]
457564
fn expand_structure_with_crate_path() {
458565
let args = parse_attr! {

foundations/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ log = { workspace = true, optional = true }
230230
parking_lot_core = { workspace = true, optional = true }
231231
regex = { workspace = true, optional = true }
232232
thiserror = { workspace = true, optional = true }
233+
pin-project = "1.1.10"
233234

234235
[target.'cfg(target_os = "linux")'.dependencies]
235236
tikv-jemalloc-ctl = { workspace = true, optional = true, features = [

foundations/src/telemetry/telemetry_context.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::utils::feature_use;
33
use std::future::Future;
44
use std::pin::Pin;
55
use std::task::{Context, Poll};
6+
use pin_project::pin_project;
67

78
feature_use!(cfg(feature = "logging"), {
89
use super::log::internal::{current_log, fork_log, LogScope, SharedLog};
@@ -23,6 +24,24 @@ feature_use!(cfg(feature = "tracing"), {
2324
#[cfg(feature = "testing")]
2425
use super::testing::TestTelemetryContext;
2526

27+
#[pin_project]
28+
pub struct WithTelemetryContextGeneric<T> {
29+
#[pin]
30+
inner: T,
31+
ctx: TelemetryContext,
32+
}
33+
34+
impl<T: Future> Future for WithTelemetryContextGeneric<T> {
35+
type Output = T::Output;
36+
37+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
38+
let this = self.project();
39+
let _telemetry_scope = this.ctx.scope();
40+
41+
this.inner.poll(cx)
42+
}
43+
}
44+
2645
/// Wrapper for a future that provides it with [`TelemetryContext`].
2746
pub struct WithTelemetryContext<'f, T> {
2847
// NOTE: we intentionally erase type here as we can get close to the type
@@ -181,7 +200,7 @@ impl TelemetryContext {
181200
/// #[tokio::main]
182201
/// async fn main() {
183202
/// let ctx = TelemetryContext::test();
184-
///
203+
///
185204
/// {
186205
/// let _scope = ctx.scope();
187206
/// let _root = tracing::span("root");
@@ -206,7 +225,7 @@ impl TelemetryContext {
206225
/// message: "Sync hello!".into(),
207226
/// fields: vec![]
208227
/// }
209-
/// ]);
228+
/// ]);
210229
///
211230
/// assert_eq!(
212231
/// ctx.traces(Default::default()),
@@ -291,6 +310,17 @@ impl TelemetryContext {
291310
ctx: self.clone(),
292311
}
293312
}
313+
314+
/// The same as [`TelemetryContext::apply`], but for futures that are not boxed.
315+
pub fn apply_generic<F>(&self, fut: F) -> WithTelemetryContextGeneric<F>
316+
where
317+
F: Future,
318+
{
319+
WithTelemetryContextGeneric {
320+
inner: fut,
321+
ctx: self.clone(),
322+
}
323+
}
294324
}
295325

296326
#[cfg(feature = "tracing")]

0 commit comments

Comments
 (0)