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}