use std::collections::VecDeque;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::{Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::time::Duration;
use bytes::Bytes;
use tokio::sync::Notify;
pub trait Secret {
fn describe(&self) -> &'static str;
}
impl<T> Secret for Option<T> {
fn describe(&self) -> &'static str {
if self.is_some() {
"Some(<redacted>)"
} else {
"None"
}
}
}
macro_rules! always_redacted {
($($ty:ty),+ $(,)?) => {
$( impl Secret for $ty {
fn describe(&self) -> &'static str { "<redacted>" }
} )+
};
}
always_redacted!(Vec<u8>, Bytes, String);
pub struct Redacted<'a, T: Secret + ?Sized>(pub &'a T);
impl<T: Secret + ?Sized> std::fmt::Debug for Redacted<'_, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0.describe())
}
}
#[macro_export]
macro_rules! redacted_debug {
($ty:ident { $($field:ident),* $(,)? } secret { $($secret:ident),+ $(,)? }) => {
impl ::core::fmt::Debug for $ty {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
f.debug_struct(stringify!($ty))
$( .field(stringify!($field), &self.$field) )*
$( .field(stringify!($secret), &$crate::support::Redacted(&self.$secret)) )+
.finish()
}
}
};
}
pub fn guard_callback<R>(what: &'static str, f: impl FnOnce() -> R) -> Option<R> {
match catch_unwind(AssertUnwindSafe(f)) {
Ok(value) => Some(value),
Err(_) => {
tracing::error!(callback = what, "foreign callback panicked; ignoring");
None
}
}
}
pub trait LockExt<T> {
fn lock_safe(&self) -> MutexGuard<'_, T>;
}
impl<T> LockExt<T> for Mutex<T> {
fn lock_safe(&self) -> MutexGuard<'_, T> {
self.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
}
}
pub trait RwLockExt<T> {
fn read_safe(&self) -> RwLockReadGuard<'_, T>;
fn write_safe(&self) -> RwLockWriteGuard<'_, T>;
}
impl<T> RwLockExt<T> for RwLock<T> {
fn read_safe(&self) -> RwLockReadGuard<'_, T> {
self.read().unwrap_or_else(|poisoned| poisoned.into_inner())
}
fn write_safe(&self) -> RwLockWriteGuard<'_, T> {
self.write()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
}
pub fn secs_or(value: u32, default_secs: u32) -> Duration {
Duration::from_secs(u64::from(if value == 0 { default_secs } else { value }))
}
pub struct EventQueue<T> {
state: Mutex<QueueState<T>>,
notify: Notify,
}
struct QueueState<T> {
items: VecDeque<T>,
capacity: usize,
}
impl<T> Default for EventQueue<T> {
fn default() -> Self {
EventQueue {
state: Mutex::new(QueueState {
items: VecDeque::new(),
capacity: 0,
}),
notify: Notify::new(),
}
}
}
impl<T> EventQueue<T> {
pub fn enable(&self, capacity: usize) {
let mut state = self.state.lock_safe();
state.capacity = capacity;
if capacity == 0 {
state.items.clear();
} else {
while state.items.len() > capacity {
state.items.pop_front();
}
}
}
pub fn is_enabled(&self) -> bool {
self.state.lock_safe().capacity > 0
}
pub fn push(&self, event: T) {
{
let mut state = self.state.lock_safe();
if state.capacity == 0 {
return;
}
if state.items.len() >= state.capacity {
state.items.pop_front();
}
state.items.push_back(event);
}
self.notify.notify_one();
}
fn pop(&self) -> Option<T> {
self.state.lock_safe().items.pop_front()
}
pub async fn next(&self, timeout: Duration) -> Option<T> {
let deadline = tokio::time::Instant::now() + timeout;
loop {
if let Some(event) = self.pop() {
return Some(event);
}
if tokio::time::timeout_at(deadline, self.notify.notified())
.await
.is_err()
{
return self.pop();
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn secs_or_uses_default_for_zero() {
assert_eq!(secs_or(0, 15), Duration::from_secs(15));
assert_eq!(secs_or(3, 15), Duration::from_secs(3));
}
#[test]
fn guard_callback_swallows_panics() {
assert_eq!(guard_callback("ok", || 7), Some(7));
assert_eq!(guard_callback("boom", || -> u8 { panic!("boom") }), None);
}
#[test]
fn poisoned_mutex_is_still_usable() {
let m = std::sync::Arc::new(Mutex::new(1));
let m2 = m.clone();
let _ = std::thread::spawn(move || {
let _g = m2.lock().unwrap();
panic!("poison it");
})
.join();
assert!(m.is_poisoned());
assert_eq!(*m.lock_safe(), 1);
}
#[tokio::test]
async fn event_queue_is_disabled_until_enabled() {
let q = EventQueue::<u32>::default();
q.push(1);
assert_eq!(q.next(Duration::from_millis(20)).await, None);
q.enable(4);
q.push(2);
assert_eq!(q.next(Duration::from_millis(20)).await, Some(2));
}
#[tokio::test]
async fn event_queue_drops_oldest_when_full() {
let q = EventQueue::<u32>::default();
q.enable(2);
for i in 0..5 {
q.push(i);
}
assert_eq!(q.next(Duration::ZERO).await, Some(3));
assert_eq!(q.next(Duration::ZERO).await, Some(4));
assert_eq!(q.next(Duration::ZERO).await, None);
}
#[tokio::test]
async fn event_queue_wakes_a_waiting_poller() {
let q = std::sync::Arc::new(EventQueue::<&'static str>::default());
q.enable(8);
let q2 = q.clone();
let poller = tokio::spawn(async move { q2.next(Duration::from_secs(5)).await });
tokio::time::sleep(Duration::from_millis(50)).await;
q.push("hello");
assert_eq!(poller.await.unwrap(), Some("hello"));
}
#[test]
fn option_secret_keeps_presence_signal() {
assert_eq!(Some(vec![1u8]).describe(), "Some(<redacted>)");
assert_eq!(None::<Vec<u8>>.describe(), "None");
}
}