cli/
sleep.rs

1use std::thread::sleep;
2use std::time::{Duration, Instant};
3
4use tracing::{Level, event};
5
6use super::clock::{BasicClock, Clock};
7
8#[derive(Debug)]
9struct SignedDuration {
10    negative: bool,
11    magnitude: Duration,
12}
13
14impl SignedDuration {
15    pub const ZERO: SignedDuration = SignedDuration {
16        negative: false,
17        magnitude: Duration::ZERO,
18    };
19}
20
21impl From<Duration> for SignedDuration {
22    fn from(magnitude: Duration) -> Self {
23        Self {
24            negative: false,
25            magnitude,
26        }
27    }
28}
29
30impl SignedDuration {
31    fn checked_sub(&self, d: Duration) -> Option<SignedDuration> {
32        if self.negative {
33            self.magnitude.checked_add(d).map(|result| SignedDuration {
34                negative: true,
35                magnitude: result,
36            })
37        } else {
38            match self.magnitude.checked_sub(d) {
39                Some(diff) => Some(SignedDuration {
40                    negative: false,
41                    magnitude: diff,
42                }),
43                None => match d.checked_sub(self.magnitude) {
44                    Some(diff) => Some(SignedDuration {
45                        negative: true,
46                        magnitude: diff,
47                    }),
48                    None => {
49                        panic!(
50                            "checked_sub inconsistent for {:?} - {:?}",
51                            self.magnitude, d
52                        );
53                    }
54                },
55            }
56        }
57    }
58
59    fn checked_add(&self, d: Duration) -> Option<SignedDuration> {
60        if self.negative {
61            match d.checked_sub(self.magnitude) {
62                Some(diff) => Some(SignedDuration {
63                    negative: false,
64                    magnitude: diff,
65                }),
66                None => match self.magnitude.checked_sub(d) {
67                    Some(diff) => Some(SignedDuration {
68                        negative: true,
69                        magnitude: diff,
70                    }),
71                    None => {
72                        panic!(
73                            "checked_add inconsistent for {:?} - {:?}",
74                            self.magnitude, d
75                        );
76                    }
77                },
78            }
79        } else {
80            self.magnitude.checked_add(d).map(|tot| SignedDuration {
81                negative: false,
82                magnitude: tot,
83            })
84        }
85    }
86}
87
88/// MinimalSleeper provides a facility for periodically sleeping such
89/// that on average we sleep for the requested amount of time, even
90/// though we don't necessarily sleep on every call.  The idea is to
91/// be efficient in the use of system calls.
92///
93/// # Examples
94/// ```
95/// use std::time::Duration;
96/// use cpu::MinimalSleeper;
97/// // `s` will try to sleep, on average, for the indicated amounts
98/// // of time, but will never sleep for less than 1 millisecond.
99/// let mut s = MinimalSleeper::new(Duration::from_millis(10));
100/// ```
101#[derive(Debug)]
102pub struct MinimalSleeper {
103    /// Minimum period for which we will try to sleep.
104    min_sleep: Duration,
105
106    sleep_owed: SignedDuration,
107
108    total_cumulative_sleep: Duration,
109}
110
111impl MinimalSleeper {
112    pub fn new(min_sleep: Duration) -> MinimalSleeper {
113        MinimalSleeper {
114            min_sleep,
115            sleep_owed: SignedDuration::ZERO,
116            total_cumulative_sleep: Duration::ZERO,
117        }
118    }
119
120    fn really_sleep(&mut self) {
121        match self.sleep_owed {
122            SignedDuration {
123                negative: false,
124                magnitude,
125            } => {
126                let then = Instant::now();
127                event!(Level::DEBUG, "Sleeping for {:?}...", self.sleep_owed);
128                sleep(magnitude);
129                self.total_cumulative_sleep += magnitude;
130                let now = Instant::now();
131                let slept_for = now - then;
132                event!(
133                    Level::TRACE,
134                    "MinimalSleeper: owed sleep is {:?}, actually slept for {:?}",
135                    self.sleep_owed,
136                    slept_for
137                );
138                match self.sleep_owed.checked_sub(slept_for) {
139                    Some(remainder) => {
140                        self.sleep_owed = remainder;
141                    }
142                    None => {
143                        panic!("MinimalSleeper::really_sleep: overflow in sleep_owed");
144                    }
145                }
146            }
147            _ => unreachable!(), // should not have been called.
148        }
149        event!(
150            Level::DEBUG,
151            "MinimalSleeper: updated sleep debt is {:?}...",
152            self.sleep_owed
153        );
154    }
155
156    pub fn sleep(&mut self, duration: &Duration) {
157        match self.sleep_owed.checked_add(*duration) {
158            Some(more) => {
159                self.sleep_owed = more;
160                match self.sleep_owed {
161                    SignedDuration {
162                        negative: false,
163                        magnitude,
164                    } if magnitude > self.min_sleep => self.really_sleep(),
165                    _ => {
166                        // We did not build up enough sleep debt to exceed the
167                        // threshold, or our sleep debt is negative.  Wait for
168                        // more calls to sleep to bring us over the threshold.
169                    }
170                }
171            }
172            None => {
173                panic!("MinimalSleeper::really_sleep: overflow in sleep_owed");
174            }
175        }
176    }
177}
178
179impl Drop for MinimalSleeper {
180    fn drop(&mut self) {
181        event!(
182            Level::INFO,
183            "MinimalSleeper: drop: total cumulative sleep is {:?}",
184            self.total_cumulative_sleep
185        );
186    }
187}
188
189pub fn time_passes(
190    clk: &mut BasicClock,
191    sleeper: &mut MinimalSleeper,
192    t: &Duration,
193    multiplier: Option<f64>,
194) {
195    clk.consume(t);
196    if let Some(m) = multiplier {
197        sleeper.sleep(&t.mul_f64(m));
198    }
199}