xref: /aosp_15_r20/system/security/keystore2/watchdog/src/lib.rs (revision e1997b9af69e3155ead6e072d106a0077849ffba)
1*e1997b9aSAndroid Build Coastguard Worker // Copyright 2021, The Android Open Source Project
2*e1997b9aSAndroid Build Coastguard Worker //
3*e1997b9aSAndroid Build Coastguard Worker // Licensed under the Apache License, Version 2.0 (the "License");
4*e1997b9aSAndroid Build Coastguard Worker // you may not use this file except in compliance with the License.
5*e1997b9aSAndroid Build Coastguard Worker // You may obtain a copy of the License at
6*e1997b9aSAndroid Build Coastguard Worker //
7*e1997b9aSAndroid Build Coastguard Worker //     http://www.apache.org/licenses/LICENSE-2.0
8*e1997b9aSAndroid Build Coastguard Worker //
9*e1997b9aSAndroid Build Coastguard Worker // Unless required by applicable law or agreed to in writing, software
10*e1997b9aSAndroid Build Coastguard Worker // distributed under the License is distributed on an "AS IS" BASIS,
11*e1997b9aSAndroid Build Coastguard Worker // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*e1997b9aSAndroid Build Coastguard Worker // See the License for the specific language governing permissions and
13*e1997b9aSAndroid Build Coastguard Worker // limitations under the License.
14*e1997b9aSAndroid Build Coastguard Worker 
15*e1997b9aSAndroid Build Coastguard Worker // Can be removed when instrumentations are added to keystore.
16*e1997b9aSAndroid Build Coastguard Worker #![allow(dead_code)]
17*e1997b9aSAndroid Build Coastguard Worker 
18*e1997b9aSAndroid Build Coastguard Worker //! This module implements a watchdog thread.
19*e1997b9aSAndroid Build Coastguard Worker 
20*e1997b9aSAndroid Build Coastguard Worker use std::{
21*e1997b9aSAndroid Build Coastguard Worker     cmp::min,
22*e1997b9aSAndroid Build Coastguard Worker     collections::HashMap,
23*e1997b9aSAndroid Build Coastguard Worker     sync::Arc,
24*e1997b9aSAndroid Build Coastguard Worker     sync::{Condvar, Mutex, MutexGuard},
25*e1997b9aSAndroid Build Coastguard Worker     thread,
26*e1997b9aSAndroid Build Coastguard Worker };
27*e1997b9aSAndroid Build Coastguard Worker use std::{
28*e1997b9aSAndroid Build Coastguard Worker     marker::PhantomData,
29*e1997b9aSAndroid Build Coastguard Worker     time::{Duration, Instant},
30*e1997b9aSAndroid Build Coastguard Worker };
31*e1997b9aSAndroid Build Coastguard Worker 
32*e1997b9aSAndroid Build Coastguard Worker #[cfg(test)]
33*e1997b9aSAndroid Build Coastguard Worker mod tests;
34*e1997b9aSAndroid Build Coastguard Worker 
35*e1997b9aSAndroid Build Coastguard Worker /// Represents a Watchdog record. It can be created with `Watchdog::watch` or
36*e1997b9aSAndroid Build Coastguard Worker /// `Watchdog::watch_with`. It disarms the record when dropped.
37*e1997b9aSAndroid Build Coastguard Worker pub struct WatchPoint {
38*e1997b9aSAndroid Build Coastguard Worker     id: &'static str,
39*e1997b9aSAndroid Build Coastguard Worker     wd: Arc<Watchdog>,
40*e1997b9aSAndroid Build Coastguard Worker     not_send: PhantomData<*mut ()>, // WatchPoint must not be Send.
41*e1997b9aSAndroid Build Coastguard Worker }
42*e1997b9aSAndroid Build Coastguard Worker 
43*e1997b9aSAndroid Build Coastguard Worker impl Drop for WatchPoint {
drop(&mut self)44*e1997b9aSAndroid Build Coastguard Worker     fn drop(&mut self) {
45*e1997b9aSAndroid Build Coastguard Worker         self.wd.disarm(self.id)
46*e1997b9aSAndroid Build Coastguard Worker     }
47*e1997b9aSAndroid Build Coastguard Worker }
48*e1997b9aSAndroid Build Coastguard Worker 
49*e1997b9aSAndroid Build Coastguard Worker #[derive(Debug, PartialEq, Eq)]
50*e1997b9aSAndroid Build Coastguard Worker enum State {
51*e1997b9aSAndroid Build Coastguard Worker     NotRunning,
52*e1997b9aSAndroid Build Coastguard Worker     Running,
53*e1997b9aSAndroid Build Coastguard Worker }
54*e1997b9aSAndroid Build Coastguard Worker 
55*e1997b9aSAndroid Build Coastguard Worker #[derive(Debug, Clone, Hash, PartialEq, Eq)]
56*e1997b9aSAndroid Build Coastguard Worker struct Index {
57*e1997b9aSAndroid Build Coastguard Worker     tid: thread::ThreadId,
58*e1997b9aSAndroid Build Coastguard Worker     id: &'static str,
59*e1997b9aSAndroid Build Coastguard Worker }
60*e1997b9aSAndroid Build Coastguard Worker 
61*e1997b9aSAndroid Build Coastguard Worker struct Record {
62*e1997b9aSAndroid Build Coastguard Worker     started: Instant,
63*e1997b9aSAndroid Build Coastguard Worker     deadline: Instant,
64*e1997b9aSAndroid Build Coastguard Worker     context: Option<Box<dyn std::fmt::Debug + Send + 'static>>,
65*e1997b9aSAndroid Build Coastguard Worker }
66*e1997b9aSAndroid Build Coastguard Worker 
67*e1997b9aSAndroid Build Coastguard Worker struct WatchdogState {
68*e1997b9aSAndroid Build Coastguard Worker     state: State,
69*e1997b9aSAndroid Build Coastguard Worker     thread: Option<thread::JoinHandle<()>>,
70*e1997b9aSAndroid Build Coastguard Worker     /// How long to wait before dropping the watchdog thread when idle.
71*e1997b9aSAndroid Build Coastguard Worker     idle_timeout: Duration,
72*e1997b9aSAndroid Build Coastguard Worker     records: HashMap<Index, Record>,
73*e1997b9aSAndroid Build Coastguard Worker     last_report: Option<Instant>,
74*e1997b9aSAndroid Build Coastguard Worker     noisy_timeout: Duration,
75*e1997b9aSAndroid Build Coastguard Worker }
76*e1997b9aSAndroid Build Coastguard Worker 
77*e1997b9aSAndroid Build Coastguard Worker impl WatchdogState {
78*e1997b9aSAndroid Build Coastguard Worker     /// If we have overdue records, we want to log them but slowly backoff
79*e1997b9aSAndroid Build Coastguard Worker     /// so that we do not clog the logs. We start with logs every
80*e1997b9aSAndroid Build Coastguard Worker     /// `MIN_REPORT_TIMEOUT` sec then increment the timeout by 5 up
81*e1997b9aSAndroid Build Coastguard Worker     /// to a maximum of `MAX_REPORT_TIMEOUT`.
82*e1997b9aSAndroid Build Coastguard Worker     const MIN_REPORT_TIMEOUT: Duration = Duration::from_secs(1);
83*e1997b9aSAndroid Build Coastguard Worker     const MAX_REPORT_TIMEOUT: Duration = Duration::from_secs(30);
84*e1997b9aSAndroid Build Coastguard Worker 
reset_noisy_timeout(&mut self)85*e1997b9aSAndroid Build Coastguard Worker     fn reset_noisy_timeout(&mut self) {
86*e1997b9aSAndroid Build Coastguard Worker         self.noisy_timeout = Self::MIN_REPORT_TIMEOUT;
87*e1997b9aSAndroid Build Coastguard Worker     }
88*e1997b9aSAndroid Build Coastguard Worker 
update_noisy_timeout(&mut self)89*e1997b9aSAndroid Build Coastguard Worker     fn update_noisy_timeout(&mut self) {
90*e1997b9aSAndroid Build Coastguard Worker         let noisy_update = self.noisy_timeout + Duration::from_secs(5);
91*e1997b9aSAndroid Build Coastguard Worker         self.noisy_timeout = min(Self::MAX_REPORT_TIMEOUT, noisy_update);
92*e1997b9aSAndroid Build Coastguard Worker     }
93*e1997b9aSAndroid Build Coastguard Worker 
overdue_and_next_timeout(&self) -> (bool, Option<Duration>)94*e1997b9aSAndroid Build Coastguard Worker     fn overdue_and_next_timeout(&self) -> (bool, Option<Duration>) {
95*e1997b9aSAndroid Build Coastguard Worker         let now = Instant::now();
96*e1997b9aSAndroid Build Coastguard Worker         let mut next_timeout: Option<Duration> = None;
97*e1997b9aSAndroid Build Coastguard Worker         let mut has_overdue = false;
98*e1997b9aSAndroid Build Coastguard Worker         for (_, r) in self.records.iter() {
99*e1997b9aSAndroid Build Coastguard Worker             let timeout = r.deadline.saturating_duration_since(now);
100*e1997b9aSAndroid Build Coastguard Worker             if timeout == Duration::new(0, 0) {
101*e1997b9aSAndroid Build Coastguard Worker                 // This timeout has passed.
102*e1997b9aSAndroid Build Coastguard Worker                 has_overdue = true;
103*e1997b9aSAndroid Build Coastguard Worker             } else {
104*e1997b9aSAndroid Build Coastguard Worker                 // This timeout is still to come; see if it's the closest one to now.
105*e1997b9aSAndroid Build Coastguard Worker                 next_timeout = match next_timeout {
106*e1997b9aSAndroid Build Coastguard Worker                     Some(nt) if timeout < nt => Some(timeout),
107*e1997b9aSAndroid Build Coastguard Worker                     Some(nt) => Some(nt),
108*e1997b9aSAndroid Build Coastguard Worker                     None => Some(timeout),
109*e1997b9aSAndroid Build Coastguard Worker                 };
110*e1997b9aSAndroid Build Coastguard Worker             }
111*e1997b9aSAndroid Build Coastguard Worker         }
112*e1997b9aSAndroid Build Coastguard Worker         (has_overdue, next_timeout)
113*e1997b9aSAndroid Build Coastguard Worker     }
114*e1997b9aSAndroid Build Coastguard Worker 
log_report(&mut self, has_overdue: bool)115*e1997b9aSAndroid Build Coastguard Worker     fn log_report(&mut self, has_overdue: bool) {
116*e1997b9aSAndroid Build Coastguard Worker         if !has_overdue {
117*e1997b9aSAndroid Build Coastguard Worker             // Nothing to report.
118*e1997b9aSAndroid Build Coastguard Worker             self.last_report = None;
119*e1997b9aSAndroid Build Coastguard Worker             return;
120*e1997b9aSAndroid Build Coastguard Worker         }
121*e1997b9aSAndroid Build Coastguard Worker         // Something to report...
122*e1997b9aSAndroid Build Coastguard Worker         if let Some(reported_at) = self.last_report {
123*e1997b9aSAndroid Build Coastguard Worker             if reported_at.elapsed() < self.noisy_timeout {
124*e1997b9aSAndroid Build Coastguard Worker                 // .. but it's too soon since the last report.
125*e1997b9aSAndroid Build Coastguard Worker                 self.last_report = None;
126*e1997b9aSAndroid Build Coastguard Worker                 return;
127*e1997b9aSAndroid Build Coastguard Worker             }
128*e1997b9aSAndroid Build Coastguard Worker         }
129*e1997b9aSAndroid Build Coastguard Worker         self.update_noisy_timeout();
130*e1997b9aSAndroid Build Coastguard Worker         self.last_report = Some(Instant::now());
131*e1997b9aSAndroid Build Coastguard Worker         log::warn!("### Keystore Watchdog report - BEGIN ###");
132*e1997b9aSAndroid Build Coastguard Worker 
133*e1997b9aSAndroid Build Coastguard Worker         let now = Instant::now();
134*e1997b9aSAndroid Build Coastguard Worker         let mut overdue_records: Vec<(&Index, &Record)> = self
135*e1997b9aSAndroid Build Coastguard Worker             .records
136*e1997b9aSAndroid Build Coastguard Worker             .iter()
137*e1997b9aSAndroid Build Coastguard Worker             .filter(|(_, r)| r.deadline.saturating_duration_since(now) == Duration::new(0, 0))
138*e1997b9aSAndroid Build Coastguard Worker             .collect();
139*e1997b9aSAndroid Build Coastguard Worker 
140*e1997b9aSAndroid Build Coastguard Worker         log::warn!("When extracting from a bug report, please include this header");
141*e1997b9aSAndroid Build Coastguard Worker         log::warn!("and all {} records below.", overdue_records.len());
142*e1997b9aSAndroid Build Coastguard Worker 
143*e1997b9aSAndroid Build Coastguard Worker         // Watch points can be nested, i.e., a single thread may have multiple armed
144*e1997b9aSAndroid Build Coastguard Worker         // watch points. And the most recent on each thread (thread recent) is closest to the point
145*e1997b9aSAndroid Build Coastguard Worker         // where something is blocked. Furthermore, keystore2 has various critical section
146*e1997b9aSAndroid Build Coastguard Worker         // and common backend resources KeyMint that can only be entered serialized. So if one
147*e1997b9aSAndroid Build Coastguard Worker         // thread hangs, the others will soon follow suite. Thus the oldest "thread recent" watch
148*e1997b9aSAndroid Build Coastguard Worker         // point is most likely pointing toward the culprit.
149*e1997b9aSAndroid Build Coastguard Worker         // Thus, sort by start time first.
150*e1997b9aSAndroid Build Coastguard Worker         overdue_records.sort_unstable_by(|(_, r1), (_, r2)| r1.started.cmp(&r2.started));
151*e1997b9aSAndroid Build Coastguard Worker         // Then we groups all of the watch points per thread preserving the order within
152*e1997b9aSAndroid Build Coastguard Worker         // groups.
153*e1997b9aSAndroid Build Coastguard Worker         let groups = overdue_records.iter().fold(
154*e1997b9aSAndroid Build Coastguard Worker             HashMap::<thread::ThreadId, Vec<(&Index, &Record)>>::new(),
155*e1997b9aSAndroid Build Coastguard Worker             |mut acc, (i, r)| {
156*e1997b9aSAndroid Build Coastguard Worker                 acc.entry(i.tid).or_default().push((i, r));
157*e1997b9aSAndroid Build Coastguard Worker                 acc
158*e1997b9aSAndroid Build Coastguard Worker             },
159*e1997b9aSAndroid Build Coastguard Worker         );
160*e1997b9aSAndroid Build Coastguard Worker         // Put the groups back into a vector.
161*e1997b9aSAndroid Build Coastguard Worker         let mut groups: Vec<Vec<(&Index, &Record)>> = groups.into_values().collect();
162*e1997b9aSAndroid Build Coastguard Worker         // Sort the groups by start time of the most recent (.last()) of each group.
163*e1997b9aSAndroid Build Coastguard Worker         // It is panic safe to use unwrap() here because we never add empty vectors to
164*e1997b9aSAndroid Build Coastguard Worker         // the map.
165*e1997b9aSAndroid Build Coastguard Worker         groups.sort_by(|v1, v2| v1.last().unwrap().1.started.cmp(&v2.last().unwrap().1.started));
166*e1997b9aSAndroid Build Coastguard Worker 
167*e1997b9aSAndroid Build Coastguard Worker         for g in groups.iter() {
168*e1997b9aSAndroid Build Coastguard Worker             for (i, r) in g.iter() {
169*e1997b9aSAndroid Build Coastguard Worker                 match &r.context {
170*e1997b9aSAndroid Build Coastguard Worker                     Some(ctx) => {
171*e1997b9aSAndroid Build Coastguard Worker                         log::warn!(
172*e1997b9aSAndroid Build Coastguard Worker                             "{:?} {} Pending: {:?} Overdue {:?} for {:?}",
173*e1997b9aSAndroid Build Coastguard Worker                             i.tid,
174*e1997b9aSAndroid Build Coastguard Worker                             i.id,
175*e1997b9aSAndroid Build Coastguard Worker                             r.started.elapsed(),
176*e1997b9aSAndroid Build Coastguard Worker                             r.deadline.elapsed(),
177*e1997b9aSAndroid Build Coastguard Worker                             ctx
178*e1997b9aSAndroid Build Coastguard Worker                         );
179*e1997b9aSAndroid Build Coastguard Worker                     }
180*e1997b9aSAndroid Build Coastguard Worker                     None => {
181*e1997b9aSAndroid Build Coastguard Worker                         log::warn!(
182*e1997b9aSAndroid Build Coastguard Worker                             "{:?} {} Pending: {:?} Overdue {:?}",
183*e1997b9aSAndroid Build Coastguard Worker                             i.tid,
184*e1997b9aSAndroid Build Coastguard Worker                             i.id,
185*e1997b9aSAndroid Build Coastguard Worker                             r.started.elapsed(),
186*e1997b9aSAndroid Build Coastguard Worker                             r.deadline.elapsed()
187*e1997b9aSAndroid Build Coastguard Worker                         );
188*e1997b9aSAndroid Build Coastguard Worker                     }
189*e1997b9aSAndroid Build Coastguard Worker                 }
190*e1997b9aSAndroid Build Coastguard Worker             }
191*e1997b9aSAndroid Build Coastguard Worker         }
192*e1997b9aSAndroid Build Coastguard Worker         log::warn!("### Keystore Watchdog report - END ###");
193*e1997b9aSAndroid Build Coastguard Worker     }
194*e1997b9aSAndroid Build Coastguard Worker 
disarm(&mut self, index: Index)195*e1997b9aSAndroid Build Coastguard Worker     fn disarm(&mut self, index: Index) {
196*e1997b9aSAndroid Build Coastguard Worker         let result = self.records.remove(&index);
197*e1997b9aSAndroid Build Coastguard Worker         if let Some(record) = result {
198*e1997b9aSAndroid Build Coastguard Worker             let now = Instant::now();
199*e1997b9aSAndroid Build Coastguard Worker             let timeout_left = record.deadline.saturating_duration_since(now);
200*e1997b9aSAndroid Build Coastguard Worker             if timeout_left == Duration::new(0, 0) {
201*e1997b9aSAndroid Build Coastguard Worker                 match &record.context {
202*e1997b9aSAndroid Build Coastguard Worker                     Some(ctx) => log::info!(
203*e1997b9aSAndroid Build Coastguard Worker                         "Watchdog complete for: {:?} {} Pending: {:?} Overdue {:?} for {:?}",
204*e1997b9aSAndroid Build Coastguard Worker                         index.tid,
205*e1997b9aSAndroid Build Coastguard Worker                         index.id,
206*e1997b9aSAndroid Build Coastguard Worker                         record.started.elapsed(),
207*e1997b9aSAndroid Build Coastguard Worker                         record.deadline.elapsed(),
208*e1997b9aSAndroid Build Coastguard Worker                         ctx
209*e1997b9aSAndroid Build Coastguard Worker                     ),
210*e1997b9aSAndroid Build Coastguard Worker                     None => log::info!(
211*e1997b9aSAndroid Build Coastguard Worker                         "Watchdog complete for: {:?} {} Pending: {:?} Overdue {:?}",
212*e1997b9aSAndroid Build Coastguard Worker                         index.tid,
213*e1997b9aSAndroid Build Coastguard Worker                         index.id,
214*e1997b9aSAndroid Build Coastguard Worker                         record.started.elapsed(),
215*e1997b9aSAndroid Build Coastguard Worker                         record.deadline.elapsed()
216*e1997b9aSAndroid Build Coastguard Worker                     ),
217*e1997b9aSAndroid Build Coastguard Worker                 }
218*e1997b9aSAndroid Build Coastguard Worker             }
219*e1997b9aSAndroid Build Coastguard Worker         }
220*e1997b9aSAndroid Build Coastguard Worker     }
221*e1997b9aSAndroid Build Coastguard Worker 
arm(&mut self, index: Index, record: Record)222*e1997b9aSAndroid Build Coastguard Worker     fn arm(&mut self, index: Index, record: Record) {
223*e1997b9aSAndroid Build Coastguard Worker         if self.records.insert(index.clone(), record).is_some() {
224*e1997b9aSAndroid Build Coastguard Worker             log::warn!("Recursive watchdog record at \"{:?}\" replaces previous record.", index);
225*e1997b9aSAndroid Build Coastguard Worker         }
226*e1997b9aSAndroid Build Coastguard Worker     }
227*e1997b9aSAndroid Build Coastguard Worker }
228*e1997b9aSAndroid Build Coastguard Worker 
229*e1997b9aSAndroid Build Coastguard Worker /// Watchdog spawns a thread that logs records of all overdue watch points when a deadline
230*e1997b9aSAndroid Build Coastguard Worker /// is missed and at least every second as long as overdue watch points exist.
231*e1997b9aSAndroid Build Coastguard Worker /// The thread terminates when idle for a given period of time.
232*e1997b9aSAndroid Build Coastguard Worker pub struct Watchdog {
233*e1997b9aSAndroid Build Coastguard Worker     state: Arc<(Condvar, Mutex<WatchdogState>)>,
234*e1997b9aSAndroid Build Coastguard Worker }
235*e1997b9aSAndroid Build Coastguard Worker 
236*e1997b9aSAndroid Build Coastguard Worker impl Watchdog {
237*e1997b9aSAndroid Build Coastguard Worker     /// Construct a [`Watchdog`]. When `idle_timeout` has elapsed since the watchdog thread became
238*e1997b9aSAndroid Build Coastguard Worker     /// idle, i.e., there are no more active or overdue watch points, the watchdog thread
239*e1997b9aSAndroid Build Coastguard Worker     /// terminates.
new(idle_timeout: Duration) -> Arc<Self>240*e1997b9aSAndroid Build Coastguard Worker     pub fn new(idle_timeout: Duration) -> Arc<Self> {
241*e1997b9aSAndroid Build Coastguard Worker         Arc::new(Self {
242*e1997b9aSAndroid Build Coastguard Worker             state: Arc::new((
243*e1997b9aSAndroid Build Coastguard Worker                 Condvar::new(),
244*e1997b9aSAndroid Build Coastguard Worker                 Mutex::new(WatchdogState {
245*e1997b9aSAndroid Build Coastguard Worker                     state: State::NotRunning,
246*e1997b9aSAndroid Build Coastguard Worker                     thread: None,
247*e1997b9aSAndroid Build Coastguard Worker                     idle_timeout,
248*e1997b9aSAndroid Build Coastguard Worker                     records: HashMap::new(),
249*e1997b9aSAndroid Build Coastguard Worker                     last_report: None,
250*e1997b9aSAndroid Build Coastguard Worker                     noisy_timeout: WatchdogState::MIN_REPORT_TIMEOUT,
251*e1997b9aSAndroid Build Coastguard Worker                 }),
252*e1997b9aSAndroid Build Coastguard Worker             )),
253*e1997b9aSAndroid Build Coastguard Worker         })
254*e1997b9aSAndroid Build Coastguard Worker     }
255*e1997b9aSAndroid Build Coastguard Worker 
watch_with_optional( wd: Arc<Self>, context: Option<Box<dyn std::fmt::Debug + Send + 'static>>, id: &'static str, timeout: Duration, ) -> Option<WatchPoint>256*e1997b9aSAndroid Build Coastguard Worker     fn watch_with_optional(
257*e1997b9aSAndroid Build Coastguard Worker         wd: Arc<Self>,
258*e1997b9aSAndroid Build Coastguard Worker         context: Option<Box<dyn std::fmt::Debug + Send + 'static>>,
259*e1997b9aSAndroid Build Coastguard Worker         id: &'static str,
260*e1997b9aSAndroid Build Coastguard Worker         timeout: Duration,
261*e1997b9aSAndroid Build Coastguard Worker     ) -> Option<WatchPoint> {
262*e1997b9aSAndroid Build Coastguard Worker         let Some(deadline) = Instant::now().checked_add(timeout) else {
263*e1997b9aSAndroid Build Coastguard Worker             log::warn!("Deadline computation failed for WatchPoint \"{}\"", id);
264*e1997b9aSAndroid Build Coastguard Worker             log::warn!("WatchPoint not armed.");
265*e1997b9aSAndroid Build Coastguard Worker             return None;
266*e1997b9aSAndroid Build Coastguard Worker         };
267*e1997b9aSAndroid Build Coastguard Worker         wd.arm(context, id, deadline);
268*e1997b9aSAndroid Build Coastguard Worker         Some(WatchPoint { id, wd, not_send: Default::default() })
269*e1997b9aSAndroid Build Coastguard Worker     }
270*e1997b9aSAndroid Build Coastguard Worker 
271*e1997b9aSAndroid Build Coastguard Worker     /// Create a new watch point. If the WatchPoint is not dropped before the timeout
272*e1997b9aSAndroid Build Coastguard Worker     /// expires, a report is logged at least every second, which includes the id string
273*e1997b9aSAndroid Build Coastguard Worker     /// and any provided context.
watch_with( wd: &Arc<Self>, id: &'static str, timeout: Duration, context: impl std::fmt::Debug + Send + 'static, ) -> Option<WatchPoint>274*e1997b9aSAndroid Build Coastguard Worker     pub fn watch_with(
275*e1997b9aSAndroid Build Coastguard Worker         wd: &Arc<Self>,
276*e1997b9aSAndroid Build Coastguard Worker         id: &'static str,
277*e1997b9aSAndroid Build Coastguard Worker         timeout: Duration,
278*e1997b9aSAndroid Build Coastguard Worker         context: impl std::fmt::Debug + Send + 'static,
279*e1997b9aSAndroid Build Coastguard Worker     ) -> Option<WatchPoint> {
280*e1997b9aSAndroid Build Coastguard Worker         Self::watch_with_optional(wd.clone(), Some(Box::new(context)), id, timeout)
281*e1997b9aSAndroid Build Coastguard Worker     }
282*e1997b9aSAndroid Build Coastguard Worker 
283*e1997b9aSAndroid Build Coastguard Worker     /// Like `watch_with`, but without context.
watch(wd: &Arc<Self>, id: &'static str, timeout: Duration) -> Option<WatchPoint>284*e1997b9aSAndroid Build Coastguard Worker     pub fn watch(wd: &Arc<Self>, id: &'static str, timeout: Duration) -> Option<WatchPoint> {
285*e1997b9aSAndroid Build Coastguard Worker         Self::watch_with_optional(wd.clone(), None, id, timeout)
286*e1997b9aSAndroid Build Coastguard Worker     }
287*e1997b9aSAndroid Build Coastguard Worker 
arm( &self, context: Option<Box<dyn std::fmt::Debug + Send + 'static>>, id: &'static str, deadline: Instant, )288*e1997b9aSAndroid Build Coastguard Worker     fn arm(
289*e1997b9aSAndroid Build Coastguard Worker         &self,
290*e1997b9aSAndroid Build Coastguard Worker         context: Option<Box<dyn std::fmt::Debug + Send + 'static>>,
291*e1997b9aSAndroid Build Coastguard Worker         id: &'static str,
292*e1997b9aSAndroid Build Coastguard Worker         deadline: Instant,
293*e1997b9aSAndroid Build Coastguard Worker     ) {
294*e1997b9aSAndroid Build Coastguard Worker         let tid = thread::current().id();
295*e1997b9aSAndroid Build Coastguard Worker         let index = Index { tid, id };
296*e1997b9aSAndroid Build Coastguard Worker         let record = Record { started: Instant::now(), deadline, context };
297*e1997b9aSAndroid Build Coastguard Worker 
298*e1997b9aSAndroid Build Coastguard Worker         let (ref condvar, ref state) = *self.state;
299*e1997b9aSAndroid Build Coastguard Worker 
300*e1997b9aSAndroid Build Coastguard Worker         let mut state = state.lock().unwrap();
301*e1997b9aSAndroid Build Coastguard Worker         state.arm(index, record);
302*e1997b9aSAndroid Build Coastguard Worker 
303*e1997b9aSAndroid Build Coastguard Worker         if state.state != State::Running {
304*e1997b9aSAndroid Build Coastguard Worker             self.spawn_thread(&mut state);
305*e1997b9aSAndroid Build Coastguard Worker         }
306*e1997b9aSAndroid Build Coastguard Worker         drop(state);
307*e1997b9aSAndroid Build Coastguard Worker         condvar.notify_all();
308*e1997b9aSAndroid Build Coastguard Worker     }
309*e1997b9aSAndroid Build Coastguard Worker 
disarm(&self, id: &'static str)310*e1997b9aSAndroid Build Coastguard Worker     fn disarm(&self, id: &'static str) {
311*e1997b9aSAndroid Build Coastguard Worker         let tid = thread::current().id();
312*e1997b9aSAndroid Build Coastguard Worker         let index = Index { tid, id };
313*e1997b9aSAndroid Build Coastguard Worker         let (_, ref state) = *self.state;
314*e1997b9aSAndroid Build Coastguard Worker 
315*e1997b9aSAndroid Build Coastguard Worker         let mut state = state.lock().unwrap();
316*e1997b9aSAndroid Build Coastguard Worker         state.disarm(index);
317*e1997b9aSAndroid Build Coastguard Worker         // There is no need to notify condvar. There is no action required for the
318*e1997b9aSAndroid Build Coastguard Worker         // watchdog thread before the next deadline.
319*e1997b9aSAndroid Build Coastguard Worker     }
320*e1997b9aSAndroid Build Coastguard Worker 
spawn_thread(&self, state: &mut MutexGuard<WatchdogState>)321*e1997b9aSAndroid Build Coastguard Worker     fn spawn_thread(&self, state: &mut MutexGuard<WatchdogState>) {
322*e1997b9aSAndroid Build Coastguard Worker         if let Some(t) = state.thread.take() {
323*e1997b9aSAndroid Build Coastguard Worker             t.join().expect("Watchdog thread panicked.");
324*e1997b9aSAndroid Build Coastguard Worker         }
325*e1997b9aSAndroid Build Coastguard Worker 
326*e1997b9aSAndroid Build Coastguard Worker         let cloned_state = self.state.clone();
327*e1997b9aSAndroid Build Coastguard Worker 
328*e1997b9aSAndroid Build Coastguard Worker         state.thread = Some(thread::spawn(move || {
329*e1997b9aSAndroid Build Coastguard Worker             let (ref condvar, ref state) = *cloned_state;
330*e1997b9aSAndroid Build Coastguard Worker 
331*e1997b9aSAndroid Build Coastguard Worker             let mut state = state.lock().unwrap();
332*e1997b9aSAndroid Build Coastguard Worker 
333*e1997b9aSAndroid Build Coastguard Worker             loop {
334*e1997b9aSAndroid Build Coastguard Worker                 let (has_overdue, next_timeout) = state.overdue_and_next_timeout();
335*e1997b9aSAndroid Build Coastguard Worker                 state.log_report(has_overdue);
336*e1997b9aSAndroid Build Coastguard Worker 
337*e1997b9aSAndroid Build Coastguard Worker                 let (next_timeout, idle) = match (has_overdue, next_timeout) {
338*e1997b9aSAndroid Build Coastguard Worker                     (true, Some(next_timeout)) => (min(next_timeout, state.noisy_timeout), false),
339*e1997b9aSAndroid Build Coastguard Worker                     (true, None) => (state.noisy_timeout, false),
340*e1997b9aSAndroid Build Coastguard Worker                     (false, Some(next_timeout)) => (next_timeout, false),
341*e1997b9aSAndroid Build Coastguard Worker                     (false, None) => (state.idle_timeout, true),
342*e1997b9aSAndroid Build Coastguard Worker                 };
343*e1997b9aSAndroid Build Coastguard Worker 
344*e1997b9aSAndroid Build Coastguard Worker                 // Wait until the closest timeout pops, but use a condition variable so that if a
345*e1997b9aSAndroid Build Coastguard Worker                 // new watchpoint is started in the meanwhile it will interrupt the wait so we can
346*e1997b9aSAndroid Build Coastguard Worker                 // recalculate.
347*e1997b9aSAndroid Build Coastguard Worker                 let (s, timeout) = condvar.wait_timeout(state, next_timeout).unwrap();
348*e1997b9aSAndroid Build Coastguard Worker                 state = s;
349*e1997b9aSAndroid Build Coastguard Worker 
350*e1997b9aSAndroid Build Coastguard Worker                 if idle && timeout.timed_out() && state.records.is_empty() {
351*e1997b9aSAndroid Build Coastguard Worker                     state.reset_noisy_timeout();
352*e1997b9aSAndroid Build Coastguard Worker                     state.state = State::NotRunning;
353*e1997b9aSAndroid Build Coastguard Worker                     break;
354*e1997b9aSAndroid Build Coastguard Worker                 }
355*e1997b9aSAndroid Build Coastguard Worker             }
356*e1997b9aSAndroid Build Coastguard Worker             log::info!("Watchdog thread idle -> terminating. Have a great day.");
357*e1997b9aSAndroid Build Coastguard Worker         }));
358*e1997b9aSAndroid Build Coastguard Worker         state.state = State::Running;
359*e1997b9aSAndroid Build Coastguard Worker     }
360*e1997b9aSAndroid Build Coastguard Worker }
361