use std::{
any::Any,
io::{self, Read, Seek, SeekFrom},
panic::{AssertUnwindSafe, catch_unwind},
sync::{
Arc, Mutex,
atomic::{AtomicBool, AtomicU64, Ordering},
},
};
use smol_str::SmolStr;
#[derive(Debug, Default)]
pub(crate) struct PanicLatch {
latched: AtomicBool,
message: Mutex<Option<SmolStr>>,
}
impl PanicLatch {
fn latch(&self, payload: &(dyn Any + Send)) {
let message = describe(payload);
let mut slot = self.message.lock().unwrap_or_else(|e| e.into_inner());
if slot.is_none() {
*slot = Some(message);
}
drop(slot);
self.latched.store(true, Ordering::Release);
}
pub(crate) fn message(&self) -> Option<SmolStr> {
if !self.latched.load(Ordering::Acquire) {
return None;
}
self
.message
.lock()
.unwrap_or_else(|e| e.into_inner())
.clone()
.or_else(|| Some(SmolStr::new_static(UNNAMED)))
}
}
fn describe(payload: &(dyn Any + Send)) -> SmolStr {
if let Some(s) = payload.downcast_ref::<&'static str>() {
return SmolStr::new(s);
}
if let Some(s) = payload.downcast_ref::<String>() {
return SmolStr::new(s);
}
SmolStr::new_static(UNNAMED)
}
const UNNAMED: &str = "panicked with a payload of an unknown type";
pub(crate) struct GuardedReader<R> {
inner: R,
latch: Arc<PanicLatch>,
meter: Arc<ReadMeter>,
}
#[derive(Debug, Default)]
pub(crate) struct ReadMeter {
read: AtomicU64,
budget: AtomicU64,
tripped: AtomicBool,
}
impl ReadMeter {
pub(crate) fn new(budget: u64) -> Self {
Self {
read: AtomicU64::new(0),
budget: AtomicU64::new(budget),
tripped: AtomicBool::new(false),
}
}
pub(crate) fn tripped(&self) -> bool {
self.tripped.load(Ordering::Acquire)
}
pub(crate) fn read(&self) -> u64 {
self.read.load(Ordering::Relaxed)
}
pub(crate) fn budget(&self) -> u64 {
self.budget.load(Ordering::Relaxed)
}
pub(crate) fn release(&self) {
self.budget.store(u64::MAX, Ordering::Relaxed);
}
fn remaining(&self) -> u64 {
self
.budget
.load(Ordering::Relaxed)
.saturating_sub(self.read.load(Ordering::Relaxed))
}
fn charge(&self, bytes: u64) {
self.read.fetch_add(bytes, Ordering::Relaxed);
}
fn trip(&self) {
self.tripped.store(true, Ordering::Release);
}
}
impl<R> GuardedReader<R> {
pub(crate) fn new(inner: R, budget: u64) -> (Self, Arc<PanicLatch>, Arc<ReadMeter>) {
let latch = Arc::new(PanicLatch::default());
let meter = Arc::new(ReadMeter::new(budget));
(
Self {
inner,
latch: Arc::clone(&latch),
meter: Arc::clone(&meter),
},
latch,
meter,
)
}
fn guard<T>(&mut self, call: impl FnOnce(&mut R) -> io::Result<T>) -> io::Result<T> {
let Self {
inner,
latch,
meter: _,
} = self;
match catch_unwind(AssertUnwindSafe(|| call(inner))) {
Ok(result) => result,
Err(payload) => {
latch.latch(&*payload);
core::mem::forget(payload);
Err(io::Error::other("the caller's reader panicked"))
}
}
}
}
impl<R: Read> Read for GuardedReader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if buf.is_empty() {
return self.guard(|inner| inner.read(buf));
}
let remaining = self.meter.remaining();
if remaining == 0 {
self.meter.trip();
return Err(io::Error::other("mediadecode: probe read budget exhausted"));
}
let capped = usize::try_from(remaining)
.unwrap_or(usize::MAX)
.min(buf.len());
let read = self.guard(|inner| inner.read(&mut buf[..capped]))?;
self.meter.charge(read as u64);
Ok(read)
}
}
impl<R: Seek> Seek for GuardedReader<R> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
self.guard(|inner| inner.seek(pos))
}
}
#[cfg(test)]
mod tests {
use super::*;
struct Panicking;
impl Read for Panicking {
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
panic!("read exploded");
}
}
impl Seek for Panicking {
fn seek(&mut self, _pos: SeekFrom) -> io::Result<u64> {
panic!("seek exploded");
}
}
fn quietly<T>(call: impl FnOnce() -> T) -> T {
let previous = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let out = call();
std::panic::set_hook(previous);
out
}
#[test]
fn a_panicking_read_becomes_an_error_and_latches_its_message() {
let (mut guarded, latch, _meter) = GuardedReader::new(Panicking, u64::MAX);
assert!(latch.message().is_none(), "nothing has panicked yet");
let mut buf = [0u8; 8];
let err = quietly(|| guarded.read(&mut buf)).expect_err("a panic is an error, not an abort");
assert_eq!(err.kind(), io::ErrorKind::Other);
assert_eq!(latch.message().as_deref(), Some("read exploded"));
}
#[test]
fn a_panicking_seek_becomes_an_error_and_latches_its_message() {
let (mut guarded, latch, _meter) = GuardedReader::new(Panicking, u64::MAX);
let err = quietly(|| guarded.seek(SeekFrom::Start(4))).expect_err("a panic is an error");
assert_eq!(err.kind(), io::ErrorKind::Other);
assert_eq!(latch.message().as_deref(), Some("seek exploded"));
assert!(quietly(|| guarded.stream_position()).is_err());
}
#[test]
fn the_first_panic_is_the_one_reported() {
struct TwoFaced(u32);
impl Read for TwoFaced {
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
self.0 += 1;
panic!("panic number {}", self.0);
}
}
let (mut guarded, latch, _meter) = GuardedReader::new(TwoFaced(0), u64::MAX);
let mut buf = [0u8; 4];
quietly(|| {
let _ = guarded.read(&mut buf);
let _ = guarded.read(&mut buf);
});
assert_eq!(
latch.message().as_deref(),
Some("panic number 1"),
"the description nearest the cause is the one kept",
);
}
struct PanicOnDrop;
impl Drop for PanicOnDrop {
fn drop(&mut self) {
panic!("and the payload went too");
}
}
struct PanicsWithAPayload;
impl Read for PanicsWithAPayload {
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
std::panic::panic_any(PanicOnDrop);
}
}
impl Seek for PanicsWithAPayload {
fn seek(&mut self, _pos: SeekFrom) -> io::Result<u64> {
std::panic::panic_any(PanicOnDrop);
}
}
#[test]
fn a_payload_that_panics_on_drop_does_not_get_dropped() {
let (mut guarded, latch, _meter) = GuardedReader::new(PanicsWithAPayload, u64::MAX);
let mut buf = [0u8; 8];
assert!(quietly(|| guarded.read(&mut buf)).is_err());
assert_eq!(latch.message().as_deref(), Some(UNNAMED));
assert!(quietly(|| guarded.seek(SeekFrom::Start(0))).is_err());
}
#[test]
fn an_unremarkable_reader_passes_straight_through() {
let (mut guarded, latch, _meter) =
GuardedReader::new(std::io::Cursor::new(vec![1u8, 2, 3, 4]), u64::MAX);
let mut buf = [0u8; 4];
assert_eq!(guarded.read(&mut buf).expect("read"), 4);
assert_eq!(buf, [1, 2, 3, 4]);
assert_eq!(guarded.seek(SeekFrom::Start(1)).expect("seek"), 1);
assert!(latch.message().is_none());
}
}