1 use crate::Clock;
2 use std::{
3     io,
4     sync::{
5         atomic::{AtomicBool, Ordering},
6         Arc,
7     },
8     thread::{self, JoinHandle},
9     time::Duration,
10 };
11 
12 /// Builder for creating an upkeep task.
13 #[derive(Debug)]
14 pub struct Builder {
15     interval: Duration,
16     clock: Clock,
17 }
18 
19 /// Handle to a running upkeep task.
20 ///
21 /// If a handle is dropped, the background upkeep thread it belongs to is stopped, which will stop
22 /// updating the recent time.
23 #[derive(Debug)]
24 pub struct Handle {
25     done: Arc<AtomicBool>,
26     handle: Option<JoinHandle<()>>,
27 }
28 
29 impl Builder {
30     /// Creates a new [`Builder`].
31     ///
32     /// This creates a new internal clock for acquiring the current time.  If you have an existing
33     /// [`Clock`] that is already calibrated, it is slightly faster to clone it and construct the
34     /// builder with [`new_with_clock`](Builder::new_with_clock) to avoid recalibrating.
new(interval: Duration) -> Builder35     pub fn new(interval: Duration) -> Builder { Self::new_with_clock(interval, Clock::new()) }
36 
37     /// Creates a new [`Builder`] with the specified [`Clock`] instance.
new_with_clock(interval: Duration, clock: Clock) -> Builder38     pub fn new_with_clock(interval: Duration, clock: Clock) -> Builder { Builder { interval, clock } }
39 
40     /// Start the upkeep thread, periodically updating the global coarse time.
41     ///
42     /// If the return value is [`Ok(handle)`], then the thread was spawned successfully and can be
43     /// stopped by dropping the returned handle.  Otherwise, [`Err`] contains the error that was
44     /// returned when trying to spawn the thread.
start(self) -> Result<Handle, io::Error>45     pub fn start(self) -> Result<Handle, io::Error> {
46         let interval = self.interval;
47         let clock = self.clock;
48 
49         let done = Arc::new(AtomicBool::new(false));
50         let their_done = done.clone();
51 
52         let handle = thread::Builder::new()
53             .name("quanta-upkeep".to_string())
54             .spawn(move || {
55                 while !their_done.load(Ordering::Acquire) {
56                     let now = clock.now();
57                     Clock::upkeep(now);
58 
59                     thread::sleep(interval);
60                 }
61             })?;
62 
63         Ok(Handle {
64             done,
65             handle: Some(handle),
66         })
67     }
68 }
69 
70 impl Drop for Handle {
drop(&mut self)71     fn drop(&mut self) {
72         self.done.store(true, Ordering::Release);
73 
74         if let Some(handle) = self.handle.take() {
75             let _ = handle
76                 .join()
77                 .map_err(|_| io::Error::new(io::ErrorKind::Other, "failed to stop upkeep thread"));
78         }
79     }
80 }
81