Skip to content

Commit 7cb43f3

Browse files
dqminhTheJokr
authored andcommitted
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 cfg `foundations_generic_telemetry_wrapper` so user can enable this behavior by default.
1 parent 02531c5 commit 7cb43f3

File tree

5 files changed

+155
-4
lines changed

5 files changed

+155
-4
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.13", default-features = false }
4848
opentelemetry-proto = "0.30"
4949
parking_lot = "0.12"
50+
pin-project-lite = "0.2.16"
5051
proc-macro2 = { version = "1", default-features = false }
5152
prometheus = { version = "0.14", default-features = false }
5253
prometheus-client = "0.18"

foundations-macros/src/span_fn.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// NOTE: required to allow `foundations_generic_telemetry_wrapper` cfg
2+
#![allow(unexpected_cfgs)]
3+
14
use crate::common::parse_optional_trailing_meta_list;
25
use darling::FromMeta;
36
use proc_macro::TokenStream;
@@ -44,6 +47,9 @@ struct Options {
4447

4548
#[darling(default = "Options::default_async_local")]
4649
async_local: bool,
50+
51+
#[darling(default = "Options::default_generic")]
52+
generic: bool,
4753
}
4854

4955
impl Options {
@@ -54,6 +60,10 @@ impl Options {
5460
fn default_async_local() -> bool {
5561
false
5662
}
63+
64+
fn default_generic() -> bool {
65+
cfg!(foundations_generic_telemetry_wrapper)
66+
}
5767
}
5868

5969
struct Args {
@@ -176,6 +186,8 @@ fn try_async_trait_fn_rewrite(args: &Args, body: &Block) -> Option<TokenStream2>
176186
fn wrap_with_span(args: &Args, block: TokenStream2) -> TokenStream2 {
177187
let apply_fn = if args.options.async_local {
178188
quote!(apply_local)
189+
} else if args.options.generic {
190+
quote!(apply_generic)
179191
} else {
180192
quote!(apply)
181193
};
@@ -321,6 +333,38 @@ mod tests {
321333
assert_eq!(actual, expected);
322334
}
323335

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

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

foundations/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,10 @@ tracing-rs-compat = ["dep:tracing-slog"]
175175

176176
[package.metadata.docs.rs]
177177
all-features = true
178-
rustdoc-args = ["--cfg", "docsrs", "--cfg", "tokio_unstable", "--cfg", "foundations_unstable"]
178+
rustdoc-args = ["--cfg", "docsrs", "--cfg", "tokio_unstable", "--cfg", "foundations_unstable", "--cfg", "foundations_generic_telemetry_wrapper"]
179179
# it's necessary to _also_ pass `--cfg tokio_unstable` and `--cfg foundations_unstable`
180180
# to rustc, or else dependencies will not be enabled, and the docs build will fail.
181-
rustc-args = ["--cfg", "tokio_unstable", "--cfg", "foundations_unstable"]
181+
rustc-args = ["--cfg", "tokio_unstable", "--cfg", "foundations_unstable", "--cfg", "foundations_generic_telemetry_wrapper"]
182182

183183
[dependencies]
184184
anyhow = { workspace = true, features = ["backtrace", "std"] }
@@ -235,6 +235,7 @@ parking_lot_core = { workspace = true, optional = true }
235235
regex = { workspace = true, optional = true }
236236
thiserror = { workspace = true, optional = true }
237237
tower = { workspace = true, optional = true }
238+
pin-project-lite = { workspace = true }
238239

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

foundations/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@
4343
//! - **cli**: Enables command line interface (CLI) functionality. Implicitly enabled **settings**
4444
//! feature.
4545
//!
46+
//! # Generic telemetry
47+
//! Foundations currently box the future with TelemetryContext by default. A default generic
48+
//! wrapper is gated behind `--cfg foundations_generic_telemetry_wrapper`.
49+
//!
50+
//! To enable this, you must add `--cfg foundations_generic_telemetry_wrapper` to your RUSTFLAGS
51+
//! environment variable.
52+
//!
4653
//! # Unstable Features
4754
//! Foundations has unstable features which are gated behind `--cfg foundations_unstable`:
4855
//!

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)