Skip to main content

shutdown_shim/
shutdown_watcher.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::reboot_reasons::ShutdownOptionsWrapper;
6
7use fidl::endpoints::Proxy;
8use fidl_fuchsia_hardware_power_statecontrol::{self as fpower, ShutdownAction};
9use fuchsia_async::{self as fasync, DurationExt, TimeoutExt};
10use fuchsia_inspect::{self as inspect, NumericProperty};
11use futures::TryStreamExt;
12use futures::lock::Mutex;
13use futures::prelude::*;
14use std::collections::HashMap;
15use std::sync::Arc;
16use zx::AsHandleRef;
17use zx::sys::zx_handle_t;
18
19/// Summary: Provides an implementation of the
20/// fuchsia.hardware.power.statecontrol.ShutdownWatcherRegister and RebootMethodsWatcherRegister
21/// protocols that allows other components in the system to register to be notified of pending
22/// system shutdown requests and the associated shutdown reason.
23///
24/// FIDL dependencies:
25///     - fuchsia.hardware.power.statecontrol.ShutdownWatcherRegister and
26///       RebootMethodsWatcherRegister: the server provides a Register API that other components in
27///       the system can use to receive notifications about system shutdown events and reasons
28///     - fuchsia.hardware.power.statecontrol.ShutdownWatcher, TerminalStateWatcher, and
29///       RebootMethodsWatcher: the server receives an instance of this protocol over the
30///       ShutdownWatcherRegister and RebootMethodsWatcherRegister channels, and uses this channel
31///       to send shutdown notifications.
32//
33// TODO(https://fxbug.dev/414413282): Remove everything related to RebootMethodsWatcherRegister once
34// only F28+ is supported.
35pub struct ShutdownWatcher {
36    /// Contains all the registered RebootMethodsWatcher channels to be notified when a reboot
37    /// request is received.
38    reboot_watchers: Arc<Mutex<HashMap<u32, fpower::RebootWatcherProxy>>>,
39    /// Contains all the registered ShutdownWatcher channels to be notified when a shutdown request
40    /// is received.
41    shutdown_watchers: Arc<Mutex<HashMap<zx_handle_t, fpower::ShutdownWatcherProxy>>>,
42    /// Contains all the registered TerminalStateWatcher channels to be notified when a transition
43    /// to a terminal state begins.
44    terminal_state_watchers: Arc<Mutex<HashMap<zx_handle_t, fpower::TerminalStateWatcherProxy>>>,
45    inspect: Arc<Mutex<InspectData>>,
46}
47
48impl ShutdownWatcher {
49    const NOTIFY_SHUTDOWN_RESPONSE_TIMEOUT: zx::MonotonicDuration =
50        zx::MonotonicDuration::from_seconds(
51            fpower::MAX_SHUTDOWN_WATCHER_RESPONSE_TIME_SECONDS as i64,
52        );
53
54    const NOTIFY_REBOOT_RESPONSE_TIMEOUT: zx::MonotonicDuration =
55        zx::MonotonicDuration::from_seconds(
56            fpower::MAX_REBOOT_WATCHER_RESPONSE_TIME_SECONDS as i64,
57        );
58
59    const NOTIFY_TERMINAL_STATE_RESPONSE_TIMEOUT: zx::MonotonicDuration =
60        zx::MonotonicDuration::from_seconds(
61            fpower::MAX_TERMINAL_STATE_WATCHER_RESPONSE_TIME_SECONDS as i64,
62        );
63
64    #[cfg(test)]
65    fn new() -> Arc<Self> {
66        let inspector = inspect::Inspector::new(fuchsia_inspect::InspectorConfig::default());
67        Self::new_with_inspector(&inspector)
68    }
69
70    pub fn new_with_inspector(inspector: &inspect::Inspector) -> Arc<Self> {
71        Arc::new(Self {
72            reboot_watchers: Arc::new(Mutex::new(HashMap::new())),
73            shutdown_watchers: Arc::new(Mutex::new(HashMap::new())),
74            terminal_state_watchers: Arc::new(Mutex::new(HashMap::new())),
75            inspect: Arc::new(Mutex::new(InspectData::new(
76                inspector.root(),
77                "ShutdownWatcher".to_string(),
78            ))),
79        })
80    }
81
82    /// Handles a new client connection to the RebootMethodsWatcherRegister service.
83    pub async fn handle_reboot_register_request(
84        self: Arc<Self>,
85        mut stream: fpower::RebootMethodsWatcherRegisterRequestStream,
86    ) {
87        while let Ok(Some(req)) = stream.try_next().await {
88            match req {
89                fpower::RebootMethodsWatcherRegisterRequest::RegisterWatcher {
90                    watcher,
91                    responder,
92                } => {
93                    self.add_reboot_watcher(watcher.into_proxy()).await;
94                    let _ = responder.send();
95                }
96            }
97        }
98    }
99
100    /// Handles a new client connection to the ShutdownWatcherRegister service.
101    pub async fn handle_shutdown_register_request(
102        self: Arc<Self>,
103        mut stream: fpower::ShutdownWatcherRegisterRequestStream,
104    ) {
105        while let Ok(Some(req)) = stream.try_next().await {
106            match req {
107                fpower::ShutdownWatcherRegisterRequest::RegisterWatcher { watcher, responder } => {
108                    self.add_shutdown_watcher(watcher.into_proxy()).await;
109                    let _ = responder.send();
110                }
111                fpower::ShutdownWatcherRegisterRequest::RegisterTerminalStateWatcher {
112                    watcher,
113                    responder,
114                } => {
115                    self.add_terminal_state_watcher(watcher.into_proxy()).await;
116                    let _ = responder.send();
117                }
118                fpower::ShutdownWatcherRegisterRequest::_UnknownMethod { ordinal, .. } => {
119                    println!("[shutdown-shim]: error, unimplemented method ordinal: {ordinal}")
120                }
121            }
122        }
123    }
124
125    /// Adds a new RebootMethodsWatcher channel to the list of registered watchers.
126    async fn add_reboot_watcher(&self, watcher: fpower::RebootWatcherProxy) {
127        fuchsia_trace::duration!(
128            c"shutdown-shim",
129            c"ShutdownWatcher::add_reboot_watcher",
130            "watcher" => watcher.as_channel().as_handle_ref().raw_handle()
131        );
132
133        // If the client closes the watcher channel, remove it from our `reboot_watchers` map
134        println!("[shutdown-shim] Adding a reboot watcher");
135        let key = watcher.as_channel().as_handle_ref().raw_handle();
136        let proxy = watcher.clone();
137        let reboot_watchers = self.reboot_watchers.clone();
138        let inspect = self.inspect.clone();
139        fasync::Task::spawn(async move {
140            let _ = proxy.on_closed().await;
141            {
142                reboot_watchers.lock().await.remove(&key);
143            }
144            inspect.lock().await.remove_reboot_watcher();
145        })
146        .detach();
147
148        {
149            let mut watchers_mut = self.reboot_watchers.lock().await;
150            watchers_mut.insert(key, watcher);
151        }
152        self.inspect.lock().await.add_reboot_watcher();
153    }
154
155    /// Adds a new ShutdownWatcher channel to the list of registered watchers.
156    async fn add_shutdown_watcher(&self, watcher: fpower::ShutdownWatcherProxy) {
157        fuchsia_trace::duration!(
158            c"shutdown-shim",
159            c"ShutdownWatcher::add_shutdown_watcher",
160            "watcher" => watcher.as_channel().as_handle_ref().raw_handle()
161        );
162
163        // If the client closes the watcher channel, remove it from our `shutdown_watchers` map
164        println!("[shutdown-shim] Adding a shutdown watcher");
165        let key = watcher.as_channel().as_handle_ref().raw_handle();
166        let proxy = watcher.clone();
167        let shutdown_watchers = self.shutdown_watchers.clone();
168        let inspect = self.inspect.clone();
169        fasync::Task::spawn(async move {
170            let _ = proxy.on_closed().await;
171            {
172                shutdown_watchers.lock().await.remove(&key);
173            }
174            inspect.lock().await.remove_reboot_watcher();
175        })
176        .detach();
177
178        {
179            let mut watchers_mut = self.shutdown_watchers.lock().await;
180            watchers_mut.insert(key, watcher);
181        }
182        self.inspect.lock().await.add_reboot_watcher();
183    }
184
185    /// Adds a new TerminalStateWatcher channel to the list of registered watchers.
186    async fn add_terminal_state_watcher(&self, watcher: fpower::TerminalStateWatcherProxy) {
187        fuchsia_trace::duration!(
188            c"shutdown-shim",
189            c"ShutdownWatcher::add_terminal_state_watcher",
190            "watcher" => watcher.as_channel().as_handle_ref().raw_handle()
191        );
192
193        // If the client closes the watcher channel, remove it from our `terminal_state_watchers` map
194        println!("[shutdown-shim] Adding a terminal state watcher");
195        let key = watcher.as_channel().as_handle_ref().raw_handle();
196        let proxy = watcher.clone();
197        let terminal_state_watchers = self.terminal_state_watchers.clone();
198        let inspect = self.inspect.clone();
199        fasync::Task::spawn(async move {
200            let _ = proxy.on_closed().await;
201            {
202                terminal_state_watchers.lock().await.remove(&key);
203            }
204            inspect.lock().await.remove_terminal_state_watcher();
205        })
206        .detach();
207
208        {
209            let mut watchers_mut = self.terminal_state_watchers.lock().await;
210            watchers_mut.insert(key, watcher);
211        }
212        self.inspect.lock().await.add_terminal_state_watcher();
213    }
214
215    /// Handles the SystemShutdown message by notifying the appropriate registered watchers.
216    pub async fn handle_system_shutdown_message(&self, options: Option<ShutdownOptionsWrapper>) {
217        let terminal_state_fut =
218            self.notify_terminal_state_watchers(Self::NOTIFY_TERMINAL_STATE_RESPONSE_TIMEOUT);
219
220        let Some(options) = options else {
221            terminal_state_fut.await;
222            return;
223        };
224
225        if options.action == ShutdownAction::Reboot && !options.reasons.is_empty() {
226            futures::join!(
227                self.notify_shutdown_watchers(
228                    options.clone(),
229                    Self::NOTIFY_SHUTDOWN_RESPONSE_TIMEOUT
230                ),
231                self.notify_reboot_watchers(options, Self::NOTIFY_REBOOT_RESPONSE_TIMEOUT),
232                terminal_state_fut
233            );
234        } else {
235            futures::join!(
236                self.notify_shutdown_watchers(options, Self::NOTIFY_SHUTDOWN_RESPONSE_TIMEOUT),
237                terminal_state_fut
238            );
239        }
240    }
241
242    async fn notify_reboot_watchers(
243        &self,
244        options: ShutdownOptionsWrapper,
245        timeout: zx::MonotonicDuration,
246    ) {
247        // Instead of waiting for https://fxbug.dev/42120903, use begin and end pair of macros.
248        fuchsia_trace::duration_begin!(
249            c"shutdown-shim",
250            c"ShutdownWatcher::notify_reboot_watchers",
251            "options" => format!("{:?}", options).as_str()
252        );
253
254        // Create a future for each watcher that calls the watcher's `on_reboot` method and returns
255        // the watcher proxy if the response was received within the timeout, or None otherwise. We
256        // take this approach so that watchers that timed out have their channel dropped
257        // (https://fxbug.dev/42131208).
258        let watcher_futures = {
259            // Take the current watchers out of the RefCell because we'll be modifying the vector
260            let watchers = self.reboot_watchers.lock().await;
261            println!("[shutdown-shim] notifying {:?} watchers of reboot", watchers.len());
262            watchers.clone().into_iter().map(|(key, watcher_proxy)| {
263                let options = options.clone();
264                async move {
265                    let deadline = timeout.after_now();
266                    let result = watcher_proxy
267                        .on_reboot(&options.into())
268                        .map_err(|_| ())
269                        .on_timeout(deadline, || Err(()))
270                        .await;
271                    match result {
272                        Ok(()) => Some((key, watcher_proxy)),
273                        Err(()) => None,
274                    }
275                }
276            })
277        };
278
279        // Run all of the futures, collecting the successful watcher proxies into a vector
280        let new_watchers = futures::future::join_all(watcher_futures)
281            .await
282            .into_iter()
283            .filter_map(|watcher_opt| watcher_opt) // Unwrap the Options while filtering out None
284            .collect();
285
286        // Repopulate the successful watcher proxies back into the `reboot_watchers` RefCell
287        *self.reboot_watchers.lock().await = new_watchers;
288
289        fuchsia_trace::duration_end!(
290            c"shutdown-shim",
291            c"ShutdownWatcher::notify_reboot_watchers",
292            "options" => format!("{:?}", options).as_str()
293        );
294    }
295
296    async fn notify_shutdown_watchers(
297        &self,
298        options: ShutdownOptionsWrapper,
299        timeout: zx::MonotonicDuration,
300    ) {
301        // Instead of waiting for https://fxbug.dev/42120903, use begin and end pair of macros.
302        fuchsia_trace::duration_begin!(
303            c"shutdown-shim",
304            c"ShutdownWatcher::notify_shutdown_watchers",
305            "options" => format!("{:?}", options).as_str()
306        );
307
308        // Create a future for each watcher that calls the watcher's `on_shutdown` method and
309        // returns the watcher proxy if the response was received within the timeout, or None
310        // otherwise. We take this approach so that watchers that timed out have their channel
311        // dropped (https://fxbug.dev/42131208).
312        let watcher_futures = {
313            // Take the current watchers out of `shutdown_watchers` because we'll be modifying the
314            // vector.
315            let watchers = self.shutdown_watchers.lock().await;
316            println!("[shutdown-shim] notifying {:?} watchers of shutdown", watchers.len());
317            watchers.clone().into_iter().map(|(key, watcher_proxy)| {
318                let options = options.clone();
319                async move {
320                    let deadline = timeout.after_now();
321                    let result = watcher_proxy
322                        .on_shutdown(&options.into())
323                        .map_err(|_| ())
324                        .on_timeout(deadline, || Err(()))
325                        .await;
326
327                    match result {
328                        Ok(()) => Some((key, watcher_proxy)),
329                        Err(()) => None,
330                    }
331                }
332            })
333        };
334
335        // Run all of the futures, collecting the successful watcher proxies into a vector
336        let new_watchers = futures::future::join_all(watcher_futures)
337            .await
338            .into_iter()
339            .filter_map(|watcher_opt| watcher_opt) // Unwrap the Options while filtering out None
340            .collect();
341
342        // Repopulate the successful watcher proxies back into `shutdown_watchers`
343        *self.shutdown_watchers.lock().await = new_watchers;
344
345        fuchsia_trace::duration_end!(
346            c"shutdown-shim",
347            c"ShutdownWatcher::notify_shutdown_watchers",
348            "options" => format!("{:?}", options).as_str()
349        );
350    }
351
352    async fn notify_terminal_state_watchers(&self, timeout: zx::MonotonicDuration) {
353        fuchsia_trace::duration!(
354            c"shutdown-shim",
355            c"ShutdownWatcher::notify_terminal_state_watchers",
356        );
357
358        // Create a future for each watcher that calls the watcher's
359        // `on_terminal_state_transition_started` method and returns the watcher proxy if the
360        // response was received within the timeout, or None otherwise. We take this approach so
361        // that watchers that timed out have their channel dropped (https://fxbug.dev/42131208).
362        let watcher_futures = {
363            // Take the current watchers out of `terminal_state_watchers` because we'll be modifying
364            // the vector.
365            let watchers = self.terminal_state_watchers.lock().await;
366            println!("[shutdown-shim] notifying {:?} watchers of terminal state", watchers.len());
367            watchers.clone().into_iter().map(|(key, watcher_proxy)| async move {
368                let deadline = timeout.after_now();
369                let result = watcher_proxy
370                    .on_terminal_state_transition_started()
371                    .map_err(|_| ())
372                    .on_timeout(deadline, || Err(()))
373                    .await;
374
375                match result {
376                    Ok(()) => Some((key, watcher_proxy)),
377                    Err(()) => None,
378                }
379            })
380        };
381
382        // Run all of the futures, collecting the successful watcher proxies into a vector
383        let new_watchers = futures::future::join_all(watcher_futures)
384            .await
385            .into_iter()
386            .filter_map(|watcher_opt| watcher_opt) // Unwrap the Options while filtering out None
387            .collect();
388
389        // Repopulate the successful watcher proxies back into `terminal_state_watchers`
390        *self.terminal_state_watchers.lock().await = new_watchers;
391    }
392}
393
394struct InspectData {
395    reboot_watcher_current_connections: inspect::UintProperty,
396    reboot_watcher_total_connections: inspect::UintProperty,
397    terminal_state_watcher_current_connections: inspect::UintProperty,
398    terminal_state_watcher_total_connections: inspect::UintProperty,
399}
400
401// TODO(https://fxbug.dev/414413282): rename nodes to shutdown_watcher_*. This will require also
402// updating the inspect selectors. For now, both reboot and shutdown watchers will increment these
403// counters.
404impl InspectData {
405    fn new(parent: &inspect::Node, name: String) -> Self {
406        // Create a local root node and properties
407        let root = parent.create_child(name);
408        let reboot_watcher_current_connections =
409            root.create_uint("reboot_watcher_current_connections", 0);
410        let reboot_watcher_total_connections =
411            root.create_uint("reboot_watcher_total_connections", 0);
412        let terminal_state_watcher_current_connections =
413            root.create_uint("terminal_state_watcher_current_connections", 0);
414        let terminal_state_watcher_total_connections =
415            root.create_uint("terminal_state_watcher_total_connections", 0);
416
417        // Pass ownership of the new node to the parent node, otherwise it'll be dropped
418        parent.record(root);
419
420        InspectData {
421            reboot_watcher_current_connections,
422            reboot_watcher_total_connections,
423            terminal_state_watcher_current_connections,
424            terminal_state_watcher_total_connections,
425        }
426    }
427
428    fn add_reboot_watcher(&self) {
429        self.reboot_watcher_current_connections.add(1);
430        self.reboot_watcher_total_connections.add(1);
431    }
432
433    fn remove_reboot_watcher(&self) {
434        self.reboot_watcher_current_connections.subtract(1);
435    }
436
437    fn add_terminal_state_watcher(&self) {
438        self.terminal_state_watcher_current_connections.add(1);
439        self.terminal_state_watcher_total_connections.add(1);
440    }
441
442    fn remove_terminal_state_watcher(&self) {
443        self.terminal_state_watcher_current_connections.subtract(1);
444    }
445}
446
447#[cfg(test)]
448mod tests {
449    use super::*;
450    use assert_matches::assert_matches;
451    use diagnostics_assertions::assert_data_tree;
452    use fidl::endpoints::{ControlHandle, RequestStream};
453    use fidl_fuchsia_hardware_power_statecontrol::{ShutdownAction, ShutdownReason};
454
455    // In this test, convert float to integer for simpilification
456    fn seconds(seconds: f64) -> zx::MonotonicDuration {
457        zx::MonotonicDuration::from_seconds(seconds as i64)
458    }
459
460    /// Tests for the presence and correctness of inspect data
461    #[fuchsia::test]
462    async fn test_inspect_data() {
463        let inspector = inspect::Inspector::default();
464        let registrar = ShutdownWatcher::new_with_inspector(&inspector);
465
466        assert_data_tree!(
467            inspector,
468            root: {
469                ShutdownWatcher: {
470                    reboot_watcher_current_connections: 0u64,
471                    reboot_watcher_total_connections: 0u64,
472                    terminal_state_watcher_current_connections: 0u64,
473                    terminal_state_watcher_total_connections: 0u64,
474                }
475            }
476        );
477
478        let (watcher_proxy, s) = fidl::endpoints::create_proxy::<fpower::RebootWatcherMarker>();
479        registrar.add_reboot_watcher(watcher_proxy.clone()).await;
480
481        assert_data_tree!(
482            inspector,
483            root: {
484                ShutdownWatcher: {
485                    reboot_watcher_current_connections: 1u64,
486                    reboot_watcher_total_connections: 1u64,
487                    terminal_state_watcher_current_connections: 0u64,
488                    terminal_state_watcher_total_connections: 0u64,
489                }
490            }
491        );
492
493        drop(s);
494        watcher_proxy.on_closed().await.expect("closed");
495
496        assert_data_tree!(
497            @retry 10,
498            inspector,
499            root: {
500                ShutdownWatcher: {
501                    reboot_watcher_current_connections: 0u64,
502                    reboot_watcher_total_connections: 1u64,
503                    terminal_state_watcher_current_connections: 0u64,
504                    terminal_state_watcher_total_connections: 0u64,
505                }
506            }
507        );
508
509        let (watcher_proxy, s) = fidl::endpoints::create_proxy::<fpower::RebootWatcherMarker>();
510        registrar.add_reboot_watcher(watcher_proxy.clone()).await;
511
512        assert_data_tree!(
513            @retry 10,
514            inspector,
515            root: {
516                ShutdownWatcher: {
517                    reboot_watcher_current_connections: 1u64,
518                    reboot_watcher_total_connections: 2u64,
519                    terminal_state_watcher_current_connections: 0u64,
520                    terminal_state_watcher_total_connections: 0u64,
521                }
522            }
523        );
524        drop(s);
525        watcher_proxy.on_closed().await.expect("closed");
526
527        assert_data_tree!(
528            @retry 10,
529            inspector,
530            root: {
531                ShutdownWatcher: {
532                    reboot_watcher_current_connections: 0u64,
533                    reboot_watcher_total_connections: 2u64,
534                    terminal_state_watcher_current_connections: 0u64,
535                    terminal_state_watcher_total_connections: 0u64,
536                }
537            }
538        );
539
540        let (watcher_proxy, s) = fidl::endpoints::create_proxy::<fpower::ShutdownWatcherMarker>();
541        registrar.add_shutdown_watcher(watcher_proxy.clone()).await;
542
543        assert_data_tree!(
544            @retry 10,
545            inspector,
546            root: {
547                ShutdownWatcher: {
548                    reboot_watcher_current_connections: 1u64,
549                    reboot_watcher_total_connections: 3u64,
550                    terminal_state_watcher_current_connections: 0u64,
551                    terminal_state_watcher_total_connections: 0u64,
552                }
553            }
554        );
555        drop(s);
556        watcher_proxy.on_closed().await.expect("closed");
557
558        assert_data_tree!(
559            @retry 10,
560            inspector,
561            root: {
562                ShutdownWatcher: {
563                    reboot_watcher_current_connections: 0u64,
564                    reboot_watcher_total_connections: 3u64,
565                    terminal_state_watcher_current_connections: 0u64,
566                    terminal_state_watcher_total_connections: 0u64,
567                }
568            }
569        );
570
571        let (watcher_proxy, s) =
572            fidl::endpoints::create_proxy::<fpower::TerminalStateWatcherMarker>();
573        registrar.add_terminal_state_watcher(watcher_proxy.clone()).await;
574
575        assert_data_tree!(
576            @retry 10,
577            inspector,
578            root: {
579                ShutdownWatcher: {
580                    reboot_watcher_current_connections: 0u64,
581                    reboot_watcher_total_connections: 3u64,
582                    terminal_state_watcher_current_connections: 1u64,
583                    terminal_state_watcher_total_connections: 1u64,
584                }
585            }
586        );
587        drop(s);
588        watcher_proxy.on_closed().await.expect("closed");
589
590        assert_data_tree!(
591            @retry 10,
592            inspector,
593            root: {
594                ShutdownWatcher: {
595                    reboot_watcher_current_connections: 0u64,
596                    reboot_watcher_total_connections: 3u64,
597                    terminal_state_watcher_current_connections: 0u64,
598                    terminal_state_watcher_total_connections: 1u64,
599                }
600            }
601        );
602    }
603
604    /// Tests that a client can successfully register a reboot watcher, and the registered watcher
605    /// receives the expected reboot notification.
606    #[fasync::run_singlethreaded(test)]
607    async fn test_add_reboot_watcher() {
608        let registrar = ShutdownWatcher::new();
609
610        // Create the proxy/stream to register the watcher
611        let (register_proxy, register_stream) = fidl::endpoints::create_proxy_and_stream::<
612            fpower::RebootMethodsWatcherRegisterMarker,
613        >();
614
615        // Start the RebootMethodsWatcherRegister server that will handle Register calls from
616        // register_proxy
617        let registrar_clone = registrar.clone();
618        fasync::Task::local(async move {
619            registrar_clone.handle_reboot_register_request(register_stream).await;
620        })
621        .detach();
622
623        // Create the watcher proxy/stream to receive reboot notifications
624        let (watcher_client, mut watcher_stream) =
625            fidl::endpoints::create_request_stream::<fpower::RebootWatcherMarker>();
626
627        // Call the Register API, passing in the watcher_client end
628        assert_matches!(register_proxy.register_watcher(watcher_client).await, Ok(()));
629        // Signal the watchers
630        registrar
631            .notify_reboot_watchers(
632                ShutdownOptionsWrapper::new(ShutdownAction::Reboot, ShutdownReason::UserRequest),
633                seconds(0.0),
634            )
635            .await;
636
637        // Verify the watcher_stream gets the correct reboot notification
638        let reasons = assert_matches!(
639            watcher_stream.try_next().await.unwrap().unwrap(),
640            fpower::RebootWatcherRequest::OnReboot {
641                options: fpower::RebootOptions{reasons: Some(reasons), ..},
642                ..
643            } => reasons
644        );
645        assert_eq!(&reasons[..], [fpower::RebootReason2::UserRequest]);
646    }
647
648    /// Tests that a client can successfully register a shutdown watcher, and the registered watcher
649    /// receives the expected shutdown notification.
650    #[fasync::run_singlethreaded(test)]
651    async fn test_add_shutdown_watcher() {
652        let registrar = ShutdownWatcher::new();
653
654        // Create the proxy/stream to register the watcher
655        let (register_proxy, register_stream) =
656            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherRegisterMarker>();
657
658        // Start the ShutdownWatcherRegister server that will handle Register calls from
659        // register_proxy.
660        let registrar_clone = registrar.clone();
661        fasync::Task::local(async move {
662            registrar_clone.handle_shutdown_register_request(register_stream).await;
663        })
664        .detach();
665
666        // Create the watcher proxy/stream to receive shutdown notifications
667        let (watcher_client, mut watcher_stream) =
668            fidl::endpoints::create_request_stream::<fpower::ShutdownWatcherMarker>();
669
670        // Call the Register API, passing in the watcher_client end
671        assert_matches!(register_proxy.register_watcher(watcher_client).await, Ok(()));
672
673        // Signal the watchers
674        registrar
675            .notify_shutdown_watchers(
676                ShutdownOptionsWrapper::new(ShutdownAction::Poweroff, ShutdownReason::UserRequest),
677                seconds(0.0),
678            )
679            .await;
680
681        // Verify the watcher_stream gets the correct shutdown notification
682        let (action, reasons) = assert_matches!(
683            watcher_stream.try_next().await.unwrap().unwrap(),
684            fpower::ShutdownWatcherRequest::OnShutdown {
685                options: fpower::ShutdownOptions{action: Some(action), reasons: Some(reasons), ..},
686                ..
687            } => (action, reasons)
688        );
689        assert_eq!(action, ShutdownAction::Poweroff);
690        assert_eq!(&reasons[..], [ShutdownReason::UserRequest]);
691    }
692
693    /// Tests that a client can successfully register a terminal state watcher, and the registered
694    /// watcher receives the expected notification.
695    #[fasync::run_singlethreaded(test)]
696    async fn test_add_terminal_state_watcher() {
697        let registrar = ShutdownWatcher::new();
698
699        // Create the proxy/stream to register the watcher
700        let (register_proxy, register_stream) =
701            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherRegisterMarker>();
702
703        // Start the ShutdownWatcherRegister server that will handle Register calls from
704        // register_proxy.
705        let registrar_clone = registrar.clone();
706        fasync::Task::local(async move {
707            registrar_clone.handle_shutdown_register_request(register_stream).await;
708        })
709        .detach();
710
711        // Create the watcher proxy/stream to receive terminal state notifications
712        let (watcher_client, mut watcher_stream) =
713            fidl::endpoints::create_request_stream::<fpower::TerminalStateWatcherMarker>();
714
715        // Call the Register API, passing in the watcher_client end
716        assert_matches!(
717            register_proxy.register_terminal_state_watcher(watcher_client).await,
718            Ok(())
719        );
720
721        // Signal the watchers
722        registrar.notify_terminal_state_watchers(seconds(0.0)).await;
723
724        // Verify the watcher_stream gets the correct notification
725        assert_matches!(
726            watcher_stream.try_next().await.unwrap().unwrap(),
727            fpower::TerminalStateWatcherRequest::OnTerminalStateTransitionStarted { .. }
728        );
729    }
730
731    /// Tests that a reboot watcher is delivered the correct reboot reason
732    #[fasync::run_singlethreaded(test)]
733    async fn test_reboot_watcher_reason() {
734        let registrar = ShutdownWatcher::new();
735        let (watcher_proxy, mut watcher_stream) =
736            fidl::endpoints::create_proxy_and_stream::<fpower::RebootWatcherMarker>();
737        registrar.add_reboot_watcher(watcher_proxy).await;
738        registrar
739            .notify_reboot_watchers(
740                ShutdownOptionsWrapper::new(
741                    ShutdownAction::Reboot,
742                    ShutdownReason::HighTemperature,
743                ),
744                seconds(0.0),
745            )
746            .await;
747
748        let reasons = match watcher_stream.try_next().await {
749            Ok(Some(fpower::RebootWatcherRequest::OnReboot {
750                options: fpower::RebootOptions { reasons: Some(reasons), .. },
751                ..
752            })) => reasons,
753            e => panic!("Unexpected watcher_stream result: {:?}", e),
754        };
755
756        assert_eq!(&reasons[..], [fpower::RebootReason2::HighTemperature]);
757    }
758
759    /// Tests that a shutdown watcher is delivered the correct shutdown reason. This test does not
760    /// register via the ShutdownWatcherRegister protocol.
761    #[fasync::run_singlethreaded(test)]
762    async fn test_shutdown_watcher_reason() {
763        let registrar = ShutdownWatcher::new();
764        let (watcher_proxy, mut watcher_stream) =
765            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherMarker>();
766        registrar.add_shutdown_watcher(watcher_proxy).await;
767        registrar
768            .notify_shutdown_watchers(
769                ShutdownOptionsWrapper::new(
770                    ShutdownAction::Poweroff,
771                    ShutdownReason::HighTemperature,
772                ),
773                seconds(0.0),
774            )
775            .await;
776
777        let (action, reasons) = assert_matches!(
778            watcher_stream.try_next().await.unwrap().unwrap(),
779            fpower::ShutdownWatcherRequest::OnShutdown {
780                options: fpower::ShutdownOptions{action: Some(action), reasons: Some(reasons), ..},
781                ..
782            } => (action, reasons)
783        );
784        assert_eq!(action, ShutdownAction::Poweroff);
785        assert_eq!(&reasons[..], [ShutdownReason::HighTemperature]);
786    }
787
788    /// Tests that if there are multiple registered reboot watchers, each one will receive the
789    /// expected reboot notification.
790    #[fasync::run_singlethreaded(test)]
791    async fn test_multiple_reboot_watchers() {
792        let registrar = ShutdownWatcher::new();
793
794        // Create three separate reboot watchers
795        let (watcher_proxy1, mut watcher_stream1) =
796            fidl::endpoints::create_proxy_and_stream::<fpower::RebootWatcherMarker>();
797        registrar.add_reboot_watcher(watcher_proxy1).await;
798
799        let (watcher_proxy2, mut watcher_stream2) =
800            fidl::endpoints::create_proxy_and_stream::<fpower::RebootWatcherMarker>();
801        registrar.add_reboot_watcher(watcher_proxy2).await;
802
803        let (watcher_proxy3, mut watcher_stream3) =
804            fidl::endpoints::create_proxy_and_stream::<fpower::RebootWatcherMarker>();
805        registrar.add_reboot_watcher(watcher_proxy3).await;
806
807        // Close the channel of the first watcher to verify the registrar still correctly notifies the
808        // second and third watchers
809        watcher_stream1.control_handle().shutdown();
810
811        registrar
812            .notify_reboot_watchers(
813                ShutdownOptionsWrapper::new(
814                    ShutdownAction::Reboot,
815                    ShutdownReason::HighTemperature,
816                ),
817                seconds(0.0),
818            )
819            .await;
820
821        // The first watcher should get None because its channel was closed
822        match watcher_stream1.try_next().await {
823            Ok(None) => {}
824            e => panic!("Unexpected watcher_stream1 result: {:?}", e),
825        };
826
827        // Verify the watcher received the correct OnReboot request
828        match watcher_stream2.try_next().await {
829            Ok(Some(fpower::RebootWatcherRequest::OnReboot {
830                options: fpower::RebootOptions { reasons: Some(reasons), .. },
831                ..
832            })) => {
833                assert_eq!(&reasons[..], [fpower::RebootReason2::HighTemperature])
834            }
835            e => panic!("Unexpected watcher_stream2 result: {:?}", e),
836        };
837
838        // Verify the watcher received the correct OnReboot request
839        match watcher_stream3.try_next().await {
840            Ok(Some(fpower::RebootWatcherRequest::OnReboot {
841                options: fpower::RebootOptions { reasons: Some(reasons), .. },
842                ..
843            })) => assert_eq!(&reasons[..], [fpower::RebootReason2::HighTemperature]),
844            e => panic!("Unexpected watcher_stream3 result: {:?}", e),
845        };
846    }
847
848    /// Tests that if there are multiple registered shutdown watchers, each one will receive the
849    /// expected shutdown notification.
850    #[fasync::run_singlethreaded(test)]
851    async fn test_multiple_shutdown_watchers() {
852        let registrar = ShutdownWatcher::new();
853
854        // Create three separate shutdown watchers
855        let (watcher_proxy1, mut watcher_stream1) =
856            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherMarker>();
857        registrar.add_shutdown_watcher(watcher_proxy1).await;
858
859        let (watcher_proxy2, mut watcher_stream2) =
860            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherMarker>();
861        registrar.add_shutdown_watcher(watcher_proxy2).await;
862
863        let (watcher_proxy3, mut watcher_stream3) =
864            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherMarker>();
865        registrar.add_shutdown_watcher(watcher_proxy3).await;
866
867        // Close the channel of the first watcher to verify the registrar still correctly notifies
868        // the second and third watchers.
869        watcher_stream1.control_handle().shutdown();
870
871        registrar
872            .notify_shutdown_watchers(
873                ShutdownOptionsWrapper::new(
874                    ShutdownAction::Reboot,
875                    ShutdownReason::HighTemperature,
876                ),
877                seconds(0.0),
878            )
879            .await;
880
881        // The first watcher should get None because its channel was closed
882        match watcher_stream1.try_next().await {
883            Ok(None) => {}
884            e => panic!("Unexpected watcher_stream1 result: {:?}", e),
885        };
886
887        // Verify the watcher received the correct OnShutdown request
888        let (action, reasons) = assert_matches!(
889            watcher_stream2.try_next().await.unwrap().unwrap(),
890            fpower::ShutdownWatcherRequest::OnShutdown {
891                options: fpower::ShutdownOptions{action: Some(action), reasons: Some(reasons), ..},
892                ..
893            } => (action, reasons)
894        );
895        assert_eq!(action, ShutdownAction::Reboot);
896        assert_eq!(&reasons[..], [ShutdownReason::HighTemperature]);
897
898        // Verify the watcher received the correct OnShutdown request
899        let (action, reasons) = assert_matches!(
900            watcher_stream3.try_next().await.unwrap().unwrap(),
901            fpower::ShutdownWatcherRequest::OnShutdown {
902                options: fpower::ShutdownOptions{action: Some(action), reasons: Some(reasons), ..},
903                ..
904            } => (action, reasons)
905        );
906        assert_eq!(action, ShutdownAction::Reboot);
907        assert_eq!(&reasons[..], [ShutdownReason::HighTemperature]);
908    }
909
910    /// Tests that the deprecated reboot watchers only receive notifications for
911    /// ShutdownAction::Reboot.
912    #[fasync::run_singlethreaded(test)]
913    async fn reboot_watchers_only_notified_of_reboots() {
914        let registrar = ShutdownWatcher::new();
915        let (watcher_proxy, mut watcher_stream) =
916            fidl::endpoints::create_proxy_and_stream::<fpower::RebootWatcherMarker>();
917        registrar.add_reboot_watcher(watcher_proxy).await;
918
919        registrar
920            .handle_system_shutdown_message(Some(ShutdownOptionsWrapper::new(
921                ShutdownAction::Poweroff,
922                ShutdownReason::HighTemperature,
923            )))
924            .await;
925        assert!(futures::poll!(watcher_stream.try_next()).is_pending());
926
927        registrar
928            .handle_system_shutdown_message(Some(ShutdownOptionsWrapper::new(
929                ShutdownAction::RebootToBootloader,
930                ShutdownReason::HighTemperature,
931            )))
932            .await;
933        assert!(futures::poll!(watcher_stream.try_next()).is_pending());
934
935        registrar
936            .handle_system_shutdown_message(Some(ShutdownOptionsWrapper::new(
937                ShutdownAction::RebootToRecovery,
938                ShutdownReason::HighTemperature,
939            )))
940            .await;
941        assert!(futures::poll!(watcher_stream.try_next()).is_pending());
942
943        registrar
944            .handle_system_shutdown_message(Some(ShutdownOptionsWrapper::new(
945                ShutdownAction::Reboot,
946                ShutdownReason::HighTemperature,
947            )))
948            .await;
949        match watcher_stream.try_next().await {
950            Ok(Some(fpower::RebootWatcherRequest::OnReboot {
951                options: fpower::RebootOptions { reasons: Some(reasons), .. },
952                ..
953            })) => {
954                assert_eq!(&reasons[..], [fpower::RebootReason2::HighTemperature])
955            }
956            e => panic!("Unexpected watcher_stream result: {:?}", e),
957        };
958    }
959
960    /// Tests that shutdown, terminal state, and reboot watchers are notified of reboots.
961    /// Shutdown watchers are notified of other shutdown actions.
962    /// Terminal state watchers are notified of all reboot or shutdown actions.
963    #[fasync::run_singlethreaded(test)]
964    async fn shutdown_terminal_state_and_reboot_watchers_notified() {
965        let registrar = ShutdownWatcher::new();
966
967        let (shutdown_watcher_proxy, mut shutdown_watcher_stream) =
968            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherMarker>();
969        registrar.add_shutdown_watcher(shutdown_watcher_proxy).await;
970
971        let (reboot_watcher_proxy, mut reboot_watcher_stream) =
972            fidl::endpoints::create_proxy_and_stream::<fpower::RebootWatcherMarker>();
973        registrar.add_reboot_watcher(reboot_watcher_proxy).await;
974
975        let (terminal_state_watcher_proxy, mut terminal_state_watcher_stream) =
976            fidl::endpoints::create_proxy_and_stream::<fpower::TerminalStateWatcherMarker>();
977        registrar.add_terminal_state_watcher(terminal_state_watcher_proxy).await;
978
979        registrar
980            .handle_system_shutdown_message(Some(ShutdownOptionsWrapper::new(
981                ShutdownAction::Reboot,
982                ShutdownReason::HighTemperature,
983            )))
984            .await;
985
986        let (action, reasons) = assert_matches!(
987            shutdown_watcher_stream.try_next().await.unwrap().unwrap(),
988            fpower::ShutdownWatcherRequest::OnShutdown {
989                options: fpower::ShutdownOptions{action: Some(action), reasons: Some(reasons), ..},
990                ..
991            } => (action, reasons)
992        );
993        assert_eq!(action, ShutdownAction::Reboot);
994        assert_eq!(&reasons[..], [ShutdownReason::HighTemperature]);
995
996        match reboot_watcher_stream.try_next().await {
997            Ok(Some(fpower::RebootWatcherRequest::OnReboot {
998                options: fpower::RebootOptions { reasons: Some(reasons), .. },
999                ..
1000            })) => {
1001                assert_eq!(&reasons[..], [fpower::RebootReason2::HighTemperature])
1002            }
1003            e => panic!("Unexpected watcher_stream result: {:?}", e),
1004        };
1005
1006        assert_matches!(
1007            terminal_state_watcher_stream.try_next().await.unwrap().unwrap(),
1008            fpower::TerminalStateWatcherRequest::OnTerminalStateTransitionStarted { .. }
1009        );
1010    }
1011
1012    #[fasync::run_singlethreaded(test)]
1013    async fn shutdown_watchers_notified_for_poweroff() {
1014        let registrar = ShutdownWatcher::new();
1015
1016        let (watcher_proxy, mut watcher_stream) =
1017            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherMarker>();
1018        registrar.add_shutdown_watcher(watcher_proxy).await;
1019
1020        registrar
1021            .handle_system_shutdown_message(Some(ShutdownOptionsWrapper::new(
1022                ShutdownAction::Poweroff,
1023                ShutdownReason::HighTemperature,
1024            )))
1025            .await;
1026        let (action, reasons) = assert_matches!(
1027            watcher_stream.try_next().await.unwrap().unwrap(),
1028            fpower::ShutdownWatcherRequest::OnShutdown {
1029                options: fpower::ShutdownOptions{action: Some(action), reasons: Some(reasons), ..},
1030                ..
1031            } => (action, reasons)
1032        );
1033        assert_eq!(action, ShutdownAction::Poweroff);
1034        assert_eq!(&reasons[..], [ShutdownReason::HighTemperature]);
1035    }
1036
1037    #[fasync::run_singlethreaded(test)]
1038    async fn shutdown_watchers_notified_for_reboot_to_bootloader() {
1039        let registrar = ShutdownWatcher::new();
1040
1041        let (watcher_proxy, mut watcher_stream) =
1042            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherMarker>();
1043        registrar.add_shutdown_watcher(watcher_proxy).await;
1044
1045        registrar
1046            .handle_system_shutdown_message(Some(ShutdownOptionsWrapper::new(
1047                ShutdownAction::RebootToBootloader,
1048                ShutdownReason::HighTemperature,
1049            )))
1050            .await;
1051        let (action, reasons) = assert_matches!(
1052            watcher_stream.try_next().await.unwrap().unwrap(),
1053            fpower::ShutdownWatcherRequest::OnShutdown {
1054                options: fpower::ShutdownOptions{action: Some(action), reasons: Some(reasons), ..},
1055                ..
1056            } => (action, reasons)
1057        );
1058        assert_eq!(action, ShutdownAction::RebootToBootloader);
1059        assert_eq!(&reasons[..], [ShutdownReason::HighTemperature]);
1060    }
1061
1062    #[fasync::run_singlethreaded(test)]
1063    async fn shutdown_watchers_notified_for_reboot_to_recovery() {
1064        let registrar = ShutdownWatcher::new();
1065
1066        let (watcher_proxy, mut watcher_stream) =
1067            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherMarker>();
1068        registrar.add_shutdown_watcher(watcher_proxy).await;
1069
1070        registrar
1071            .handle_system_shutdown_message(Some(ShutdownOptionsWrapper::new(
1072                ShutdownAction::RebootToRecovery,
1073                ShutdownReason::HighTemperature,
1074            )))
1075            .await;
1076        let (action, reasons) = assert_matches!(
1077            watcher_stream.try_next().await.unwrap().unwrap(),
1078            fpower::ShutdownWatcherRequest::OnShutdown {
1079                options: fpower::ShutdownOptions{action: Some(action), reasons: Some(reasons), ..},
1080                ..
1081            } => (action, reasons)
1082        );
1083        assert_eq!(action, ShutdownAction::RebootToRecovery);
1084        assert_eq!(&reasons[..], [ShutdownReason::HighTemperature]);
1085    }
1086
1087    #[fuchsia::test]
1088    fn test_reboot_watcher_response_delay() {
1089        let mut exec = fasync::TestExecutor::new();
1090        let registrar = ShutdownWatcher::new();
1091
1092        // Register the reboot watcher
1093        let (watcher_proxy, mut watcher_stream) =
1094            fidl::endpoints::create_proxy_and_stream::<fpower::RebootWatcherMarker>();
1095        let fut = async {
1096            registrar.add_reboot_watcher(watcher_proxy).await;
1097            assert_eq!(registrar.reboot_watchers.lock().await.len(), 1);
1098        };
1099        exec.run_singlethreaded(fut);
1100
1101        // Set up the notify future
1102        let notify_future = registrar.notify_reboot_watchers(
1103            ShutdownOptionsWrapper::new(ShutdownAction::Reboot, ShutdownReason::HighTemperature),
1104            seconds(1.0),
1105        );
1106        futures::pin_mut!(notify_future);
1107
1108        // Verify that the notify future can't complete on the first attempt (because the watcher
1109        // will not have responded)
1110        assert!(exec.run_until_stalled(&mut notify_future).is_pending());
1111
1112        // Ack the reboot notification, allowing the shutdown flow to continue
1113        let fpower::RebootWatcherRequest::OnReboot { responder, .. } =
1114            exec.run_singlethreaded(&mut watcher_stream.try_next()).unwrap().unwrap();
1115        assert_matches!(responder.send(), Ok(()));
1116
1117        // Verify the notify future can now complete
1118        assert!(exec.run_until_stalled(&mut notify_future).is_ready());
1119    }
1120
1121    #[fuchsia::test]
1122    fn test_shutdown_watcher_response_delay() {
1123        let mut exec = fasync::TestExecutor::new();
1124        let registrar = ShutdownWatcher::new();
1125
1126        // Register the shutdown watcher
1127        let (watcher_proxy, mut watcher_stream) =
1128            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherMarker>();
1129        let fut = async {
1130            registrar.add_shutdown_watcher(watcher_proxy).await;
1131            assert_eq!(registrar.shutdown_watchers.lock().await.len(), 1);
1132        };
1133        exec.run_singlethreaded(fut);
1134
1135        // Set up the notify future
1136        let notify_future = registrar.notify_shutdown_watchers(
1137            ShutdownOptionsWrapper::new(ShutdownAction::Reboot, ShutdownReason::HighTemperature),
1138            seconds(1.0),
1139        );
1140        futures::pin_mut!(notify_future);
1141
1142        // Verify that the notify future can't complete on the first attempt (because the watcher
1143        // will not have responded)
1144        assert!(exec.run_until_stalled(&mut notify_future).is_pending());
1145
1146        // Ack the shutdown notification, allowing the shutdown flow to continue
1147        match exec.run_singlethreaded(&mut watcher_stream.try_next()).unwrap().unwrap() {
1148            fpower::ShutdownWatcherRequest::OnShutdown { responder, .. } => {
1149                assert_matches!(responder.send(), Ok(()));
1150            }
1151            fpower::ShutdownWatcherRequest::_UnknownMethod { .. } => unimplemented!(),
1152        }
1153
1154        // Verify the notify future can now complete
1155        assert!(exec.run_until_stalled(&mut notify_future).is_ready());
1156    }
1157
1158    /// Tests that a reboot watcher is able to delay the shutdown but will time out after the
1159    /// expected duration. The test also verifies that when a watcher times out, it is removed
1160    /// from the list of registered reboot watchers.
1161    #[fuchsia::test]
1162    fn test_reboot_watcher_response_timeout() {
1163        let mut exec = fasync::TestExecutor::new_with_fake_time();
1164        let registrar = ShutdownWatcher::new();
1165        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
1166
1167        // Register the reboot watcher
1168        let (watcher_proxy, _watcher_stream) =
1169            fidl::endpoints::create_proxy_and_stream::<fpower::RebootWatcherMarker>();
1170        let fut = async {
1171            registrar.add_reboot_watcher(watcher_proxy).await;
1172            assert_eq!(registrar.reboot_watchers.lock().await.len(), 1);
1173        };
1174        futures::pin_mut!(fut);
1175        exec.run_until_stalled(&mut fut).is_ready();
1176
1177        // Set up the notify future
1178        let notify_future = registrar.notify_reboot_watchers(
1179            ShutdownOptionsWrapper::new(ShutdownAction::Reboot, ShutdownReason::HighTemperature),
1180            seconds(1.0),
1181        );
1182        futures::pin_mut!(notify_future);
1183
1184        // Verify that the notify future can't complete on the first attempt (because the watcher
1185        // will not have responded)
1186        assert!(exec.run_until_stalled(&mut notify_future).is_pending());
1187
1188        // Wake the timer that causes the watcher timeout to fire
1189        assert_eq!(exec.wake_next_timer(), Some(fasync::MonotonicInstant::from_nanos(1e9 as i64)));
1190
1191        // Verify the notify future can now complete
1192        assert!(exec.run_until_stalled(&mut notify_future).is_ready());
1193
1194        // Since the watcher timed out, verify it is removed from `reboot_watchers`
1195        let fut = async {
1196            assert_eq!(registrar.reboot_watchers.lock().await.len(), 0);
1197        };
1198        futures::pin_mut!(fut);
1199        exec.run_until_stalled(&mut fut).is_ready();
1200    }
1201
1202    /// Tests that a shutdown watcher is able to delay the shutdown but will time out after the
1203    /// expected duration. The test also verifies that when a watcher times out, it is removed
1204    /// from the list of registered shutdown watchers.
1205    #[fuchsia::test]
1206    fn test_shutdown_watcher_response_timeout() {
1207        let mut exec = fasync::TestExecutor::new_with_fake_time();
1208        let registrar = ShutdownWatcher::new();
1209        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
1210
1211        // Register the shutdown watcher
1212        let (watcher_proxy, _watcher_stream) =
1213            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherMarker>();
1214        let fut = async {
1215            registrar.add_shutdown_watcher(watcher_proxy).await;
1216            assert_eq!(registrar.shutdown_watchers.lock().await.len(), 1);
1217        };
1218        futures::pin_mut!(fut);
1219        exec.run_until_stalled(&mut fut).is_ready();
1220
1221        // Set up the notify future
1222        let notify_future = registrar.notify_shutdown_watchers(
1223            ShutdownOptionsWrapper::new(ShutdownAction::Reboot, ShutdownReason::HighTemperature),
1224            seconds(1.0),
1225        );
1226        futures::pin_mut!(notify_future);
1227
1228        // Verify that the notify future can't complete on the first attempt (because the watcher
1229        // will not have responded)
1230        assert!(exec.run_until_stalled(&mut notify_future).is_pending());
1231
1232        // Wake the timer that causes the watcher timeout to fire
1233        assert_eq!(exec.wake_next_timer(), Some(fasync::MonotonicInstant::from_nanos(1e9 as i64)));
1234
1235        // Verify the notify future can now complete
1236        assert!(exec.run_until_stalled(&mut notify_future).is_ready());
1237
1238        // Since the watcher timed out, verify it is removed from `shutdown_watchers`
1239        let fut = async {
1240            assert_eq!(registrar.shutdown_watchers.lock().await.len(), 0);
1241        };
1242        futures::pin_mut!(fut);
1243        exec.run_until_stalled(&mut fut).is_ready();
1244    }
1245
1246    /// Tests that an unsuccessful RebootWatcher registration results in the
1247    /// RebootMethodsWatcherRegister channel being closed.
1248    #[fasync::run_singlethreaded(test)]
1249    async fn test_watcher_register_fail() {
1250        let registrar = ShutdownWatcher::new();
1251
1252        // Create the registration proxy/stream
1253        let (register_proxy, register_stream) = fidl::endpoints::create_proxy_and_stream::<
1254            fpower::RebootMethodsWatcherRegisterMarker,
1255        >();
1256
1257        // Start the RebootMethodsWatcherRegister server that will handle Register requests from
1258        // `register_proxy`
1259        fasync::Task::local(async move {
1260            registrar.handle_reboot_register_request(register_stream).await;
1261        })
1262        .detach();
1263
1264        // Send an invalid request to the server to force a failure
1265        assert_matches!(register_proxy.as_channel().write(&[], &mut []), Ok(()));
1266
1267        // Verify the RebootMethodsWatcherRegister channel is closed
1268        assert_matches!(register_proxy.on_closed().await, Ok(zx::Signals::CHANNEL_PEER_CLOSED));
1269    }
1270
1271    /// Tests that an unsuccessful ShutdownWatcher registration results in the
1272    /// ShutdownWatcherRegister channel being closed.
1273    #[fasync::run_singlethreaded(test)]
1274    async fn test_shutdown_watcher_register_fail() {
1275        let registrar = ShutdownWatcher::new();
1276
1277        // Create the registration proxy/stream
1278        let (register_proxy, register_stream) =
1279            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherRegisterMarker>();
1280
1281        // Start the ShutdownWatcherRegister server that will handle Register requests from
1282        // `register_proxy`
1283        fasync::Task::local(async move {
1284            registrar.handle_shutdown_register_request(register_stream).await;
1285        })
1286        .detach();
1287
1288        // Send an invalid request to the server to force a failure
1289        assert_matches!(register_proxy.as_channel().write(&[], &mut []), Ok(()));
1290
1291        // Verify the ShutdownWatcherRegister channel is closed
1292        assert_matches!(register_proxy.on_closed().await, Ok(zx::Signals::CHANNEL_PEER_CLOSED));
1293    }
1294
1295    /// Tests that if there are multiple currently registered terminal state watchers, each one will
1296    /// receive the expected notification.
1297    #[fasync::run_singlethreaded(test)]
1298    async fn test_multiple_terminal_state_watchers() {
1299        let registrar = ShutdownWatcher::new();
1300
1301        // Create three separate terminal state watchers
1302        let (watcher_proxy1, mut watcher_stream1) =
1303            fidl::endpoints::create_proxy_and_stream::<fpower::TerminalStateWatcherMarker>();
1304        registrar.add_terminal_state_watcher(watcher_proxy1).await;
1305
1306        let (watcher_proxy2, mut watcher_stream2) =
1307            fidl::endpoints::create_proxy_and_stream::<fpower::TerminalStateWatcherMarker>();
1308        registrar.add_terminal_state_watcher(watcher_proxy2).await;
1309
1310        let (watcher_proxy3, mut watcher_stream3) =
1311            fidl::endpoints::create_proxy_and_stream::<fpower::TerminalStateWatcherMarker>();
1312        registrar.add_terminal_state_watcher(watcher_proxy3).await;
1313
1314        // Signal the watchers
1315        registrar.notify_terminal_state_watchers(seconds(0.0)).await;
1316
1317        // Verify each watcher stream gets the correct notification
1318        assert_matches!(
1319            watcher_stream1.try_next().await.unwrap().unwrap(),
1320            fpower::TerminalStateWatcherRequest::OnTerminalStateTransitionStarted { .. }
1321        );
1322        assert_matches!(
1323            watcher_stream2.try_next().await.unwrap().unwrap(),
1324            fpower::TerminalStateWatcherRequest::OnTerminalStateTransitionStarted { .. }
1325        );
1326        assert_matches!(
1327            watcher_stream3.try_next().await.unwrap().unwrap(),
1328            fpower::TerminalStateWatcherRequest::OnTerminalStateTransitionStarted { .. }
1329        );
1330    }
1331
1332    #[fuchsia::test]
1333    fn test_terminal_state_watcher_response_delay() {
1334        let mut exec = fasync::TestExecutor::new();
1335        let registrar = ShutdownWatcher::new();
1336
1337        // Register the terminal state watcher
1338        let (watcher_proxy, mut watcher_stream) =
1339            fidl::endpoints::create_proxy_and_stream::<fpower::TerminalStateWatcherMarker>();
1340        let fut = async {
1341            registrar.add_terminal_state_watcher(watcher_proxy).await;
1342            assert_eq!(registrar.terminal_state_watchers.lock().await.len(), 1);
1343        };
1344        exec.run_singlethreaded(fut);
1345
1346        // Set up the notify future
1347        let notify_future = registrar.notify_terminal_state_watchers(seconds(1.0));
1348        futures::pin_mut!(notify_future);
1349
1350        // Verify that the notify future can't complete on the first attempt (because the watcher
1351        // will not have responded)
1352        assert!(exec.run_until_stalled(&mut notify_future).is_pending());
1353
1354        // Ack the notification, allowing the shutdown flow to continue
1355        match exec.run_singlethreaded(&mut watcher_stream.try_next()).unwrap().unwrap() {
1356            fpower::TerminalStateWatcherRequest::OnTerminalStateTransitionStarted {
1357                responder,
1358                ..
1359            } => {
1360                assert_matches!(responder.send(), Ok(()));
1361            }
1362            fpower::TerminalStateWatcherRequest::_UnknownMethod { .. } => unimplemented!(),
1363        }
1364
1365        // Verify the notify future can now complete
1366        assert!(exec.run_until_stalled(&mut notify_future).is_ready());
1367    }
1368
1369    /// Tests that a terminal state watcher is able to delay the shutdown but will time out after the
1370    /// expected duration. The test also verifies that when a watcher times out, it is removed
1371    /// from the list of registered terminal state watchers.
1372    #[fuchsia::test]
1373    fn test_terminal_state_watcher_response_timeout() {
1374        let mut exec = fasync::TestExecutor::new_with_fake_time();
1375        let registrar = ShutdownWatcher::new();
1376        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
1377
1378        // Register the terminal state watcher
1379        let (watcher_proxy, _watcher_stream) =
1380            fidl::endpoints::create_proxy_and_stream::<fpower::TerminalStateWatcherMarker>();
1381        let fut = async {
1382            registrar.add_terminal_state_watcher(watcher_proxy).await;
1383            assert_eq!(registrar.terminal_state_watchers.lock().await.len(), 1);
1384        };
1385        futures::pin_mut!(fut);
1386        exec.run_until_stalled(&mut fut).is_ready();
1387
1388        // Set up the notify future
1389        let notify_future = registrar.notify_terminal_state_watchers(seconds(1.0));
1390        futures::pin_mut!(notify_future);
1391
1392        // Verify that the notify future can't complete on the first attempt (because the watcher
1393        // will not have responded)
1394        assert!(exec.run_until_stalled(&mut notify_future).is_pending());
1395
1396        // Wake the timer that causes the watcher timeout to fire
1397        assert_eq!(exec.wake_next_timer(), Some(fasync::MonotonicInstant::from_nanos(1e9 as i64)));
1398
1399        // Verify the notify future can now complete
1400        assert!(exec.run_until_stalled(&mut notify_future).is_ready());
1401
1402        // Since the watcher timed out, verify it is removed from `terminal_state_watchers`
1403        let fut = async {
1404            assert_eq!(registrar.terminal_state_watchers.lock().await.len(), 0);
1405        };
1406        futures::pin_mut!(fut);
1407        exec.run_until_stalled(&mut fut).is_ready();
1408    }
1409}