diff --git a/Cargo.lock b/Cargo.lock index e4f8d910..24758552 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -991,7 +991,7 @@ dependencies = [ [[package]] name = "common" -version = "0.2.13" +version = "0.2.14" dependencies = [ "bincode", "criterion", @@ -2304,7 +2304,7 @@ checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libertem-asi-mpx3" -version = "0.2.13" +version = "0.2.14" dependencies = [ "bincode", "common", @@ -2325,7 +2325,7 @@ dependencies = [ [[package]] name = "libertem-asi-tpx3" -version = "0.2.13" +version = "0.2.14" dependencies = [ "bincode", "common", @@ -2341,13 +2341,14 @@ dependencies = [ "serde", "stats", "tempfile", + "thiserror", "uuid", "zerocopy", ] [[package]] name = "libertem-dectris" -version = "0.2.13" +version = "0.2.14" dependencies = [ "bincode", "bs_sys", @@ -2377,7 +2378,7 @@ dependencies = [ [[package]] name = "libertem_qd_mpx" -version = "0.2.13" +version = "0.2.14" dependencies = [ "bincode", "common", @@ -3900,6 +3901,7 @@ dependencies = [ "reqwest", "serde", "serde_json", + "thiserror", "url", ] diff --git a/common/Cargo.toml b/common/Cargo.toml index ac523604..87e98e85 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -2,7 +2,7 @@ name = "common" authors = ["Alexander Clausen "] license = "MIT" -version = "0.2.13" +version = "0.2.14" edition = "2021" rust-version = "1.71" @@ -34,3 +34,6 @@ criterion = "0.5.1" [[bench]] name = "casting" harness = false + +[lints.rust] +unused_must_use = "deny" diff --git a/common/src/frame_stack.rs b/common/src/frame_stack.rs index fc92bb7f..258011f2 100644 --- a/common/src/frame_stack.rs +++ b/common/src/frame_stack.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; -use ipc_test::{SharedSlabAllocator, SlotForWriting}; +use ipc_test::{slab::ShmError, SharedSlabAllocator, SlotForWriting}; use log::{error, warn}; use pyo3::{ exceptions::{PyRuntimeError, PyValueError}, @@ -45,6 +45,9 @@ pub enum FrameStackWriteError { #[error("too small")] TooSmall, + + #[error("SHM access error: {0}")] + ShmAccessError(#[from] ShmError), } impl From for PyErr { @@ -56,6 +59,7 @@ impl From for PyErr { FrameStackWriteError::TooSmall => { PyValueError::new_err("frame stack too small to handle single frame") } + FrameStackWriteError::ShmAccessError(e) => PyValueError::new_err(e.to_string()), } } } @@ -64,6 +68,9 @@ impl From for PyErr { pub enum SplitError { #[error("shm full")] ShmFull(FrameStackHandle), + + #[error("shm access error: {0}")] + AccessError(#[from] ShmError), } pub struct FrameStackForWriting @@ -160,7 +167,7 @@ where ) -> Result, FrameStackWriteError> { if self.is_empty() { let slot_info = shm.writing_done(self.slot); - shm.free_idx(slot_info.slot_idx); + shm.free_idx(slot_info.slot_idx)?; return Err(FrameStackWriteError::Empty); } @@ -180,7 +187,7 @@ where ) -> Result<(), FrameStackWriteError> { if self.is_empty() { let slot_info = shm.writing_done(self.slot); - shm.free_idx(slot_info.slot_idx); + shm.free_idx(slot_info.slot_idx)?; Ok(()) } else { Err(FrameStackWriteError::NonEmpty) @@ -296,15 +303,17 @@ mod inner { let mut slot_left = match shm.try_get_mut() { Ok(s) => s, Err(ShmError::NoSlotAvailable) => return Err(SplitError::ShmFull(self)), + Err(e @ ShmError::MutexError(_)) => return Err(e.into()), }; let mut slot_right = match shm.try_get_mut() { Ok(s) => s, Err(ShmError::NoSlotAvailable) => { // don't leak the left slot! let l = shm.writing_done(slot_left); - shm.free_idx(l.slot_idx); + shm.free_idx(l.slot_idx)?; return Err(SplitError::ShmFull(self)); } + Err(e @ ShmError::MutexError(_)) => return Err(e.into()), }; let slice_left = slot_left.as_slice_mut(); @@ -316,7 +325,7 @@ mod inner { let left = shm.writing_done(slot_left); let right = shm.writing_done(slot_right); - shm.free_idx(self.slot.slot_idx); + shm.free_idx(self.slot.slot_idx)?; (left, right) }; @@ -351,8 +360,8 @@ mod inner { f(&slot_r) } - pub fn free_slot(self, shm: &mut SharedSlabAllocator) { - shm.free_idx(self.slot.slot_idx); + pub fn free_slot(self, shm: &mut SharedSlabAllocator) -> Result<(), ShmError> { + shm.free_idx(self.slot.slot_idx) } } @@ -493,7 +502,7 @@ impl<'b, M: FrameMeta> Drop for WriteGuard<'b, M> { match frame_stack.writing_done(self.shm) { Ok(frame_stack) => { warn!("discarding non-empty frame stack as result of previous errors"); - frame_stack.free_slot(self.shm); + let _ = frame_stack.free_slot(self.shm); } Err(e) => error!("WriteGuard::drop failed: {e:?}"), } @@ -616,12 +625,12 @@ mod tests { assert_eq!(a.offsets.len() + b.offsets.len(), 2); // when the split is done, there should be one free shm slot: - assert_eq!(shm.num_slots_free(), 1); + assert_eq!(shm.num_slots_free().unwrap(), 1); // and we can free them again: - shm.free_idx(a.slot.slot_idx); - shm.free_idx(b.slot.slot_idx); + shm.free_idx(a.slot.slot_idx).unwrap(); + shm.free_idx(b.slot.slot_idx).unwrap(); - assert_eq!(shm.num_slots_free(), 3); + assert_eq!(shm.num_slots_free().unwrap(), 3); } } diff --git a/common/src/generic_cam_client.rs b/common/src/generic_cam_client.rs index ee750694..ea4ab268 100644 --- a/common/src/generic_cam_client.rs +++ b/common/src/generic_cam_client.rs @@ -1,6 +1,9 @@ use std::fmt::Debug; -use ipc_test::{slab::SlabInitError, SharedSlabAllocator}; +use ipc_test::{ + slab::{ShmError, SlabInitError}, + SharedSlabAllocator, +}; use multiversion::multiversion; use ndarray::ArrayViewMut3; use num::cast::AsPrimitive; @@ -18,6 +21,9 @@ pub enum CamClientError { error: SlabInitError, }, + #[error("failed to access SHM: {0}")] + ShmError(#[from] ShmError), + #[error("operation on closed client")] Closed, @@ -166,7 +172,7 @@ where M: FrameMeta, { let shm = self.get_shm_mut()?; - handle.free_slot(shm); + handle.free_slot(shm)?; Ok(()) } diff --git a/common/src/generic_connection.rs b/common/src/generic_connection.rs index 5c812823..f74f4516 100644 --- a/common/src/generic_connection.rs +++ b/common/src/generic_connection.rs @@ -6,7 +6,10 @@ use std::{ time::{Duration, Instant}, }; -use ipc_test::{slab::SlabInitError, SharedSlabAllocator}; +use ipc_test::{ + slab::{ShmError, SlabInitError}, + SharedSlabAllocator, +}; use log::{debug, info, trace, warn}; use stats::Stats; @@ -53,6 +56,9 @@ pub enum ConnectionError { #[error("could not connect to SHM area: {0}")] ShmConnectError(#[from] SlabInitError), + #[error("could not access SHM")] + ShmAccessError(#[from] ShmError), + #[error("background thread is dead")] Disconnected, @@ -117,6 +123,7 @@ where frame_stack = old_frame_stack; continue; } + Err(SplitError::AccessError(e)) => return Err(e.into()), }; } } @@ -314,13 +321,13 @@ where return Err(ConnectionError::FatalError(error)) } ReceiverMsg::FrameStack { frame_stack } => { - frame_stack.free_slot(&mut self.shm); + frame_stack.free_slot(&mut self.shm)?; return Err(ConnectionError::UnexpectedMessage( "ReceiverMsg::FrameStack in wait_for_arm".to_owned(), )); } ReceiverMsg::Finished { frame_stack } => { - frame_stack.free_slot(&mut self.shm); + frame_stack.free_slot(&mut self.shm)?; return Err(ConnectionError::UnexpectedMessage( "ReceiverMsg::Finished in wait_for_arm".to_owned(), )); @@ -406,11 +413,11 @@ where match res { ReceiverMsg::FrameStack { frame_stack } => { trace!("wait_for_status: ignoring received FrameStackHandle"); - frame_stack.free_slot(&mut self.shm); + frame_stack.free_slot(&mut self.shm)?; } ReceiverMsg::Finished { frame_stack } => { warn!("wait_for_status: ignoring FrameStackHandle received in ReceiverMsg::Finished message"); - frame_stack.free_slot(&mut self.shm); + frame_stack.free_slot(&mut self.shm)?; } ReceiverMsg::FatalError { error } => { return Err(ConnectionError::FatalError(error)); @@ -568,12 +575,13 @@ where self.wait_for_status(ConnectionStatus::Idle, *timeout, periodic_callback) } - pub fn log_shm_stats(&self) { + pub fn log_shm_stats(&self) -> Result<(), ConnectionError> { let shm = &self.shm; - let free = shm.num_slots_free(); + let free = shm.num_slots_free()?; let total = shm.num_slots_total(); self.stats.log_stats(); info!("shm stats free/total: {}/{}", free, total); + Ok(()) } pub fn reset_stats(&mut self) { diff --git a/common/src/py_connection.rs b/common/src/py_connection.rs index 34d7160c..7ad0cb2d 100644 --- a/common/src/py_connection.rs +++ b/common/src/py_connection.rs @@ -191,7 +191,9 @@ macro_rules! impl_py_connection { let _trace_guard = span_from_py(py, &format!("{}::close", stringify!($name)))?; if let Some(mut conn_impl) = self.conn_impl.take() { - conn_impl.log_shm_stats(); + conn_impl + .log_shm_stats() + .map_err(|e| PyConnectionError::new_err(e.to_string()))?; conn_impl.reset_stats(); conn_impl.close(); Ok(()) @@ -261,7 +263,9 @@ macro_rules! impl_py_connection { pub fn log_shm_stats(&self) -> PyResult<()> { let conn_impl = self.get_conn()?; - conn_impl.log_shm_stats(); + conn_impl + .log_shm_stats() + .map_err(|e| PyConnectionError::new_err(e.to_string()))?; Ok(()) } } diff --git a/ipc_test/Cargo.toml b/ipc_test/Cargo.toml index 62945ccd..82073015 100644 --- a/ipc_test/Cargo.toml +++ b/ipc_test/Cargo.toml @@ -29,3 +29,6 @@ sendfd = "0.4.3" [target.'cfg(not(windows))'.dependencies] nix = { version = "0.29.0", features = ["poll"] } + +[lints.rust] +unused_must_use = "deny" diff --git a/ipc_test/examples/consumer/main.rs b/ipc_test/examples/consumer/main.rs index 54d4d994..f7397dc8 100644 --- a/ipc_test/examples/consumer/main.rs +++ b/ipc_test/examples/consumer/main.rs @@ -75,13 +75,13 @@ fn main() { // some additional "work": //std::thread::sleep(Duration::from_micros(1)); - ssa.free_idx(slot_info.slot_idx); + ssa.free_idx(slot_info.slot_idx).unwrap(); sum += sum_part.0 as f64; bytes_processed += SLOT_SIZE_BYTES; if t0.elapsed() > Duration::from_secs(1) { - let slots_free = ssa.num_slots_free(); + let slots_free = ssa.num_slots_free().unwrap(); println!( "idx: {idx:5}, sum: {sum_part}, throughput: {:7.2} MiB/s, slots free: {slots_free}", bytes_processed as f32 / 1024.0 / 1024.0 diff --git a/ipc_test/examples/producer/main.rs b/ipc_test/examples/producer/main.rs index e8561b37..35486eab 100644 --- a/ipc_test/examples/producer/main.rs +++ b/ipc_test/examples/producer/main.rs @@ -70,7 +70,7 @@ fn handle_connection( } println!("done sending {} items", send_num_items); - while ssa.num_slots_free() < ssa.num_slots_total() { + while ssa.num_slots_free().unwrap() < ssa.num_slots_total() { thread::sleep(Duration::from_millis(100)); } println!("done!") diff --git a/ipc_test/src/backend_shm.rs b/ipc_test/src/backend_shm.rs index 9a669e1b..a98f9377 100644 --- a/ipc_test/src/backend_shm.rs +++ b/ipc_test/src/backend_shm.rs @@ -111,7 +111,7 @@ impl SharedMemory { impl Drop for SharedMemory { fn drop(&mut self) { if self.is_owner { - remove_file(&self.handle_path).unwrap(); + let _ = remove_file(&self.handle_path); } } } diff --git a/ipc_test/src/slab.rs b/ipc_test/src/slab.rs index 41bff6d6..d5ebe8fa 100644 --- a/ipc_test/src/slab.rs +++ b/ipc_test/src/slab.rs @@ -69,10 +69,13 @@ pub struct SHMHandle { impl SHMHandle {} -#[derive(Debug, thiserror::Error)] +#[derive(Debug, Clone, thiserror::Error)] pub enum ShmError { #[error("no slot available")] NoSlotAvailable, + + #[error("mutex error: {0}")] + MutexError(String), } /// Additional information needed to re-crate a `SharedSlabAllocator` in a @@ -103,6 +106,9 @@ pub struct SharedSlabAllocator { pub enum SlabInitError { #[error("connection failed: {0}")] ConnectError(#[from] ShmConnectError), + + #[error("mutex error: {0}")] + MutexError(String), } /// @@ -185,7 +191,8 @@ impl SharedSlabAllocator { let free_list_ptr = unsafe { ptr.offset(Self::MUTEX_SIZE.try_into().unwrap()) }; let (_lock, bg_thread) = if init_structures { - let (lock, used_size) = unsafe { Mutex::new(ptr, free_list_ptr).unwrap() }; + let (lock, used_size) = unsafe { Mutex::new(ptr, free_list_ptr) } + .map_err(|e| SlabInitError::MutexError(e.to_string()))?; if used_size > Self::MUTEX_SIZE { panic!("Mutex size larger than expected!"); @@ -231,7 +238,8 @@ impl SharedSlabAllocator { (lock, Some((j, cleanup_chan_s))) } else { - let (lock, used_size) = unsafe { Mutex::from_existing(ptr, free_list_ptr).unwrap() }; + let (lock, used_size) = unsafe { Mutex::from_existing(ptr, free_list_ptr) } + .map_err(|e| SlabInitError::MutexError(e.to_string()))?; if used_size > Self::MUTEX_SIZE { panic!("Mutex size larger than expected!"); @@ -259,11 +267,12 @@ impl SharedSlabAllocator { Self::connect(&handle.os_handle) } - fn get_mutex(&self) -> Box { + fn get_mutex(&self) -> Result, ShmError> { let ptr = self.shm.as_mut_ptr(); let free_list_ptr = unsafe { ptr.offset(Self::MUTEX_SIZE.try_into().unwrap()) }; - let (lock, _) = unsafe { Mutex::from_existing(ptr, free_list_ptr).unwrap() }; - lock + let (lock, _) = unsafe { Mutex::from_existing(ptr, free_list_ptr) } + .map_err(|e| ShmError::MutexError(e.to_string()))?; + Ok(lock) } pub fn get_slab_info(&self) -> SlabInfo { @@ -290,19 +299,27 @@ impl SharedSlabAllocator { /// `SlotInfo` struct using `writing_done`, which can then be sent to /// a consumer. pub fn get_mut(&mut self) -> Option { - let slot_idx: usize = self.pop_free_slot_idx()?; + match self.try_get_mut() { + Ok(slot) => Some(slot), + Err(_) => None, + } + } - Some(SlotForWriting { + pub fn try_get_mut(&mut self) -> Result { + let slot_idx: usize = match self.pop_free_slot_idx()? { + Some(idx) => idx, + None => { + return Err(ShmError::NoSlotAvailable); + } + }; + + Ok(SlotForWriting { ptr: self.get_mut_ptr_for_slot(slot_idx), slot_idx, size: self.slot_size, }) } - pub fn try_get_mut(&mut self) -> Result { - self.get_mut().ok_or(ShmError::NoSlotAvailable) - } - /// Exchange the `SlotForWriting` token into /// a `SlotInfo` that can be sent to readers /// which can not be used to write anymore @@ -314,11 +331,11 @@ impl SharedSlabAllocator { } } - pub fn num_slots_free(&self) -> usize { - let mutex = self.get_mutex(); + pub fn num_slots_free(&self) -> Result { + let mutex = self.get_mutex()?; let guard = mutex.lock().unwrap(); let stack = Self::get_free_list(*guard, self.num_slots); - stack.get_stack_idx() + Ok(stack.get_stack_idx()) } pub fn num_slots_total(&self) -> usize { @@ -338,11 +355,12 @@ impl SharedSlabAllocator { } } - pub fn free_idx(&mut self, slot_idx: usize) { - let mutex = self.get_mutex(); + pub fn free_idx(&mut self, slot_idx: usize) -> Result<(), ShmError> { + let mutex = self.get_mutex()?; let guard = mutex.lock().unwrap(); let mut stack = Self::get_free_list(*guard, self.num_slots); stack.push(slot_idx); + Ok(()) } /// @@ -416,11 +434,11 @@ impl SharedSlabAllocator { FreeStack::new(stack_ptr, free_list_size) } - fn pop_free_slot_idx(&mut self) -> Option { - let mutex = self.get_mutex(); + fn pop_free_slot_idx(&mut self) -> Result, ShmError> { + let mutex = self.get_mutex()?; let guard = mutex.lock().unwrap(); let mut stack = Self::get_free_list(*guard, self.num_slots); - stack.pop() + Ok(stack.pop()) } } @@ -477,7 +495,7 @@ mod test { for i in 0..255u8 { assert_eq!(slotr.as_slice()[i as usize], i); } - ssa.free_idx(slotw.slot_idx); + ssa.free_idx(slotw.slot_idx).unwrap(); } #[test] @@ -505,7 +523,7 @@ mod test { for i in 0..255u8 { assert_eq!(slotr.as_slice()[i as usize], i); } - ssa2.free_idx(slotw.slot_idx); + ssa2.free_idx(slotw.slot_idx).unwrap(); } #[test] @@ -534,7 +552,7 @@ mod test { for i in 0..255u8 { assert_eq!(slotr.as_slice()[i as usize], i); } - ssa2.free_idx(slotw.slot_idx); + ssa2.free_idx(slotw.slot_idx).unwrap(); } #[test] @@ -575,7 +593,7 @@ mod test { // We are done with the data in the slot, make it available // to the producer: - ssa2.free_idx(idx); + ssa2.free_idx(idx).unwrap(); // for keeping the test robust: signal main thread we are done done_s.send(()).unwrap(); diff --git a/libertem_asi_mpx3/Cargo.toml b/libertem_asi_mpx3/Cargo.toml index 1a52291f..6b5976df 100644 --- a/libertem_asi_mpx3/Cargo.toml +++ b/libertem_asi_mpx3/Cargo.toml @@ -2,7 +2,7 @@ name = "libertem-asi-mpx3" authors = ["Alexander Clausen "] license = "MIT" -version = "0.2.13" +version = "0.2.14" edition = "2021" readme = "README.md" rust-version = "1.71" @@ -33,3 +33,6 @@ extension-module = ["pyo3/extension-module"] [dev-dependencies] tempfile = "3.3.0" + +[lints.rust] +unused_must_use = "deny" diff --git a/libertem_asi_mpx3/src/background_thread.rs b/libertem_asi_mpx3/src/background_thread.rs index 2a65358e..0fcea2d6 100644 --- a/libertem_asi_mpx3/src/background_thread.rs +++ b/libertem_asi_mpx3/src/background_thread.rs @@ -1,5 +1,4 @@ use std::{ - fmt::Display, io::ErrorKind, net::TcpStream, sync::mpsc::{channel, Receiver, RecvTimeoutError, SendError, Sender, TryRecvError}, @@ -13,7 +12,7 @@ use common::{ tcp::{self, ReadExactError}, utils::{num_from_byte_slice, three_way_shift, NumParseError}, }; -use ipc_test::SharedSlabAllocator; +use ipc_test::{slab::ShmError, SharedSlabAllocator}; use log::{debug, error, info, trace, warn}; use opentelemetry::Context; use serval_client::{DetectorConfig, ServalClient, ServalError}; @@ -261,6 +260,9 @@ enum AcquisitionError { #[error("error writing to shm: {0}")] WriteError(#[from] FrameStackWriteError), + + #[error("shm access error: {0}")] + ShmError(#[from] ShmError), } impl From for AcquisitionError { @@ -388,7 +390,7 @@ fn passive_acquisition( shm, )?; - let free = shm.num_slots_free(); + let free = shm.num_slots_free()?; let total = shm.num_slots_total(); info!("passive acquisition done; free slots: {}/{}", free, total); @@ -602,17 +604,6 @@ fn background_thread( Ok(()) } -pub struct ReceiverError { - pub msg: String, -} - -impl Display for ReceiverError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let msg = &self.msg; - write!(f, "{msg}") - } -} - pub struct ASIMpxBackgroundThread { bg_thread: JoinHandle<()>, to_thread: Sender>, diff --git a/libertem_asi_tpx3/Cargo.toml b/libertem_asi_tpx3/Cargo.toml index 0093e493..d9578e0b 100644 --- a/libertem_asi_tpx3/Cargo.toml +++ b/libertem_asi_tpx3/Cargo.toml @@ -2,7 +2,7 @@ name = "libertem-asi-tpx3" authors = ["Alexander Clausen "] license = "MIT" -version = "0.2.13" +version = "0.2.14" edition = "2021" readme = "README.md" rust-version = "1.71" @@ -28,9 +28,13 @@ common = { path = "../common" } zerocopy = "0.7.35" page_size = "0.5.0" opentelemetry = "0.25.0" +thiserror = "1.0.64" [dev-dependencies] tempfile = "3.3.0" [features] extension-module = ["pyo3/extension-module"] + +[lints.rust] +unused_must_use = "deny" diff --git a/libertem_asi_tpx3/src/background_thread.rs b/libertem_asi_tpx3/src/background_thread.rs index 97a88d45..d20d2c34 100644 --- a/libertem_asi_tpx3/src/background_thread.rs +++ b/libertem_asi_tpx3/src/background_thread.rs @@ -1,5 +1,4 @@ use std::{ - fmt::Display, mem::replace, net::TcpStream, thread::JoinHandle, @@ -7,7 +6,7 @@ use std::{ }; use crossbeam_channel::{unbounded, Receiver, RecvTimeoutError, SendError, Sender, TryRecvError}; -use ipc_test::{SHMHandle, SharedSlabAllocator}; +use ipc_test::{slab::ShmError, SHMHandle, SharedSlabAllocator}; use log::{debug, error, info, trace, warn}; use opentelemetry::Context; @@ -66,25 +65,31 @@ pub enum ReceiverStatus { Closed, } -#[derive(Debug)] +#[derive(thiserror::Error, Debug)] enum AcquisitionError { + #[error("other end has disconnected")] Disconnected, + + #[error("acquisition cancelled")] Cancelled, + + #[error("shm buffer is full")] BufferFull, - SlotSizeTooSmall { - slot_size: usize, - chunk_size: usize, - }, - /// Example: an unexpected header was received - StateError { - msg: String, - }, + #[error("slot size {slot_size} too small for chunk {chunk_size}")] + SlotSizeTooSmall { slot_size: usize, chunk_size: usize }, - /// An error occurred while reading the socket - StreamError { - err: StreamError, - }, + // Example: an unexpected header was received + #[error("state error: {msg}")] + StateError { msg: String }, + + // An error occurred while reading the socket + #[error("stream error: {err}")] + StreamError { err: StreamError }, + + // An error occured while trying to access the SHM + #[error("SHM error: {err}")] + ShmAccessError { err: ShmError }, } impl From for AcquisitionError { @@ -99,37 +104,18 @@ impl From> for AcquisitionError { } } -impl Display for AcquisitionError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - AcquisitionError::Cancelled => { - write!(f, "acquisition cancelled") - } - AcquisitionError::SlotSizeTooSmall { - slot_size, - chunk_size, - } => { - write!(f, "slot size {slot_size} too small for chunk {chunk_size}") - } - AcquisitionError::Disconnected => { - write!(f, "other end has disconnected") - } - AcquisitionError::BufferFull => { - write!(f, "shm buffer is full") - } - AcquisitionError::StateError { msg } => { - write!(f, "state error: {msg}") - } - AcquisitionError::StreamError { err } => { - write!(f, "stream error: {err:?}") - } - } +impl From for AcquisitionError { + fn from(value: ShmError) -> Self { + AcquisitionError::ShmAccessError { err: value } } } -#[derive(Debug)] +#[derive(thiserror::Error, Debug)] pub enum ControlError { + #[error("cancelled")] Cancelled, + + #[error("state error: {msg}")] StateError { msg: String }, } @@ -199,7 +185,7 @@ fn wait_for_acquisition( } } - let free = shm.num_slots_free(); + let free = shm.num_slots_free()?; let total = shm.num_slots_total(); info!("passive acquisition done; free slots: {}/{}", free, total); } @@ -255,7 +241,7 @@ fn wait_for_scan( Err(e) => return Err(e), }; - let free = shm.num_slots_free(); + let free = shm.num_slots_free()?; let total = shm.num_slots_total(); info!("passive scan done; free slots: {}/{}", free, total); } @@ -547,15 +533,10 @@ fn background_thread( Ok(()) } -pub struct ReceiverError { - pub msg: String, -} - -impl Display for ReceiverError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let msg = &self.msg; - write!(f, "{msg}") - } +#[derive(thiserror::Error, Debug, Clone)] +pub enum ReceiverError { + #[error("receiver is closed")] + Closed, } /// Start a background thread that received data from the zeromq socket and @@ -643,9 +624,7 @@ impl TPXReceiver { pub fn start_passive(&mut self) -> Result<(), ReceiverError> { if self.status == ReceiverStatus::Closed { - return Err(ReceiverError { - msg: "receiver is closed".to_string(), - }); + return Err(ReceiverError::Closed); } self.to_thread .send(ControlMsg::StartAcquisitionPassive) diff --git a/libertem_asi_tpx3/src/cam_client.rs b/libertem_asi_tpx3/src/cam_client.rs index 1516a40e..9a55f29a 100644 --- a/libertem_asi_tpx3/src/cam_client.rs +++ b/libertem_asi_tpx3/src/cam_client.rs @@ -90,7 +90,8 @@ impl CamClient { fn done(mut slf: PyRefMut, handle: &ChunkStackHandle) -> PyResult<()> { let slot_idx = handle.slot.slot_idx; if let Some(shm) = &mut slf.shm { - shm.free_idx(slot_idx); + shm.free_idx(slot_idx) + .map_err(|e| PyRuntimeError::new_err(e.to_string()))?; Ok(()) } else { Err(PyRuntimeError::new_err( diff --git a/libertem_asi_tpx3/src/chunk_stack.rs b/libertem_asi_tpx3/src/chunk_stack.rs index 0413fae0..13194115 100644 --- a/libertem_asi_tpx3/src/chunk_stack.rs +++ b/libertem_asi_tpx3/src/chunk_stack.rs @@ -1,5 +1,5 @@ use bincode::serialize; -use ipc_test::{SharedSlabAllocator, Slot, SlotForWriting, SlotInfo}; +use ipc_test::{slab::ShmError, SharedSlabAllocator, Slot, SlotForWriting, SlotInfo}; use log::trace; use pyo3::{ exceptions::{PyRuntimeError, PyValueError}, @@ -17,6 +17,12 @@ use crate::{ sparse_csr::{CSRSizes, CSRSplitter}, }; +#[derive(thiserror::Error, Debug, Clone)] +pub enum SplitError { + #[error("shm access error: {0}")] + ShmAccessError(#[from] ShmError), +} + /// Information about one array chunk and the layout of the CSR sub-arrays in memory #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] #[pyclass] @@ -265,7 +271,11 @@ impl ChunkStackHandle { /// /// The length of the left stack returned is equal to `mid`. /// - pub fn split_at(self, mid: u32, shm: &mut SharedSlabAllocator) -> (Self, Self) { + pub fn split_at( + self, + mid: u32, + shm: &mut SharedSlabAllocator, + ) -> Result<(Self, Self), SplitError> { // FIXME: this whole thing is falliable, so modify return type to Result<> (or PyResult<>?) // First, let's plan our operation: @@ -362,9 +372,9 @@ impl ChunkStackHandle { let right = stack_right.writing_done(shm); // free our own slot - shm.free_idx(self.slot.slot_idx); + shm.free_idx(self.slot.slot_idx)?; - (left, right) + Ok((left, right)) } // FIXME: this doesn't make sense for stacks with different V-type per chunk! @@ -554,7 +564,7 @@ mod tests { let _old_layout_len = fs_handle.layout.len(); - let (a, b) = fs_handle.split_at(2, &mut shm); + let (a, b) = fs_handle.split_at(2, &mut shm).unwrap(); let slot_a: Slot = shm.get(a.slot.slot_idx); let slot_b: Slot = shm.get(b.slot.slot_idx); @@ -575,13 +585,13 @@ mod tests { assert_eq!(view_b.values, &[256, 512, 1024, 2048]); // when the split is done, there should be one free shm slot: - assert_eq!(shm.num_slots_free(), 1); + assert_eq!(shm.num_slots_free().unwrap(), 1); // and we can free them again: - shm.free_idx(a.slot.slot_idx); - shm.free_idx(b.slot.slot_idx); + shm.free_idx(a.slot.slot_idx).unwrap(); + shm.free_idx(b.slot.slot_idx).unwrap(); - assert_eq!(shm.num_slots_free(), 3); + assert_eq!(shm.num_slots_free().unwrap(), 3); } #[test] @@ -669,16 +679,16 @@ mod tests { let fs_handle = fs.writing_done(&mut shm); - let (a, b) = fs_handle.split_at(14, &mut shm); + let (a, b) = fs_handle.split_at(14, &mut shm).unwrap(); // when the split is done, there should be one free shm slot: - assert_eq!(shm.num_slots_free(), 1); + assert_eq!(shm.num_slots_free().unwrap(), 1); // and we can free them again: - shm.free_idx(a.slot.slot_idx); - shm.free_idx(b.slot.slot_idx); + shm.free_idx(a.slot.slot_idx).unwrap(); + shm.free_idx(b.slot.slot_idx).unwrap(); - assert_eq!(shm.num_slots_free(), 3); + assert_eq!(shm.num_slots_free().unwrap(), 3); } #[test] diff --git a/libertem_asi_tpx3/src/headers.rs b/libertem_asi_tpx3/src/headers.rs index f2a90a99..110b3d96 100644 --- a/libertem_asi_tpx3/src/headers.rs +++ b/libertem_asi_tpx3/src/headers.rs @@ -13,10 +13,15 @@ pub enum HeaderTypes { AcquisitionEnd { header: AcquisitionEnd }, } -#[derive(Debug)] +#[derive(thiserror::Error, Debug)] pub enum WireFormatError { + #[error("unknown header: {id}")] UnknownHeader { id: u8 }, + + #[error("unknown version")] UnknownVersion, + + #[error("serde error: {err}")] SerdeError { err: Box }, } diff --git a/libertem_asi_tpx3/src/main_py.rs b/libertem_asi_tpx3/src/main_py.rs index bc2f60f7..746168a1 100644 --- a/libertem_asi_tpx3/src/main_py.rs +++ b/libertem_asi_tpx3/src/main_py.rs @@ -90,7 +90,9 @@ impl<'a, 'b, 'c, 'd> ChunkIterator<'a, 'b, 'c, 'd> { frame_stack.len() ); self.stats.count_split(); - let (left, right) = frame_stack.split_at(max_size, self.shm); + let (left, right) = frame_stack + .split_at(max_size, self.shm) + .map_err(|e| PyRuntimeError::new_err(e.to_string()))?; self.remainder.push(right); assert!(left.len() <= max_size); return Ok(Some(left)); @@ -192,7 +194,7 @@ impl ASITpx3Connection { fn start_passive_impl(&mut self) -> PyResult<()> { self.receiver .start_passive() - .map_err(|err| exceptions::PyRuntimeError::new_err(err.msg)) + .map_err(|err| exceptions::PyRuntimeError::new_err(err.to_string())) } fn close_impl(&mut self) { @@ -336,10 +338,14 @@ impl ASITpx3Connection { }) } - fn log_shm_stats(&self) { - let free = self.local_shm.num_slots_free(); + fn log_shm_stats(&self) -> PyResult<()> { + let free = self + .local_shm + .num_slots_free() + .map_err(|e| PyRuntimeError::new_err(e.to_string()))?; let total = self.local_shm.num_slots_total(); self.stats.log_stats(); info!("shm stats free/total: {}/{}", free, total); + Ok(()) } } diff --git a/libertem_asi_tpx3/src/stream.rs b/libertem_asi_tpx3/src/stream.rs index 33365f99..b3ef666c 100644 --- a/libertem_asi_tpx3/src/stream.rs +++ b/libertem_asi_tpx3/src/stream.rs @@ -7,13 +7,22 @@ use crate::{ headers::{HeaderTypes, WireFormatError}, }; -#[derive(Debug)] +#[derive(thiserror::Error, Debug)] pub enum StreamError { + #[error("timeout")] Timeout, + + #[error("I/O error: {0}")] IoError(std::io::Error), + + #[error("EOF")] Eof, - FormatError(WireFormatError), - ControlError(ControlError), + + #[error("format error: {0}")] + FormatError(#[from] WireFormatError), + + #[error("control error: {0}")] + ControlError(#[from] ControlError), } impl From for StreamError { @@ -25,18 +34,6 @@ impl From for StreamError { } } -impl From for StreamError { - fn from(value: WireFormatError) -> Self { - Self::FormatError(value) - } -} - -impl From for StreamError { - fn from(value: ControlError) -> Self { - Self::ControlError(value) - } -} - impl From> for StreamError { fn from(value: ReadExactError) -> Self { match value { diff --git a/libertem_dectris/Cargo.toml b/libertem_dectris/Cargo.toml index 24233824..ca48184a 100644 --- a/libertem_dectris/Cargo.toml +++ b/libertem_dectris/Cargo.toml @@ -2,7 +2,7 @@ name = "libertem-dectris" authors = ["Alexander Clausen "] license = "MIT" -version = "0.2.13" +version = "0.2.14" edition = "2021" readme = "README.md" rust-version = "1.71" @@ -46,3 +46,6 @@ extension-module = ["pyo3/extension-module"] [dev-dependencies] tempfile = "3.3.0" + +[lints.rust] +unused_must_use = "deny" diff --git a/libertem_dectris/examples/passive_low_level.py b/libertem_dectris/examples/passive_low_level.py index 991c0f96..0d7509bd 100644 --- a/libertem_dectris/examples/passive_low_level.py +++ b/libertem_dectris/examples/passive_low_level.py @@ -41,7 +41,9 @@ def main(width: int, height: int): break tq.update(len(stack_handle)) - continue + if False: + cam_client.done(stack_handle) + continue # the expected shape and data type: frame_shape = tuple(reversed(stack_handle.get_shape())) diff --git a/libertem_dectris/src/background_thread.rs b/libertem_dectris/src/background_thread.rs index 4c801969..34837602 100644 --- a/libertem_dectris/src/background_thread.rs +++ b/libertem_dectris/src/background_thread.rs @@ -1,6 +1,5 @@ use std::{ convert::Infallible, - fmt::Display, mem::replace, ops::Deref, sync::mpsc::{channel, Receiver, RecvTimeoutError, SendError, Sender, TryRecvError}, @@ -75,56 +74,35 @@ fn recv_frame_into( #[derive(Debug, Clone, thiserror::Error)] enum AcquisitionError { + #[error("other end has disconnected")] Disconnected, - SeriesMismatch, + + #[error("series mismatch; expected {expected_id}, got {got_id}")] + SeriesMismatch { expected_id: u64, got_id: u64 }, + + #[error("frame id mismatch; got {got_id}, expected {expected_id}")] FrameIdMismatch { expected_id: u64, got_id: u64 }, + + #[error("deserialization failed: {msg}; got msg {recvd_msg}")] SerdeError { recvd_msg: String, msg: String }, + + #[error("acquisition cancelled")] StopThread, + + #[error("acquisition cancelled")] Cancelled, + + #[error("zmq error: {err}")] ZmqError { err: zmq::Error }, - BufferFull, + + #[error("state error: {msg}")] StateError { msg: String }, + + #[error("configuration error: {msg}")] ConfigurationError { msg: String }, -} -impl Display for AcquisitionError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - AcquisitionError::ZmqError { err } => { - write!(f, "zmq error {err}") - } - AcquisitionError::StopThread => { - write!(f, "acquisition cancelled") - } - AcquisitionError::SerdeError { recvd_msg, msg } => { - write!(f, "deserialization failed: {msg}; got msg {recvd_msg}") - } - AcquisitionError::SeriesMismatch => { - write!(f, "series mismatch") - } - AcquisitionError::FrameIdMismatch { - expected_id, - got_id, - } => { - write!(f, "frame id mismatch; got {got_id}, expected {expected_id}") - } - AcquisitionError::Disconnected => { - write!(f, "other end has disconnected") - } - AcquisitionError::BufferFull => { - write!(f, "shm buffer is full") - } - AcquisitionError::StateError { msg } => { - write!(f, "state error: {msg}") - } - AcquisitionError::ConfigurationError { msg } => { - write!(f, "configuration error: {msg}") - } - AcquisitionError::Cancelled => { - write!(f, "acquisition cancelled") - } - } - } + #[error("SHM access error: {err}")] + ShmAccessError { err: ShmError }, } impl From> for AcquisitionError { @@ -133,6 +111,12 @@ impl From> for AcquisitionError { } } +impl From for AcquisitionError { + fn from(value: ShmError) -> Self { + Self::ShmAccessError { err: value } + } +} + impl AcquisitionError { fn serde_from_msg(err: &serde_json::Error, msg: &Message) -> Self { Self::SerdeError { @@ -207,7 +191,7 @@ fn make_frame_stack( ) -> Result, AcquisitionError> { loop { // keep some slots free for splitting frame stacks - if shm.num_slots_free() < 3 && shm.num_slots_total() >= 3 { + if shm.num_slots_free()? < 3 && shm.num_slots_total() >= 3 { trace!("shm is almost full; waiting and creating backpressure..."); check_for_control(to_thread_r)?; std::thread::sleep(Duration::from_millis(1)); @@ -222,6 +206,9 @@ fn make_frame_stack( std::thread::sleep(Duration::from_millis(1)); continue; } + Err(e @ ShmError::MutexError(_)) => { + return Err(e.into()); + } } } } @@ -285,7 +272,7 @@ fn passive_acquisition( shm, )?; - let free = shm.num_slots_free(); + let free = shm.num_slots_free()?; let total = shm.num_slots_total(); info!("passive acquisition done; free slots: {}/{}", free, total); } else { @@ -342,7 +329,10 @@ fn acquisition( recv_frame_into(socket, to_thread_r, &mut msg, &mut msg_image)?; if dimage.series != series { - return Err(AcquisitionError::SeriesMismatch); + return Err(AcquisitionError::SeriesMismatch { + expected_id: series, + got_id: dimage.series, + }); } if dimage.frame != expected_frame_id { @@ -373,6 +363,9 @@ fn acquisition( Err(FrameStackWriteError::TooSmall) => { warn!("acquisition: frame stack too small") } + Err(FrameStackWriteError::ShmAccessError(e)) => { + return Err(AcquisitionError::ShmAccessError { err: e }) + } } } @@ -419,6 +412,9 @@ fn acquisition( Err(FrameStackWriteError::TooSmall) => { warn!("acquisition: frame stack too small") } + Err(FrameStackWriteError::ShmAccessError(e)) => { + return Err(AcquisitionError::ShmAccessError { err: e }) + } } return Ok(()); @@ -622,17 +618,6 @@ fn background_thread( Ok(()) } -pub struct ReceiverError { - pub msg: String, -} - -impl Display for ReceiverError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let msg = &self.msg; - write!(f, "{msg}") - } -} - #[derive(Debug)] pub enum DectrisExtraControl { StartAcquisitionWithSeries { series: u64 }, diff --git a/libertem_qd_mpx/Cargo.toml b/libertem_qd_mpx/Cargo.toml index 4df051b5..8ef34ed1 100644 --- a/libertem_qd_mpx/Cargo.toml +++ b/libertem_qd_mpx/Cargo.toml @@ -2,7 +2,7 @@ name = "libertem_qd_mpx" authors = ["Alexander Clausen "] license = "MIT" -version = "0.2.13" +version = "0.2.14" edition = "2021" readme = "README.md" rust-version = "1.71" @@ -41,3 +41,6 @@ tokio = { version = "1", features = ["rt", "net", "time", "sync", "io-util", "rt [[bench]] name = "decoders" harness = false + +[lints.rust] +unused_must_use = "deny" diff --git a/libertem_qd_mpx/src/background_thread.rs b/libertem_qd_mpx/src/background_thread.rs index 0f6752c6..acd5a7f6 100644 --- a/libertem_qd_mpx/src/background_thread.rs +++ b/libertem_qd_mpx/src/background_thread.rs @@ -49,8 +49,8 @@ pub enum AcquisitionError { #[error("configuration error: {msg}")] ConfigurationError { msg: String }, - #[error("shm buffer full")] - NoSlotAvailable, + #[error("error accessing shm: {0}")] + ShmAccessError(#[from] ShmError), #[error("error writing to shm: {0}")] WriteError(#[from] FrameStackWriteError), @@ -95,14 +95,6 @@ impl From> for AcquisitionError { } } -impl From for AcquisitionError { - fn from(value: ShmError) -> Self { - match value { - ShmError::NoSlotAvailable => AcquisitionError::NoSlotAvailable, - } - } -} - impl From> for AcquisitionError { fn from(value: ReadExactError) -> Self { match value { @@ -370,7 +362,7 @@ fn make_frame_stack<'a>( ) -> Result, AcquisitionError> { loop { // keep some slots free for splitting frame stacks - if shm.num_slots_free() < 3 && shm.num_slots_total() >= 3 { + if shm.num_slots_free()? < 3 && shm.num_slots_total() >= 3 { trace!("shm is almost full; waiting and creating backpressure..."); check_for_control(to_thread_r)?; std::thread::sleep(Duration::from_millis(1)); @@ -394,6 +386,9 @@ fn make_frame_stack<'a>( std::thread::sleep(Duration::from_millis(1)); continue; } + Err(e @ ShmError::MutexError(_)) => { + return Err(e.into()); + } } } } @@ -624,7 +619,7 @@ fn passive_acquisition( shm, )?; - let free = shm.num_slots_free(); + let free = shm.num_slots_free()?; let total = shm.num_slots_total(); info!("passive acquisition done; free slots: {}/{}", free, total); @@ -974,7 +969,7 @@ End conn.close(); server_thread.join().unwrap(); - assert_eq!(shm.num_slots_total(), shm.num_slots_free()); + assert_eq!(shm.num_slots_total(), shm.num_slots_free().unwrap()); }); } @@ -1051,7 +1046,7 @@ End conn.close(); server_thread.join().unwrap(); - assert_eq!(shm.num_slots_total(), shm.num_slots_free()); + assert_eq!(shm.num_slots_total(), shm.num_slots_free().unwrap()); }); } @@ -1156,7 +1151,7 @@ End stack.with_slot(&shm, |s| { assert_eq!(s.as_slice(), vec![idx as u8; 256 * 256],); }); - stack.free_slot(&mut shm); + stack.free_slot(&mut shm).unwrap(); idx += 1; } else { eprintln!("done!"); @@ -1173,7 +1168,7 @@ End conn.close(); server_thread.join().unwrap(); - assert_eq!(shm.num_slots_total(), shm.num_slots_free()); + assert_eq!(shm.num_slots_total(), shm.num_slots_free().unwrap()); }); } @@ -1257,7 +1252,7 @@ End stack.with_slot(&shm, |s| { assert_eq!(s.as_slice(), vec![idx as u8; 256 * 256],); }); - stack.free_slot(&mut shm); + stack.free_slot(&mut shm).unwrap(); } else { eprintln!("done!"); break; @@ -1272,7 +1267,7 @@ End conn.close(); server_thread.join().unwrap(); - assert_eq!(shm.num_slots_total(), shm.num_slots_free()); + assert_eq!(shm.num_slots_total(), shm.num_slots_free().unwrap()); }); } @@ -1365,7 +1360,7 @@ End stack.with_slot(&shm, |s| { assert_eq!(s.as_slice(), vec![idx as u8; 256 * 256],); }); - stack.free_slot(&mut shm); + stack.free_slot(&mut shm).unwrap(); } else { eprintln!("done!"); break; @@ -1380,7 +1375,7 @@ End conn.close(); server_thread.join().unwrap(); - assert_eq!(shm.num_slots_total(), shm.num_slots_free()); + assert_eq!(shm.num_slots_total(), shm.num_slots_free().unwrap()); }); } @@ -1478,7 +1473,7 @@ End stack.with_slot(&shm, |s| { assert_eq!(s.as_slice(), vec![idx as u8; 256 * 256],); }); - stack.free_slot(&mut shm); + stack.free_slot(&mut shm).unwrap(); } else { eprintln!("done!"); break; @@ -1493,7 +1488,7 @@ End conn.close(); server_thread.join().unwrap(); - assert_eq!(shm.num_slots_total(), shm.num_slots_free()); + assert_eq!(shm.num_slots_total(), shm.num_slots_free().unwrap()); }); } @@ -1586,7 +1581,7 @@ End stack.with_slot(&shm, |s| { assert_eq!(s.as_slice(), vec![idx as u8; 256 * 256],); }); - stack.free_slot(&mut shm); + stack.free_slot(&mut shm).unwrap(); } let next = conn.get_next_stack(1, || Ok::<_, std::io::Error>(())); @@ -1601,7 +1596,7 @@ End server_thread.join().unwrap(); // we don't leak any slots, yay! - assert_eq!(shm.num_slots_total(), shm.num_slots_free()); + assert_eq!(shm.num_slots_total(), shm.num_slots_free().unwrap()); }); } @@ -1690,7 +1685,7 @@ End stack.with_slot(&shm, |s| { assert_eq!(s.as_slice(), vec![idx as u8; 256 * 256],); }); - stack.free_slot(&mut shm); + stack.free_slot(&mut shm).unwrap(); idx += 1; if Instant::now() > deadline { @@ -1820,7 +1815,7 @@ End first.with_slot(&shm, |s| { assert_eq!(s.as_slice(), vec![0u8; 256 * 256],); }); - first.free_slot(&mut shm); + first.free_slot(&mut shm).unwrap(); info!("got the first one"); @@ -1848,7 +1843,7 @@ End stack.with_slot(&shm, |s| { assert_eq!(s.as_slice(), vec![idx as u8; 256 * 256],); }); - stack.free_slot(&mut shm); + stack.free_slot(&mut shm).unwrap(); } // allow the server thread to end: @@ -1859,7 +1854,7 @@ End server_thread.join().unwrap(); // we don't leak any slots, yay! - assert_eq!(shm.num_slots_total(), shm.num_slots_free()); + assert_eq!(shm.num_slots_total(), shm.num_slots_free().unwrap()); }); } @@ -1970,7 +1965,7 @@ End stack.with_slot(&shm, |s| { assert_eq!(s.as_slice(), vec![idx as u8; 256 * 256],); }); - stack.free_slot(&mut shm); + stack.free_slot(&mut shm).unwrap(); } // next stack is an error: @@ -2001,7 +1996,7 @@ End stack.with_slot(&shm, |s| { assert_eq!(s.as_slice(), vec![idx as u8; 256 * 256],); }); - stack.free_slot(&mut shm); + stack.free_slot(&mut shm).unwrap(); } // that was the last stack: @@ -2022,7 +2017,7 @@ End .expect("server should be able to send everything"); // we don't leak any slots, yay! - assert_eq!(shm.num_slots_total(), shm.num_slots_free()); + assert_eq!(shm.num_slots_total(), shm.num_slots_free().unwrap()); }); } } diff --git a/serval-client/Cargo.toml b/serval-client/Cargo.toml index 5f7bde41..b8d8f628 100644 --- a/serval-client/Cargo.toml +++ b/serval-client/Cargo.toml @@ -12,4 +12,8 @@ rust-version = "1.71" reqwest = { version = "0.11.17", features = ["json", "blocking"] } serde = { version = "1.0.210", features = ["derive"] } serde_json = "1.0.128" +thiserror = "1.0.64" url = "2.3.1" + +[lints.rust] +unused_must_use = "deny" diff --git a/serval-client/src/lib.rs b/serval-client/src/lib.rs index 321a4e98..0afc5c28 100644 --- a/serval-client/src/lib.rs +++ b/serval-client/src/lib.rs @@ -1,12 +1,15 @@ -use std::fmt::Display; - use serde::{de::DeserializeOwned, Deserialize, Serialize}; use url::Url; -#[derive(Debug)] +#[derive(thiserror::Error, Debug)] pub enum ServalError { + #[error("request failed: {msg}")] RequestFailed { msg: String }, + + #[error("serialization error: {msg}")] SerializationError { msg: String }, + + #[error("URL error: {msg}")] URLError { msg: String }, } @@ -34,16 +37,6 @@ impl From for ServalError { } } -impl Display for ServalError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ServalError::RequestFailed { msg } => write!(f, "request failed: {msg}"), - ServalError::SerializationError { msg } => write!(f, "serialization error: {msg}"), - ServalError::URLError { msg } => write!(f, "URL error: {msg}"), - } - } -} - #[derive(PartialEq, Eq, Clone, Serialize, Deserialize, Debug)] pub enum TriggerMode { /// Start: Positive Edge External Trigger Input, Stop: Negative Edge diff --git a/stats/Cargo.toml b/stats/Cargo.toml index 9124a24f..5cb0e744 100644 --- a/stats/Cargo.toml +++ b/stats/Cargo.toml @@ -10,3 +10,6 @@ rust-version = "1.71" [dependencies] log = "0.4.22" + +[lints.rust] +unused_must_use = "deny"