From 9c5cad037fd78adb087bc4734f7670d5732122c5 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 22 Mar 2019 16:21:46 -0700 Subject: [PATCH] trace-core: Add overrideable downcasting to `Subscriber`s (#974) ## Motivation In order to implement "out of band" `Subscriber` APIs in third-party subscriber implementations (see [this comment]) users may want to downcast the current `Dispatch` to a concrete subscriber type. For example, in a library for integrating `tokio-trace` with a fancy new (hypothetical) distributed tracing technology "ElizaTracing", which uses 256-bit span IDs, we might expect to see a function like this: ```rust pub fn correlate(tt: tokio_trace::span::Id, et: elizatracing::SpanId) { tokio_trace::dispatcher::with(|c| { if let Some(s) = c.downcast_ref::() { s.do_elizatracing_correlation_magic(tt, et); } }); } ``` This allows users to correlate `tokio-trace` IDs with IDs in the distributed tracing system without having to pass a special handle to the subscriber through application code (as one is already present in thread-local storage, but with its type erased). ## Solution This branch makes the following changes: * Add an object-safe `downcast_raw` method to the `Subscriber` trait, taking a `TypeId` and returning an `*const ()` if the type ID matches the subscriber's type ID, or `None` if it does not, and * Add `is` and `downcast_ref` functions to `Subscriber` and `Dispatch`, using `downcast_raw`. Unlike the approach implemented in #950, the `downcast_raw` method is object-safe, since it takes a `TypeId` rather than a type _parameter_ and returns a void pointer rather than an `&T`. This means that `Subscriber` implementations can override this method if necessary. For example, a `Subscriber` that fans out to multiple component subscribers can downcast to their component parts, and "chained" or "middleware" subscribers, which wrap an inner `Subscriber` and modify its behaviour somehow, can downcast to the inner type if they choose to. [this comment]: https://github.com/tokio-rs/tokio/issues/932#issuecomment-469473501 [`std::error::Error`'s]: https://doc.rust-lang.org/1.33.0/src/std/error.rs.html#204 Refs: #950, #953, https://github.com/tokio-rs/tokio/issues/948#issuecomment-469444293 Signed-off-by: Eliza Weisman --- .../tokio-trace-core/src/dispatcher.rs | 32 ++++++++++ .../tokio-trace-core/src/subscriber.rs | 60 +++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/tokio-trace/tokio-trace-core/src/dispatcher.rs b/tokio-trace/tokio-trace-core/src/dispatcher.rs index 840a9e668..82b945d72 100644 --- a/tokio-trace/tokio-trace-core/src/dispatcher.rs +++ b/tokio-trace/tokio-trace-core/src/dispatcher.rs @@ -6,6 +6,7 @@ use { }; use std::{ + any::Any, cell::RefCell, fmt, sync::{Arc, Weak}, @@ -226,6 +227,20 @@ impl Dispatch { pub fn drop_span(&self, id: span::Id) { self.subscriber.drop_span(id) } + + /// Returns `true` if this `Dispatch` forwards to a `Subscriber` of type + /// `T`. + #[inline] + pub fn is(&self) -> bool { + Subscriber::is::(&*self.subscriber) + } + + /// Returns some reference to the `Subscriber` this `Dispatch` forwards to + /// if it is of type `T`, or `None` if it isn't. + #[inline] + pub fn downcast_ref(&self) -> Option<&T> { + Subscriber::downcast_ref(&*self.subscriber) + } } impl fmt::Debug for Dispatch { @@ -275,3 +290,20 @@ impl Registrar { self.0.upgrade().map(|s| s.register_callsite(metadata)) } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn dispatch_is() { + let dispatcher = Dispatch::new(NoSubscriber); + assert!(dispatcher.is::()); + } + + #[test] + fn dispatch_downcasts() { + let dispatcher = Dispatch::new(NoSubscriber); + assert!(dispatcher.downcast_ref::().is_some()); + } +} diff --git a/tokio-trace/tokio-trace-core/src/subscriber.rs b/tokio-trace/tokio-trace-core/src/subscriber.rs index d56829e49..7fc07d616 100644 --- a/tokio-trace/tokio-trace-core/src/subscriber.rs +++ b/tokio-trace/tokio-trace-core/src/subscriber.rs @@ -1,6 +1,11 @@ //! Subscribers collect and record trace data. use {span, Event, Metadata}; +use std::{ + any::{Any, TypeId}, + ptr, +}; + /// Trait representing the functions required to collect trace data. /// /// Crates that provide implementations of methods for collecting or recording @@ -262,6 +267,61 @@ pub trait Subscriber: 'static { fn drop_span(&self, id: span::Id) { let _ = id; } + + // === Downcasting methods ================================================ + + /// If `self` is the same type as the provided `TypeId`, returns an untyped + /// `*const` pointer to that type. Otherwise, returns `None`. + /// + /// If you wish to downcast a `Subscriber`, it is strongly advised to use + /// the safe API provided by [`downcast_ref`] instead. + /// + /// This API is required for `downcast_raw` to be a trait method; a method + /// signature like [`downcast_ref`] (with a generic type parameter) is not + /// object-safe, and thus cannot be a trait method for `Subscriber`. This + /// means that if we only exposed `downcast_ref`, `Subscriber` + /// implementations could not override the downcasting behavior + /// + /// This method may be overridden by "fan out" or "chained" subscriber + /// implementations which consist of multiple composed types. Such + /// subscribers might allow `downcast_raw` by returning references to those + /// component if they contain components with the given `TypeId`. + /// + /// # Safety + /// + /// The [`downcast_ref`] method expects that the pointer returned by + /// `downcast_raw` is non-null and points to a valid instance of the type + /// with the provided `TypeId`. Failure to ensure this will result in + /// undefined behaviour, so implementing `downcast_raw` is unsafe. + /// + /// [`downcast_ref`]: #method.downcast_ref + unsafe fn downcast_raw(&self, id: TypeId) -> Option<*const ()> { + if id == TypeId::of::() { + Some(self as *const Self as *const ()) + } else { + None + } + } +} + +impl Subscriber { + /// Returns `true` if this `Subscriber` is the same type as `T`. + pub fn is(&self) -> bool { + self.downcast_ref::().is_some() + } + + /// Returns some reference to this `Subscriber` value if it is of type `T`, + /// or `None` if it isn't. + pub fn downcast_ref(&self) -> Option<&T> { + unsafe { + let raw = self.downcast_raw(TypeId::of::())?; + if raw == ptr::null() { + None + } else { + Some(&*(raw as *const _)) + } + } + } } /// Indicates a [`Subscriber`]'s interest in a particular callsite.