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 and RebootMethodsWatcher: the server
29///       receives an instance of this protocol over the ShutdownWatcherRegister and
30///       RebootMethodsWatcherRegister channels, and uses this channel to send shutdown
31///       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    inspect: Arc<Mutex<InspectData>>,
43}
44
45impl ShutdownWatcher {
46    const NOTIFY_SHUTDOWN_RESPONSE_TIMEOUT: zx::MonotonicDuration =
47        zx::MonotonicDuration::from_seconds(
48            fpower::MAX_SHUTDOWN_WATCHER_RESPONSE_TIME_SECONDS as i64,
49        );
50
51    const NOTIFY_REBOOT_RESPONSE_TIMEOUT: zx::MonotonicDuration =
52        zx::MonotonicDuration::from_seconds(
53            fpower::MAX_REBOOT_WATCHER_RESPONSE_TIME_SECONDS as i64,
54        );
55
56    #[cfg(test)]
57    fn new() -> Arc<Self> {
58        let inspector = inspect::Inspector::new(fuchsia_inspect::InspectorConfig::default());
59        Self::new_with_inspector(&inspector)
60    }
61
62    pub fn new_with_inspector(inspector: &inspect::Inspector) -> Arc<Self> {
63        Arc::new(Self {
64            reboot_watchers: Arc::new(Mutex::new(HashMap::new())),
65            shutdown_watchers: Arc::new(Mutex::new(HashMap::new())),
66            inspect: Arc::new(Mutex::new(InspectData::new(
67                inspector.root(),
68                "ShutdownWatcher".to_string(),
69            ))),
70        })
71    }
72
73    /// Handles a new client connection to the RebootMethodsWatcherRegister service.
74    pub async fn handle_reboot_register_request(
75        self: Arc<Self>,
76        mut stream: fpower::RebootMethodsWatcherRegisterRequestStream,
77    ) {
78        while let Ok(Some(req)) = stream.try_next().await {
79            match req {
80                fpower::RebootMethodsWatcherRegisterRequest::RegisterWatcher {
81                    watcher,
82                    responder,
83                } => {
84                    self.add_reboot_watcher(watcher.into_proxy()).await;
85                    let _ = responder.send();
86                }
87            }
88        }
89    }
90
91    /// Handles a new client connection to the ShutdownWatcherRegister service.
92    pub async fn handle_shutdown_register_request(
93        self: Arc<Self>,
94        mut stream: fpower::ShutdownWatcherRegisterRequestStream,
95    ) {
96        while let Ok(Some(req)) = stream.try_next().await {
97            match req {
98                fpower::ShutdownWatcherRegisterRequest::RegisterWatcher { watcher, responder } => {
99                    self.add_shutdown_watcher(watcher.into_proxy()).await;
100                    let _ = responder.send();
101                }
102                fpower::ShutdownWatcherRegisterRequest::_UnknownMethod { ordinal, .. } => {
103                    println!("[shutdown-shim]: error, unimplemented method ordinal: {ordinal}")
104                }
105            }
106        }
107    }
108
109    /// Adds a new RebootMethodsWatcher channel to the list of registered watchers.
110    async fn add_reboot_watcher(&self, watcher: fpower::RebootWatcherProxy) {
111        fuchsia_trace::duration!(
112            c"shutdown-shim",
113            c"ShutdownWatcher::add_reboot_watcher",
114            "watcher" => watcher.as_channel().raw_handle()
115        );
116
117        // If the client closes the watcher channel, remove it from our `reboot_watchers` map
118        println!("[shutdown-shim] Adding a reboot watcher");
119        let key = watcher.as_channel().raw_handle();
120        let proxy = watcher.clone();
121        let reboot_watchers = self.reboot_watchers.clone();
122        let inspect = self.inspect.clone();
123        fasync::Task::spawn(async move {
124            let _ = proxy.on_closed().await;
125            {
126                reboot_watchers.lock().await.remove(&key);
127            }
128            inspect.lock().await.remove_reboot_watcher();
129        })
130        .detach();
131
132        {
133            let mut watchers_mut = self.reboot_watchers.lock().await;
134            watchers_mut.insert(key, watcher);
135        }
136        self.inspect.lock().await.add_reboot_watcher();
137    }
138
139    /// Adds a new ShutdownWatcher channel to the list of registered watchers.
140    async fn add_shutdown_watcher(&self, watcher: fpower::ShutdownWatcherProxy) {
141        fuchsia_trace::duration!(
142            c"shutdown-shim",
143            c"ShutdownWatcher::add_shutdown_watcher",
144            "watcher" => watcher.as_channel().raw_handle()
145        );
146
147        // If the client closes the watcher channel, remove it from our `shutdown_watchers` map
148        println!("[shutdown-shim] Adding a shutdown watcher");
149        let key = watcher.as_channel().raw_handle();
150        let proxy = watcher.clone();
151        let shutdown_watchers = self.shutdown_watchers.clone();
152        let inspect = self.inspect.clone();
153        fasync::Task::spawn(async move {
154            let _ = proxy.on_closed().await;
155            {
156                shutdown_watchers.lock().await.remove(&key);
157            }
158            inspect.lock().await.remove_reboot_watcher();
159        })
160        .detach();
161
162        {
163            let mut watchers_mut = self.shutdown_watchers.lock().await;
164            watchers_mut.insert(key, watcher);
165        }
166        self.inspect.lock().await.add_reboot_watcher();
167    }
168
169    /// Handles the SystemShutdown message by notifying the appropriate registered watchers.
170    pub async fn handle_system_shutdown_message(&self, options: ShutdownOptionsWrapper) {
171        if options.action == ShutdownAction::Reboot && !options.reasons.is_empty() {
172            futures::join!(
173                self.notify_shutdown_watchers(
174                    options.clone(),
175                    Self::NOTIFY_SHUTDOWN_RESPONSE_TIMEOUT
176                ),
177                self.notify_reboot_watchers(options, Self::NOTIFY_REBOOT_RESPONSE_TIMEOUT)
178            );
179            return;
180        }
181
182        self.notify_shutdown_watchers(options, Self::NOTIFY_SHUTDOWN_RESPONSE_TIMEOUT).await;
183    }
184
185    async fn notify_reboot_watchers(
186        &self,
187        options: ShutdownOptionsWrapper,
188        timeout: zx::MonotonicDuration,
189    ) {
190        // Instead of waiting for https://fxbug.dev/42120903, use begin and end pair of macros.
191        fuchsia_trace::duration_begin!(
192            c"shutdown-shim",
193            c"ShutdownWatcher::notify_reboot_watchers",
194            "options" => format!("{:?}", options).as_str()
195        );
196
197        // Create a future for each watcher that calls the watcher's `on_reboot` method and returns
198        // the watcher proxy if the response was received within the timeout, or None otherwise. We
199        // take this approach so that watchers that timed out have their channel dropped
200        // (https://fxbug.dev/42131208).
201        let watcher_futures = {
202            // Take the current watchers out of the RefCell because we'll be modifying the vector
203            let watchers = self.reboot_watchers.lock().await;
204            println!("[shutdown-shim] notifying {:?} watchers of reboot", watchers.len());
205            watchers.clone().into_iter().map(|(key, watcher_proxy)| {
206                let options = options.clone();
207                async move {
208                    let deadline = timeout.after_now();
209                    let result = watcher_proxy
210                        .on_reboot(&options.into())
211                        .map_err(|_| ())
212                        .on_timeout(deadline, || Err(()))
213                        .await;
214                    match result {
215                        Ok(()) => Some((key, watcher_proxy)),
216                        Err(()) => None,
217                    }
218                }
219            })
220        };
221
222        // Run all of the futures, collecting the successful watcher proxies into a vector
223        let new_watchers = futures::future::join_all(watcher_futures)
224            .await
225            .into_iter()
226            .filter_map(|watcher_opt| watcher_opt) // Unwrap the Options while filtering out None
227            .collect();
228
229        // Repopulate the successful watcher proxies back into the `reboot_watchers` RefCell
230        *self.reboot_watchers.lock().await = new_watchers;
231
232        fuchsia_trace::duration_end!(
233            c"shutdown-shim",
234            c"ShutdownWatcher::notify_reboot_watchers",
235            "options" => format!("{:?}", options).as_str()
236        );
237    }
238
239    async fn notify_shutdown_watchers(
240        &self,
241        options: ShutdownOptionsWrapper,
242        timeout: zx::MonotonicDuration,
243    ) {
244        // Instead of waiting for https://fxbug.dev/42120903, use begin and end pair of macros.
245        fuchsia_trace::duration_begin!(
246            c"shutdown-shim",
247            c"ShutdownWatcher::notify_shutdown_watchers",
248            "options" => format!("{:?}", options).as_str()
249        );
250
251        // Create a future for each watcher that calls the watcher's `on_shutdown` method and
252        // returns the watcher proxy if the response was received within the timeout, or None
253        // otherwise. We take this approach so that watchers that timed out have their channel
254        // dropped (https://fxbug.dev/42131208).
255        let watcher_futures = {
256            // Take the current watchers out of `shutdown_watchers` because we'll be modifying the
257            // vector.
258            let watchers = self.shutdown_watchers.lock().await;
259            println!("[shutdown-shim] notifying {:?} watchers of shutdown", watchers.len());
260            watchers.clone().into_iter().map(|(key, watcher_proxy)| {
261                let options = options.clone();
262                async move {
263                    let deadline = timeout.after_now();
264                    let result = watcher_proxy
265                        .on_shutdown(&options.into())
266                        .map_err(|_| ())
267                        .on_timeout(deadline, || Err(()))
268                        .await;
269
270                    match result {
271                        Ok(()) => Some((key, watcher_proxy)),
272                        Err(()) => None,
273                    }
274                }
275            })
276        };
277
278        // Run all of the futures, collecting the successful watcher proxies into a vector
279        let new_watchers = futures::future::join_all(watcher_futures)
280            .await
281            .into_iter()
282            .filter_map(|watcher_opt| watcher_opt) // Unwrap the Options while filtering out None
283            .collect();
284
285        // Repopulate the successful watcher proxies back into `shutdown_watchers`
286        *self.shutdown_watchers.lock().await = new_watchers;
287
288        fuchsia_trace::duration_end!(
289            c"shutdown-shim",
290            c"ShutdownWatcher::notify_shutdown_watchers",
291            "options" => format!("{:?}", options).as_str()
292        );
293    }
294}
295
296struct InspectData {
297    reboot_watcher_current_connections: inspect::UintProperty,
298    reboot_watcher_total_connections: inspect::UintProperty,
299}
300
301// TODO(https://fxbug.dev/414413282): rename nodes to shutdown_watcher_*. This will require also
302// updating the inspect selectors. For now, both reboot and shutdown watchers will increment these
303// counters.
304impl InspectData {
305    fn new(parent: &inspect::Node, name: String) -> Self {
306        // Create a local root node and properties
307        let root = parent.create_child(name);
308        let reboot_watcher_current_connections =
309            root.create_uint("reboot_watcher_current_connections", 0);
310        let reboot_watcher_total_connections =
311            root.create_uint("reboot_watcher_total_connections", 0);
312
313        // Pass ownership of the new node to the parent node, otherwise it'll be dropped
314        parent.record(root);
315
316        InspectData { reboot_watcher_current_connections, reboot_watcher_total_connections }
317    }
318
319    fn add_reboot_watcher(&self) {
320        self.reboot_watcher_current_connections.add(1);
321        self.reboot_watcher_total_connections.add(1);
322    }
323
324    fn remove_reboot_watcher(&self) {
325        self.reboot_watcher_current_connections.subtract(1);
326    }
327}
328
329#[cfg(test)]
330mod tests {
331    use super::*;
332    use assert_matches::assert_matches;
333    use diagnostics_assertions::assert_data_tree;
334    use fidl::endpoints::{ControlHandle, RequestStream};
335    use fidl_fuchsia_hardware_power_statecontrol::{ShutdownAction, ShutdownReason};
336
337    // In this test, convert float to integer for simpilification
338    fn seconds(seconds: f64) -> zx::MonotonicDuration {
339        zx::MonotonicDuration::from_seconds(seconds as i64)
340    }
341
342    /// Tests for the presence and correctness of inspect data
343    #[fuchsia::test]
344    async fn test_inspect_data() {
345        let inspector = inspect::Inspector::default();
346        let registrar = ShutdownWatcher::new_with_inspector(&inspector);
347
348        assert_data_tree!(
349            inspector,
350            root: {
351                ShutdownWatcher: {
352                    reboot_watcher_current_connections: 0u64,
353                    reboot_watcher_total_connections: 0u64
354                }
355            }
356        );
357
358        let (watcher_proxy, s) = fidl::endpoints::create_proxy::<fpower::RebootWatcherMarker>();
359        registrar.add_reboot_watcher(watcher_proxy.clone()).await;
360
361        assert_data_tree!(
362            inspector,
363            root: {
364                ShutdownWatcher: {
365                    reboot_watcher_current_connections: 1u64,
366                    reboot_watcher_total_connections: 1u64
367                }
368            }
369        );
370
371        drop(s);
372        watcher_proxy.on_closed().await.expect("closed");
373
374        assert_data_tree!(
375            @retry 10,
376            inspector,
377            root: {
378                ShutdownWatcher: {
379                    reboot_watcher_current_connections: 0u64,
380                    reboot_watcher_total_connections: 1u64
381                }
382            }
383        );
384
385        let (watcher_proxy, s) = fidl::endpoints::create_proxy::<fpower::RebootWatcherMarker>();
386        registrar.add_reboot_watcher(watcher_proxy.clone()).await;
387
388        assert_data_tree!(
389            @retry 10,
390            inspector,
391            root: {
392                ShutdownWatcher: {
393                    reboot_watcher_current_connections: 1u64,
394                    reboot_watcher_total_connections: 2u64
395                }
396            }
397        );
398        drop(s);
399        watcher_proxy.on_closed().await.expect("closed");
400
401        assert_data_tree!(
402            @retry 10,
403            inspector,
404            root: {
405                ShutdownWatcher: {
406                    reboot_watcher_current_connections: 0u64,
407                    reboot_watcher_total_connections: 2u64
408                }
409            }
410        );
411
412        let (watcher_proxy, s) = fidl::endpoints::create_proxy::<fpower::ShutdownWatcherMarker>();
413        registrar.add_shutdown_watcher(watcher_proxy.clone()).await;
414
415        assert_data_tree!(
416            @retry 10,
417            inspector,
418            root: {
419                ShutdownWatcher: {
420                    reboot_watcher_current_connections: 1u64,
421                    reboot_watcher_total_connections: 3u64
422                }
423            }
424        );
425        drop(s);
426        watcher_proxy.on_closed().await.expect("closed");
427
428        assert_data_tree!(
429            @retry 10,
430            inspector,
431            root: {
432                ShutdownWatcher: {
433                    reboot_watcher_current_connections: 0u64,
434                    reboot_watcher_total_connections: 3u64
435                }
436            }
437        );
438    }
439
440    /// Tests that a client can successfully register a reboot watcher, and the registered watcher
441    /// receives the expected reboot notification.
442    #[fasync::run_singlethreaded(test)]
443    async fn test_add_reboot_watcher() {
444        let registrar = ShutdownWatcher::new();
445
446        // Create the proxy/stream to register the watcher
447        let (register_proxy, register_stream) = fidl::endpoints::create_proxy_and_stream::<
448            fpower::RebootMethodsWatcherRegisterMarker,
449        >();
450
451        // Start the RebootMethodsWatcherRegister server that will handle Register calls from
452        // register_proxy
453        let registrar_clone = registrar.clone();
454        fasync::Task::local(async move {
455            registrar_clone.handle_reboot_register_request(register_stream).await;
456        })
457        .detach();
458
459        // Create the watcher proxy/stream to receive reboot notifications
460        let (watcher_client, mut watcher_stream) =
461            fidl::endpoints::create_request_stream::<fpower::RebootWatcherMarker>();
462
463        // Call the Register API, passing in the watcher_client end
464        assert_matches!(register_proxy.register_watcher(watcher_client).await, Ok(()));
465        // Signal the watchers
466        registrar
467            .notify_reboot_watchers(
468                ShutdownOptionsWrapper::new(ShutdownAction::Reboot, ShutdownReason::UserRequest),
469                seconds(0.0),
470            )
471            .await;
472
473        // Verify the watcher_stream gets the correct reboot notification
474        let reasons = assert_matches!(
475            watcher_stream.try_next().await.unwrap().unwrap(),
476            fpower::RebootWatcherRequest::OnReboot {
477                options: fpower::RebootOptions{reasons: Some(reasons), ..},
478                ..
479            } => reasons
480        );
481        assert_eq!(&reasons[..], [fpower::RebootReason2::UserRequest]);
482    }
483
484    /// Tests that a client can successfully register a shutdown watcher, and the registered watcher
485    /// receives the expected shutdown notification.
486    #[fasync::run_singlethreaded(test)]
487    async fn test_add_shutdown_watcher() {
488        let registrar = ShutdownWatcher::new();
489
490        // Create the proxy/stream to register the watcher
491        let (register_proxy, register_stream) =
492            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherRegisterMarker>();
493
494        // Start the ShutdownWatcherRegister server that will handle Register calls from
495        // register_proxy.
496        let registrar_clone = registrar.clone();
497        fasync::Task::local(async move {
498            registrar_clone.handle_shutdown_register_request(register_stream).await;
499        })
500        .detach();
501
502        // Create the watcher proxy/stream to receive shutdown notifications
503        let (watcher_client, mut watcher_stream) =
504            fidl::endpoints::create_request_stream::<fpower::ShutdownWatcherMarker>();
505
506        // Call the Register API, passing in the watcher_client end
507        assert_matches!(register_proxy.register_watcher(watcher_client).await, Ok(()));
508
509        // Signal the watchers
510        registrar
511            .notify_shutdown_watchers(
512                ShutdownOptionsWrapper::new(ShutdownAction::Poweroff, ShutdownReason::UserRequest),
513                seconds(0.0),
514            )
515            .await;
516
517        // Verify the watcher_stream gets the correct shutdown notification
518        let (action, reasons) = assert_matches!(
519            watcher_stream.try_next().await.unwrap().unwrap(),
520            fpower::ShutdownWatcherRequest::OnShutdown {
521                options: fpower::ShutdownOptions{action: Some(action), reasons: Some(reasons), ..},
522                ..
523            } => (action, reasons)
524        );
525        assert_eq!(action, ShutdownAction::Poweroff);
526        assert_eq!(&reasons[..], [ShutdownReason::UserRequest]);
527    }
528
529    /// Tests that a reboot watcher is delivered the correct reboot reason
530    #[fasync::run_singlethreaded(test)]
531    async fn test_reboot_watcher_reason() {
532        let registrar = ShutdownWatcher::new();
533        let (watcher_proxy, mut watcher_stream) =
534            fidl::endpoints::create_proxy_and_stream::<fpower::RebootWatcherMarker>();
535        registrar.add_reboot_watcher(watcher_proxy).await;
536        registrar
537            .notify_reboot_watchers(
538                ShutdownOptionsWrapper::new(
539                    ShutdownAction::Reboot,
540                    ShutdownReason::HighTemperature,
541                ),
542                seconds(0.0),
543            )
544            .await;
545
546        let reasons = match watcher_stream.try_next().await {
547            Ok(Some(fpower::RebootWatcherRequest::OnReboot {
548                options: fpower::RebootOptions { reasons: Some(reasons), .. },
549                ..
550            })) => reasons,
551            e => panic!("Unexpected watcher_stream result: {:?}", e),
552        };
553
554        assert_eq!(&reasons[..], [fpower::RebootReason2::HighTemperature]);
555    }
556
557    /// Tests that a shutdown watcher is delivered the correct shutdown reason. This test does not
558    /// register via the ShutdownWatcherRegister protocol.
559    #[fasync::run_singlethreaded(test)]
560    async fn test_shutdown_watcher_reason() {
561        let registrar = ShutdownWatcher::new();
562        let (watcher_proxy, mut watcher_stream) =
563            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherMarker>();
564        registrar.add_shutdown_watcher(watcher_proxy).await;
565        registrar
566            .notify_shutdown_watchers(
567                ShutdownOptionsWrapper::new(
568                    ShutdownAction::Poweroff,
569                    ShutdownReason::HighTemperature,
570                ),
571                seconds(0.0),
572            )
573            .await;
574
575        let (action, reasons) = assert_matches!(
576            watcher_stream.try_next().await.unwrap().unwrap(),
577            fpower::ShutdownWatcherRequest::OnShutdown {
578                options: fpower::ShutdownOptions{action: Some(action), reasons: Some(reasons), ..},
579                ..
580            } => (action, reasons)
581        );
582        assert_eq!(action, ShutdownAction::Poweroff);
583        assert_eq!(&reasons[..], [ShutdownReason::HighTemperature]);
584    }
585
586    /// Tests that if there are multiple registered reboot watchers, each one will receive the
587    /// expected reboot notification.
588    #[fasync::run_singlethreaded(test)]
589    async fn test_multiple_reboot_watchers() {
590        let registrar = ShutdownWatcher::new();
591
592        // Create three separate reboot watchers
593        let (watcher_proxy1, mut watcher_stream1) =
594            fidl::endpoints::create_proxy_and_stream::<fpower::RebootWatcherMarker>();
595        registrar.add_reboot_watcher(watcher_proxy1).await;
596
597        let (watcher_proxy2, mut watcher_stream2) =
598            fidl::endpoints::create_proxy_and_stream::<fpower::RebootWatcherMarker>();
599        registrar.add_reboot_watcher(watcher_proxy2).await;
600
601        let (watcher_proxy3, mut watcher_stream3) =
602            fidl::endpoints::create_proxy_and_stream::<fpower::RebootWatcherMarker>();
603        registrar.add_reboot_watcher(watcher_proxy3).await;
604
605        // Close the channel of the first watcher to verify the registrar still correctly notifies the
606        // second and third watchers
607        watcher_stream1.control_handle().shutdown();
608
609        registrar
610            .notify_reboot_watchers(
611                ShutdownOptionsWrapper::new(
612                    ShutdownAction::Reboot,
613                    ShutdownReason::HighTemperature,
614                ),
615                seconds(0.0),
616            )
617            .await;
618
619        // The first watcher should get None because its channel was closed
620        match watcher_stream1.try_next().await {
621            Ok(None) => {}
622            e => panic!("Unexpected watcher_stream1 result: {:?}", e),
623        };
624
625        // Verify the watcher received the correct OnReboot request
626        match watcher_stream2.try_next().await {
627            Ok(Some(fpower::RebootWatcherRequest::OnReboot {
628                options: fpower::RebootOptions { reasons: Some(reasons), .. },
629                ..
630            })) => {
631                assert_eq!(&reasons[..], [fpower::RebootReason2::HighTemperature])
632            }
633            e => panic!("Unexpected watcher_stream2 result: {:?}", e),
634        };
635
636        // Verify the watcher received the correct OnReboot request
637        match watcher_stream3.try_next().await {
638            Ok(Some(fpower::RebootWatcherRequest::OnReboot {
639                options: fpower::RebootOptions { reasons: Some(reasons), .. },
640                ..
641            })) => assert_eq!(&reasons[..], [fpower::RebootReason2::HighTemperature]),
642            e => panic!("Unexpected watcher_stream3 result: {:?}", e),
643        };
644    }
645
646    /// Tests that if there are multiple registered shutdown watchers, each one will receive the
647    /// expected shutdown notification.
648    #[fasync::run_singlethreaded(test)]
649    async fn test_multiple_shutdown_watchers() {
650        let registrar = ShutdownWatcher::new();
651
652        // Create three separate shutdown watchers
653        let (watcher_proxy1, mut watcher_stream1) =
654            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherMarker>();
655        registrar.add_shutdown_watcher(watcher_proxy1).await;
656
657        let (watcher_proxy2, mut watcher_stream2) =
658            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherMarker>();
659        registrar.add_shutdown_watcher(watcher_proxy2).await;
660
661        let (watcher_proxy3, mut watcher_stream3) =
662            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherMarker>();
663        registrar.add_shutdown_watcher(watcher_proxy3).await;
664
665        // Close the channel of the first watcher to verify the registrar still correctly notifies
666        // the second and third watchers.
667        watcher_stream1.control_handle().shutdown();
668
669        registrar
670            .notify_shutdown_watchers(
671                ShutdownOptionsWrapper::new(
672                    ShutdownAction::Reboot,
673                    ShutdownReason::HighTemperature,
674                ),
675                seconds(0.0),
676            )
677            .await;
678
679        // The first watcher should get None because its channel was closed
680        match watcher_stream1.try_next().await {
681            Ok(None) => {}
682            e => panic!("Unexpected watcher_stream1 result: {:?}", e),
683        };
684
685        // Verify the watcher received the correct OnShutdown request
686        let (action, reasons) = assert_matches!(
687            watcher_stream2.try_next().await.unwrap().unwrap(),
688            fpower::ShutdownWatcherRequest::OnShutdown {
689                options: fpower::ShutdownOptions{action: Some(action), reasons: Some(reasons), ..},
690                ..
691            } => (action, reasons)
692        );
693        assert_eq!(action, ShutdownAction::Reboot);
694        assert_eq!(&reasons[..], [ShutdownReason::HighTemperature]);
695
696        // Verify the watcher received the correct OnShutdown request
697        let (action, reasons) = assert_matches!(
698            watcher_stream3.try_next().await.unwrap().unwrap(),
699            fpower::ShutdownWatcherRequest::OnShutdown {
700                options: fpower::ShutdownOptions{action: Some(action), reasons: Some(reasons), ..},
701                ..
702            } => (action, reasons)
703        );
704        assert_eq!(action, ShutdownAction::Reboot);
705        assert_eq!(&reasons[..], [ShutdownReason::HighTemperature]);
706    }
707
708    /// Tests that the deprecated reboot watchers only receive notifications for
709    /// ShutdownAction::Reboot.
710    #[fasync::run_singlethreaded(test)]
711    async fn reboot_watchers_only_notified_of_reboots() {
712        let registrar = ShutdownWatcher::new();
713        let (watcher_proxy, mut watcher_stream) =
714            fidl::endpoints::create_proxy_and_stream::<fpower::RebootWatcherMarker>();
715        registrar.add_reboot_watcher(watcher_proxy).await;
716
717        registrar
718            .handle_system_shutdown_message(ShutdownOptionsWrapper::new(
719                ShutdownAction::Poweroff,
720                ShutdownReason::HighTemperature,
721            ))
722            .await;
723        assert!(futures::poll!(watcher_stream.try_next()).is_pending());
724
725        registrar
726            .handle_system_shutdown_message(ShutdownOptionsWrapper::new(
727                ShutdownAction::RebootToBootloader,
728                ShutdownReason::HighTemperature,
729            ))
730            .await;
731        assert!(futures::poll!(watcher_stream.try_next()).is_pending());
732
733        registrar
734            .handle_system_shutdown_message(ShutdownOptionsWrapper::new(
735                ShutdownAction::RebootToRecovery,
736                ShutdownReason::HighTemperature,
737            ))
738            .await;
739        assert!(futures::poll!(watcher_stream.try_next()).is_pending());
740
741        registrar
742            .handle_system_shutdown_message(ShutdownOptionsWrapper::new(
743                ShutdownAction::Reboot,
744                ShutdownReason::HighTemperature,
745            ))
746            .await;
747        match watcher_stream.try_next().await {
748            Ok(Some(fpower::RebootWatcherRequest::OnReboot {
749                options: fpower::RebootOptions { reasons: Some(reasons), .. },
750                ..
751            })) => {
752                assert_eq!(&reasons[..], [fpower::RebootReason2::HighTemperature])
753            }
754            e => panic!("Unexpected watcher_stream result: {:?}", e),
755        };
756    }
757
758    /// Tests that both shutdown and reboot watchers are notified of reboots. Only shutdown watchers
759    /// are notified of other shutdown actions.
760    #[fasync::run_singlethreaded(test)]
761    async fn shutdown_and_reboot_watchers_notified() {
762        let registrar = ShutdownWatcher::new();
763
764        let (shutdown_watcher_proxy, mut shutdown_watcher_stream) =
765            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherMarker>();
766        registrar.add_shutdown_watcher(shutdown_watcher_proxy).await;
767
768        let (reboot_watcher_proxy, mut reboot_watcher_stream) =
769            fidl::endpoints::create_proxy_and_stream::<fpower::RebootWatcherMarker>();
770        registrar.add_reboot_watcher(reboot_watcher_proxy).await;
771
772        registrar
773            .handle_system_shutdown_message(ShutdownOptionsWrapper::new(
774                ShutdownAction::Reboot,
775                ShutdownReason::HighTemperature,
776            ))
777            .await;
778
779        let (action, reasons) = assert_matches!(
780            shutdown_watcher_stream.try_next().await.unwrap().unwrap(),
781            fpower::ShutdownWatcherRequest::OnShutdown {
782                options: fpower::ShutdownOptions{action: Some(action), reasons: Some(reasons), ..},
783                ..
784            } => (action, reasons)
785        );
786        assert_eq!(action, ShutdownAction::Reboot);
787        assert_eq!(&reasons[..], [ShutdownReason::HighTemperature]);
788
789        match reboot_watcher_stream.try_next().await {
790            Ok(Some(fpower::RebootWatcherRequest::OnReboot {
791                options: fpower::RebootOptions { reasons: Some(reasons), .. },
792                ..
793            })) => {
794                assert_eq!(&reasons[..], [fpower::RebootReason2::HighTemperature])
795            }
796            e => panic!("Unexpected watcher_stream result: {:?}", e),
797        };
798    }
799
800    #[fasync::run_singlethreaded(test)]
801    async fn shutdown_watchers_notified_for_poweroff() {
802        let registrar = ShutdownWatcher::new();
803
804        let (watcher_proxy, mut watcher_stream) =
805            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherMarker>();
806        registrar.add_shutdown_watcher(watcher_proxy).await;
807
808        registrar
809            .handle_system_shutdown_message(ShutdownOptionsWrapper::new(
810                ShutdownAction::Poweroff,
811                ShutdownReason::HighTemperature,
812            ))
813            .await;
814        let (action, reasons) = assert_matches!(
815            watcher_stream.try_next().await.unwrap().unwrap(),
816            fpower::ShutdownWatcherRequest::OnShutdown {
817                options: fpower::ShutdownOptions{action: Some(action), reasons: Some(reasons), ..},
818                ..
819            } => (action, reasons)
820        );
821        assert_eq!(action, ShutdownAction::Poweroff);
822        assert_eq!(&reasons[..], [ShutdownReason::HighTemperature]);
823    }
824
825    #[fasync::run_singlethreaded(test)]
826    async fn shutdown_watchers_notified_for_reboot_to_bootloader() {
827        let registrar = ShutdownWatcher::new();
828
829        let (watcher_proxy, mut watcher_stream) =
830            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherMarker>();
831        registrar.add_shutdown_watcher(watcher_proxy).await;
832
833        registrar
834            .handle_system_shutdown_message(ShutdownOptionsWrapper::new(
835                ShutdownAction::RebootToBootloader,
836                ShutdownReason::HighTemperature,
837            ))
838            .await;
839        let (action, reasons) = assert_matches!(
840            watcher_stream.try_next().await.unwrap().unwrap(),
841            fpower::ShutdownWatcherRequest::OnShutdown {
842                options: fpower::ShutdownOptions{action: Some(action), reasons: Some(reasons), ..},
843                ..
844            } => (action, reasons)
845        );
846        assert_eq!(action, ShutdownAction::RebootToBootloader);
847        assert_eq!(&reasons[..], [ShutdownReason::HighTemperature]);
848    }
849
850    #[fasync::run_singlethreaded(test)]
851    async fn shutdown_watchers_notified_for_reboot_to_recovery() {
852        let registrar = ShutdownWatcher::new();
853
854        let (watcher_proxy, mut watcher_stream) =
855            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherMarker>();
856        registrar.add_shutdown_watcher(watcher_proxy).await;
857
858        registrar
859            .handle_system_shutdown_message(ShutdownOptionsWrapper::new(
860                ShutdownAction::RebootToRecovery,
861                ShutdownReason::HighTemperature,
862            ))
863            .await;
864        let (action, reasons) = assert_matches!(
865            watcher_stream.try_next().await.unwrap().unwrap(),
866            fpower::ShutdownWatcherRequest::OnShutdown {
867                options: fpower::ShutdownOptions{action: Some(action), reasons: Some(reasons), ..},
868                ..
869            } => (action, reasons)
870        );
871        assert_eq!(action, ShutdownAction::RebootToRecovery);
872        assert_eq!(&reasons[..], [ShutdownReason::HighTemperature]);
873    }
874
875    #[fuchsia::test]
876    fn test_reboot_watcher_response_delay() {
877        let mut exec = fasync::TestExecutor::new();
878        let registrar = ShutdownWatcher::new();
879
880        // Register the reboot watcher
881        let (watcher_proxy, mut watcher_stream) =
882            fidl::endpoints::create_proxy_and_stream::<fpower::RebootWatcherMarker>();
883        let fut = async {
884            registrar.add_reboot_watcher(watcher_proxy).await;
885            assert_eq!(registrar.reboot_watchers.lock().await.len(), 1);
886        };
887        exec.run_singlethreaded(fut);
888
889        // Set up the notify future
890        let notify_future = registrar.notify_reboot_watchers(
891            ShutdownOptionsWrapper::new(ShutdownAction::Reboot, ShutdownReason::HighTemperature),
892            seconds(1.0),
893        );
894        futures::pin_mut!(notify_future);
895
896        // Verify that the notify future can't complete on the first attempt (because the watcher
897        // will not have responded)
898        assert!(exec.run_until_stalled(&mut notify_future).is_pending());
899
900        // Ack the reboot notification, allowing the shutdown flow to continue
901        let fpower::RebootWatcherRequest::OnReboot { responder, .. } =
902            exec.run_singlethreaded(&mut watcher_stream.try_next()).unwrap().unwrap();
903        assert_matches!(responder.send(), Ok(()));
904
905        // Verify the notify future can now complete
906        assert!(exec.run_until_stalled(&mut notify_future).is_ready());
907    }
908
909    #[fuchsia::test]
910    fn test_shutdown_watcher_response_delay() {
911        let mut exec = fasync::TestExecutor::new();
912        let registrar = ShutdownWatcher::new();
913
914        // Register the shutdown watcher
915        let (watcher_proxy, mut watcher_stream) =
916            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherMarker>();
917        let fut = async {
918            registrar.add_shutdown_watcher(watcher_proxy).await;
919            assert_eq!(registrar.shutdown_watchers.lock().await.len(), 1);
920        };
921        exec.run_singlethreaded(fut);
922
923        // Set up the notify future
924        let notify_future = registrar.notify_shutdown_watchers(
925            ShutdownOptionsWrapper::new(ShutdownAction::Reboot, ShutdownReason::HighTemperature),
926            seconds(1.0),
927        );
928        futures::pin_mut!(notify_future);
929
930        // Verify that the notify future can't complete on the first attempt (because the watcher
931        // will not have responded)
932        assert!(exec.run_until_stalled(&mut notify_future).is_pending());
933
934        // Ack the shutdown notification, allowing the shutdown flow to continue
935        match exec.run_singlethreaded(&mut watcher_stream.try_next()).unwrap().unwrap() {
936            fpower::ShutdownWatcherRequest::OnShutdown { responder, .. } => {
937                assert_matches!(responder.send(), Ok(()));
938            }
939            fpower::ShutdownWatcherRequest::_UnknownMethod { .. } => unimplemented!(),
940        }
941
942        // Verify the notify future can now complete
943        assert!(exec.run_until_stalled(&mut notify_future).is_ready());
944    }
945
946    /// Tests that a reboot watcher is able to delay the shutdown but will time out after the
947    /// expected duration. The test also verifies that when a watcher times out, it is removed
948    /// from the list of registered reboot watchers.
949    #[fuchsia::test]
950    fn test_reboot_watcher_response_timeout() {
951        let mut exec = fasync::TestExecutor::new_with_fake_time();
952        let registrar = ShutdownWatcher::new();
953        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
954
955        // Register the reboot watcher
956        let (watcher_proxy, _watcher_stream) =
957            fidl::endpoints::create_proxy_and_stream::<fpower::RebootWatcherMarker>();
958        let fut = async {
959            registrar.add_reboot_watcher(watcher_proxy).await;
960            assert_eq!(registrar.reboot_watchers.lock().await.len(), 1);
961        };
962        futures::pin_mut!(fut);
963        exec.run_until_stalled(&mut fut).is_ready();
964
965        // Set up the notify future
966        let notify_future = registrar.notify_reboot_watchers(
967            ShutdownOptionsWrapper::new(ShutdownAction::Reboot, ShutdownReason::HighTemperature),
968            seconds(1.0),
969        );
970        futures::pin_mut!(notify_future);
971
972        // Verify that the notify future can't complete on the first attempt (because the watcher
973        // will not have responded)
974        assert!(exec.run_until_stalled(&mut notify_future).is_pending());
975
976        // Wake the timer that causes the watcher timeout to fire
977        assert_eq!(exec.wake_next_timer(), Some(fasync::MonotonicInstant::from_nanos(1e9 as i64)));
978
979        // Verify the notify future can now complete
980        assert!(exec.run_until_stalled(&mut notify_future).is_ready());
981
982        // Since the watcher timed out, verify it is removed from `reboot_watchers`
983        let fut = async {
984            assert_eq!(registrar.reboot_watchers.lock().await.len(), 0);
985        };
986        futures::pin_mut!(fut);
987        exec.run_until_stalled(&mut fut).is_ready();
988    }
989
990    /// Tests that a shutdown watcher is able to delay the shutdown but will time out after the
991    /// expected duration. The test also verifies that when a watcher times out, it is removed
992    /// from the list of registered shutdown watchers.
993    #[fuchsia::test]
994    fn test_shutdown_watcher_response_timeout() {
995        let mut exec = fasync::TestExecutor::new_with_fake_time();
996        let registrar = ShutdownWatcher::new();
997        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
998
999        // Register the shutdown watcher
1000        let (watcher_proxy, _watcher_stream) =
1001            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherMarker>();
1002        let fut = async {
1003            registrar.add_shutdown_watcher(watcher_proxy).await;
1004            assert_eq!(registrar.shutdown_watchers.lock().await.len(), 1);
1005        };
1006        futures::pin_mut!(fut);
1007        exec.run_until_stalled(&mut fut).is_ready();
1008
1009        // Set up the notify future
1010        let notify_future = registrar.notify_shutdown_watchers(
1011            ShutdownOptionsWrapper::new(ShutdownAction::Reboot, ShutdownReason::HighTemperature),
1012            seconds(1.0),
1013        );
1014        futures::pin_mut!(notify_future);
1015
1016        // Verify that the notify future can't complete on the first attempt (because the watcher
1017        // will not have responded)
1018        assert!(exec.run_until_stalled(&mut notify_future).is_pending());
1019
1020        // Wake the timer that causes the watcher timeout to fire
1021        assert_eq!(exec.wake_next_timer(), Some(fasync::MonotonicInstant::from_nanos(1e9 as i64)));
1022
1023        // Verify the notify future can now complete
1024        assert!(exec.run_until_stalled(&mut notify_future).is_ready());
1025
1026        // Since the watcher timed out, verify it is removed from `shutdown_watchers`
1027        let fut = async {
1028            assert_eq!(registrar.shutdown_watchers.lock().await.len(), 0);
1029        };
1030        futures::pin_mut!(fut);
1031        exec.run_until_stalled(&mut fut).is_ready();
1032    }
1033
1034    /// Tests that an unsuccessful RebootWatcher registration results in the
1035    /// RebootMethodsWatcherRegister channel being closed.
1036    #[fasync::run_singlethreaded(test)]
1037    async fn test_watcher_register_fail() {
1038        let registrar = ShutdownWatcher::new();
1039
1040        // Create the registration proxy/stream
1041        let (register_proxy, register_stream) = fidl::endpoints::create_proxy_and_stream::<
1042            fpower::RebootMethodsWatcherRegisterMarker,
1043        >();
1044
1045        // Start the RebootMethodsWatcherRegister server that will handle Register requests from
1046        // `register_proxy`
1047        fasync::Task::local(async move {
1048            registrar.handle_reboot_register_request(register_stream).await;
1049        })
1050        .detach();
1051
1052        // Send an invalid request to the server to force a failure
1053        assert_matches!(register_proxy.as_channel().write(&[], &mut []), Ok(()));
1054
1055        // Verify the RebootMethodsWatcherRegister channel is closed
1056        assert_matches!(register_proxy.on_closed().await, Ok(zx::Signals::CHANNEL_PEER_CLOSED));
1057    }
1058
1059    /// Tests that an unsuccessful ShutdownWatcher registration results in the
1060    /// ShutdownWatcherRegister channel being closed.
1061    #[fasync::run_singlethreaded(test)]
1062    async fn test_shutdown_watcher_register_fail() {
1063        let registrar = ShutdownWatcher::new();
1064
1065        // Create the registration proxy/stream
1066        let (register_proxy, register_stream) =
1067            fidl::endpoints::create_proxy_and_stream::<fpower::ShutdownWatcherRegisterMarker>();
1068
1069        // Start the ShutdownWatcherRegister server that will handle Register requests from
1070        // `register_proxy`
1071        fasync::Task::local(async move {
1072            registrar.handle_shutdown_register_request(register_stream).await;
1073        })
1074        .detach();
1075
1076        // Send an invalid request to the server to force a failure
1077        assert_matches!(register_proxy.as_channel().write(&[], &mut []), Ok(()));
1078
1079        // Verify the ShutdownWatcherRegister channel is closed
1080        assert_matches!(register_proxy.on_closed().await, Ok(zx::Signals::CHANNEL_PEER_CLOSED));
1081    }
1082}