use std::{
any::Any,
io::{self, Read, Seek, SeekFrom},
panic::{AssertUnwindSafe, catch_unwind},
sync::{
Arc, Mutex,
atomic::{AtomicBool, 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>,
}
impl<R> GuardedReader<R> {
pub(crate) fn new(inner: R) -> (Self, Arc<PanicLatch>) {
let latch = Arc::new(PanicLatch::default());
(
Self {
inner,
latch: Arc::clone(&latch),
},
latch,
)
}
fn guard<T>(&mut self, call: impl FnOnce(&mut R) -> io::Result<T>) -> io::Result<T> {
let Self { inner, latch } = 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> {
self.guard(|inner| inner.read(buf))
}
}
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) = GuardedReader::new(Panicking);
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) = GuardedReader::new(Panicking);
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) = GuardedReader::new(TwoFaced(0));
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) = GuardedReader::new(PanicsWithAPayload);
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) = GuardedReader::new(std::io::Cursor::new(vec![1u8, 2, 3, 4]));
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());
}
}