Skip to main content

starnix_core/device/
service_watcher.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
5//! Per-[`Kernel`] discovery, driver fan-out, and connection lifecycle management for Fuchsia FIDL
6//! service directories (`/svc/<ServiceName>`).
7
8use crate::device::kobject::Device;
9use crate::task::{CurrentTask, Kernel};
10use anyhow::Context as _;
11use fidl::endpoints::{
12    MemberOpener, Proxy as _, ServiceMarker, ServiceProxy as _, SynchronousProxy,
13};
14use fidl_fuchsia_io as fio;
15use fuchsia_async as fasync;
16use fuchsia_component::client as fclient;
17use fuchsia_fs::directory::{WatchEvent, WatchMessage, Watcher};
18use futures::{StreamExt as _, TryStreamExt as _};
19use starnix_logging::{log_info, log_warn};
20use starnix_sync::Mutex;
21use std::future::Future;
22use std::marker::PhantomData;
23use std::pin::Pin;
24use std::sync::atomic::{AtomicBool, Ordering};
25use std::sync::{Arc, Weak};
26use zx::Peered as _;
27
28type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
29
30trait MonitoredChannel: Send + Sync + 'static {
31    fn as_channel(&self) -> &zx::Channel;
32}
33
34impl<P: SynchronousProxy + Send + Sync + 'static> MonitoredChannel for P {
35    fn as_channel(&self) -> &zx::Channel {
36        SynchronousProxy::as_channel(self)
37    }
38}
39
40/// Registration scope passed to a driver's `bind` callback for a discovered FIDL service instance.
41///
42/// Devices registered through [`Self::add_scoped_device`] and cleanup callbacks registered via
43/// [`Self::on_disconnect`] are tied to the lifetime of the service instance and any channel
44/// passed to [`Self::monitor_channel`] (later calls overwrite any previously monitored channel).
45/// When a monitored channel observes `ZX_CHANNEL_PEER_CLOSED`, or when the service instance
46/// directory entry is removed or replaced in `/svc/<S::SERVICE_NAME>`, the watcher runs all
47/// registered teardown hooks and device removals in reverse registration order on the kernel's
48/// main async executor.
49#[derive(Default)]
50pub struct ServiceBindingScope {
51    monitored_channel: Option<Arc<dyn MonitoredChannel>>,
52    teardown_hooks: Vec<Box<dyn FnOnce(&CurrentTask) + Send>>,
53}
54
55impl ServiceBindingScope {
56    /// Retains `proxy` for the duration of the binding and monitors its underlying Zircon
57    /// channel for `ZX_CHANNEL_PEER_CLOSED`, running all registered teardown hooks when the
58    /// remote peer closes its endpoint.
59    pub fn monitor_channel<P: SynchronousProxy + Send + Sync + 'static>(&mut self, proxy: Arc<P>) {
60        self.monitored_channel = Some(proxy);
61    }
62
63    /// Registers `hook` to be invoked on the kernel's main async executor with
64    /// [`KernelThreads::system_task`](crate::task::KernelThreads::system_task) when the binding
65    /// disconnects, executing in reverse registration order relative to other hooks and scoped
66    /// devices.
67    pub fn on_disconnect(&mut self, hook: impl FnOnce(&CurrentTask) + Send + 'static) {
68        self.teardown_hooks.push(Box::new(hook));
69    }
70
71    /// Registers a disconnect hook that removes `device` via
72    /// [`DeviceRegistry::remove_device`](crate::device::DeviceRegistry::remove_device) when the
73    /// binding disconnects, returning `device`.
74    pub fn add_scoped_device(&mut self, device: Device) -> Device {
75        let device_to_remove = device.clone();
76        self.on_disconnect(move |current_task| {
77            current_task.kernel().device_registry.remove_device(current_task, device_to_remove);
78        });
79        device
80    }
81}
82
83impl std::fmt::Debug for ServiceBindingScope {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        f.debug_struct("ServiceBindingScope")
86            .field("has_monitored_channel", &self.monitored_channel.is_some())
87            .field("teardown_hooks_len", &self.teardown_hooks.len())
88            .finish()
89    }
90}
91
92struct InstanceEntry<P> {
93    name: String,
94    proxy: Arc<P>,
95    active: Arc<AtomicBool>,
96}
97
98impl<P> Clone for InstanceEntry<P> {
99    fn clone(&self) -> Self {
100        Self { name: self.name.clone(), proxy: self.proxy.clone(), active: self.active.clone() }
101    }
102}
103
104struct ActiveBinding {
105    instance_active: Arc<AtomicBool>,
106    monitored_channel: Option<Arc<dyn MonitoredChannel>>,
107    _cancel_monitor: Option<futures::channel::oneshot::Sender<()>>,
108    teardown_hooks: Vec<Box<dyn FnOnce(&CurrentTask) + Send>>,
109}
110
111impl ActiveBinding {
112    fn is_stale(&self) -> bool {
113        !self.instance_active.load(Ordering::Acquire)
114            || self
115                .monitored_channel
116                .as_ref()
117                .is_some_and(|ch| ch.as_channel().is_closed().unwrap_or(true))
118    }
119
120    fn teardown(self, current_task: &CurrentTask) {
121        for hook in self.teardown_hooks.into_iter().rev() {
122            hook(current_task);
123        }
124    }
125}
126
127struct DriverState<Bind> {
128    bind: Bind,
129    active_bindings: Vec<ActiveBinding>,
130}
131
132trait ServiceDriver<S: ServiceMarker>: Send + Sync {
133    fn handle_instance<'a>(
134        &'a self,
135        state: &'a Arc<ServiceWatcherState<S>>,
136        weak_kernel: &'a Weak<Kernel>,
137        instance: InstanceEntry<S::Proxy>,
138    ) -> BoxFuture<'a, ()>;
139
140    fn cleanup_stale_bindings<'a>(
141        &'a self,
142        state: &'a Arc<ServiceWatcherState<S>>,
143        weak_kernel: &'a Weak<Kernel>,
144    ) -> BoxFuture<'a, ()>;
145}
146
147struct DriverEntry<S: ServiceMarker, P, Probe, ProbeFut, Bind> {
148    singleton: bool,
149    probe: Probe,
150    /// Protects `bind` and `active_bindings`, serializing all `teardown` and `bind` calls for
151    /// this driver so an old binding's teardown always completes before a replacement or
152    /// restarted instance's `bind` closure runs.
153    ///
154    /// To prevent lock inversion with [`ServiceWatcherState::inner`], code holding `state` must
155    /// never lock `ServiceWatcherState::inner`; instance liveness and channel closure are checked
156    /// via lock-free atomics (`InstanceEntry::active`) and kernel handle signals.
157    state: Mutex<DriverState<Bind>>,
158    _marker: PhantomData<fn(S, P, ProbeFut)>,
159}
160
161impl<S, P, Probe, ProbeFut, Bind> DriverEntry<S, P, Probe, ProbeFut, Bind>
162where
163    S: ServiceMarker<Proxy: Send + Sync + 'static>,
164    P: Send + 'static,
165    Probe: Fn(Arc<S::Proxy>) -> ProbeFut + Send + Sync + 'static,
166    ProbeFut: Future<Output = Option<P>> + Send + 'static,
167    Bind: FnMut(&CurrentTask, P, &mut ServiceBindingScope) + Send + 'static,
168{
169    fn new(singleton: bool, probe: Probe, bind: Bind) -> Arc<Self> {
170        Arc::new(Self {
171            singleton,
172            probe,
173            state: Mutex::new(DriverState { bind, active_bindings: Vec::new() }),
174            _marker: PhantomData,
175        })
176    }
177
178    fn cleanup_stale_bindings_locked(
179        state: &mut DriverState<Bind>,
180        current_task: &CurrentTask,
181    ) -> bool {
182        let mut removed_any = false;
183        let mut i = 0;
184        while i < state.active_bindings.len() {
185            if state.active_bindings[i].is_stale() {
186                let binding = state.active_bindings.swap_remove(i);
187                binding.instance_active.store(false, Ordering::Release);
188                binding.teardown(current_task);
189                removed_any = true;
190            } else {
191                i += 1;
192            }
193        }
194        removed_any
195    }
196
197    async fn cleanup_stale_bindings(
198        &self,
199        watcher_state: &Arc<ServiceWatcherState<S>>,
200        weak_kernel: &Weak<Kernel>,
201    ) {
202        let should_promote = {
203            let Some(kernel) = weak_kernel.upgrade() else {
204                return;
205            };
206            let system_task = kernel.kthreads.system_task();
207            let mut state = self.state.lock();
208            let removed = Self::cleanup_stale_bindings_locked(&mut state, system_task);
209            self.singleton && removed && state.active_bindings.is_empty()
210        };
211        watcher_state.prune_inactive_instances();
212        if should_promote {
213            self.promote_singleton_instance(watcher_state, weak_kernel).await;
214        }
215    }
216
217    async fn promote_singleton_instance(
218        &self,
219        watcher_state: &Arc<ServiceWatcherState<S>>,
220        weak_kernel: &Weak<Kernel>,
221    ) {
222        for candidate in watcher_state.active_instances() {
223            if !self.state.lock().active_bindings.is_empty() {
224                break;
225            }
226            self.probe_and_bind_instance(watcher_state, weak_kernel, candidate).await;
227        }
228    }
229
230    async fn probe_and_bind_instance(
231        &self,
232        watcher_state: &Arc<ServiceWatcherState<S>>,
233        weak_kernel: &Weak<Kernel>,
234        instance: InstanceEntry<S::Proxy>,
235    ) {
236        let Some(probed) = (self.probe)(instance.proxy.clone()).await else {
237            return;
238        };
239        if let Some(kernel) = weak_kernel.upgrade()
240            && instance.active.load(Ordering::Acquire)
241        {
242            self.bind_probed(kernel.kthreads.system_task(), watcher_state, &instance, probed);
243        }
244    }
245
246    fn bind_probed(
247        &self,
248        system_task: &CurrentTask,
249        watcher_state: &Arc<ServiceWatcherState<S>>,
250        instance: &InstanceEntry<S::Proxy>,
251        probed: P,
252    ) {
253        let monitored_channel = {
254            let mut state = self.state.lock();
255
256            // Synchronously tear down any bindings whose channels have already closed or whose
257            // service directory entries have been removed or replaced so a restarted provider
258            // instance never races against a pending `OnSignals` or `REMOVE_FILE` wakeup.
259            Self::cleanup_stale_bindings_locked(&mut state, system_task);
260
261            if !instance.active.load(Ordering::Acquire) {
262                None
263            } else if state
264                .active_bindings
265                .iter()
266                .any(|binding| Arc::ptr_eq(&binding.instance_active, &instance.active))
267            {
268                None
269            } else if self.singleton && !state.active_bindings.is_empty() {
270                log_warn!(
271                    "{}: ignoring additional service instance while singleton driver is bound",
272                    S::SERVICE_NAME
273                );
274                None
275            } else {
276                let mut scope = ServiceBindingScope::default();
277                (state.bind)(system_task, probed, &mut scope);
278
279                let (cancel_tx, monitored) = match scope.monitored_channel.clone() {
280                    Some(channel) => {
281                        let (tx, rx) = futures::channel::oneshot::channel();
282                        (Some(tx), Some((channel, rx)))
283                    }
284                    None => (None, None),
285                };
286                state.active_bindings.push(ActiveBinding {
287                    instance_active: instance.active.clone(),
288                    monitored_channel: scope.monitored_channel,
289                    _cancel_monitor: cancel_tx,
290                    teardown_hooks: scope.teardown_hooks,
291                });
292                monitored
293            }
294        };
295
296        watcher_state.prune_inactive_instances();
297
298        if let Some((monitored_channel, cancel_rx)) = monitored_channel {
299            let kernel = system_task.kernel();
300            let weak_kernel = kernel.weak_self.clone();
301            let watcher_state = watcher_state.clone();
302            let instance_active = instance.active.clone();
303            kernel.kthreads.spawn_future(
304                move || async move {
305                    let peer_closed = {
306                        let on_signals = std::pin::pin!(fasync::OnSignals::new(
307                            monitored_channel.as_channel(),
308                            zx::Signals::CHANNEL_PEER_CLOSED,
309                        ));
310                        matches!(
311                            futures::future::select(on_signals, cancel_rx).await,
312                            futures::future::Either::Left(_)
313                        )
314                    };
315                    drop(monitored_channel);
316                    if peer_closed {
317                        let drivers = watcher_state.deactivate_instance(&instance_active);
318                        futures::future::join_all(drivers.into_iter().map(|driver| {
319                            let watcher_state = &watcher_state;
320                            let weak_kernel = &weak_kernel;
321                            async move {
322                                driver.cleanup_stale_bindings(watcher_state, weak_kernel).await;
323                            }
324                        }))
325                        .await;
326                    }
327                },
328                "service_watcher_disconnect_monitor",
329            );
330        }
331    }
332}
333
334impl<S, P, Probe, ProbeFut, Bind> ServiceDriver<S> for DriverEntry<S, P, Probe, ProbeFut, Bind>
335where
336    S: ServiceMarker<Proxy: Send + Sync + 'static>,
337    P: Send + 'static,
338    Probe: Fn(Arc<S::Proxy>) -> ProbeFut + Send + Sync + 'static,
339    ProbeFut: Future<Output = Option<P>> + Send + 'static,
340    Bind: FnMut(&CurrentTask, P, &mut ServiceBindingScope) + Send + 'static,
341{
342    fn handle_instance<'a>(
343        &'a self,
344        state: &'a Arc<ServiceWatcherState<S>>,
345        weak_kernel: &'a Weak<Kernel>,
346        instance: InstanceEntry<S::Proxy>,
347    ) -> BoxFuture<'a, ()> {
348        Box::pin(async move {
349            self.probe_and_bind_instance(state, weak_kernel, instance).await;
350            DriverEntry::cleanup_stale_bindings(self, state, weak_kernel).await;
351        })
352    }
353
354    fn cleanup_stale_bindings<'a>(
355        &'a self,
356        state: &'a Arc<ServiceWatcherState<S>>,
357        weak_kernel: &'a Weak<Kernel>,
358    ) -> BoxFuture<'a, ()> {
359        Box::pin(DriverEntry::cleanup_stale_bindings(self, state, weak_kernel))
360    }
361}
362
363struct ServiceWatcherInner<S: ServiceMarker> {
364    instances: Vec<InstanceEntry<S::Proxy>>,
365    drivers: Vec<Arc<dyn ServiceDriver<S>>>,
366}
367
368impl<S: ServiceMarker> Default for ServiceWatcherInner<S> {
369    fn default() -> Self {
370        Self { instances: Vec::new(), drivers: Vec::new() }
371    }
372}
373
374struct ServiceWatcherState<S: ServiceMarker> {
375    watch_started: AtomicBool,
376    inner: Mutex<ServiceWatcherInner<S>>,
377}
378
379impl<S: ServiceMarker> Default for ServiceWatcherState<S> {
380    fn default() -> Self {
381        Self {
382            watch_started: AtomicBool::new(false),
383            inner: Mutex::new(ServiceWatcherInner::default()),
384        }
385    }
386}
387
388enum WatchDispatch<S: ServiceMarker> {
389    Bind(InstanceEntry<S::Proxy>, Vec<Arc<dyn ServiceDriver<S>>>),
390    Remove(Vec<Arc<dyn ServiceDriver<S>>>),
391}
392
393impl<S: ServiceMarker<Proxy: Send + Sync + 'static>> ServiceWatcherState<S> {
394    fn deactivate_instance(
395        &self,
396        instance_active: &Arc<AtomicBool>,
397    ) -> Vec<Arc<dyn ServiceDriver<S>>> {
398        instance_active.store(false, Ordering::Release);
399        let mut inner = self.inner.lock();
400        inner.instances.retain(|entry| entry.active.load(Ordering::Acquire));
401        inner.drivers.clone()
402    }
403
404    fn prune_inactive_instances(&self) {
405        self.inner.lock().instances.retain(|entry| entry.active.load(Ordering::Acquire));
406    }
407
408    fn active_instances(&self) -> Vec<InstanceEntry<S::Proxy>> {
409        let mut inner = self.inner.lock();
410        inner.instances.retain(|entry| entry.active.load(Ordering::Acquire));
411        inner.instances.clone()
412    }
413
414    fn record_instance(
415        &self,
416        instance_name: String,
417        proxy: Arc<S::Proxy>,
418    ) -> (InstanceEntry<S::Proxy>, Vec<Arc<dyn ServiceDriver<S>>>) {
419        let mut inner = self.inner.lock();
420        inner.instances.retain(|entry| {
421            if entry.name == instance_name {
422                entry.active.store(false, Ordering::Release);
423                false
424            } else {
425                entry.active.load(Ordering::Acquire)
426            }
427        });
428        let entry =
429            InstanceEntry { name: instance_name, proxy, active: Arc::new(AtomicBool::new(true)) };
430        inner.instances.push(entry.clone());
431        let drivers = inner.drivers.clone();
432        (entry, drivers)
433    }
434
435    fn remove_instance(&self, instance_name: &str) -> Vec<Arc<dyn ServiceDriver<S>>> {
436        let mut inner = self.inner.lock();
437        inner.instances.retain(|entry| {
438            if entry.name == instance_name {
439                entry.active.store(false, Ordering::Release);
440                false
441            } else {
442                entry.active.load(Ordering::Acquire)
443            }
444        });
445        inner.drivers.clone()
446    }
447
448    async fn watch_service_dir(
449        self: &Arc<Self>,
450        weak_kernel: &Weak<Kernel>,
451        service_dir: fio::DirectoryProxy,
452    ) -> Result<(), anyhow::Error> {
453        let watcher = Watcher::new(&service_dir)
454            .await
455            .with_context(|| format!("failed to watch {} service", S::SERVICE_NAME))?;
456        let sync_dir = Arc::new(fio::DirectorySynchronousProxy::new(
457            service_dir
458                .into_channel()
459                .map_err(|_| anyhow::anyhow!("service directory proxy still in use"))?
460                .into_zx_channel(),
461        ));
462        watcher
463            .take_while(|msg| {
464                let keep_going = weak_kernel.strong_count() > 0
465                    && !matches!(msg, Ok(WatchMessage { event: WatchEvent::DELETED, .. }));
466                std::future::ready(keep_going)
467            })
468            .try_for_each_concurrent(/* limit = */ None, |msg| {
469                let dispatch = msg.filename.to_str().filter(|&name| name != ".").and_then(
470                    |filename| match msg.event {
471                        WatchEvent::ADD_FILE | WatchEvent::EXISTING => {
472                            let instance_name = filename.to_owned();
473                            let proxy = Arc::new(S::Proxy::from_member_opener(Box::new(
474                                SyncServiceInstanceOpener {
475                                    service_dir: sync_dir.clone(),
476                                    instance_name: instance_name.clone(),
477                                },
478                            )));
479                            let (entry, drivers) = self.record_instance(instance_name, proxy);
480                            Some(WatchDispatch::Bind(entry, drivers))
481                        }
482                        WatchEvent::REMOVE_FILE => {
483                            Some(WatchDispatch::Remove(self.remove_instance(filename)))
484                        }
485                        _ => None,
486                    },
487                );
488                async move {
489                    match dispatch {
490                        Some(WatchDispatch::Bind(entry, drivers)) => {
491                            let futures = drivers.into_iter().map(|driver| {
492                                let entry = entry.clone();
493                                async move {
494                                    driver.handle_instance(self, weak_kernel, entry).await;
495                                }
496                            });
497                            futures::future::join_all(futures).await;
498                        }
499                        Some(WatchDispatch::Remove(drivers)) => {
500                            let futures = drivers.into_iter().map(|driver| async move {
501                                driver.cleanup_stale_bindings(self, weak_kernel).await;
502                            });
503                            futures::future::join_all(futures).await;
504                        }
505                        None => {}
506                    }
507                    Ok(())
508                }
509            })
510            .await
511            .with_context(|| format!("error watching {} service", S::SERVICE_NAME))
512    }
513}
514
515/// Per-[`Kernel`] manager that watches `/svc/<S::SERVICE_NAME>` at most once and fans out
516/// discovered [`ServiceMarker::Proxy`] instances to registered Starnix drivers.
517///
518/// Active instances are retained so drivers registered after initial discovery still receive
519/// previously discovered live instances, and each bound instance can register devices and
520/// disconnect hooks on a [`ServiceBindingScope`] that are automatically torn down when a monitored
521/// channel closes or when the service instance directory entry is removed or replaced.
522#[derive(Debug)]
523pub struct ServiceWatcher<S: ServiceMarker>(PhantomData<S>);
524
525impl<S: ServiceMarker<Proxy: Send + Sync + 'static>> ServiceWatcher<S> {
526    /// Registers a driver that binds each discovered service instance on the kernel's main async
527    /// executor.
528    pub fn register<Bind>(kernel: &Kernel, marker: S, bind: Bind)
529    where
530        Bind: FnMut(&CurrentTask, Arc<S::Proxy>, &mut ServiceBindingScope) + Send + 'static,
531    {
532        Self::register_driver(
533            kernel,
534            marker,
535            /* singleton = */ false,
536            async |proxy| Some(proxy),
537            bind,
538        );
539    }
540
541    /// Registers a singleton driver that binds at most one service instance at a time on the
542    /// kernel's main async executor, ignoring additional instances while bound and promoting a
543    /// remaining active instance if the bound instance disconnects.
544    pub fn register_singleton<Bind>(kernel: &Kernel, marker: S, bind: Bind)
545    where
546        Bind: FnMut(&CurrentTask, Arc<S::Proxy>, &mut ServiceBindingScope) + Send + 'static,
547    {
548        Self::register_driver(
549            kernel,
550            marker,
551            /* singleton = */ true,
552            async |proxy| Some(proxy),
553            bind,
554        );
555    }
556
557    /// Registers a driver that probes each discovered service instance asynchronously (without
558    /// holding kernel locks) and, when `probe` returns `Some(probed)`, invokes `bind` on the
559    /// kernel's main async executor with
560    /// [`KernelThreads::system_task`](crate::task::KernelThreads::system_task) and a
561    /// [`ServiceBindingScope`].
562    ///
563    /// `probe` futures may run concurrently across instances, whereas `bind` and teardown
564    /// invocations are serialized per driver after synchronously tearing down any stale bindings.
565    /// Any active service instances discovered before this call are replayed to the driver on the
566    /// kernel's main async executor.
567    pub fn register_with_probe<P, Probe, ProbeFut, Bind>(
568        kernel: &Kernel,
569        marker: S,
570        probe: Probe,
571        bind: Bind,
572    ) where
573        P: Send + 'static,
574        Probe: Fn(Arc<S::Proxy>) -> ProbeFut + Send + Sync + 'static,
575        ProbeFut: Future<Output = Option<P>> + Send + 'static,
576        Bind: FnMut(&CurrentTask, P, &mut ServiceBindingScope) + Send + 'static,
577    {
578        Self::register_driver(kernel, marker, /* singleton = */ false, probe, bind);
579    }
580
581    fn ensure_watcher_started(kernel: &Kernel) -> Arc<ServiceWatcherState<S>> {
582        let state = kernel.expando.get::<ServiceWatcherState<S>>();
583        if !state.watch_started.swap(true, Ordering::AcqRel) {
584            let weak_kernel = kernel.weak_self.clone();
585            let state_for_task = state.clone();
586            kernel.kthreads.spawn_future(
587                move || async move {
588                    // Open the service directory inside the future so its channel receiver is
589                    // registered on the `kthreads` executor rather than the caller's thread-local
590                    // executor.
591                    let result = match fclient::open_service::<S>() {
592                        Ok(service_dir) => {
593                            state_for_task.watch_service_dir(&weak_kernel, service_dir).await
594                        }
595                        Err(e) => Err(e.context("failed to open service directory")),
596                    };
597                    if let Err(e) = result {
598                        log_info!("{}: service unavailable: {e:#}", S::SERVICE_NAME);
599                    }
600                },
601                S::SERVICE_NAME,
602            );
603        }
604        state
605    }
606
607    fn register_driver<P, Probe, ProbeFut, Bind>(
608        kernel: &Kernel,
609        _marker: S,
610        singleton: bool,
611        probe: Probe,
612        bind: Bind,
613    ) where
614        P: Send + 'static,
615        Probe: Fn(Arc<S::Proxy>) -> ProbeFut + Send + Sync + 'static,
616        ProbeFut: Future<Output = Option<P>> + Send + 'static,
617        Bind: FnMut(&CurrentTask, P, &mut ServiceBindingScope) + Send + 'static,
618    {
619        let state = Self::ensure_watcher_started(kernel);
620        let driver: Arc<dyn ServiceDriver<S>> = DriverEntry::new(singleton, probe, bind);
621        let existing_instances = {
622            let mut inner = state.inner.lock();
623            inner.instances.retain(|entry| entry.active.load(Ordering::Acquire));
624            let instances = inner.instances.clone();
625            inner.drivers.push(driver.clone());
626            instances
627        };
628        if !existing_instances.is_empty() {
629            let weak_kernel = kernel.weak_self.clone();
630            kernel.kthreads.spawn_future(
631                move || async move {
632                    futures::future::join_all(
633                        existing_instances
634                            .into_iter()
635                            .map(|instance| driver.handle_instance(&state, &weak_kernel, instance)),
636                    )
637                    .await;
638                },
639                "service_watcher_replay",
640            );
641        }
642    }
643}
644
645struct SyncServiceInstanceOpener {
646    service_dir: Arc<fio::DirectorySynchronousProxy>,
647    instance_name: String,
648}
649
650impl MemberOpener for SyncServiceInstanceOpener {
651    fn open_member(&self, member: &str, server_end: zx::Channel) -> Result<(), fidl::Error> {
652        let path = format!("{}/{}", self.instance_name, member);
653        self.service_dir.open(
654            &path,
655            fio::Flags::PROTOCOL_SERVICE,
656            &fio::Options::default(),
657            server_end,
658        )
659    }
660
661    fn instance_name(&self) -> &str {
662        &self.instance_name
663    }
664}
665
666/// Connects `service_dir` to the per-[`Kernel`] [`ServiceWatcher`] for `S` on the kernel's main
667/// async executor while suppressing the default `/svc/<S::SERVICE_NAME>` lookup.
668#[cfg(test)]
669pub(crate) fn watch_service_dir_for_testing<S>(kernel: &Kernel, service_dir: fio::DirectoryProxy)
670where
671    S: ServiceMarker<Proxy: Send + Sync + 'static>,
672{
673    let state = kernel.expando.get::<ServiceWatcherState<S>>();
674    state.watch_started.store(true, Ordering::Release);
675    let weak_kernel = kernel.weak_self.clone();
676    kernel.kthreads.spawn_future(
677        move || async move {
678            let _ = state.watch_service_dir(&weak_kernel, service_dir).await;
679        },
680        "service_watcher_test_dir",
681    );
682}
683
684#[cfg(test)]
685mod tests {
686    use super::*;
687    use crate::device::mem::DevNull;
688    use crate::device::{DeviceMode, simple_device_ops};
689    use crate::testing::spawn_kernel_and_run;
690    use fidl_fuchsia_hardware_power_stats as fpowerstats;
691    use futures::channel::{mpsc, oneshot};
692    use starnix_uapi::error;
693    use std::collections::HashSet;
694    use vfs::directory::helper::DirectlyMutable as _;
695    use vfs::directory::immutable::Simple;
696
697    fn start_test_service_dir(kernel: &Kernel) -> Arc<Simple> {
698        let dir = vfs::directory::immutable::simple();
699        let proxy = vfs::directory::serve_read_only(dir.clone(), vfs::ExecutionScope::new());
700        watch_service_dir_for_testing::<fpowerstats::ServiceMarker>(kernel, proxy);
701        dir
702    }
703
704    fn add_instance(
705        dir: &Arc<Simple>,
706        name: &str,
707        on_open: impl Fn(zx::Channel) + Send + Sync + 'static,
708    ) {
709        let instance_dir = vfs::pseudo_directory! {
710            "device" => vfs::service::endpoint(move |_scope, channel| {
711                on_open(channel.into_zx_channel());
712            }),
713        };
714        dir.add_entry_may_overwrite(name, instance_dir, /* overwrite = */ true)
715            .expect("add service instance");
716    }
717
718    #[::fuchsia::test]
719    async fn watches_multiple_instances_concurrently() {
720        spawn_kernel_and_run(async |current_task| {
721            let kernel = current_task.kernel();
722            let dir = vfs::directory::immutable::simple();
723            add_instance(&dir, "first", |_| {});
724            add_instance(&dir, "second", |_| {});
725
726            let (second_tx, second_rx) = oneshot::channel();
727            let second_tx = Arc::new(Mutex::new(Some(second_tx)));
728            let second_rx = Arc::new(Mutex::new(Some(second_rx)));
729            let (done_tx, done_rx) = oneshot::channel();
730            let done_tx = Arc::new(Mutex::new(Some(done_tx)));
731            let seen = Arc::new(Mutex::new(HashSet::new()));
732
733            ServiceWatcher::register_with_probe(
734                kernel,
735                fpowerstats::ServiceMarker,
736                move |instance| {
737                    let second_tx = second_tx.clone();
738                    let second_rx = second_rx.clone();
739                    async move {
740                        let name = instance.instance_name().to_string();
741                        if name == "first" {
742                            // Wait for `"second"` to enter `probe` before completing `"first"`,
743                            // verifying that `probe` runs concurrently across instances.
744                            let rx = second_rx.lock().take().expect("second_rx taken once");
745                            let _ = rx.await;
746                        } else if name == "second" {
747                            if let Some(tx) = second_tx.lock().take() {
748                                let _ = tx.send(());
749                            }
750                        }
751                        Some(name)
752                    }
753                },
754                move |_current_task, name, _scope| {
755                    let mut seen = seen.lock();
756                    seen.insert(name);
757                    if seen.len() == 2
758                        && let Some(tx) = done_tx.lock().take()
759                    {
760                        let _ = tx.send(std::mem::take(&mut *seen));
761                    }
762                },
763            );
764
765            let proxy = vfs::directory::serve_read_only(dir, vfs::ExecutionScope::new());
766            watch_service_dir_for_testing::<fpowerstats::ServiceMarker>(kernel, proxy);
767
768            let seen = done_rx.await.expect("both instances discovered concurrently");
769            assert_eq!(seen, HashSet::from(["first".to_string(), "second".to_string()]));
770        })
771        .await;
772    }
773
774    #[::fuchsia::test]
775    async fn fans_out_to_multiple_drivers_and_replays_to_late_subscribers() {
776        spawn_kernel_and_run(async |current_task| {
777            let kernel = current_task.kernel();
778            let dir = start_test_service_dir(kernel);
779
780            let (early_tx, mut early_rx) = mpsc::unbounded();
781            ServiceWatcher::register(
782                kernel,
783                fpowerstats::ServiceMarker,
784                move |_current_task, instance, _scope| {
785                    let _ = early_tx.unbounded_send(instance.instance_name().to_string());
786                },
787            );
788
789            add_instance(&dir, "first", |_| {});
790            assert_eq!(early_rx.next().await.as_deref(), Some("first"));
791
792            // Register a second subscriber after `"first"` has already been discovered.
793            let (late_tx, mut late_rx) = mpsc::unbounded();
794            ServiceWatcher::register(
795                kernel,
796                fpowerstats::ServiceMarker,
797                move |_current_task, instance, _scope| {
798                    let _ = late_tx.unbounded_send(instance.instance_name().to_string());
799                },
800            );
801            assert_eq!(late_rx.next().await.as_deref(), Some("first"));
802
803            // Subsequent instances fan out to both early and late subscribers.
804            add_instance(&dir, "second", |_| {});
805            assert_eq!(early_rx.next().await.as_deref(), Some("second"));
806            assert_eq!(late_rx.next().await.as_deref(), Some("second"));
807        })
808        .await;
809    }
810
811    #[::fuchsia::test]
812    async fn tears_down_scoped_devices_and_hooks_on_peer_close() {
813        spawn_kernel_and_run(async |current_task| {
814            let kernel = current_task.kernel();
815            let dir = start_test_service_dir(kernel);
816            let (event_tx, mut event_rx) = mpsc::unbounded();
817            let (all_seen_tx, mut all_seen_rx) = mpsc::unbounded();
818            let devts = Arc::new(Mutex::new(Vec::new()));
819            let devts_for_bind = devts.clone();
820
821            ServiceWatcher::register_singleton(
822                kernel,
823                fpowerstats::ServiceMarker,
824                move |current_task, instance, scope| {
825                    let name = instance.instance_name().to_string();
826                    let proxy =
827                        Arc::new(instance.connect_to_device_sync().expect("connect to device"));
828                    scope.monitor_channel(proxy);
829
830                    let tx1 = event_tx.clone();
831                    let name1 = name.clone();
832                    scope.on_disconnect(move |_| {
833                        let _ = tx1.unbounded_send(format!("hook1:{name1}"));
834                    });
835                    let tx2 = event_tx.clone();
836                    let name2 = name.clone();
837                    scope.on_disconnect(move |_| {
838                        let _ = tx2.unbounded_send(format!("hook2:{name2}"));
839                    });
840
841                    let kernel = current_task.kernel();
842                    let misc_dev = scope.add_scoped_device(
843                        kernel
844                            .device_registry
845                            .add_misc_device(
846                                kernel,
847                                "watcher_misc".into(),
848                                simple_device_ops::<DevNull>,
849                            )
850                            .expect("add misc device"),
851                    );
852                    devts_for_bind.lock().push(misc_dev.metadata().expect("metadata").devt);
853                    let _ = event_tx.unbounded_send(format!("bind:{name}"));
854                },
855            );
856            ServiceWatcher::register(
857                kernel,
858                fpowerstats::ServiceMarker,
859                move |_current_task, instance, _scope| {
860                    let _ = all_seen_tx.unbounded_send(instance.instance_name().to_string());
861                },
862            );
863
864            let (first_srv_tx, mut first_srv_rx) = mpsc::unbounded();
865            add_instance(&dir, "first", move |ch| {
866                let _ = first_srv_tx.unbounded_send(ch);
867            });
868            assert_eq!(event_rx.next().await.as_deref(), Some("bind:first"));
869            assert_eq!(all_seen_rx.next().await.as_deref(), Some("first"));
870            let first_server = first_srv_rx.next().await.expect("first server channel");
871
872            let registry = &kernel.device_registry;
873            let first_devt = devts.lock()[0];
874            assert!(
875                registry.objects.root.lookup("devices/virtual/misc/watcher_misc".into()).is_some()
876            );
877            assert!(registry.get_device(first_devt, DeviceMode::Char).is_ok());
878
879            // Adding `"second"` while `"first"` is active is ignored by the singleton driver.
880            let (second_srv_tx, mut second_srv_rx) = mpsc::unbounded();
881            add_instance(&dir, "second", move |ch| {
882                let _ = second_srv_tx.unbounded_send(ch);
883            });
884            assert_eq!(all_seen_rx.next().await.as_deref(), Some("second"));
885            assert!(event_rx.try_recv().is_err());
886
887            // Closing `"first"` runs teardown hooks in reverse registration order, frees the misc
888            // device ID, and promotes `"second"`.
889            drop(first_server);
890            assert_eq!(event_rx.next().await.as_deref(), Some("hook2:first"));
891            assert_eq!(event_rx.next().await.as_deref(), Some("hook1:first"));
892            assert_eq!(event_rx.next().await.as_deref(), Some("bind:second"));
893            let second_server = second_srv_rx.next().await.expect("second server channel");
894            assert_eq!(devts.lock()[1], first_devt);
895
896            // Closing `"second"` tears down the promoted binding and removes the misc device.
897            drop(second_server);
898            assert_eq!(event_rx.next().await.as_deref(), Some("hook2:second"));
899            assert_eq!(event_rx.next().await.as_deref(), Some("hook1:second"));
900            assert!(
901                registry.objects.root.lookup("devices/virtual/misc/watcher_misc".into()).is_none()
902            );
903            assert_eq!(
904                registry.get_device(first_devt, DeviceMode::Char).map(|_| ()),
905                error!(ENODEV)
906            );
907        })
908        .await;
909    }
910
911    #[::fuchsia::test]
912    async fn tears_down_binding_on_directory_remove_or_replace() {
913        spawn_kernel_and_run(async |current_task| {
914            let kernel = current_task.kernel();
915            let dir = start_test_service_dir(kernel);
916            let (event_tx, mut event_rx) = mpsc::unbounded();
917            let mut generation = 0u32;
918
919            ServiceWatcher::register(
920                kernel,
921                fpowerstats::ServiceMarker,
922                move |current_task, _instance, scope| {
923                    generation += 1;
924                    let current_gen = generation;
925                    let tx_disconnect = event_tx.clone();
926                    scope.on_disconnect(move |_| {
927                        let _ = tx_disconnect.unbounded_send(format!("disconnect:{current_gen}"));
928                    });
929                    let kernel = current_task.kernel();
930                    scope.add_scoped_device(
931                        kernel
932                            .device_registry
933                            .add_misc_device(
934                                kernel,
935                                "watcher_replace_misc".into(),
936                                simple_device_ops::<DevNull>,
937                            )
938                            .expect("add misc device"),
939                    );
940                    let _ = event_tx.unbounded_send(format!("bind:{current_gen}"));
941                },
942            );
943
944            add_instance(&dir, "first", |_| {});
945            assert_eq!(event_rx.next().await.as_deref(), Some("bind:1"));
946            let root = &kernel.device_registry.objects.root;
947            assert!(root.lookup("devices/virtual/misc/watcher_replace_misc".into()).is_some());
948
949            // Replacing `"first"` in the watched directory tears down the existing binding (even
950            // without a monitored channel) before binding the replacement.
951            add_instance(&dir, "first", |_| {});
952            assert_eq!(event_rx.next().await.as_deref(), Some("disconnect:1"));
953            assert_eq!(event_rx.next().await.as_deref(), Some("bind:2"));
954            assert!(root.lookup("devices/virtual/misc/watcher_replace_misc".into()).is_some());
955
956            // Removing `"first"` from the directory tears down the active binding.
957            dir.remove_entry("first", /* must_be_directory = */ false)
958                .expect("remove service instance");
959            assert_eq!(event_rx.next().await.as_deref(), Some("disconnect:2"));
960            assert!(root.lookup("devices/virtual/misc/watcher_replace_misc".into()).is_none());
961        })
962        .await;
963    }
964
965    #[::fuchsia::test]
966    async fn stops_watching_when_kernel_is_dropped() {
967        let dir = vfs::directory::immutable::simple();
968        add_instance(&dir, "first", |_| {});
969        let service_dir = vfs::directory::serve_read_only(dir, vfs::ExecutionScope::new());
970        let state = Arc::new(ServiceWatcherState::<fpowerstats::ServiceMarker>::default());
971        let called = Arc::new(AtomicBool::new(false));
972        let called_for_bind = called.clone();
973        state.inner.lock().drivers.push(DriverEntry::new(
974            /* singleton = */ false,
975            async |_| Some(()),
976            move |_, (), _| {
977                called_for_bind.store(true, Ordering::SeqCst);
978            },
979        ));
980
981        state
982            .watch_service_dir(&Weak::new(), service_dir)
983            .await
984            .expect("service watcher should exit cleanly when kernel is dropped");
985
986        assert!(!called.load(Ordering::SeqCst));
987    }
988}