qubit_function/tester/arc_tester.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10// qubit-style: allow explicit-imports
11//! Defines the `ArcTester` public type.
12
13#![allow(unused_imports)]
14
15use super::*;
16
17// ============================================================================
18// ArcTester: Thread-Safe Shared Ownership Implementation
19// ============================================================================
20
21/// Thread-safe shared ownership Tester implemented using `Arc`
22///
23/// `ArcTester` wraps a closure in `Arc<dyn Fn() -> bool + Send + Sync>`,
24/// allowing the tester to be cloned and safely shared across threads.
25///
26/// # Characteristics
27///
28/// - **Shared ownership**: Can be cloned
29/// - **Thread-safe**: Can be sent across threads
30/// - **Lock-free overhead**: Uses `Fn` without needing `Mutex`
31/// - **Borrowing combination**: `and()`/`or()`/`not()` borrow `&self`
32///
33/// # Use Cases
34///
35/// - Multi-threaded testing scenarios
36/// - Health checks shared across threads
37/// - Test states requiring concurrent access
38/// - Background monitoring tasks
39///
40/// # Examples
41///
42/// ```rust
43/// use qubit_function::{ArcTester, Tester};
44/// use std::sync::{Arc, atomic::{AtomicUsize, Ordering}};
45/// use std::thread;
46///
47/// // Shared atomic counter
48/// let counter = Arc::new(AtomicUsize::new(0));
49/// let counter_clone = Arc::clone(&counter);
50///
51/// let shared = ArcTester::new(move || {
52/// counter_clone.load(Ordering::Relaxed) <= 5
53/// });
54///
55/// let clone = shared.clone();
56/// let handle = thread::spawn(move || {
57/// clone.test()
58/// });
59///
60/// assert!(handle.join().unwrap());
61/// counter.fetch_add(1, Ordering::Relaxed);
62/// assert!(shared.test());
63/// ```
64///
65pub struct ArcTester {
66 pub(super) function: Arc<dyn Fn() -> bool + Send + Sync>,
67}
68
69impl ArcTester {
70 /// Creates a new `ArcTester` from a closure
71 ///
72 /// # Type Parameters
73 ///
74 /// * `F` - Closure type implementing `Fn() -> bool + Send + Sync`
75 ///
76 /// # Parameters
77 ///
78 /// * `f` - The closure to wrap
79 ///
80 /// # Return Value
81 ///
82 /// A new `ArcTester` instance
83 ///
84 /// # Examples
85 ///
86 /// ```rust
87 /// use qubit_function::ArcTester;
88 ///
89 /// let tester = ArcTester::new(|| true);
90 /// ```
91 #[inline]
92 pub fn new<F>(f: F) -> Self
93 where
94 F: Fn() -> bool + Send + Sync + 'static,
95 {
96 ArcTester {
97 function: Arc::new(f),
98 }
99 }
100
101 /// Combines this tester with another tester using logical AND
102 ///
103 /// Returns a new `ArcTester` that returns `true` only when both tests
104 /// pass. Borrows `&self`, so the original tester remains available.
105 ///
106 /// # Parameters
107 ///
108 /// * `next` - The tester to combine with
109 ///
110 /// # Return Value
111 ///
112 /// A new `ArcTester` representing logical AND
113 ///
114 /// # Examples
115 ///
116 /// ```rust
117 /// use qubit_function::{ArcTester, Tester};
118 /// use std::sync::{Arc, atomic::{AtomicUsize, AtomicBool, Ordering}};
119 /// use std::thread;
120 ///
121 /// // Simulate database connection pool status
122 /// let active_connections = Arc::new(AtomicUsize::new(0));
123 /// let is_pool_healthy = Arc::new(AtomicBool::new(true));
124 /// let max_connections = 50;
125 ///
126 /// let conn_clone = Arc::clone(&active_connections);
127 /// let health_clone = Arc::clone(&is_pool_healthy);
128 ///
129 /// // Connection pool health check
130 /// let pool_healthy = ArcTester::new(move || {
131 /// health_clone.load(Ordering::Relaxed)
132 /// });
133 ///
134 /// // Connection count check
135 /// let conn_ok = ArcTester::new(move || {
136 /// conn_clone.load(Ordering::Relaxed) < max_connections
137 /// });
138 ///
139 /// // Combined check: pool healthy and connection count not exceeded
140 /// let pool_ready = pool_healthy.and(&conn_ok);
141 ///
142 /// // Multi-threaded test
143 /// let pool_ready_clone = pool_ready.clone();
144 /// let handle = thread::spawn(move || {
145 /// pool_ready_clone.test()
146 /// });
147 ///
148 /// // Initial state should pass
149 /// assert!(handle.join().unwrap());
150 /// assert!(pool_ready.test());
151 ///
152 /// // Connection count exceeded
153 /// active_connections.store(60, Ordering::Relaxed);
154 /// assert!(!pool_ready.test());
155 ///
156 /// // Connection pool unhealthy
157 /// is_pool_healthy.store(false, Ordering::Relaxed);
158 /// assert!(!pool_ready.test());
159 /// ```
160 #[inline]
161 pub fn and(&self, next: &ArcTester) -> ArcTester {
162 let self_fn = Arc::clone(&self.function);
163 let next_fn = Arc::clone(&next.function);
164 ArcTester {
165 function: Arc::new(move || self_fn() && next_fn()),
166 }
167 }
168
169 /// Combines this tester with another tester using logical OR
170 ///
171 /// Returns a new `ArcTester` that returns `true` if either test passes.
172 /// Borrows `&self`, so the original tester remains available.
173 ///
174 /// # Parameters
175 ///
176 /// * `next` - The tester to combine with
177 ///
178 /// # Return Value
179 ///
180 /// A new `ArcTester` representing logical OR
181 ///
182 /// # Examples
183 ///
184 /// ```rust
185 /// use qubit_function::{ArcTester, Tester};
186 /// use std::sync::{Arc, atomic::{AtomicUsize, AtomicBool, Ordering}};
187 /// use std::thread;
188 ///
189 /// // Simulate load balancer status
190 /// let server_load = Arc::new(AtomicUsize::new(0));
191 /// let is_server_healthy = Arc::new(AtomicBool::new(true));
192 /// let max_load = 80;
193 /// let emergency_mode = Arc::new(AtomicBool::new(false));
194 ///
195 /// let load_clone = Arc::clone(&server_load);
196 /// let health_clone = Arc::clone(&is_server_healthy);
197 /// let emergency_clone = Arc::clone(&emergency_mode);
198 ///
199 /// // Server low load
200 /// let low_load = ArcTester::new(move || {
201 /// load_clone.load(Ordering::Relaxed) < max_load
202 /// });
203 ///
204 /// // Emergency mode check
205 /// let emergency_check = ArcTester::new(move || {
206 /// emergency_clone.load(Ordering::Relaxed)
207 /// });
208 ///
209 /// // Server health check
210 /// let server_healthy = ArcTester::new(move || {
211 /// health_clone.load(Ordering::Relaxed)
212 /// });
213 ///
214 /// // Emergency mode or server healthy
215 /// let can_handle_requests = emergency_check.or(&server_healthy);
216 ///
217 /// // Combined condition: low load or can handle requests
218 /// let should_route_here = low_load.or(&can_handle_requests);
219 ///
220 /// // Multi-threaded test
221 /// let router_clone = should_route_here.clone();
222 /// let handle = thread::spawn(move || {
223 /// router_clone.test()
224 /// });
225 ///
226 /// // Initial state: low load and healthy
227 /// assert!(handle.join().unwrap());
228 /// assert!(should_route_here.test());
229 ///
230 /// // High load but server healthy
231 /// server_load.store(90, Ordering::Relaxed);
232 /// assert!(should_route_here.test()); // still healthy
233 ///
234 /// // Server unhealthy but emergency mode
235 /// is_server_healthy.store(false, Ordering::Relaxed);
236 /// emergency_mode.store(true, Ordering::Relaxed);
237 /// assert!(should_route_here.test()); // emergency mode
238 ///
239 /// // Unhealthy and not emergency mode
240 /// emergency_mode.store(false, Ordering::Relaxed);
241 /// assert!(!should_route_here.test());
242 /// ```
243 #[inline]
244 pub fn or(&self, next: &ArcTester) -> ArcTester {
245 let self_fn = Arc::clone(&self.function);
246 let next_fn = Arc::clone(&next.function);
247 ArcTester {
248 function: Arc::new(move || self_fn() || next_fn()),
249 }
250 }
251
252 /// Negates the result of this tester
253 ///
254 /// Returns a new `ArcTester` that returns the opposite value of the
255 /// original test result. Borrows `&self`, so the original tester remains
256 /// available.
257 ///
258 /// # Return Value
259 ///
260 /// A new `ArcTester` representing logical NOT
261 ///
262 /// # Examples
263 ///
264 /// ```rust
265 /// use qubit_function::{ArcTester, Tester};
266 /// use std::sync::{Arc, atomic::{AtomicUsize, Ordering}};
267 /// use std::thread;
268 ///
269 /// // Simulate task queue status
270 /// let pending_tasks = Arc::new(AtomicUsize::new(0));
271 /// let max_queue_size = 100;
272 ///
273 /// let tasks_clone = Arc::clone(&pending_tasks);
274 ///
275 /// // Queue not full
276 /// let queue_available = ArcTester::new(move || {
277 /// tasks_clone.load(Ordering::Relaxed) < max_queue_size
278 /// });
279 ///
280 /// // Queue full (negated)
281 /// let queue_full = queue_available.not();
282 ///
283 /// // Multi-threaded test
284 /// let queue_full_clone = queue_full.clone();
285 /// let handle = thread::spawn(move || {
286 /// queue_full_clone.test()
287 /// });
288 ///
289 /// // Initial state: queue not full
290 /// pending_tasks.store(50, Ordering::Relaxed);
291 /// assert!(queue_available.test());
292 /// assert!(!handle.join().unwrap());
293 /// assert!(!queue_full.test());
294 ///
295 /// // Queue near full
296 /// pending_tasks.store(95, Ordering::Relaxed);
297 /// assert!(queue_available.test());
298 /// assert!(!queue_full.test());
299 ///
300 /// // Queue full
301 /// pending_tasks.store(120, Ordering::Relaxed);
302 /// assert!(!queue_available.test());
303 /// assert!(queue_full.test());
304 /// ```
305 #[allow(clippy::should_implement_trait)]
306 #[inline]
307 pub fn not(&self) -> ArcTester {
308 let func = Arc::clone(&self.function);
309 ArcTester {
310 function: Arc::new(move || !func()),
311 }
312 }
313
314 /// Combines this tester with another tester using logical NAND
315 ///
316 /// Returns a new `ArcTester` that returns `true` unless both tests pass.
317 /// Borrows `&self`, so the original tester remains available.
318 ///
319 /// # Parameters
320 ///
321 /// * `next` - The tester to combine with
322 ///
323 /// # Return Value
324 ///
325 /// A new `ArcTester` representing logical NAND
326 ///
327 /// # Examples
328 ///
329 /// ```rust
330 /// use qubit_function::{ArcTester, Tester};
331 /// use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
332 /// use std::thread;
333 ///
334 /// let flag1 = Arc::new(AtomicBool::new(true));
335 /// let flag2 = Arc::new(AtomicBool::new(true));
336 ///
337 /// let flag1_clone = Arc::clone(&flag1);
338 /// let flag2_clone = Arc::clone(&flag2);
339 ///
340 /// let tester1 = ArcTester::new(move || {
341 /// flag1_clone.load(Ordering::Relaxed)
342 /// });
343 ///
344 /// let tester2 = ArcTester::new(move || {
345 /// flag2_clone.load(Ordering::Relaxed)
346 /// });
347 ///
348 /// let nand = tester1.nand(&tester2);
349 ///
350 /// // Both true returns false
351 /// assert!(!nand.test());
352 ///
353 /// // At least one false returns true
354 /// flag1.store(false, Ordering::Relaxed);
355 /// assert!(nand.test());
356 ///
357 /// // Original tester still available
358 /// assert!(!tester1.test());
359 /// assert!(tester2.test());
360 /// ```
361 #[inline]
362 pub fn nand(&self, next: &ArcTester) -> ArcTester {
363 let self_fn = Arc::clone(&self.function);
364 let next_fn = Arc::clone(&next.function);
365 ArcTester {
366 function: Arc::new(move || !(self_fn() && next_fn())),
367 }
368 }
369
370 /// Combines this tester with another tester using logical XOR
371 ///
372 /// Returns a new `ArcTester` that returns `true` if exactly one test
373 /// passes. Borrows `&self`, so the original tester remains available.
374 ///
375 /// # Parameters
376 ///
377 /// * `next` - The tester to combine with
378 ///
379 /// # Return Value
380 ///
381 /// A new `ArcTester` representing logical XOR
382 ///
383 /// # Examples
384 ///
385 /// ```rust
386 /// use qubit_function::{ArcTester, Tester};
387 /// use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
388 /// use std::thread;
389 ///
390 /// let flag1 = Arc::new(AtomicBool::new(true));
391 /// let flag2 = Arc::new(AtomicBool::new(false));
392 ///
393 /// let flag1_clone = Arc::clone(&flag1);
394 /// let flag2_clone = Arc::clone(&flag2);
395 ///
396 /// let tester1 = ArcTester::new(move || {
397 /// flag1_clone.load(Ordering::Relaxed)
398 /// });
399 ///
400 /// let tester2 = ArcTester::new(move || {
401 /// flag2_clone.load(Ordering::Relaxed)
402 /// });
403 ///
404 /// let xor = tester1.xor(&tester2);
405 ///
406 /// // One true one false returns true
407 /// assert!(xor.test());
408 ///
409 /// // Both true returns false
410 /// flag2.store(true, Ordering::Relaxed);
411 /// assert!(!xor.test());
412 ///
413 /// // Both false returns false
414 /// flag1.store(false, Ordering::Relaxed);
415 /// flag2.store(false, Ordering::Relaxed);
416 /// assert!(!xor.test());
417 ///
418 /// // Original tester still available
419 /// assert!(!tester1.test());
420 /// assert!(!tester2.test());
421 /// ```
422 #[inline]
423 pub fn xor(&self, next: &ArcTester) -> ArcTester {
424 let self_fn = Arc::clone(&self.function);
425 let next_fn = Arc::clone(&next.function);
426 ArcTester {
427 function: Arc::new(move || self_fn() ^ next_fn()),
428 }
429 }
430
431 /// Combines this tester with another tester using logical NOR
432 ///
433 /// Returns a new `ArcTester` that returns `true` only when both tests
434 /// fail. Borrows `&self`, so the original tester remains available.
435 ///
436 /// # Parameters
437 ///
438 /// * `next` - The tester to combine with
439 ///
440 /// # Return Value
441 ///
442 /// A new `ArcTester` representing logical NOR
443 ///
444 /// # Examples
445 ///
446 /// ```rust
447 /// use qubit_function::{ArcTester, Tester};
448 /// use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
449 /// use std::thread;
450 ///
451 /// let flag1 = Arc::new(AtomicBool::new(false));
452 /// let flag2 = Arc::new(AtomicBool::new(false));
453 ///
454 /// let flag1_clone = Arc::clone(&flag1);
455 /// let flag2_clone = Arc::clone(&flag2);
456 ///
457 /// let tester1 = ArcTester::new(move || {
458 /// flag1_clone.load(Ordering::Relaxed)
459 /// });
460 ///
461 /// let tester2 = ArcTester::new(move || {
462 /// flag2_clone.load(Ordering::Relaxed)
463 /// });
464 ///
465 /// let nor = tester1.nor(&tester2);
466 ///
467 /// // Both false returns true
468 /// assert!(nor.test());
469 ///
470 /// // At least one true returns false
471 /// flag1.store(true, Ordering::Relaxed);
472 /// assert!(!nor.test());
473 ///
474 /// // Original tester still available
475 /// assert!(tester1.test());
476 /// assert!(!tester2.test());
477 /// ```
478 #[inline]
479 pub fn nor(&self, next: &ArcTester) -> ArcTester {
480 let self_fn = Arc::clone(&self.function);
481 let next_fn = Arc::clone(&next.function);
482 ArcTester {
483 function: Arc::new(move || !(self_fn() || next_fn())),
484 }
485 }
486}
487
488impl Tester for ArcTester {
489 #[inline]
490 fn test(&self) -> bool {
491 (self.function)()
492 }
493
494 #[inline]
495 fn into_box(self) -> BoxTester {
496 let func = self.function;
497 BoxTester {
498 function: Box::new(move || func()),
499 }
500 }
501
502 #[inline]
503 fn into_rc(self) -> RcTester {
504 let func = self.function;
505 RcTester {
506 function: Rc::new(move || func()),
507 }
508 }
509
510 #[inline]
511 fn into_arc(self) -> ArcTester {
512 self
513 }
514
515 #[inline]
516 fn into_fn(self) -> impl Fn() -> bool {
517 move || (self.function)()
518 }
519
520 #[inline]
521 fn to_box(&self) -> BoxTester {
522 let self_fn = self.function.clone();
523 BoxTester {
524 function: Box::new(move || self_fn()),
525 }
526 }
527
528 #[inline]
529 fn to_rc(&self) -> RcTester {
530 let self_fn = self.function.clone();
531 RcTester {
532 function: Rc::new(move || self_fn()),
533 }
534 }
535
536 #[inline]
537 fn to_arc(&self) -> ArcTester {
538 self.clone()
539 }
540
541 #[inline]
542 fn to_fn(&self) -> impl Fn() -> bool {
543 let self_fn = self.function.clone();
544 move || self_fn()
545 }
546}
547
548impl Clone for ArcTester {
549 /// Creates a clone of this `ArcTester`.
550 ///
551 /// The cloned instance shares the same underlying function with
552 /// the original, allowing multiple references to the same test
553 /// logic.
554 #[inline]
555 fn clone(&self) -> Self {
556 Self {
557 function: Arc::clone(&self.function),
558 }
559 }
560}