use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, LazyLock, Mutex};
pub const DEFAULT_HIGH_WATERMARK: u64 = 2500;
pub const DEFAULT_LOW_WATERMARK: u64 = 2000;
pub const DEFAULT_WARNING_WATERMARK: u64 = 2250;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProxyCountEvent {
Warning { uid: u32, count: u64 },
Limit { uid: u32, count: u64 },
}
pub type ProxyCountCallback = Arc<dyn Fn(ProxyCountEvent) + Send + Sync>;
static PROXY_COUNT: AtomicU64 = AtomicU64::new(0);
static COUNT_BY_UID_ENABLED: AtomicBool = AtomicBool::new(false);
struct State {
high: u64,
low: u64,
warning: u64,
callback: Option<ProxyCountCallback>,
per_uid: HashMap<u32, UidEntry>,
}
#[derive(Default)]
struct UidEntry {
count: u64,
warning_fired: bool,
limit_fired: bool,
}
static STATE: LazyLock<Mutex<State>> = LazyLock::new(|| {
Mutex::new(State {
high: DEFAULT_HIGH_WATERMARK,
low: DEFAULT_LOW_WATERMARK,
warning: DEFAULT_WARNING_WATERMARK,
callback: None,
per_uid: HashMap::new(),
})
});
thread_local! {
static CALLBACK_DEFER: Cell<bool> = const { Cell::new(false) };
static PENDING_CALLBACKS: RefCell<Vec<(ProxyCountCallback, ProxyCountEvent)>> =
const { RefCell::new(Vec::new()) };
}
pub(crate) struct CallbackDeferGuard {
was_deferring: bool,
}
impl CallbackDeferGuard {
pub(crate) fn new() -> Self {
let was_deferring = CALLBACK_DEFER.with(|d| d.replace(true));
Self { was_deferring }
}
}
impl Drop for CallbackDeferGuard {
fn drop(&mut self) {
CALLBACK_DEFER.with(|d| d.set(self.was_deferring));
if self.was_deferring {
return;
}
let pending: Vec<_> = PENDING_CALLBACKS.with(|p| std::mem::take(&mut *p.borrow_mut()));
for (cb, event) in pending {
fire_callback(&cb, event);
}
}
}
fn fire_callback(cb: &ProxyCountCallback, event: ProxyCountEvent) {
if let Err(payload) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| cb(event))) {
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!("ProxyCountCallback panicked for {event:?}: {msg}");
}
}
pub fn get_binder_proxy_count() -> u64 {
PROXY_COUNT.load(Ordering::Relaxed)
}
pub fn get_binder_proxy_count_for_uid(uid: u32) -> u64 {
let state = STATE.lock().expect("proxy_count state poisoned");
state.per_uid.get(&uid).map(|e| e.count).unwrap_or(0)
}
pub fn get_binder_proxy_counts_by_uid() -> Vec<(u32, u64)> {
let state = STATE.lock().expect("proxy_count state poisoned");
state
.per_uid
.iter()
.map(|(uid, e)| (*uid, e.count))
.collect()
}
pub fn set_binder_proxy_count_watermarks(high: u64, low: u64, warning: u64) {
let mut state = STATE.lock().expect("proxy_count state poisoned");
state.high = high;
state.low = low;
state.warning = warning;
}
pub fn set_binder_proxy_count_event_callback(callback: Option<ProxyCountCallback>) {
let mut state = STATE.lock().expect("proxy_count state poisoned");
state.callback = callback;
}
pub fn enable_count_by_uid(enabled: bool) {
COUNT_BY_UID_ENABLED.store(enabled, Ordering::Relaxed);
}
pub fn is_count_by_uid_enabled() -> bool {
COUNT_BY_UID_ENABLED.load(Ordering::Relaxed)
}
pub fn clear_count_by_uid() {
let mut state = STATE.lock().expect("proxy_count state poisoned");
state.per_uid.clear();
}
pub(crate) fn on_proxy_create(uid: u32) -> bool {
PROXY_COUNT.fetch_add(1, Ordering::Relaxed);
if !COUNT_BY_UID_ENABLED.load(Ordering::Relaxed) {
return false;
}
let event = {
let mut state = STATE.lock().expect("proxy_count state poisoned");
let high = state.high;
let warning = state.warning;
let entry = state.per_uid.entry(uid).or_default();
entry.count += 1;
let count = entry.count;
let already_warning = entry.warning_fired;
let already_limit = entry.limit_fired;
if !already_limit && count >= high {
entry.limit_fired = true;
state
.callback
.as_ref()
.map(|cb| (cb.clone(), ProxyCountEvent::Limit { uid, count }))
} else if !already_warning && count >= warning && count < high {
entry.warning_fired = true;
state
.callback
.as_ref()
.map(|cb| (cb.clone(), ProxyCountEvent::Warning { uid, count }))
} else {
None
}
};
if let Some((cb, event)) = event {
if CALLBACK_DEFER.with(|d| d.get()) {
PENDING_CALLBACKS.with(|p| p.borrow_mut().push((cb, event)));
} else {
fire_callback(&cb, event);
}
}
true
}
pub(crate) fn on_proxy_drop(uid: u32, counted_by_uid: bool) {
PROXY_COUNT.fetch_sub(1, Ordering::Relaxed);
if !counted_by_uid {
return;
}
let mut state = STATE.lock().expect("proxy_count state poisoned");
let low = state.low;
if let Some(entry) = state.per_uid.get_mut(&uid) {
entry.count = entry.count.saturating_sub(1);
if entry.count <= low {
entry.limit_fired = false;
entry.warning_fired = false;
}
if entry.count == 0 {
state.per_uid.remove(&uid);
}
}
}
#[cfg(test)]
pub(crate) fn reset_for_test() {
PROXY_COUNT.store(0, Ordering::Relaxed);
COUNT_BY_UID_ENABLED.store(false, Ordering::Relaxed);
CALLBACK_DEFER.with(|d| d.set(false));
PENDING_CALLBACKS.with(|p| p.borrow_mut().clear());
let mut state = STATE.lock().expect("proxy_count state poisoned");
state.high = DEFAULT_HIGH_WATERMARK;
state.low = DEFAULT_LOW_WATERMARK;
state.warning = DEFAULT_WARNING_WATERMARK;
state.callback = None;
state.per_uid.clear();
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex as StdMutex;
#[test]
#[serial_test::serial(binder)]
fn global_counter_increments_and_decrements() {
reset_for_test();
assert_eq!(get_binder_proxy_count(), 0);
on_proxy_create(1000);
on_proxy_create(1000);
on_proxy_create(2000);
assert_eq!(get_binder_proxy_count(), 3);
on_proxy_drop(1000, false);
assert_eq!(get_binder_proxy_count(), 2);
on_proxy_drop(1000, false);
on_proxy_drop(2000, false);
assert_eq!(get_binder_proxy_count(), 0);
}
#[test]
#[serial_test::serial(binder)]
fn per_uid_disabled_by_default_skips_map() {
reset_for_test();
on_proxy_create(1000);
on_proxy_create(2000);
assert_eq!(get_binder_proxy_count(), 2);
assert_eq!(get_binder_proxy_count_for_uid(1000), 0);
assert_eq!(get_binder_proxy_counts_by_uid(), vec![]);
on_proxy_drop(1000, false);
on_proxy_drop(2000, false);
}
#[test]
#[serial_test::serial(binder)]
fn per_uid_enabled_tracks_each_uid() {
reset_for_test();
enable_count_by_uid(true);
on_proxy_create(1000);
on_proxy_create(1000);
on_proxy_create(2000);
assert_eq!(get_binder_proxy_count_for_uid(1000), 2);
assert_eq!(get_binder_proxy_count_for_uid(2000), 1);
assert_eq!(get_binder_proxy_count_for_uid(3000), 0);
let mut snap = get_binder_proxy_counts_by_uid();
snap.sort_by_key(|(uid, _)| *uid);
assert_eq!(snap, vec![(1000, 2), (2000, 1)]);
on_proxy_drop(1000, true);
on_proxy_drop(1000, true);
on_proxy_drop(2000, true);
assert_eq!(get_binder_proxy_counts_by_uid(), vec![]);
}
#[test]
#[serial_test::serial(binder)]
fn limit_callback_fires_once_until_low_watermark_resets() {
reset_for_test();
enable_count_by_uid(true);
set_binder_proxy_count_watermarks( 5, 2, 4);
let fired = Arc::new(StdMutex::new(Vec::<ProxyCountEvent>::new()));
let f = fired.clone();
set_binder_proxy_count_event_callback(Some(Arc::new(move |event| {
f.lock().unwrap().push(event);
})));
for _ in 0..7 {
on_proxy_create(1000);
}
let snap: Vec<_> = fired.lock().unwrap().clone();
assert_eq!(snap.len(), 2, "warning + limit, once each: {snap:?}");
assert!(matches!(
snap[0],
ProxyCountEvent::Warning { uid: 1000, .. }
));
assert!(matches!(snap[1], ProxyCountEvent::Limit { uid: 1000, .. }));
for _ in 0..6 {
on_proxy_drop(1000, true);
}
assert_eq!(get_binder_proxy_count_for_uid(1000), 1);
on_proxy_create(1000); on_proxy_create(1000); on_proxy_create(1000); on_proxy_create(1000); let snap: Vec<_> = fired.lock().unwrap().clone();
assert_eq!(snap.len(), 4, "re-fire after below-low reset: {snap:?}");
on_proxy_drop(1000, true);
on_proxy_drop(1000, true);
on_proxy_drop(1000, true);
on_proxy_drop(1000, true);
on_proxy_drop(1000, true);
}
#[test]
#[serial_test::serial(binder)]
fn callback_runs_outside_state_lock() {
reset_for_test();
enable_count_by_uid(true);
set_binder_proxy_count_watermarks(2, 1, 2);
set_binder_proxy_count_event_callback(Some(Arc::new(|_event| {
let _snapshot = get_binder_proxy_count_for_uid(1000);
})));
on_proxy_create(1000);
on_proxy_create(1000); on_proxy_drop(1000, true);
on_proxy_drop(1000, true);
}
#[test]
#[serial_test::serial(binder)]
fn clear_count_by_uid_resets_map_only() {
reset_for_test();
enable_count_by_uid(true);
on_proxy_create(1000);
on_proxy_create(2000);
assert_eq!(get_binder_proxy_count(), 2);
clear_count_by_uid();
assert_eq!(get_binder_proxy_counts_by_uid(), vec![]);
assert_eq!(get_binder_proxy_count(), 2);
on_proxy_drop(1000, true);
on_proxy_drop(2000, true);
}
#[test]
#[serial_test::serial(binder)]
fn per_uid_count_survives_tracking_toggled_off_while_live() {
reset_for_test();
enable_count_by_uid(true);
let c1 = on_proxy_create(1000);
let c2 = on_proxy_create(1000);
assert!(c1 && c2);
assert_eq!(get_binder_proxy_count_for_uid(1000), 2);
enable_count_by_uid(false);
let c3 = on_proxy_create(1000);
assert!(!c3);
on_proxy_drop(1000, c3); on_proxy_drop(1000, c2);
on_proxy_drop(1000, c1);
assert_eq!(get_binder_proxy_count_for_uid(1000), 0);
assert_eq!(get_binder_proxy_counts_by_uid(), vec![]);
}
}