Skip to main content

seams_rs_std/
lib.rs

1//! Standard-library-backed production adapters for `seams-rs-core`.
2//!
3//! - `SystemClock` wraps `std::time::SystemTime::now`.
4//! - `StdSleeper` wraps `std::thread::sleep`.
5//! - `StdSpawner` wraps `std::thread::spawn`.
6//! - `StdFileSystem` wraps `std::fs::*`.
7//! - `TokioFileSystem` wraps `tokio::fs::*`.
8
9pub mod fs;
10pub use fs::{StdFileSystem, TokioFileSystem};
11
12use std::sync::atomic::{AtomicBool, Ordering};
13use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
14
15use seams_rs_core::{Clock, JoinError, JoinHandle, Sleeper, Spawner};
16
17/// Clock driven by `SystemTime::now` + `Instant::now`.
18#[derive(Debug, Default, Clone, Copy)]
19pub struct SystemClock;
20
21impl SystemClock {
22    /// Construct a new system clock.
23    pub fn new() -> Self {
24        Self
25    }
26}
27
28impl Clock for SystemClock {
29    fn now_ns(&self) -> u64 {
30        SystemTime::now()
31            .duration_since(UNIX_EPOCH)
32            .map(|d| d.as_nanos() as u64)
33            .unwrap_or(0)
34    }
35
36    fn now_instant(&self) -> Instant {
37        Instant::now()
38    }
39}
40
41/// Sleeper backed by `std::thread::sleep`.
42#[derive(Debug, Default, Clone, Copy)]
43pub struct StdSleeper;
44
45impl StdSleeper {
46    /// Construct a new std sleeper.
47    pub fn new() -> Self {
48        Self
49    }
50}
51
52const POLL_INTERVAL: Duration = Duration::from_millis(10);
53
54impl Sleeper for StdSleeper {
55    fn sleep(&self, duration: Duration) {
56        std::thread::sleep(duration);
57    }
58
59    fn sleep_responsive(&self, total: Duration, shutdown: &AtomicBool) -> bool {
60        let deadline = Instant::now() + total;
61        loop {
62            if shutdown.load(Ordering::SeqCst) {
63                return true;
64            }
65            let now = Instant::now();
66            if now >= deadline {
67                return false;
68            }
69            let remaining = deadline - now;
70            std::thread::sleep(remaining.min(POLL_INTERVAL));
71        }
72    }
73}
74
75/// Spawner backed by `std::thread::spawn`.
76#[derive(Debug, Default, Clone, Copy)]
77pub struct StdSpawner;
78
79impl StdSpawner {
80    /// Construct a new std spawner.
81    pub fn new() -> Self {
82        Self
83    }
84}
85
86struct StdJoinHandle<T> {
87    inner: std::thread::JoinHandle<T>,
88}
89
90impl<T: Send + 'static> JoinHandle<T> for StdJoinHandle<T> {
91    fn join(self: Box<Self>) -> Result<T, JoinError> {
92        self.inner.join().map_err(|payload| {
93            let msg = if let Some(s) = payload.downcast_ref::<&'static str>() {
94                (*s).to_string()
95            } else if let Some(s) = payload.downcast_ref::<String>() {
96                s.clone()
97            } else {
98                "panic".to_string()
99            };
100            JoinError::Panicked(msg)
101        })
102    }
103}
104
105impl Spawner for StdSpawner {
106    fn spawn_blocking<F, T>(&self, f: F) -> Box<dyn JoinHandle<T>>
107    where
108        F: FnOnce() -> T + Send + 'static,
109        T: Send + 'static,
110    {
111        Box::new(StdJoinHandle {
112            inner: std::thread::spawn(f),
113        })
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120    use seams_rs_core::contract_tests as ct;
121
122    #[test]
123    fn system_clock_now_ns() {
124        ct::clock_now_ns_monotonic(&SystemClock::new());
125    }
126
127    #[test]
128    fn system_clock_now_instant() {
129        ct::clock_now_instant_monotonic(&SystemClock::new());
130    }
131
132    #[test]
133    fn std_sleeper_sleep() {
134        ct::sleeper_sleep_waits(
135            &StdSleeper::new(),
136            Duration::from_millis(5),
137            Duration::from_millis(500),
138        );
139    }
140
141    #[test]
142    fn std_sleeper_before() {
143        ct::sleeper_responsive_shutdown_before(&StdSleeper::new());
144    }
145
146    #[test]
147    fn std_sleeper_during() {
148        ct::sleeper_responsive_shutdown_during(&StdSleeper::new());
149    }
150
151    #[test]
152    fn std_sleeper_no_shutdown() {
153        ct::sleeper_responsive_no_shutdown(&StdSleeper::new());
154    }
155
156    #[test]
157    fn std_spawner_value() {
158        ct::spawner_returns_value(&StdSpawner::new());
159    }
160
161    #[test]
162    fn std_spawner_panic() {
163        let prev = std::panic::take_hook();
164        std::panic::set_hook(Box::new(|_| {}));
165        ct::spawner_propagates_panic(&StdSpawner::new());
166        std::panic::set_hook(prev);
167    }
168}