dcrypt_api/error/
registry.rs1#[cfg(feature = "std")]
4use core::any::Any;
5#[cfg(not(feature = "std"))]
6use core::sync::atomic::{AtomicBool, Ordering};
7#[cfg(feature = "std")]
8use std::boxed::Box;
9#[cfg(feature = "std")]
10use std::sync::{Mutex, MutexGuard};
11
12pub static ERROR_REGISTRY: ErrorRegistry = ErrorRegistry::new();
18
19pub struct ErrorRegistry {
27 #[cfg(feature = "std")]
28 state: Mutex<RegistryState>,
29 #[cfg(not(feature = "std"))]
30 has_error: AtomicBool,
31}
32
33#[cfg(feature = "std")]
34struct RegistryState {
35 error: Option<Box<dyn Any + Send>>,
36 generation: u64,
37}
38
39impl ErrorRegistry {
40 pub const fn new() -> Self {
42 Self {
43 #[cfg(feature = "std")]
44 state: Mutex::new(RegistryState {
45 error: None,
46 generation: 0,
47 }),
48 #[cfg(not(feature = "std"))]
49 has_error: AtomicBool::new(false),
50 }
51 }
52
53 pub fn store<E>(&self, error: E)
60 where
61 E: Send + 'static,
62 {
63 #[cfg(feature = "std")]
64 {
65 let old = {
66 let mut state = self.lock();
67 state.generation = state.generation.wrapping_add(1);
68 state.error.replace(Box::new(error))
69 };
70 drop(old);
71 }
72
73 #[cfg(not(feature = "std"))]
74 {
75 drop(error);
76 self.has_error.store(true, Ordering::Release);
77 }
78 }
79
80 pub fn clear(&self) {
82 #[cfg(feature = "std")]
83 {
84 let old = {
85 let mut state = self.lock();
86 state.generation = state.generation.wrapping_add(1);
87 state.error.take()
88 };
89 drop(old);
90 }
91
92 #[cfg(not(feature = "std"))]
93 self.has_error.store(false, Ordering::Release);
94 }
95
96 pub fn has_error(&self) -> bool {
98 #[cfg(feature = "std")]
99 {
100 self.lock().error.is_some()
101 }
102
103 #[cfg(not(feature = "std"))]
104 {
105 self.has_error.load(Ordering::Acquire)
106 }
107 }
108
109 #[cfg(feature = "std")]
114 pub fn get_error<E>(&self) -> Option<E>
115 where
116 E: Clone + Send + 'static,
117 {
118 let (stored, generation) = {
123 let mut state = self.lock();
124 let stored = state.error.take()?;
125 (stored, state.generation)
126 };
127
128 let cloned = stored.downcast_ref::<E>().cloned();
129
130 let displaced = {
131 let mut state = self.lock();
132 if state.generation == generation && state.error.is_none() {
133 state.error = Some(stored);
134 None
135 } else {
136 Some(stored)
137 }
138 };
139 drop(displaced);
140
141 cloned
142 }
143
144 #[cfg(feature = "std")]
145 fn lock(&self) -> MutexGuard<'_, RegistryState> {
146 self.state
150 .lock()
151 .unwrap_or_else(std::sync::PoisonError::into_inner)
152 }
153}
154
155impl Default for ErrorRegistry {
156 fn default() -> Self {
157 Self::new()
158 }
159}
160
161impl Drop for ErrorRegistry {
162 fn drop(&mut self) {
163 #[cfg(feature = "std")]
164 {
165 let state = self
166 .state
167 .get_mut()
168 .unwrap_or_else(std::sync::PoisonError::into_inner);
169 drop(state.error.take());
170 }
171
172 #[cfg(not(feature = "std"))]
173 self.has_error.store(false, Ordering::Release);
174 }
175}
176
177#[cfg(all(test, feature = "std"))]
178mod tests {
179 use super::ErrorRegistry;
180 use std::sync::atomic::{AtomicUsize, Ordering};
181 use std::sync::{Arc, Barrier};
182 use std::thread;
183
184 #[test]
185 fn retrieves_only_the_stored_concrete_type() {
186 let registry = ErrorRegistry::new();
187 registry.store([0xA5_u8; 4096]);
188
189 assert_eq!(registry.get_error::<u8>(), None);
190 assert_eq!(registry.get_error::<String>(), None);
191 assert_eq!(registry.get_error::<[u8; 4096]>(), Some([0xA5; 4096]));
192 }
193
194 #[test]
195 fn replacing_and_clearing_drop_each_value_exactly_once() {
196 #[derive(Clone)]
197 struct DropTracker(Arc<AtomicUsize>);
198
199 impl Drop for DropTracker {
200 fn drop(&mut self) {
201 self.0.fetch_add(1, Ordering::SeqCst);
202 }
203 }
204
205 let drops = Arc::new(AtomicUsize::new(0));
206 let registry = ErrorRegistry::new();
207
208 registry.store(DropTracker(Arc::clone(&drops)));
209 registry.store(DropTracker(Arc::clone(&drops)));
210 assert_eq!(drops.load(Ordering::SeqCst), 1);
211
212 registry.clear();
213 assert_eq!(drops.load(Ordering::SeqCst), 2);
214 assert!(!registry.has_error());
215 }
216
217 #[test]
218 fn concurrent_store_get_and_clear_keep_values_owned() {
219 #[derive(Clone, Debug)]
220 struct Payload {
221 value: usize,
222 complement: usize,
223 padding: [usize; 16],
224 }
225
226 #[derive(Clone, Debug)]
227 struct SmallPayload {
228 value: usize,
229 complement: usize,
230 }
231
232 let registry = Arc::new(ErrorRegistry::new());
233 let barrier = Arc::new(Barrier::new(8));
234 let mut threads = Vec::new();
235 let iterations = if cfg!(miri) { 16 } else { 2_000 };
236
237 for worker in 0..8 {
238 let registry = Arc::clone(®istry);
239 let barrier = Arc::clone(&barrier);
240 threads.push(thread::spawn(move || {
241 barrier.wait();
242 for sequence in 0..iterations {
243 let value = (worker << 24) | sequence;
244 if sequence % 2 == 0 {
245 registry.store(Payload {
246 value,
247 complement: !value,
248 padding: [value; 16],
249 });
250 } else {
251 registry.store(SmallPayload {
252 value,
253 complement: !value,
254 });
255 }
256
257 if let Some(payload) = registry.get_error::<Payload>() {
258 assert_eq!(payload.complement, !payload.value);
259 assert!(payload.padding.iter().all(|item| *item == payload.value));
260 }
261
262 if let Some(payload) = registry.get_error::<SmallPayload>() {
263 assert_eq!(payload.complement, !payload.value);
264 }
265
266 if sequence % 7 == 0 {
267 registry.clear();
268 }
269 }
270 }));
271 }
272
273 for thread in threads {
274 thread.join().unwrap();
275 }
276 }
277
278 #[test]
279 fn user_clone_can_reenter_registry_without_deadlock() {
280 struct ReentrantClone(Arc<ErrorRegistry>);
281
282 impl Clone for ReentrantClone {
283 fn clone(&self) -> Self {
284 assert!(!self.0.has_error());
288 Self(Arc::clone(&self.0))
289 }
290 }
291
292 let registry = Arc::new(ErrorRegistry::new());
293 registry.store(ReentrantClone(Arc::clone(®istry)));
294
295 assert!(registry.get_error::<ReentrantClone>().is_some());
296 assert!(registry.has_error());
297 registry.clear();
300 assert!(!registry.has_error());
301 }
302}
303
304#[cfg(all(test, not(feature = "std")))]
305mod no_std_tests {
306 use super::ErrorRegistry;
307 use core::sync::atomic::{AtomicUsize, Ordering};
308
309 static DROPS: AtomicUsize = AtomicUsize::new(0);
310
311 struct DropTracker;
312
313 impl Drop for DropTracker {
314 fn drop(&mut self) {
315 DROPS.fetch_add(1, Ordering::SeqCst);
316 }
317 }
318
319 #[test]
320 fn presence_only_registry_drops_values_and_tracks_state() {
321 DROPS.store(0, Ordering::SeqCst);
322 let registry = ErrorRegistry::new();
323
324 registry.store(DropTracker);
325 assert_eq!(DROPS.load(Ordering::SeqCst), 1);
326 assert!(registry.has_error());
327
328 registry.clear();
329 assert!(!registry.has_error());
330 }
331}