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