Skip to content

Commit 998ce3c

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)] ``` Also add a feature `generic-telemetry-wrapper` so user can enable this behavior by default.
1 parent 8e61b18 commit 998ce3c

File tree

5 files changed

+154
-2
lines changed

5 files changed

+154
-2
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ once_cell = "1.5"
4747
tonic = { version = "0.12", default-features = false }
4848
opentelemetry-proto = "0.7"
4949
parking_lot = "0.12.1"
50+
pin-project-lite = "0.2.16"
5051
proc-macro2 = { version = "1", default-features = false }
5152
prometheus = { version = "0.13.3", default-features = false }
5253
prometheus-client = "0.18.1"

foundations-macros/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ readme = "../README.md"
1111
[features]
1212
default = ["settings_deny_unknown_fields_by_default"]
1313
settings_deny_unknown_fields_by_default = []
14+
# Enable generic telemetry wrapper for futures
15+
generic-telemetry-wrapper = []
1416

1517
[lib]
1618
proc-macro = true

foundations-macros/src/span_fn.rs

Lines changed: 113 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,16 @@ impl Options {
5457
fn default_async_local() -> bool {
5558
false
5659
}
60+
61+
#[cfg(feature = "generic-telemetry-wrapper")]
62+
fn default_generic() -> bool {
63+
true
64+
}
65+
66+
#[cfg(not(feature = "generic-telemetry-wrapper"))]
67+
fn default_generic() -> bool {
68+
false
69+
}
5770
}
5871

5972
struct Args {
@@ -176,6 +189,8 @@ fn try_async_trait_fn_rewrite(args: &Args, body: &Block) -> Option<TokenStream2>
176189
fn wrap_with_span(args: &Args, block: TokenStream2) -> TokenStream2 {
177190
let apply_fn = if args.options.async_local {
178191
quote!(apply_local)
192+
} else if args.options.generic {
193+
quote!(apply_generic)
179194
} else {
180195
quote!(apply)
181196
};
@@ -321,6 +336,38 @@ mod tests {
321336
assert_eq!(actual, expected);
322337
}
323338

339+
#[test]
340+
fn expand_async_fn_generic() {
341+
let args = parse_attr! {
342+
#[span_fn("async_span", generic = true)]
343+
};
344+
345+
let item_fn = parse_quote! {
346+
async fn do_async() -> io::Result<String> {
347+
do_something_else().await;
348+
349+
Ok("foo".into())
350+
}
351+
};
352+
353+
let actual = expand_from_parsed(args, item_fn).to_string();
354+
355+
let expected = code_str! {
356+
async fn do_async<>() -> io::Result<String> {
357+
::foundations::telemetry::tracing::span("async_span")
358+
.into_context()
359+
.apply_generic(async move {{
360+
do_something_else().await;
361+
362+
Ok("foo".into())
363+
}})
364+
.await
365+
}
366+
};
367+
368+
assert_eq!(actual, expected);
369+
}
370+
324371
#[test]
325372
fn expand_async_trait_fn() {
326373
let args = parse_attr! {
@@ -453,6 +500,72 @@ mod tests {
453500
assert_eq!(actual, expected);
454501
}
455502

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

foundations/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ settings = [
6060
# Whether settings structs annotated with `#[settings]` will, by default, error on unknown fields.
6161
settings_deny_unknown_fields_by_default = ["foundations-macros?/settings_deny_unknown_fields_by_default"]
6262

63+
# Enable generic telemetry wrapper for futures
64+
generic-telemetry-wrapper = ["foundations-macros?/generic-telemetry-wrapper"]
65+
6366
# Enables all the telemetry-related features ("logging", "metrics", "tracing", "telemetry-server").
6467
telemetry = [
6568
"logging",
@@ -233,6 +236,7 @@ log = { workspace = true, optional = true }
233236
parking_lot_core = { workspace = true, optional = true }
234237
regex = { workspace = true, optional = true }
235238
thiserror = { workspace = true, optional = true }
239+
pin-project-lite = { workspace = true }
236240

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

foundations/src/telemetry/telemetry_context.rs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::TelemetryScope;
22
use crate::utils::feature_use;
3+
use pin_project_lite::pin_project;
34
use std::future::Future;
45
use std::pin::Pin;
56
use std::task::{Context, Poll};
@@ -61,6 +62,26 @@ impl<T> Future for WithTelemetryContextLocal<'_, T> {
6162
}
6263
}
6364

65+
pin_project! {
66+
/// The same as [`WithTelemetryContext`], but for futures that are not boxed
67+
pub struct WithTelemetryContextGeneric<T> {
68+
#[pin]
69+
inner: T,
70+
ctx: TelemetryContext,
71+
}
72+
}
73+
74+
impl<T: Future> Future for WithTelemetryContextGeneric<T> {
75+
type Output = T::Output;
76+
77+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
78+
let this = self.project();
79+
let _telemetry_scope = this.ctx.scope();
80+
81+
this.inner.poll(cx)
82+
}
83+
}
84+
6485
/// Implicit context for logging and tracing.
6586
///
6687
/// Current context can be obtained with the [`TelemetryContext::current`] method.
@@ -181,7 +202,7 @@ impl TelemetryContext {
181202
/// #[tokio::main]
182203
/// async fn main() {
183204
/// let ctx = TelemetryContext::test();
184-
///
205+
///
185206
/// {
186207
/// let _scope = ctx.scope();
187208
/// let _root = tracing::span("root");
@@ -206,7 +227,7 @@ impl TelemetryContext {
206227
/// message: "Sync hello!".into(),
207228
/// fields: vec![]
208229
/// }
209-
/// ]);
230+
/// ]);
210231
///
211232
/// assert_eq!(
212233
/// ctx.traces(Default::default()),
@@ -291,6 +312,17 @@ impl TelemetryContext {
291312
ctx: self.clone(),
292313
}
293314
}
315+
316+
/// The same as [`TelemetryContext::apply`], but for futures that are not boxed.
317+
pub fn apply_generic<F>(&self, fut: F) -> WithTelemetryContextGeneric<F>
318+
where
319+
F: Future,
320+
{
321+
WithTelemetryContextGeneric {
322+
inner: fut,
323+
ctx: self.clone(),
324+
}
325+
}
294326
}
295327

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

0 commit comments

Comments
 (0)