1use crate::{power, startup};
6use anyhow::{anyhow, Context as _, Error};
7use fidl::endpoints::{create_proxy, ClientEnd, ServerEnd};
8use fuchsia_component::server::{ServiceFs, ServiceObjLocal};
9use fuchsia_inspect_contrib::nodes::BoundedListNode;
10use futures::{StreamExt, TryFutureExt, TryStreamExt};
11use log::{error, warn};
12use std::sync::{Arc, Mutex};
13use zx::HandleBased;
14use {
15 fidl_fuchsia_component as fcomponent, fidl_fuchsia_component_decl as fdecl,
16 fidl_fuchsia_io as fio, fidl_fuchsia_power_broker as fbroker, fidl_fuchsia_session as fsession,
17 fidl_fuchsia_session_power as fpower,
18};
19
20const MAX_CONCURRENT_CONNECTIONS: usize = 10_000;
22
23const DIAGNOSTICS_SESSION_STARTED_AT_NAME: &str = "session_started_at";
25
26const DIAGNOSTICS_SESSION_STARTED_AT_SIZE: usize = 100;
28
29const DIAGNOSTICS_TIME_PROPERTY_NAME: &str = "@time";
32
33pub enum IncomingRequest {
35 Launcher(fsession::LauncherRequestStream),
36 Restarter(fsession::RestarterRequestStream),
37 Lifecycle(fsession::LifecycleRequestStream),
38 Handoff(fpower::HandoffRequestStream),
39}
40
41struct Diagnostics {
42 session_started_at: BoundedListNode,
44}
45
46impl Diagnostics {
47 pub fn record_session_start(&mut self) {
48 self.session_started_at.add_entry(|node| {
49 node.record_int(
50 DIAGNOSTICS_TIME_PROPERTY_NAME,
51 zx::MonotonicInstant::get().into_nanos(),
52 );
53 });
54 }
55}
56
57struct PendingSession {
59 pub exposed_dir_server_end: ServerEnd<fio::DirectoryMarker>,
63}
64
65impl PendingSession {
66 fn new() -> (fio::DirectoryProxy, Self) {
67 let (exposed_dir, exposed_dir_server_end) = create_proxy::<fio::DirectoryMarker>();
68 (exposed_dir, Self { exposed_dir_server_end })
69 }
70}
71
72struct StartedSession {
77 url: String,
79}
80
81enum Session {
82 Pending(PendingSession),
83 Started(StartedSession),
84}
85
86impl Session {
87 fn new_pending() -> (fio::DirectoryProxy, Self) {
88 let (proxy, pending_session) = PendingSession::new();
89 (proxy, Self::Pending(pending_session))
90 }
91}
92
93struct PowerState {
94 power_element: futures::lock::Mutex<Option<power::PowerElement>>,
99
100 suspend_enabled: bool,
102}
103
104impl PowerState {
105 pub fn new(suspend_enabled: bool) -> Self {
106 Self { power_element: futures::lock::Mutex::default(), suspend_enabled }
107 }
108
109 pub async fn ensure_power_lease(&self) {
113 if !self.suspend_enabled {
114 return;
115 }
116 let power_element = &mut *self.power_element.lock().await;
117 if let Some(power_element) = power_element {
118 if power_element.has_lease() {
119 return;
120 }
121 }
122 *power_element = match power::PowerElement::new().await {
123 Ok(element) => Some(element),
124 Err(err) => {
125 warn!("Failed to create power element: {err}");
126 None
127 }
128 };
129 }
130
131 pub async fn take_power_lease(
132 &self,
133 ) -> Result<ClientEnd<fbroker::LeaseControlMarker>, fpower::HandoffError> {
134 if !self.suspend_enabled {
135 log::warn!(
136 "Session component wants to take our power lease, but the platform is \
137 configured to not support suspend"
138 );
139 return Err(fpower::HandoffError::Unavailable);
140 }
141 log::info!("Session component is taking our power lease");
142 let lease = match &mut *self.power_element.lock().await {
143 Some(power_element) => power_element.take_lease(),
144 None => return Err(fpower::HandoffError::Unavailable),
145 }
146 .ok_or(fpower::HandoffError::AlreadyTaken)?;
147 Ok(lease)
148 }
149}
150
151struct SessionManagerState {
152 default_session_url: Option<String>,
154
155 session: futures::lock::Mutex<Session>,
157
158 realm: fcomponent::RealmProxy,
160
161 power: PowerState,
163
164 inner: Mutex<Inner>,
166}
167
168struct Inner {
169 diagnostics: Diagnostics,
171
172 exposed_dir: fio::DirectoryProxy,
174}
175
176impl SessionManagerState {
177 async fn start_default(&self) -> Result<(), Error> {
183 let session_url = self
184 .default_session_url
185 .as_ref()
186 .ok_or_else(|| anyhow!("no default session URL configured"))?
187 .clone();
188 self.start(session_url, vec![]).await?;
189 Ok(())
190 }
191
192 async fn start(
194 &self,
195 url: String,
196 config_capabilities: Vec<fdecl::Configuration>,
197 ) -> Result<(), startup::StartupError> {
198 self.power.ensure_power_lease().await;
199 self.start_impl(&mut *self.session.lock().await, config_capabilities, url).await
200 }
201
202 async fn start_impl(
203 &self,
204 session: &mut Session,
205 config_capabilities: Vec<fdecl::Configuration>,
206 url: String,
207 ) -> Result<(), startup::StartupError> {
208 let (proxy_on_failure, new_pending) = Session::new_pending();
209 let pending_session = std::mem::replace(session, new_pending);
210 let pending = match pending_session {
211 Session::Pending(pending) => pending,
212 Session::Started(_) => {
213 let (proxy, pending) = PendingSession::new();
214 self.inner.lock().expect("mutex should not be poisoned").exposed_dir = proxy;
215 pending
216 }
217 };
218 if let Err(e) = startup::launch_session(
219 &url,
220 config_capabilities,
221 pending.exposed_dir_server_end,
222 &self.realm,
223 )
224 .await
225 {
226 self.inner.lock().expect("mutex should not be poisoned").exposed_dir = proxy_on_failure;
227 return Err(e);
228 }
229 *session = Session::Started(StartedSession { url });
230 self.inner.lock().expect("mutex should not be poisoned").diagnostics.record_session_start();
231 Ok(())
232 }
233
234 async fn stop(&self) -> Result<(), startup::StartupError> {
236 self.power.ensure_power_lease().await;
237 let mut session = self.session.lock().await;
238 if let Session::Started(_) = &*session {
239 let (proxy, new_pending) = Session::new_pending();
240 *session = new_pending;
241 self.inner.lock().expect("mutex should not be poisoned").exposed_dir = proxy;
242 startup::stop_session(&self.realm).await?;
243 }
244 Ok(())
245 }
246
247 async fn restart(&self) -> Result<(), startup::StartupError> {
249 self.power.ensure_power_lease().await;
250 let mut session = self.session.lock().await;
251 let Session::Started(StartedSession { url }) = &mut *session else {
252 return Err(startup::StartupError::NotRunning);
253 };
254 let url = url.clone();
255 self.start_impl(&mut session, vec![], url).await?;
256 Ok(())
257 }
258
259 async fn take_power_lease(
260 &self,
261 ) -> Result<ClientEnd<fbroker::LeaseControlMarker>, fpower::HandoffError> {
262 let lease = self.power.take_power_lease().await?;
263 Ok(lease)
264 }
265}
266
267impl vfs::remote::GetRemoteDir for SessionManagerState {
268 #[allow(clippy::unwrap_in_result)]
269 fn get_remote_dir(&self) -> Result<fio::DirectoryProxy, zx::Status> {
270 Ok(Clone::clone(&self.inner.lock().expect("mutex should not be poisoned").exposed_dir))
271 }
272}
273
274#[derive(Clone)]
276pub struct SessionManager {
277 state: Arc<SessionManagerState>,
278}
279
280impl SessionManager {
281 pub fn new(
286 realm: fcomponent::RealmProxy,
287 inspector: &fuchsia_inspect::Inspector,
288 default_session_url: Option<String>,
289 suspend_enabled: bool,
290 ) -> Self {
291 let session_started_at = BoundedListNode::new(
292 inspector.root().create_child(DIAGNOSTICS_SESSION_STARTED_AT_NAME),
293 DIAGNOSTICS_SESSION_STARTED_AT_SIZE,
294 );
295 let diagnostics = Diagnostics { session_started_at };
296 let (proxy, new_pending) = Session::new_pending();
297 let state = SessionManagerState {
298 default_session_url,
299 session: futures::lock::Mutex::new(new_pending),
300 realm,
301 power: PowerState::new(suspend_enabled),
302 inner: Mutex::new(Inner { exposed_dir: proxy, diagnostics }),
303 };
304 SessionManager { state: Arc::new(state) }
305 }
306
307 #[cfg(test)]
308 pub fn new_default(
309 realm: fcomponent::RealmProxy,
310 inspector: &fuchsia_inspect::Inspector,
311 ) -> Self {
312 Self::new(realm, inspector, None, false)
313 }
314
315 pub async fn start_default_session(&mut self) -> Result<(), Error> {
321 self.state.start_default().await?;
322 Ok(())
323 }
324
325 pub async fn serve(
332 &mut self,
333 fs: &mut ServiceFs<ServiceObjLocal<'_, IncomingRequest>>,
334 ) -> Result<(), Error> {
335 fs.dir("svc")
336 .add_fidl_service(IncomingRequest::Launcher)
337 .add_fidl_service(IncomingRequest::Restarter)
338 .add_fidl_service(IncomingRequest::Lifecycle)
339 .add_fidl_service(IncomingRequest::Handoff);
340
341 fs.add_entry_at("svc_from_session", self.state.clone());
343
344 fs.take_and_serve_directory_handle()?;
345
346 fs.for_each_concurrent(MAX_CONCURRENT_CONNECTIONS, |request| {
347 let mut session_manager = self.clone();
348 async move {
349 session_manager
350 .handle_incoming_request(request)
351 .unwrap_or_else(|err| error!("{err:?}"))
352 .await
353 }
354 })
355 .await;
356
357 Ok(())
358 }
359
360 async fn handle_incoming_request(&mut self, request: IncomingRequest) -> Result<(), Error> {
367 match request {
368 IncomingRequest::Launcher(request_stream) => {
369 self.handle_launcher_request_stream(request_stream)
370 .await
371 .context("Session Launcher request stream got an error.")?;
372 }
373 IncomingRequest::Restarter(request_stream) => {
374 self.handle_restarter_request_stream(request_stream)
375 .await
376 .context("Session Restarter request stream got an error.")?;
377 }
378 IncomingRequest::Lifecycle(request_stream) => {
379 self.handle_lifecycle_request_stream(request_stream)
380 .await
381 .context("Session Lifecycle request stream got an error.")?;
382 }
383 IncomingRequest::Handoff(request_stream) => {
384 self.handle_handoff_request_stream(request_stream)
385 .await
386 .context("Session Handoff request stream got an error.")?;
387 }
388 }
389
390 Ok(())
391 }
392
393 pub async fn handle_launcher_request_stream(
401 &mut self,
402 mut request_stream: fsession::LauncherRequestStream,
403 ) -> Result<(), Error> {
404 while let Some(request) =
405 request_stream.try_next().await.context("Error handling Launcher request stream")?
406 {
407 match request {
408 fsession::LauncherRequest::Launch { configuration, responder } => {
409 let result = self.handle_launch_request(configuration).await;
410 let _ = responder.send(result);
411 }
412 }
413 }
414 Ok(())
415 }
416
417 pub async fn handle_restarter_request_stream(
425 &mut self,
426 mut request_stream: fsession::RestarterRequestStream,
427 ) -> Result<(), Error> {
428 while let Some(request) =
429 request_stream.try_next().await.context("Error handling Restarter request stream")?
430 {
431 match request {
432 fsession::RestarterRequest::Restart { responder } => {
433 let result = self.handle_restart_request().await;
434 let _ = responder.send(result);
435 }
436 }
437 }
438 Ok(())
439 }
440
441 pub async fn handle_lifecycle_request_stream(
449 &mut self,
450 mut request_stream: fsession::LifecycleRequestStream,
451 ) -> Result<(), Error> {
452 while let Some(request) =
453 request_stream.try_next().await.context("Error handling Lifecycle request stream")?
454 {
455 match request {
456 fsession::LifecycleRequest::Start { payload, responder } => {
457 let result = self.handle_lifecycle_start_request(payload.session_url).await;
458 let _ = responder.send(result);
459 }
460 fsession::LifecycleRequest::Stop { responder } => {
461 let result = self.handle_lifecycle_stop_request().await;
462 let _ = responder.send(result);
463 }
464 fsession::LifecycleRequest::Restart { responder } => {
465 let result = self.handle_lifecycle_restart_request().await;
466 let _ = responder.send(result);
467 }
468 fsession::LifecycleRequest::_UnknownMethod { ordinal, .. } => {
469 warn!(ordinal:%; "Lifecycle received an unknown method");
470 }
471 }
472 }
473 Ok(())
474 }
475
476 pub async fn handle_handoff_request_stream(
477 &mut self,
478 mut request_stream: fpower::HandoffRequestStream,
479 ) -> Result<(), Error> {
480 while let Some(request) =
481 request_stream.try_next().await.context("Error handling Handoff request stream")?
482 {
483 match request {
484 fpower::HandoffRequest::Take { responder } => {
485 let result = self.handle_handoff_take_request().await;
486 let _ = responder.send(result.map(|lease| lease.into_channel().into_handle()));
487 }
488 fpower::HandoffRequest::_UnknownMethod { ordinal, .. } => {
489 warn!(ordinal:%; "Lifecycle received an unknown method")
490 }
491 }
492 }
493 Ok(())
494 }
495
496 async fn handle_launch_request(
501 &mut self,
502 configuration: fsession::LaunchConfiguration,
503 ) -> Result<(), fsession::LaunchError> {
504 let session_url = configuration.session_url.ok_or(fsession::LaunchError::InvalidArgs)?;
505 let config_capabilities = configuration.config_capabilities.unwrap_or_default();
506 self.state.start(session_url, config_capabilities).await.map_err(Into::into)
507 }
508
509 async fn handle_restart_request(&mut self) -> Result<(), fsession::RestartError> {
511 self.state.restart().await.map_err(Into::into)
512 }
513
514 async fn handle_lifecycle_start_request(
519 &mut self,
520 session_url: Option<String>,
521 ) -> Result<(), fsession::LifecycleError> {
522 let session_url = session_url
523 .as_ref()
524 .or(self.state.default_session_url.as_ref())
525 .ok_or(fsession::LifecycleError::NotFound)?
526 .to_owned();
527 self.state.start(session_url, vec![]).await.map_err(Into::into)
528 }
529
530 async fn handle_lifecycle_stop_request(&mut self) -> Result<(), fsession::LifecycleError> {
532 self.state.stop().await.map_err(Into::into)
533 }
534
535 async fn handle_lifecycle_restart_request(&mut self) -> Result<(), fsession::LifecycleError> {
537 self.state.restart().await.map_err(Into::into)
538 }
539
540 async fn handle_handoff_take_request(
542 &mut self,
543 ) -> Result<ClientEnd<fbroker::LeaseControlMarker>, fpower::HandoffError> {
544 self.state.take_power_lease().await
545 }
546}
547
548#[cfg(test)]
549#[allow(clippy::unwrap_used)]
550mod tests {
551 use super::SessionManager;
552 use anyhow::{anyhow, Error};
553 use diagnostics_assertions::{assert_data_tree, AnyProperty};
554 use fidl::endpoints::{create_proxy_and_stream, ServerEnd};
555 use fidl_test_util::spawn_stream_handler;
556 use futures::channel::mpsc;
557 use futures::prelude::*;
558 use session_testing::{spawn_directory_server, spawn_noop_directory_server, spawn_server};
559 use std::sync::LazyLock;
560 use test_util::Counter;
561 use {
562 fidl_fuchsia_component as fcomponent, fidl_fuchsia_io as fio,
563 fidl_fuchsia_session as fsession,
564 };
565
566 fn serve_launcher(session_manager: SessionManager) -> fsession::LauncherProxy {
567 let (launcher_proxy, launcher_stream) =
568 create_proxy_and_stream::<fsession::LauncherMarker>();
569 {
570 let mut session_manager_ = session_manager.clone();
571 fuchsia_async::Task::spawn(async move {
572 session_manager_
573 .handle_launcher_request_stream(launcher_stream)
574 .await
575 .expect("Session launcher request stream got an error.");
576 })
577 .detach();
578 }
579 launcher_proxy
580 }
581
582 fn serve_restarter(session_manager: SessionManager) -> fsession::RestarterProxy {
583 let (restarter_proxy, restarter_stream) =
584 create_proxy_and_stream::<fsession::RestarterMarker>();
585 {
586 let mut session_manager_ = session_manager.clone();
587 fuchsia_async::Task::spawn(async move {
588 session_manager_
589 .handle_restarter_request_stream(restarter_stream)
590 .await
591 .expect("Session restarter request stream got an error.");
592 })
593 .detach();
594 }
595 restarter_proxy
596 }
597
598 fn serve_lifecycle(session_manager: SessionManager) -> fsession::LifecycleProxy {
599 let (lifecycle_proxy, lifecycle_stream) =
600 create_proxy_and_stream::<fsession::LifecycleMarker>();
601 {
602 let mut session_manager_ = session_manager.clone();
603 fuchsia_async::Task::spawn(async move {
604 session_manager_
605 .handle_lifecycle_request_stream(lifecycle_stream)
606 .await
607 .expect("Session lifecycle request stream got an error.");
608 })
609 .detach();
610 }
611 lifecycle_proxy
612 }
613
614 fn spawn_noop_controller_server(server_end: ServerEnd<fcomponent::ControllerMarker>) {
615 spawn_server(server_end, move |controller_request| match controller_request {
616 fcomponent::ControllerRequest::Start { responder, .. } => {
617 let _ = responder.send(Ok(()));
618 }
619 fcomponent::ControllerRequest::IsStarted { .. } => unimplemented!(),
620 fcomponent::ControllerRequest::GetExposedDictionary { .. } => {
621 unimplemented!()
622 }
623 fcomponent::ControllerRequest::Destroy { .. } => {
624 unimplemented!()
625 }
626 fcomponent::ControllerRequest::_UnknownMethod { .. } => {
627 unimplemented!()
628 }
629 });
630 }
631
632 fn open_session_exposed_dir(
633 session_manager: SessionManager,
634 path: &str,
635 server_end: ServerEnd<fio::DirectoryMarker>,
636 ) {
637 session_manager
638 .state
639 .inner
640 .lock()
641 .unwrap()
642 .exposed_dir
643 .open(path, fio::PERM_READABLE, &fio::Options::default(), server_end.into_channel())
644 .unwrap();
645 }
646
647 #[fuchsia::test]
649 async fn test_launch() {
650 let session_url = "session";
651
652 let realm = spawn_stream_handler(move |realm_request| async move {
653 match realm_request {
654 fcomponent::RealmRequest::DestroyChild { child: _, responder } => {
655 let _ = responder.send(Ok(()));
656 }
657 fcomponent::RealmRequest::CreateChild { collection: _, decl, args, responder } => {
658 assert_eq!(decl.url.unwrap(), session_url);
659 spawn_noop_controller_server(args.controller.unwrap());
660 let _ = responder.send(Ok(()));
661 }
662 fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
663 spawn_noop_directory_server(exposed_dir);
664 let _ = responder.send(Ok(()));
665 }
666 _ => panic!("Realm handler received an unexpected request"),
667 }
668 });
669
670 let inspector = fuchsia_inspect::Inspector::default();
671 let session_manager = SessionManager::new_default(realm, &inspector);
672 let launcher = serve_launcher(session_manager);
673
674 assert!(launcher
675 .launch(&fsession::LaunchConfiguration {
676 session_url: Some(session_url.to_string()),
677 ..Default::default()
678 })
679 .await
680 .is_ok());
681 assert_data_tree!(inspector, root: {
682 session_started_at: {
683 "0": {
684 "@time": AnyProperty
685 }
686 }
687 });
688 }
689
690 #[fuchsia::test]
692 async fn test_restarter_restart() {
693 let session_url = "session";
694
695 let realm = spawn_stream_handler(move |realm_request| async move {
696 match realm_request {
697 fcomponent::RealmRequest::DestroyChild { child: _, responder } => {
698 let _ = responder.send(Ok(()));
699 }
700 fcomponent::RealmRequest::CreateChild { collection: _, decl, args, responder } => {
701 assert_eq!(decl.url.unwrap(), session_url);
702 spawn_noop_controller_server(args.controller.unwrap());
703 let _ = responder.send(Ok(()));
704 }
705 fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
706 spawn_noop_directory_server(exposed_dir);
707 let _ = responder.send(Ok(()));
708 }
709 _ => panic!("Realm handler received an unexpected request"),
710 }
711 });
712
713 let inspector = fuchsia_inspect::Inspector::default();
714 let session_manager = SessionManager::new_default(realm, &inspector);
715 let launcher = serve_launcher(session_manager.clone());
716 let restarter = serve_restarter(session_manager);
717
718 assert!(launcher
719 .launch(&fsession::LaunchConfiguration {
720 session_url: Some(session_url.to_string()),
721 ..Default::default()
722 })
723 .await
724 .expect("could not call Launch")
725 .is_ok());
726
727 assert!(restarter.restart().await.expect("could not call Restart").is_ok());
728
729 assert_data_tree!(inspector, root: {
730 session_started_at: {
731 "0": {
732 "@time": AnyProperty
733 },
734 "1": {
735 "@time": AnyProperty
736 }
737 }
738 });
739 }
740
741 #[fuchsia::test]
743 async fn test_restarter_restart_error_not_running() {
744 let realm = spawn_stream_handler(move |_realm_request| async move {
745 panic!("Realm should not receive any requests as there is no session to launch")
746 });
747
748 let inspector = fuchsia_inspect::Inspector::default();
749 let session_manager = SessionManager::new_default(realm, &inspector);
750 let restarter = serve_restarter(session_manager);
751
752 assert_eq!(
753 Err(fsession::RestartError::NotRunning),
754 restarter.restart().await.expect("could not call Restart")
755 );
756
757 assert_data_tree!(inspector, root: {
758 session_started_at: {}
759 });
760 }
761
762 #[fuchsia::test]
764 async fn test_start() {
765 let session_url = "session";
766
767 let realm = spawn_stream_handler(move |realm_request| async move {
768 match realm_request {
769 fcomponent::RealmRequest::DestroyChild { child: _, responder } => {
770 let _ = responder.send(Ok(()));
771 }
772 fcomponent::RealmRequest::CreateChild { collection: _, decl, args, responder } => {
773 assert_eq!(decl.url.unwrap(), session_url);
774 spawn_noop_controller_server(args.controller.unwrap());
775 let _ = responder.send(Ok(()));
776 }
777 fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
778 spawn_noop_directory_server(exposed_dir);
779 let _ = responder.send(Ok(()));
780 }
781 _ => panic!("Realm handler received an unexpected request"),
782 }
783 });
784
785 let inspector = fuchsia_inspect::Inspector::default();
786 let session_manager = SessionManager::new_default(realm, &inspector);
787 let lifecycle = serve_lifecycle(session_manager);
788
789 assert!(lifecycle
790 .start(&fsession::LifecycleStartRequest {
791 session_url: Some(session_url.to_string()),
792 ..Default::default()
793 })
794 .await
795 .is_ok());
796 assert_data_tree!(inspector, root: {
797 session_started_at: {
798 "0": {
799 "@time": AnyProperty
800 }
801 }
802 });
803 }
804
805 #[fuchsia::test]
807 async fn test_start_default() {
808 let default_session_url = "session";
809
810 let realm = spawn_stream_handler(move |realm_request| async move {
811 match realm_request {
812 fcomponent::RealmRequest::DestroyChild { child: _, responder } => {
813 let _ = responder.send(Ok(()));
814 }
815 fcomponent::RealmRequest::CreateChild { collection: _, decl, args, responder } => {
816 assert_eq!(decl.url.unwrap(), default_session_url);
817 spawn_noop_controller_server(args.controller.unwrap());
818 let _ = responder.send(Ok(()));
819 }
820 fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
821 spawn_noop_directory_server(exposed_dir);
822 let _ = responder.send(Ok(()));
823 }
824 _ => panic!("Realm handler received an unexpected request"),
825 }
826 });
827
828 let inspector = fuchsia_inspect::Inspector::default();
829 let session_manager =
830 SessionManager::new(realm, &inspector, Some(default_session_url.to_owned()), false);
831 let lifecycle = serve_lifecycle(session_manager);
832
833 assert!(lifecycle
834 .start(&fsession::LifecycleStartRequest { session_url: None, ..Default::default() })
835 .await
836 .is_ok());
837 assert_data_tree!(inspector, root: {
838 session_started_at: {
839 "0": {
840 "@time": AnyProperty
841 }
842 }
843 });
844 }
845
846 #[fuchsia::test]
848 async fn test_stop_destroys_component() {
849 static NUM_DESTROY_CHILD_CALLS: LazyLock<Counter> = LazyLock::new(|| Counter::new(0));
850
851 let session_url = "session";
852
853 let realm = spawn_stream_handler(move |realm_request| async move {
854 match realm_request {
855 fcomponent::RealmRequest::DestroyChild { child: _, responder } => {
856 NUM_DESTROY_CHILD_CALLS.inc();
857 let _ = responder.send(Ok(()));
858 }
859 fcomponent::RealmRequest::CreateChild { collection: _, decl, args, responder } => {
860 assert_eq!(decl.url.unwrap(), session_url);
861 spawn_noop_controller_server(args.controller.unwrap());
862 let _ = responder.send(Ok(()));
863 }
864 fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
865 spawn_noop_directory_server(exposed_dir);
866 let _ = responder.send(Ok(()));
867 }
868 _ => panic!("Realm handler received an unexpected request"),
869 }
870 });
871
872 let inspector = fuchsia_inspect::Inspector::default();
873 let session_manager = SessionManager::new_default(realm, &inspector);
874 let lifecycle = serve_lifecycle(session_manager);
875
876 assert!(lifecycle
877 .start(&fsession::LifecycleStartRequest {
878 session_url: Some(session_url.to_string()),
879 ..Default::default()
880 })
881 .await
882 .is_ok());
883 assert_eq!(NUM_DESTROY_CHILD_CALLS.get(), 1);
885 assert_data_tree!(inspector, root: {
886 session_started_at: {
887 "0": {
888 "@time": AnyProperty
889 }
890 }
891 });
892
893 assert!(lifecycle.stop().await.is_ok());
894 assert_eq!(NUM_DESTROY_CHILD_CALLS.get(), 2);
895 }
896
897 #[fuchsia::test]
899 async fn test_lifecycle_restart() {
900 let session_url = "session";
901
902 let realm = spawn_stream_handler(move |realm_request| async move {
903 match realm_request {
904 fcomponent::RealmRequest::DestroyChild { child: _, responder } => {
905 let _ = responder.send(Ok(()));
906 }
907 fcomponent::RealmRequest::CreateChild { collection: _, decl, args, responder } => {
908 assert_eq!(decl.url.unwrap(), session_url);
909 spawn_noop_controller_server(args.controller.unwrap());
910 let _ = responder.send(Ok(()));
911 }
912 fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
913 spawn_noop_directory_server(exposed_dir);
914 let _ = responder.send(Ok(()));
915 }
916 _ => panic!("Realm handler received an unexpected request"),
917 }
918 });
919
920 let inspector = fuchsia_inspect::Inspector::default();
921 let session_manager = SessionManager::new_default(realm, &inspector);
922 let lifecycle = serve_lifecycle(session_manager.clone());
923
924 assert!(lifecycle
925 .start(&fsession::LifecycleStartRequest {
926 session_url: Some(session_url.to_string()),
927 ..Default::default()
928 })
929 .await
930 .expect("could not call Launch")
931 .is_ok());
932
933 assert!(lifecycle.restart().await.expect("could not call Restart").is_ok());
934
935 assert_data_tree!(inspector, root: {
936 session_started_at: {
937 "0": {
938 "@time": AnyProperty
939 },
940 "1": {
941 "@time": AnyProperty
942 }
943 }
944 });
945 }
946
947 #[fuchsia::test]
950 async fn test_svc_from_session_before_start() -> Result<(), Error> {
951 let session_url = "session";
952 let svc_path = "foo";
953
954 let (path_sender, mut path_receiver) = mpsc::channel(1);
955
956 let session_exposed_dir_handler = move |directory_request| match directory_request {
957 fio::DirectoryRequest::Open { path, .. } => {
958 let mut path_sender: mpsc::Sender<String> = path_sender.clone();
959 path_sender.try_send(path).unwrap();
960 }
961 _ => panic!("Directory handler received an unexpected request"),
962 };
963
964 let realm = spawn_stream_handler(move |realm_request| {
965 let session_exposed_dir_handler = session_exposed_dir_handler.clone();
966 async move {
967 match realm_request {
968 fcomponent::RealmRequest::DestroyChild { responder, .. } => {
969 let _ = responder.send(Ok(()));
970 }
971 fcomponent::RealmRequest::CreateChild { args, responder, .. } => {
972 spawn_noop_controller_server(args.controller.unwrap());
973 let _ = responder.send(Ok(()));
974 }
975 fcomponent::RealmRequest::OpenExposedDir { exposed_dir, responder, .. } => {
976 spawn_directory_server(exposed_dir, session_exposed_dir_handler);
977 let _ = responder.send(Ok(()));
978 }
979 _ => panic!("Realm handler received an unexpected request"),
980 }
981 }
982 });
983
984 let inspector = fuchsia_inspect::Inspector::default();
985 let session_manager = SessionManager::new_default(realm, &inspector);
986 let lifecycle = serve_lifecycle(session_manager.clone());
987
988 let (_client_end, server_end) = fidl::endpoints::create_proxy();
991
992 open_session_exposed_dir(session_manager, svc_path, server_end);
993 lifecycle
995 .start(&fsession::LifecycleStartRequest {
996 session_url: Some(session_url.to_string()),
997 ..Default::default()
998 })
999 .await?
1000 .map_err(|err| anyhow!("failed to start: {:?}", err))?;
1001
1002 assert_eq!(path_receiver.next().await.unwrap(), svc_path);
1004
1005 Ok(())
1006 }
1007
1008 #[fuchsia::test]
1011 async fn test_svc_from_session_after_start() -> Result<(), Error> {
1012 let session_url = "session";
1013 let svc_path = "foo";
1014
1015 let (path_sender, mut path_receiver) = mpsc::channel(1);
1016
1017 let session_exposed_dir_handler = move |directory_request| match directory_request {
1018 fio::DirectoryRequest::Open { path, .. } => {
1019 let mut path_sender = path_sender.clone();
1020 path_sender.try_send(path).unwrap();
1021 }
1022 _ => panic!("Directory handler received an unexpected request"),
1023 };
1024
1025 let realm = spawn_stream_handler(move |realm_request| {
1026 let session_exposed_dir_handler = session_exposed_dir_handler.clone();
1027 async move {
1028 match realm_request {
1029 fcomponent::RealmRequest::DestroyChild { responder, .. } => {
1030 let _ = responder.send(Ok(()));
1031 }
1032 fcomponent::RealmRequest::CreateChild { args, responder, .. } => {
1033 spawn_noop_controller_server(args.controller.unwrap());
1034 let _ = responder.send(Ok(()));
1035 }
1036 fcomponent::RealmRequest::OpenExposedDir { exposed_dir, responder, .. } => {
1037 spawn_directory_server(exposed_dir, session_exposed_dir_handler);
1038 let _ = responder.send(Ok(()));
1039 }
1040 _ => panic!("Realm handler received an unexpected request"),
1041 }
1042 }
1043 });
1044
1045 let inspector = fuchsia_inspect::Inspector::default();
1046 let session_manager = SessionManager::new_default(realm, &inspector);
1047 let lifecycle = serve_lifecycle(session_manager.clone());
1048
1049 lifecycle
1050 .start(&fsession::LifecycleStartRequest {
1051 session_url: Some(session_url.to_string()),
1052 ..Default::default()
1053 })
1054 .await?
1055 .map_err(|err| anyhow!("failed to start: {:?}", err))?;
1056
1057 let (_client_end, server_end) = fidl::endpoints::create_proxy();
1060
1061 open_session_exposed_dir(session_manager, svc_path, server_end);
1062
1063 assert_eq!(path_receiver.next().await.unwrap(), svc_path);
1064
1065 Ok(())
1066 }
1067}