use crate::device::{DeviceId, DeviceService, ServerUtilitiesHandle};
use alloc::boxed::Box;
use alloc::string::String;
use core::any::Any;
pub struct CallError {
payload: Option<Box<dyn Any + Send>>,
}
impl CallError {
#[cfg_attr(not(multi_threading), allow(dead_code))]
pub(crate) fn from_panic(payload: Box<dyn Any + Send>) -> Self {
Self {
payload: Some(payload),
}
}
#[cfg_attr(not(multi_threading), allow(dead_code))]
pub(crate) fn disconnected() -> Self {
Self { payload: None }
}
pub fn message(&self) -> Option<&str> {
let payload = self.payload.as_ref()?;
if let Some(s) = payload.downcast_ref::<&'static str>() {
Some(*s)
} else if let Some(s) = payload.downcast_ref::<String>() {
Some(s.as_str())
} else {
None
}
}
pub fn into_panic(self) -> Option<Box<dyn Any + Send>> {
self.payload
}
#[track_caller]
pub fn resume(self) -> ! {
match self.into_panic() {
#[cfg(feature = "std")]
Some(payload) => std::panic::resume_unwind(payload),
#[cfg(not(feature = "std"))]
Some(_payload) => {
panic!("a device task panicked but its payload cannot be re-raised without `std`")
}
None => panic!("device runner channel disconnected before producing a result"),
}
}
}
pub trait CallResultExt<R> {
fn unwrap_or_resume(self) -> R;
}
impl<R> CallResultExt<R> for Result<R, CallError> {
#[track_caller]
fn unwrap_or_resume(self) -> R {
match self {
Ok(value) => value,
Err(err) => err.resume(),
}
}
}
impl core::fmt::Debug for CallError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self.message() {
Some(message) => write!(
f,
"CallError(task panicked on device runner thread: {message})"
),
None if self.payload.is_some() => f.write_str(
"CallError(task panicked on device runner thread with a non-string payload)",
),
None => f.write_str(
"CallError(device runner channel disconnected before producing a result)",
),
}
}
}
#[derive(new, Clone, Debug)]
pub struct ServiceCreationError {
#[allow(dead_code)] reason: alloc::string::String,
}
pub(crate) trait DeviceHandleSpec<S: DeviceService>: Sized {
const BLOCKING: bool;
fn insert(device_id: DeviceId, service: S) -> Result<Self, ServiceCreationError>;
fn new(device_id: DeviceId) -> Self;
fn device_id(&self) -> DeviceId;
fn utilities(&self) -> ServerUtilitiesHandle;
fn flush_queue(&self);
fn submit_blocking<'a, R: Send, T: FnOnce(&mut S) -> R + Send + 'a>(
&self,
task: T,
) -> Result<R, CallError>;
fn submit<T: FnOnce(&mut S) + Send + 'static>(&self, task: T);
fn exclusive<R: Send, T: FnOnce() -> R + Send>(&self, task: T) -> Result<R, CallError>;
}
#[cfg(test)]
mod tests {
use super::CallError;
use alloc::boxed::Box;
use core::any::Any;
#[test]
fn test_disconnected_has_no_message_or_payload() {
let err = CallError::disconnected();
assert_eq!(err.message(), None);
assert!(err.into_panic().is_none());
}
#[test]
fn test_debug_string_payload_includes_message() {
let err = CallError::from_panic(Box::new("oops") as Box<dyn Any + Send>);
let debug = alloc::format!("{err:?}");
assert!(debug.contains("task panicked on device runner thread"));
assert!(debug.contains("oops"));
}
#[test]
fn test_debug_non_string_payload() {
let err = CallError::from_panic(Box::new(123u8) as Box<dyn Any + Send>);
let debug = alloc::format!("{err:?}");
assert!(debug.contains("non-string payload"), "got: {debug}");
}
#[test]
fn test_debug_disconnected() {
let err = CallError::disconnected();
let debug = alloc::format!("{err:?}");
assert!(debug.contains("disconnected"), "got: {debug}");
}
}