cubecl_common/device/handle/
base.rs1use crate::device::{DeviceId, DeviceService, ServerUtilitiesHandle};
2use alloc::boxed::Box;
3use alloc::string::String;
4use core::any::Any;
5
6pub struct CallError {
13 payload: Option<Box<dyn Any + Send>>,
16}
17
18impl CallError {
19 #[cfg_attr(not(multi_threading), allow(dead_code))]
23 pub(crate) fn from_panic(payload: Box<dyn Any + Send>) -> Self {
24 Self {
25 payload: Some(payload),
26 }
27 }
28
29 #[cfg_attr(not(multi_threading), allow(dead_code))]
32 pub(crate) fn disconnected() -> Self {
33 Self { payload: None }
34 }
35
36 pub fn message(&self) -> Option<&str> {
39 let payload = self.payload.as_ref()?;
40 if let Some(s) = payload.downcast_ref::<&'static str>() {
41 Some(*s)
42 } else if let Some(s) = payload.downcast_ref::<String>() {
43 Some(s.as_str())
44 } else {
45 None
46 }
47 }
48
49 pub fn into_panic(self) -> Option<Box<dyn Any + Send>> {
54 self.payload
55 }
56
57 #[track_caller]
59 pub fn resume(self) -> ! {
60 match self.into_panic() {
61 #[cfg(feature = "std")]
62 Some(payload) => std::panic::resume_unwind(payload),
63 #[cfg(not(feature = "std"))]
65 Some(_payload) => {
66 panic!("a device task panicked but its payload cannot be re-raised without `std`")
67 }
68 None => panic!("device runner channel disconnected before producing a result"),
69 }
70 }
71}
72
73pub trait CallResultExt<R> {
75 fn unwrap_or_resume(self) -> R;
80}
81
82impl<R> CallResultExt<R> for Result<R, CallError> {
83 #[track_caller]
84 fn unwrap_or_resume(self) -> R {
85 match self {
86 Ok(value) => value,
87 Err(err) => err.resume(),
88 }
89 }
90}
91
92impl core::fmt::Debug for CallError {
93 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
94 match self.message() {
95 Some(message) => write!(
96 f,
97 "CallError(task panicked on device runner thread: {message})"
98 ),
99 None if self.payload.is_some() => f.write_str(
100 "CallError(task panicked on device runner thread with a non-string payload)",
101 ),
102 None => f.write_str(
103 "CallError(device runner channel disconnected before producing a result)",
104 ),
105 }
106 }
107}
108
109#[derive(new, Clone, Debug)]
110pub struct ServiceCreationError {
112 #[allow(dead_code)] reason: alloc::string::String,
114}
115
116pub trait DeviceHandleSpec: Sized + Clone {
121 const BLOCKING: bool;
123
124 fn insert<S: DeviceService>(
128 device_id: DeviceId,
129 service: S,
130 ) -> Result<Self, ServiceCreationError>;
131
132 fn new<S: DeviceService>(device_id: DeviceId) -> Self;
136
137 fn device_id(&self) -> DeviceId;
139
140 fn utilities(&self) -> ServerUtilitiesHandle;
142
143 fn flush_queue(&self);
150
151 fn submit_blocking<'a, R: Send, T: FnOnce(&mut dyn Any) -> R + Send + 'a>(
152 &self,
153 task: T,
154 ) -> Result<R, CallError>;
155
156 fn submit<T: FnOnce(&mut dyn Any) + Send + 'static>(&self, task: T);
157
158 fn exclusive<R: Send, T: FnOnce() -> R + Send>(&self, task: T) -> Result<R, CallError>;
159
160 fn shutdown(device_id: DeviceId) {
161 let _ = device_id;
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::CallError;
168 use alloc::boxed::Box;
169 use core::any::Any;
170
171 #[test]
174 fn test_disconnected_has_no_message_or_payload() {
175 let err = CallError::disconnected();
176 assert_eq!(err.message(), None);
177 assert!(err.into_panic().is_none());
178 }
179
180 #[test]
182 fn test_debug_string_payload_includes_message() {
183 let err = CallError::from_panic(Box::new("oops") as Box<dyn Any + Send>);
184 let debug = alloc::format!("{err:?}");
185 assert!(debug.contains("task panicked on device runner thread"));
186 assert!(debug.contains("oops"));
187 }
188
189 #[test]
191 fn test_debug_non_string_payload() {
192 let err = CallError::from_panic(Box::new(123u8) as Box<dyn Any + Send>);
193 let debug = alloc::format!("{err:?}");
194 assert!(debug.contains("non-string payload"), "got: {debug}");
195 }
196
197 #[test]
199 fn test_debug_disconnected() {
200 let err = CallError::disconnected();
201 let debug = alloc::format!("{err:?}");
202 assert!(debug.contains("disconnected"), "got: {debug}");
203 }
204}