Skip to main content

dig_service/
handle.rs

1//! Out-of-lifetime service handle.
2//!
3//! A [`ServiceHandle`] is a clone-safe, shareable reference into a running
4//! `Service`. Intended for:
5//!
6//! - OS signal handlers that need to call `request_shutdown`.
7//! - The RPC layer invoking admin operations like `stop_node`.
8//! - Tests that need a way to tear down the service from the outside.
9//!
10//! Handles remain valid after the owning `Service` has dropped — their
11//! operations become no-ops rather than panicking.
12
13use std::sync::Arc;
14
15use parking_lot::RwLock;
16
17use crate::shutdown::{ShutdownReason, ShutdownToken};
18use crate::tasks::TaskRegistry;
19pub use crate::tasks::TaskSummary;
20
21/// Shared state between a `Service` and its handles.
22///
23/// Not part of the public surface — but the `pub(crate)` fields are used
24/// by `Service::new` / `Service::handle`.
25#[derive(Debug)]
26pub(crate) struct HandleState {
27    pub(crate) name: &'static str,
28}
29
30impl HandleState {
31    pub(crate) fn new(name: &'static str) -> Self {
32        Self { name }
33    }
34}
35
36/// A cheap-clone handle into a running `Service`.
37#[derive(Clone)]
38pub struct ServiceHandle {
39    shutdown: ShutdownToken,
40    tasks: TaskRegistry,
41    state: Arc<RwLock<HandleState>>,
42}
43
44impl ServiceHandle {
45    /// Construct from pre-owned inner components. Only called by
46    /// `Service::handle`; not public.
47    pub(crate) fn new(
48        shutdown: ShutdownToken,
49        tasks: TaskRegistry,
50        state: Arc<RwLock<HandleState>>,
51    ) -> Self {
52        Self {
53            shutdown,
54            tasks,
55            state,
56        }
57    }
58
59    /// The service's static name (from `NodeLifecycle::NAME`). Empty string
60    /// if the node chose not to declare one.
61    pub fn name(&self) -> &'static str {
62        self.state.read().name
63    }
64
65    /// Request a graceful shutdown. Idempotent.
66    pub fn request_shutdown(&self, reason: ShutdownReason) {
67        self.shutdown.cancel(reason);
68    }
69
70    /// Whether the underlying service is still running (i.e., shutdown has
71    /// not been cancelled).
72    pub fn is_running(&self) -> bool {
73        !self.shutdown.is_cancelled()
74    }
75
76    /// Borrow the shutdown token. Mostly useful in tests.
77    pub fn shutdown_token(&self) -> &ShutdownToken {
78        &self.shutdown
79    }
80
81    /// Snapshot of all currently-tracked tasks.
82    pub fn tasks(&self) -> Vec<TaskSummary> {
83        self.tasks.snapshot()
84    }
85}
86
87impl std::fmt::Debug for ServiceHandle {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        f.debug_struct("ServiceHandle")
90            .field("name", &self.state.read().name)
91            .field("is_running", &self.is_running())
92            .field("tasks", &self.tasks.len())
93            .finish()
94    }
95}