use std::any::Any;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, PoisonError, RwLock};
use std::time::{Duration, Instant};
use crate::{binder::TransactionCode, Result, TransportCaps};
mod log_observer;
mod span;
mod stats;
pub use log_observer::LogObserver;
#[cfg(feature = "tracing")]
pub use span::TracingObserver;
#[doc(hidden)]
pub use span::{__ClientSpan, __trace_client};
pub use stats::{LatencyHistogram, MethodStats, StatsObserver, StatsSnapshot, LATENCY_BUCKETS};
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct TxnContext<'a> {
pub descriptor: &'a str,
pub code: TransactionCode,
pub method: Option<&'static str>,
pub is_oneway: bool,
pub calling_uid: crate::sys::binder::uid_t,
pub calling_pid: crate::sys::binder::pid_t,
pub transport: TransportCaps,
}
pub trait TransactionObserver: Send + Sync {
fn on_transact(&self, ctx: &TxnContext<'_>) -> Option<Box<dyn Any + Send>>;
fn on_reply(
&self,
ctx: &TxnContext<'_>,
tag: Option<Box<dyn Any + Send>>,
result: &Result<()>,
elapsed: Duration,
);
}
static INSTALLED: AtomicBool = AtomicBool::new(false);
static OBSERVER: RwLock<Option<Arc<dyn TransactionObserver>>> = RwLock::new(None);
pub fn set_observer(observer: Option<Arc<dyn TransactionObserver>>) {
let old = {
let mut slot = OBSERVER.write().unwrap_or_else(PoisonError::into_inner);
INSTALLED.store(observer.is_some(), Ordering::Release);
std::mem::replace(&mut *slot, observer)
};
drop(old);
}
fn current() -> Option<Arc<dyn TransactionObserver>> {
if !INSTALLED.load(Ordering::Acquire) {
return None;
}
OBSERVER
.read()
.unwrap_or_else(PoisonError::into_inner)
.clone()
}
fn log_panic(which: &str, payload: &(dyn Any + Send)) {
let msg = payload
.downcast_ref::<&'static str>()
.copied()
.or_else(|| payload.downcast_ref::<String>().map(String::as_str))
.unwrap_or("<non-string panic payload>");
log::error!("{which} panicked: {msg}");
}
pub(crate) fn observed<'a>(
ctx: impl FnOnce() -> TxnContext<'a>,
dispatch: impl FnOnce() -> Result<()>,
) -> Result<()> {
let Some(observer) = current() else {
return dispatch();
};
let ctx = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(ctx)) {
Ok(ctx) => ctx,
Err(payload) => {
log_panic("TxnContext (transaction_name/descriptor)", &*payload);
return dispatch();
}
};
let tag = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| observer.on_transact(&ctx)))
.unwrap_or_else(|payload| {
log_panic("TransactionObserver::on_transact", &*payload);
None
});
let start = Instant::now();
let result = dispatch();
let elapsed = start.elapsed();
if let Err(payload) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
observer.on_reply(&ctx, tag, &result, elapsed)
})) {
log_panic("TransactionObserver::on_reply", &*payload);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::StatusCode;
use std::sync::Mutex;
fn ctx() -> TxnContext<'static> {
TxnContext {
descriptor: "x.y.IZ",
code: 7,
method: Some("m"),
is_oneway: false,
calling_uid: 1,
calling_pid: 2,
transport: TransportCaps::KERNEL,
}
}
#[derive(Default)]
struct Recorder {
events: Mutex<Vec<String>>,
panic_in: Option<&'static str>,
}
impl TransactionObserver for Recorder {
fn on_transact(&self, ctx: &TxnContext<'_>) -> Option<Box<dyn Any + Send>> {
if ctx.descriptor != "x.y.IZ" {
return None;
}
self.events
.lock()
.unwrap()
.push(format!("transact {}", ctx.code));
if self.panic_in == Some("on_transact") {
panic!("observer bug");
}
Some(Box::new(42u32))
}
fn on_reply(
&self,
ctx: &TxnContext<'_>,
tag: Option<Box<dyn Any + Send>>,
result: &Result<()>,
_elapsed: Duration,
) {
if ctx.descriptor != "x.y.IZ" {
return;
}
let tag = tag.and_then(|t| t.downcast::<u32>().ok()).map(|t| *t);
self.events
.lock()
.unwrap()
.push(format!("reply {} {tag:?} {result:?}", ctx.code));
if self.panic_in == Some("on_reply") {
panic!("observer bug");
}
}
}
#[test]
#[serial_test::serial(observer)]
fn observer_pairs_calls_passes_the_tag_and_survives_its_own_panics() {
let dispatches = Mutex::new(0);
let run = |result: Result<()>| {
observed(ctx, || {
*dispatches.lock().unwrap() += 1;
result
})
};
assert_eq!(
observed(
|| unreachable!("context built without an observer"),
|| Ok(())
),
Ok(())
);
let recorder = Arc::new(Recorder::default());
set_observer(Some(recorder.clone()));
assert_eq!(run(Err(StatusCode::BadValue)), Err(StatusCode::BadValue));
assert_eq!(
*recorder.events.lock().unwrap(),
["transact 7", "reply 7 Some(42) Err(BadValue)"]
);
for which in ["on_transact", "on_reply"] {
let panicking = Arc::new(Recorder {
panic_in: Some(which),
..Default::default()
});
set_observer(Some(panicking.clone()));
assert_eq!(run(Ok(())), Ok(()), "a panic in {which} changed the result");
let events = panicking.events.lock().unwrap();
assert_eq!(events.len(), 2, "{which}: {events:?}");
let expected_tag = if which == "on_transact" {
"None"
} else {
"Some(42)"
};
assert_eq!(events[1], format!("reply 7 {expected_tag} Ok(())"));
}
set_observer(None);
assert_eq!(run(Ok(())), Ok(()));
assert_eq!(*dispatches.lock().unwrap(), 4);
}
}