starnix_core/device/
service_watcher.rs1use crate::task::Kernel;
8use anyhow::Context as _;
9use fidl::endpoints::ServiceMarker;
10use fuchsia_component::client as fclient;
11use futures::{StreamExt as _, TryStreamExt as _};
12use starnix_logging::log_info;
13use std::sync::Weak;
14
15pub fn watch_fidl_service<S, F>(kernel: &Kernel, marker: S, task_name: &'static str, on_instance: F)
24where
25 S: ServiceMarker + Send + 'static,
26 F: AsyncFn(&Weak<Kernel>, S::Proxy) + Send + 'static,
27{
28 let weak_kernel = kernel.weak_self.clone();
29 kernel.kthreads.spawn_future(
30 move || async move {
31 let result = match fclient::Service::open(marker) {
34 Ok(service) => run_fidl_service_watcher(&weak_kernel, service, on_instance).await,
35 Err(e) => Err(e.context("failed to open service directory")),
36 };
37 if let Err(e) = result {
38 log_info!("{}: service unavailable: {e:#}", S::SERVICE_NAME);
39 }
40 },
41 task_name,
42 );
43}
44
45pub(super) async fn run_fidl_service_watcher<S, F>(
48 kernel: &Weak<Kernel>,
49 service: fclient::Service<S>,
50 on_instance: F,
51) -> Result<(), anyhow::Error>
52where
53 S: ServiceMarker,
54 F: AsyncFn(&Weak<Kernel>, S::Proxy),
55{
56 let on_instance = &on_instance;
57 service
58 .watch()
59 .await
60 .with_context(|| format!("failed to watch {} service", S::SERVICE_NAME))?
61 .take_while(|_| std::future::ready(kernel.strong_count() > 0))
62 .try_for_each_concurrent(None, |instance| async move {
63 on_instance(kernel, instance).await;
64 Ok(())
65 })
66 .await
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72 use crate::testing::spawn_kernel_and_run;
73 use fidl::endpoints::create_endpoints;
74 use fidl_fuchsia_hardware_power_stats as fpowerstats;
75 use fidl_fuchsia_io as fio;
76 use fuchsia_async as fasync;
77 use fuchsia_component::server::ServiceFs;
78 use futures::channel::oneshot;
79 use futures::future::{Either, select};
80 use starnix_sync::Mutex;
81 use std::collections::HashSet;
82 use std::pin::pin;
83 use std::sync::atomic::{AtomicBool, Ordering};
84
85 fn serve_instances(
86 names: &'static [&'static str],
87 ) -> fclient::Service<fpowerstats::ServiceMarker> {
88 let (client, server) = create_endpoints::<fio::DirectoryMarker>();
89 std::thread::spawn(move || {
90 fasync::LocalExecutor::default().run_singlethreaded(async move {
91 let mut fs = ServiceFs::new_local();
92 for name in names {
93 fs.add_fidl_service_instance(*name, |_request: fpowerstats::ServiceRequest| ());
94 }
95 fs.serve_connection(server).expect("serve service directory");
96 fs.collect::<()>().await;
97 })
98 });
99 fclient::Service::open_from_dir(client, fpowerstats::ServiceMarker).expect("open service")
100 }
101
102 #[::fuchsia::test]
103 async fn watches_multiple_instances() {
104 spawn_kernel_and_run(async |current_task| {
105 let kernel = current_task.kernel();
106 let service = serve_instances(&["first", "second"]);
107 let seen = Mutex::new(HashSet::new());
108 let (second_tx, second_rx) = oneshot::channel();
109 let second_tx = Mutex::new(Some(second_tx));
110 let second_rx = Mutex::new(Some(second_rx));
111 let (done_tx, done_rx) = oneshot::channel();
112 let done_tx = Mutex::new(Some(done_tx));
113
114 let watcher = pin!(run_fidl_service_watcher(
115 &kernel.weak_self,
116 service,
117 async |weak_kernel, instance| {
118 assert!(weak_kernel.upgrade().is_some());
119 let name = instance.instance_name().to_string();
120 if name == "first" {
121 let rx = second_rx.lock().take().expect("second_rx taken once");
124 let _ = rx.await;
125 } else if name == "second" {
126 if let Some(tx) = second_tx.lock().take() {
127 let _ = tx.send(());
128 }
129 }
130 let mut seen = seen.lock();
131 seen.insert(name);
132 if seen.len() == 2 {
133 if let Some(tx) = done_tx.lock().take() {
134 let _ = tx.send(std::mem::take(&mut *seen));
135 }
136 }
137 }
138 ));
139
140 let seen = match select(watcher, done_rx).await {
141 Either::Left((result, _)) => {
142 panic!("watcher exited before both instances were discovered: {result:?}");
143 }
144 Either::Right((Ok(seen), _)) => seen,
145 Either::Right((Err(e), _)) => {
146 panic!("oneshot sender dropped before signalling: {e:?}");
147 }
148 };
149
150 assert_eq!(seen, HashSet::from(["first".to_string(), "second".to_string()]));
151 })
152 .await;
153 }
154
155 #[::fuchsia::test]
156 async fn stops_watching_when_kernel_is_dropped() {
157 let service = serve_instances(&["first", "second"]);
158 let called = AtomicBool::new(false);
159
160 run_fidl_service_watcher(&Weak::new(), service, async |_, _| {
161 called.store(true, Ordering::SeqCst);
162 })
163 .await
164 .expect("service watcher should exit cleanly when kernel is dropped");
165
166 assert!(!called.load(Ordering::SeqCst));
167 }
168}