Skip to main content

driver_manager_shutdown/
shutdown_manager.rs

1// Copyright 2026 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::node_remover::NodeRemover;
6use async_trait::async_trait;
7use fidl_fuchsia_diagnostics as fdiagnostics;
8use fidl_fuchsia_kernel as fkernel;
9use fidl_fuchsia_process_lifecycle as flifecycle;
10use fidl_fuchsia_system_state as fsystem_state;
11use fuchsia_async as fasync;
12use fuchsia_component::client::{connect_to_protocol, connect_to_protocol_sync};
13use fuchsia_component::server::{FidlService, ServiceFs, ServiceObjLocal};
14use futures::channel::oneshot;
15use futures::prelude::*;
16use log::{error, info, warn};
17use std::cell::RefCell;
18use std::rc::Rc;
19use zx::sys::{
20    ZX_SYSTEM_POWERCTL_ACK_KERNEL_INITIATED_REBOOT, ZX_SYSTEM_POWERCTL_REBOOT,
21    ZX_SYSTEM_POWERCTL_REBOOT_BOOTLOADER, ZX_SYSTEM_POWERCTL_REBOOT_RECOVERY,
22    ZX_SYSTEM_POWERCTL_SHUTDOWN,
23};
24
25/// System power operations.
26#[async_trait(?Send)]
27pub trait SystemPower {
28    /// Returns the current system power state.
29    async fn get_system_power_state(&self) -> fsystem_state::SystemPowerState;
30
31    /// Executes a system power control command (e.g. reboot, shutdown).
32    fn system_powerctl(&self, cmd: u32) -> Result<(), zx::Status>;
33
34    /// Returns true if the necessary power resource is available.
35    fn has_resources(&self) -> bool;
36
37    /// Exits the process.
38    fn exit(&self, status: i32);
39}
40
41/// Real implementation of [`SystemPower`] that makes actual system calls.
42pub struct RealSystemPower {
43    power_resource: Option<zx::Resource>,
44}
45
46impl RealSystemPower {
47    pub fn new(power_resource: Option<zx::Resource>) -> Self {
48        Self { power_resource }
49    }
50}
51
52#[async_trait(?Send)]
53impl SystemPower for RealSystemPower {
54    async fn get_system_power_state(&self) -> fsystem_state::SystemPowerState {
55        get_system_power_state().await
56    }
57
58    fn system_powerctl(&self, cmd: u32) -> Result<(), zx::Status> {
59        let Some(power_resource) = &self.power_resource else {
60            return Err(zx::Status::INVALID_ARGS);
61        };
62        zx::Status::ok(unsafe { zx::sys::zx_system_powerctl(power_resource.raw_handle(), cmd) })
63    }
64
65    fn has_resources(&self) -> bool {
66        self.power_resource.is_some()
67    }
68
69    fn exit(&self, status: i32) {
70        std::process::exit(status);
71    }
72}
73
74#[async_trait(?Send)]
75impl<T: SystemPower + ?Sized> SystemPower for Rc<T> {
76    async fn get_system_power_state(&self) -> fsystem_state::SystemPowerState {
77        (**self).get_system_power_state().await
78    }
79    fn system_powerctl(&self, cmd: u32) -> Result<(), zx::Status> {
80        (**self).system_powerctl(cmd)
81    }
82    fn has_resources(&self) -> bool {
83        (**self).has_resources()
84    }
85    fn exit(&self, status: i32) {
86        (**self).exit(status)
87    }
88}
89
90#[derive(Copy, Clone, PartialEq, Debug)]
91enum State {
92    Running,
93    PackageStopping,
94    PackageStopped,
95    BootStopping,
96    Stopped,
97}
98
99type ShutdownSender = oneshot::Sender<Result<(), zx::Status>>;
100
101struct LifecycleServer {
102    on_stop: RefCell<Option<oneshot::Sender<ShutdownSender>>>,
103}
104
105impl LifecycleServer {
106    fn new(on_stop: oneshot::Sender<ShutdownSender>) -> Self {
107        Self { on_stop: RefCell::new(Some(on_stop)) }
108    }
109
110    async fn serve(
111        self: Rc<Self>,
112        mut stream: flifecycle::LifecycleRequestStream,
113    ) -> Result<(), fidl::Error> {
114        if let Some(request) = stream.try_next().await? {
115            match request {
116                flifecycle::LifecycleRequest::Stop { control_handle } => {
117                    let (tx, rx) = oneshot::channel();
118                    let on_stop = self.on_stop.borrow_mut().take();
119                    if let Some(on_stop) = on_stop {
120                        let _ = on_stop.send(tx);
121                        if let Ok(result) = rx.await {
122                            control_handle.shutdown_with_epitaph(result);
123                        } else {
124                            control_handle.shutdown_with_epitaph(zx::Status::INTERNAL);
125                        }
126                    }
127                }
128            }
129        }
130        Ok(())
131    }
132}
133
134struct ShutdownManagerState {
135    state: State,
136    received_boot_shutdown_signal: bool,
137    package_shutdown_complete_callbacks: Vec<ShutdownSender>,
138    boot_shutdown_complete_callbacks: Vec<ShutdownSender>,
139    lifecycle_stop: bool,
140}
141
142pub struct ShutdownManager<P: SystemPower + 'static = RealSystemPower> {
143    node_remover: Rc<dyn NodeRemover>,
144    log_flush: Option<fdiagnostics::LogFlusherProxy>,
145    internal_state: RefCell<ShutdownManagerState>,
146    scope: fasync::Scope,
147    system_power: P,
148}
149
150fn get_power_resource() -> Result<zx::Resource, anyhow::Error> {
151    let client = connect_to_protocol_sync::<fkernel::PowerResourceMarker>()?;
152    let resource = client.get(zx::MonotonicInstant::INFINITE)?;
153    Ok(resource)
154}
155
156async fn get_system_power_state() -> fsystem_state::SystemPowerState {
157    let client = match connect_to_protocol::<fsystem_state::SystemStateTransitionMarker>() {
158        Ok(c) => c,
159        Err(e) => {
160            error!("Failed to connect to StateStateTransition: {}, falling back to default", e);
161            return fsystem_state::SystemPowerState::Reboot;
162        }
163    };
164
165    match client.get_termination_system_state().await {
166        Ok(state) => state,
167        Err(e) => {
168            error!("Failed to get termination system state: {}, falling back to default", e);
169            fsystem_state::SystemPowerState::Reboot
170        }
171    }
172}
173
174impl ShutdownManager<RealSystemPower> {
175    /// Creates a [`ShutdownManager`] from the FIDL services available in the incoming namespace.
176    pub fn from_incoming(node_remover: Rc<dyn NodeRemover>) -> Rc<Self> {
177        let power_resource = get_power_resource()
178            .inspect_err(|e| {
179                info!("Failed to get power resource, assuming test environment: {}", e)
180            })
181            .ok();
182        let log_flush = connect_to_protocol::<fdiagnostics::LogFlusherMarker>()
183            .inspect_err(|e| error!("Failed to connect to LogFlusher: {}", e))
184            .ok();
185        Self::new(node_remover, RealSystemPower::new(power_resource), log_flush)
186    }
187}
188
189impl<P: SystemPower + 'static> ShutdownManager<P> {
190    pub fn new(
191        node_remover: Rc<dyn NodeRemover>,
192        system_power: P,
193        log_flush: Option<fdiagnostics::LogFlusherProxy>,
194    ) -> Rc<Self> {
195        let shutdown_manager = Rc::new(Self {
196            node_remover: node_remover.clone(),
197            internal_state: RefCell::new(ShutdownManagerState {
198                state: State::Running,
199                received_boot_shutdown_signal: false,
200                package_shutdown_complete_callbacks: Vec::new(),
201                boot_shutdown_complete_callbacks: Vec::new(),
202                lifecycle_stop: false,
203            }),
204            log_flush,
205            scope: fasync::Scope::new_with_name("shutdown_manager"),
206            system_power,
207        });
208
209        let weak_manager = Rc::downgrade(&shutdown_manager);
210        node_remover.set_on_removal_timeout_callback(Box::new(move || {
211            if let Some(strong_manager) = weak_manager.upgrade() {
212                info!(
213                    "Timed out waiting for nodes to be removed, issuing syscall to reboot/shutdown"
214                );
215                let strong_manager_clone = strong_manager.clone();
216                strong_manager.scope.spawn_local(async move {
217                    strong_manager_clone.execute_shutdown_strategy().await;
218                });
219            }
220        }));
221
222        shutdown_manager
223    }
224
225    pub fn publish<'a>(self: &Rc<Self>, fs: &mut ServiceFs<ServiceObjLocal<'a, ()>>) {
226        let self_clone = self.clone();
227        let (tx, rx) = oneshot::channel::<ShutdownSender>();
228        self.scope.spawn_local(async move {
229            if let Ok(sender) = rx.await {
230                let status = self_clone.signal_package_shutdown().await;
231                let _ = sender.send(status);
232            }
233        });
234        let devfs_with_pkg_lifecycle = Rc::new(LifecycleServer::new(tx));
235
236        let scope = self.scope.as_handle().clone();
237        fs.dir("svc").add_service_at(
238            "fuchsia.device.fs.with.pkg.lifecycle.Lifecycle",
239            FidlService::from(move |stream: flifecycle::LifecycleRequestStream| {
240                let devfs_with_pkg_lifecycle = devfs_with_pkg_lifecycle.clone();
241                scope.spawn_local(async move {
242                    devfs_with_pkg_lifecycle.serve(stream).await.unwrap_or_else(|e| {
243                        error!("Failed to serve devfs with pkg lifecycle: {}", e)
244                    });
245                });
246            }),
247        );
248
249        let self_clone = self.clone();
250        let (tx, rx) = oneshot::channel::<ShutdownSender>();
251        self.scope.spawn_local(async move {
252            if let Ok(sender) = rx.await {
253                let status = self_clone.signal_boot_shutdown().await;
254                let _ = sender.send(status);
255            }
256        });
257        let devfs_lifecycle = Rc::new(LifecycleServer::new(tx));
258
259        let scope = self.scope.as_handle().clone();
260        fs.dir("svc").add_service_at(
261            "fuchsia.device.fs.lifecycle.Lifecycle",
262            FidlService::from(move |stream: flifecycle::LifecycleRequestStream| {
263                let devfs_lifecycle = devfs_lifecycle.clone();
264                scope.spawn_local(async move {
265                    devfs_lifecycle
266                        .serve(stream)
267                        .await
268                        .unwrap_or_else(|e| error!("Failed to serve devfs lifecycle: {}", e));
269                });
270            }),
271        );
272
273        // Bind to process lifecycle
274        let self_clone = self.clone();
275        let (tx, rx) = oneshot::channel::<ShutdownSender>();
276        self.scope.spawn_local(async move {
277            if let Ok(sender) = rx.await {
278                self_clone.internal_state.borrow_mut().lifecycle_stop = true;
279                let status = self_clone.signal_boot_shutdown().await;
280                let _ = sender.send(status);
281            }
282        });
283        let lifecycle_server = Rc::new(LifecycleServer::new(tx));
284
285        if let Some(handle) =
286            fuchsia_runtime::take_startup_handle(fuchsia_runtime::HandleType::Lifecycle.into())
287        {
288            let channel = zx::Channel::from(handle);
289            let server_end =
290                fidl::endpoints::ServerEnd::<flifecycle::LifecycleMarker>::new(channel);
291            let stream = server_end.into_stream();
292
293            let self_clone = self.clone();
294            self.scope.spawn_local(async move {
295                if let Err(e) = lifecycle_server.serve(stream).await {
296                    error!("Lifecycle connection got unbound: {}", e);
297                    // Per C++ implementation, we should shut down if this happens.
298                    let _ = self_clone.signal_boot_shutdown().await;
299                }
300            });
301        } else {
302            info!(concat!(
303                "No valid handle found for lifecycle events, assuming test environment ",
304                "and continuing"
305            ));
306        }
307    }
308
309    async fn on_package_shutdown_complete(&self) {
310        info!("Package shutdown complete");
311        let received_boot_shutdown_signal = {
312            let mut internal_state = self.internal_state.borrow_mut();
313            assert_eq!(internal_state.state, State::PackageStopping);
314            internal_state.state = State::PackageStopped;
315
316            for sender in internal_state.package_shutdown_complete_callbacks.drain(..) {
317                let _ = sender.send(Ok(()));
318            }
319
320            if internal_state.received_boot_shutdown_signal {
321                internal_state.state = State::BootStopping;
322                true
323            } else {
324                false
325            }
326        };
327
328        if received_boot_shutdown_signal {
329            self.node_remover.shutdown_all_drivers().await;
330            self.on_boot_shutdown_complete().await;
331        }
332    }
333
334    async fn on_boot_shutdown_complete(&self) {
335        {
336            let mut internal_state = self.internal_state.borrow_mut();
337            assert_eq!(internal_state.state, State::BootStopping);
338            internal_state.state = State::Stopped;
339        }
340        self.execute_shutdown_strategy().await;
341        let mut internal_state = self.internal_state.borrow_mut();
342        for sender in internal_state.boot_shutdown_complete_callbacks.drain(..) {
343            let _ = sender.send(Ok(()));
344        }
345    }
346
347    async fn signal_package_shutdown(&self) -> Result<(), zx::Status> {
348        // TODO: switch logs to debuglog
349
350        // We explicitly drop this before going into the await.
351        #![allow(clippy::await_holding_refcell_ref)]
352        let mut internal_state = self.internal_state.borrow_mut();
353
354        match internal_state.state {
355            State::Running | State::PackageStopping => {
356                let (tx, rx) = oneshot::channel();
357                internal_state.package_shutdown_complete_callbacks.push(tx);
358                if internal_state.state == State::Running {
359                    internal_state.state = State::PackageStopping;
360                    drop(internal_state);
361                    self.node_remover.shutdown_pkg_drivers().await;
362                    self.on_package_shutdown_complete().await;
363                } else {
364                    drop(internal_state);
365                }
366                rx.await.unwrap_or(Err(zx::Status::INTERNAL))
367            }
368            _ => Ok(()),
369        }
370    }
371
372    async fn signal_boot_shutdown(&self) -> Result<(), zx::Status> {
373        // We explicitly drop this before going into the await.
374        #![allow(clippy::await_holding_refcell_ref)]
375        let mut internal_state = self.internal_state.borrow_mut();
376
377        if internal_state.state == State::Stopped {
378            return Ok(());
379        }
380
381        let (tx, rx) = oneshot::channel();
382        internal_state.boot_shutdown_complete_callbacks.push(tx);
383
384        internal_state.received_boot_shutdown_signal = true;
385        let state = internal_state.state;
386        match state {
387            State::Running | State::PackageStopped => {
388                internal_state.state = State::BootStopping;
389                drop(internal_state);
390
391                self.node_remover.shutdown_all_drivers().await;
392                self.on_boot_shutdown_complete().await;
393            }
394            State::BootStopping => {
395                error!("SignalBootShutdown() called during shutdown.");
396            }
397            _ => {}
398        }
399        rx.await.unwrap_or(Err(zx::Status::INTERNAL))
400    }
401
402    async fn execute_shutdown_strategy(&self) {
403        if !self.system_power.has_resources() {
404            warn!("Invalid Power resource. Assuming test.");
405            let internal_state = self.internal_state.borrow();
406            if internal_state.lifecycle_stop {
407                info!("Exiting driver manager gracefully");
408                self.system_power.exit(0);
409            }
410            return;
411        }
412
413        let shutdown_system_state = self.system_power.get_system_power_state().await;
414        info!("Suspend fallback with flags {:?}", shutdown_system_state);
415        let what = "zx_system_powerctl";
416
417        info!("Flushing logs.");
418        if let Some(log_flush) = &self.log_flush
419            && let Err(e) = log_flush.wait_until_flushed().await
420        {
421            warn!("Failed to flush logs: {}", e);
422        }
423
424        info!("Executing powerctl.");
425        let status = match shutdown_system_state {
426            fsystem_state::SystemPowerState::Reboot => {
427                self.system_power.system_powerctl(ZX_SYSTEM_POWERCTL_REBOOT)
428            }
429            fsystem_state::SystemPowerState::RebootBootloader => {
430                self.system_power.system_powerctl(ZX_SYSTEM_POWERCTL_REBOOT_BOOTLOADER)
431            }
432            fsystem_state::SystemPowerState::RebootRecovery => {
433                self.system_power.system_powerctl(ZX_SYSTEM_POWERCTL_REBOOT_RECOVERY)
434            }
435            fsystem_state::SystemPowerState::RebootKernelInitiated => {
436                let status = self
437                    .system_power
438                    .system_powerctl(ZX_SYSTEM_POWERCTL_ACK_KERNEL_INITIATED_REBOOT);
439                if status.is_ok() {
440                    // sleep indefinitely
441                    loop {
442                        fasync::Timer::new(std::time::Duration::from_secs(5 * 60)).await;
443                        println!(
444                            "driver_manager: unexpectedly still running after successful reboot syscall"
445                        );
446                    }
447                }
448                status
449            }
450            fsystem_state::SystemPowerState::Poweroff => {
451                self.system_power.system_powerctl(ZX_SYSTEM_POWERCTL_SHUTDOWN)
452            }
453            fsystem_state::SystemPowerState::FullyOn => {
454                error!("Unexpected shutdown state requested: {:?}", shutdown_system_state);
455                Err(zx::Status::INVALID_ARGS)
456            }
457        };
458
459        let internal_state = self.internal_state.borrow();
460        if internal_state.lifecycle_stop {
461            info!("Exiting driver manager gracefully");
462            self.system_power.exit(0);
463        }
464
465        warn!("{}: {status:?}", what);
466    }
467}
468
469#[cfg(test)]
470mod tests {
471    use super::*;
472
473    struct MockNodeRemover {
474        timeout_callback: RefCell<Option<Box<dyn Fn()>>>,
475        shutdown_all_tx: RefCell<Option<oneshot::Sender<()>>>,
476        received_shutdown_all_request_tx: RefCell<Option<oneshot::Sender<()>>>,
477        received_shutdown_all_request_rx: RefCell<Option<oneshot::Receiver<()>>>,
478    }
479
480    impl MockNodeRemover {
481        fn new() -> Self {
482            let (tx, rx) = oneshot::channel();
483            Self {
484                timeout_callback: RefCell::new(None),
485                shutdown_all_tx: RefCell::new(None),
486                received_shutdown_all_request_tx: RefCell::new(Some(tx)),
487                received_shutdown_all_request_rx: RefCell::new(Some(rx)),
488            }
489        }
490
491        fn complete_shutdown_all(&self) {
492            if let Some(tx) = self.shutdown_all_tx.borrow_mut().take() {
493                let _ = tx.send(());
494            }
495        }
496
497        fn timeout_node_removal(&self) {
498            if let Some(cb) = self.timeout_callback.borrow().as_ref() {
499                cb();
500            }
501        }
502
503        async fn wait_for_shutdown_all_request(&self) {
504            let rx = self.received_shutdown_all_request_rx.borrow_mut().take().unwrap();
505            let _ = rx.await;
506        }
507    }
508
509    #[async_trait(?Send)]
510    impl NodeRemover for MockNodeRemover {
511        async fn shutdown_all_drivers(&self) {
512            if let Some(tx) = self.received_shutdown_all_request_tx.borrow_mut().take() {
513                let _ = tx.send(());
514            }
515            let (tx, rx) = oneshot::channel();
516            *self.shutdown_all_tx.borrow_mut() = Some(tx);
517            let _ = rx.await;
518        }
519        async fn shutdown_pkg_drivers(&self) {}
520        fn set_on_removal_timeout_callback(&self, callback: Box<dyn Fn()>) {
521            *self.timeout_callback.borrow_mut() = Some(callback);
522        }
523    }
524
525    struct MockSystemPower {
526        state: fsystem_state::SystemPowerState,
527        system_powerctl_called: RefCell<bool>,
528        system_powerctl_cmd: RefCell<Option<u32>>,
529        system_powerctl_tx: RefCell<Option<oneshot::Sender<()>>>,
530        system_powerctl_rx: RefCell<Option<oneshot::Receiver<()>>>,
531        exit_called: RefCell<bool>,
532        exit_tx: RefCell<Option<oneshot::Sender<()>>>,
533        exit_rx: RefCell<Option<oneshot::Receiver<()>>>,
534    }
535
536    impl MockSystemPower {
537        fn new(state: fsystem_state::SystemPowerState) -> Self {
538            let (p_tx, p_rx) = oneshot::channel();
539            let (e_tx, e_rx) = oneshot::channel();
540            Self {
541                state,
542                system_powerctl_called: RefCell::new(false),
543                system_powerctl_cmd: RefCell::new(None),
544                system_powerctl_tx: RefCell::new(Some(p_tx)),
545                system_powerctl_rx: RefCell::new(Some(p_rx)),
546                exit_called: RefCell::new(false),
547                exit_tx: RefCell::new(Some(e_tx)),
548                exit_rx: RefCell::new(Some(e_rx)),
549            }
550        }
551
552        // Blocks until the shutdown manager calls `system_powerctl()`.
553        async fn wait_for_system_powerctl(&self) {
554            let rx = self.system_powerctl_rx.borrow_mut().take().unwrap();
555            let _ = rx.await;
556        }
557
558        // Blocks until the shutdown manager calls `exit()`.
559        async fn wait_for_exit(&self) {
560            let rx = self.exit_rx.borrow_mut().take().unwrap();
561            let _ = rx.await;
562        }
563    }
564
565    #[async_trait(?Send)]
566    impl SystemPower for MockSystemPower {
567        async fn get_system_power_state(&self) -> fsystem_state::SystemPowerState {
568            self.state
569        }
570
571        fn system_powerctl(&self, cmd: u32) -> Result<(), zx::Status> {
572            *self.system_powerctl_called.borrow_mut() = true;
573            *self.system_powerctl_cmd.borrow_mut() = Some(cmd);
574            if let Some(tx) = self.system_powerctl_tx.borrow_mut().take() {
575                let _ = tx.send(());
576            }
577            Ok(())
578        }
579
580        fn has_resources(&self) -> bool {
581            true
582        }
583
584        fn exit(&self, _status: i32) {
585            *self.exit_called.borrow_mut() = true;
586            if let Some(tx) = self.exit_tx.borrow_mut().take() {
587                let _ = tx.send(());
588            }
589        }
590    }
591
592    struct FakeLogFlusher {
593        flushed: RefCell<bool>,
594    }
595
596    impl FakeLogFlusher {
597        fn new() -> Self {
598            Self { flushed: RefCell::new(false) }
599        }
600    }
601
602    async fn serve_log_flusher(
603        fake_log_flusher: Rc<FakeLogFlusher>,
604        mut stream: fdiagnostics::LogFlusherRequestStream,
605    ) -> Result<(), fidl::Error> {
606        while let Some(request) = stream.try_next().await? {
607            if let fdiagnostics::LogFlusherRequest::WaitUntilFlushed { responder } = request {
608                *fake_log_flusher.flushed.borrow_mut() = true;
609                responder.send()?;
610            }
611        }
612        Ok(())
613    }
614
615    fn setup_log_flusher(
616        scope: &fasync::Scope,
617    ) -> (fdiagnostics::LogFlusherProxy, Rc<FakeLogFlusher>) {
618        let (proxy, stream) = fidl::endpoints::create_proxy::<fdiagnostics::LogFlusherMarker>();
619        let fake = Rc::new(FakeLogFlusher::new());
620        let fake_clone = fake.clone();
621        scope.spawn_local(async move {
622            if let Err(e) = serve_log_flusher(fake_clone, stream.into_stream()).await {
623                error!("Error serving LogFlusher: {}", e);
624            }
625        });
626        (proxy, fake)
627    }
628
629    async fn run_shutdown_manager_test<F, Fut>(state: fsystem_state::SystemPowerState, test: F)
630    where
631        F: FnOnce(flifecycle::LifecycleProxy, Rc<MockNodeRemover>, Rc<MockSystemPower>) -> Fut,
632        Fut: std::future::Future<Output = ()>,
633    {
634        let node_remover = Rc::new(MockNodeRemover::new());
635        let system_power = Rc::new(MockSystemPower::new(state));
636
637        let scope = fasync::Scope::new();
638        let (log_flush_proxy, fake_log_flusher) = setup_log_flusher(&scope);
639
640        let shutdown_manager =
641            ShutdownManager::new(node_remover.clone(), system_power.clone(), Some(log_flush_proxy));
642
643        let (lifecycle_proxy, stream) =
644            fidl::endpoints::create_proxy::<flifecycle::LifecycleMarker>();
645        let (tx, rx) = oneshot::channel::<ShutdownSender>();
646
647        let self_clone = shutdown_manager.clone();
648        shutdown_manager.scope.spawn_local(async move {
649            if let Ok(sender) = rx.await {
650                self_clone.internal_state.borrow_mut().lifecycle_stop = true;
651                let status = self_clone.signal_boot_shutdown().await;
652                let _ = sender.send(status);
653            }
654        });
655        let lifecycle_server = Rc::new(LifecycleServer::new(tx));
656        shutdown_manager.scope.spawn_local(async move {
657            if let Err(e) = lifecycle_server.serve(stream.into_stream()).await {
658                error!("Error serving Lifecycle in test: {}", e);
659            }
660        });
661
662        test(lifecycle_proxy, node_remover, system_power).await;
663
664        assert!(*fake_log_flusher.flushed.borrow());
665    }
666
667    // Verifies that if the node remover times out then the system still reboots.
668    #[fuchsia::test]
669    async fn test_reboot_timeout() {
670        run_shutdown_manager_test(
671            fsystem_state::SystemPowerState::Reboot,
672            |lifecycle_proxy, node_remover, system_power| async move {
673                // Tell the shutdown manager to stop and shutdown all nodes.
674                lifecycle_proxy.stop().unwrap();
675
676                // Wait for the node remover to receive the request to shutdown all nodes.
677                node_remover.wait_for_shutdown_all_request().await;
678
679                // Triggers the event that the node remover has timed out waiting for nodes to be removed.
680                node_remover.timeout_node_removal();
681
682                // Wait for the shutdown manager to complete and call `system_powerctl()`.
683                system_power.wait_for_system_powerctl().await;
684
685                // Wait for the shutdown manager to exit.
686                system_power.wait_for_exit().await;
687
688                // Node remover timed out and so `system_powerctl()` should still be called.
689                assert!(*system_power.system_powerctl_called.borrow());
690                assert_eq!(
691                    Some(ZX_SYSTEM_POWERCTL_REBOOT),
692                    *system_power.system_powerctl_cmd.borrow()
693                );
694                assert!(*system_power.exit_called.borrow());
695            },
696        )
697        .await;
698    }
699
700    // Verifies the shutdown manager correctly removes all nodes when stopping and reboots the
701    // system when in the reboot power-system state.
702    #[fuchsia::test]
703    async fn test_reboot_normal() {
704        run_shutdown_manager_test(
705            fsystem_state::SystemPowerState::Reboot,
706            |lifecycle_proxy, node_remover, system_power| async move {
707                // Tell the shutdown manager to stop and shutdown all nodes.
708                lifecycle_proxy.stop().unwrap();
709
710                // Wait for the node remover to receive the request to shutdown all nodes.
711                node_remover.wait_for_shutdown_all_request().await;
712
713                // Complete the node-removal request.
714                node_remover.complete_shutdown_all();
715
716                // Wait for the shutdown manager to complete and call `system_powerctl()`.
717                system_power.wait_for_system_powerctl().await;
718
719                // Wait for the shutdown manager to exit.
720                system_power.wait_for_exit().await;
721
722                // The shutdown manager should call `system_powerctl(ZX_SYSTEM_POWERCTL_REBOOT)`.
723                assert!(*system_power.system_powerctl_called.borrow());
724                assert_eq!(
725                    Some(ZX_SYSTEM_POWERCTL_REBOOT),
726                    *system_power.system_powerctl_cmd.borrow()
727                );
728                assert!(*system_power.exit_called.borrow());
729            },
730        )
731        .await;
732    }
733}