blob: c3a39c355fc88db994c377cec7d1eace4a5d1afd [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use futures::future::BoxFuture;
use futures::FutureExt;
use std::any::Any;
use std::error::Error;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::future::Future;
use tokio::sync::OnceCell;
/// A trait for injecting instrumentation into either asynchronous futures or
/// blocking closures at runtime.
pub trait JoinSetTracer: Send + Sync + 'static {
/// Function pointer type for tracing a future.
///
/// This function takes a boxed future (with its output type erased)
/// and returns a boxed future (with its output still erased). The
/// tracer must apply instrumentation without altering the output.
fn trace_future(
&self,
fut: BoxFuture<'static, Box<dyn Any + Send>>,
) -> BoxFuture<'static, Box<dyn Any + Send>>;
/// Function pointer type for tracing a blocking closure.
///
/// This function takes a boxed closure (with its return type erased)
/// and returns a boxed closure (with its return type still erased). The
/// tracer must apply instrumentation without changing the return value.
fn trace_block(
&self,
f: Box<dyn FnOnce() -> Box<dyn Any + Send> + Send>,
) -> Box<dyn FnOnce() -> Box<dyn Any + Send> + Send>;
}
/// A no-op tracer that does not modify or instrument any futures or closures.
/// This is used as a fallback if no custom tracer is set.
struct NoopTracer;
impl JoinSetTracer for NoopTracer {
fn trace_future(
&self,
fut: BoxFuture<'static, Box<dyn Any + Send>>,
) -> BoxFuture<'static, Box<dyn Any + Send>> {
fut
}
fn trace_block(
&self,
f: Box<dyn FnOnce() -> Box<dyn Any + Send> + Send>,
) -> Box<dyn FnOnce() -> Box<dyn Any + Send> + Send> {
f
}
}
/// A custom error type for tracer injection failures.
#[derive(Debug)]
pub enum JoinSetTracerError {
/// The global tracer has already been set.
AlreadySet,
}
impl Display for JoinSetTracerError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match self {
JoinSetTracerError::AlreadySet => {
write!(f, "The global JoinSetTracer is already set")
}
}
}
}
impl Error for JoinSetTracerError {}
/// Global storage for an injected tracer. If no tracer is injected, a no-op
/// tracer is used instead. This ensures that calls to [`trace_future`] or
/// [`trace_block`] never panic due to missing instrumentation.
static GLOBAL_TRACER: OnceCell<&'static dyn JoinSetTracer> = OnceCell::const_new();
/// A no-op tracer singleton that is returned by [`get_tracer`] if no custom
/// tracer has been registered.
static NOOP_TRACER: NoopTracer = NoopTracer;
/// Return the currently registered tracer, or the no-op tracer if none was
/// registered.
#[inline]
fn get_tracer() -> &'static dyn JoinSetTracer {
GLOBAL_TRACER.get().copied().unwrap_or(&NOOP_TRACER)
}
/// Set the custom tracer for both futures and blocking closures.
///
/// This should be called once at startup. If called more than once, an
/// `Err(JoinSetTracerError)` is returned. If not called at all, a no-op tracer that does nothing
/// is used.
pub fn set_join_set_tracer(
tracer: &'static dyn JoinSetTracer,
) -> Result<(), JoinSetTracerError> {
GLOBAL_TRACER
.set(tracer)
.map_err(|_set_err| JoinSetTracerError::AlreadySet)
}
/// Optionally instruments a future with custom tracing.
///
/// If a tracer has been injected via `set_tracer`, the future's output is
/// boxed (erasing its type), passed to the tracer, and then downcast back
/// to the expected type. If no tracer is set, the original future is returned.
///
/// # Type Parameters
/// * `T` - The concrete output type of the future.
/// * `F` - The future type.
///
/// # Parameters
/// * `future` - The future to potentially instrument.
pub fn trace_future<T, F>(future: F) -> BoxFuture<'static, T>
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
// Erase the future’s output type first:
let erased_future = async move {
let result = future.await;
Box::new(result) as Box<dyn Any + Send>
}
.boxed();
// Forward through the global tracer:
get_tracer()
.trace_future(erased_future)
// Downcast from `Box<dyn Any + Send>` back to `T`:
.map(|any_box| {
*any_box
.downcast::<T>()
.expect("Tracer must preserve the future’s output type!")
})
.boxed()
}
/// Optionally instruments a blocking closure with custom tracing.
///
/// If a tracer has been injected via `set_tracer`, the closure is wrapped so that
/// its return value is boxed (erasing its type), passed to the tracer, and then the
/// result is downcast back to the original type. If no tracer is set, the closure is
/// returned unmodified (except for being boxed).
///
/// # Type Parameters
/// * `T` - The concrete return type of the closure.
/// * `F` - The closure type.
///
/// # Parameters
/// * `f` - The blocking closure to potentially instrument.
pub fn trace_block<T, F>(f: F) -> Box<dyn FnOnce() -> T + Send>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
{
// Erase the closure’s return type first:
let erased_closure = Box::new(|| {
let result = f();
Box::new(result) as Box<dyn Any + Send>
});
// Forward through the global tracer:
let traced_closure = get_tracer().trace_block(erased_closure);
// Downcast from `Box<dyn Any + Send>` back to `T`:
Box::new(move || {
let any_box = traced_closure();
*any_box
.downcast::<T>()
.expect("Tracer must preserve the closure’s return type!")
})
}