Skip to main content

rust_supervisor/control/
handle.rs

1//! Public runtime control handle.
2//!
3//! The handle owns the command sender side and exposes asynchronous control
4//! methods. It keeps command construction separate from runtime execution.
5
6use crate::control::command::{CommandMeta, CommandResult, ControlCommand};
7use crate::error::types::SupervisorError;
8use crate::id::types::{ChildId, SupervisorPath};
9use crate::runtime::control_loop::RuntimeCommand;
10use tokio::sync::{broadcast, mpsc, oneshot};
11
12/// Cloneable handle used to control a running supervisor.
13#[derive(Debug, Clone)]
14pub struct SupervisorHandle {
15    /// Sender side used to submit runtime control commands.
16    command_sender: mpsc::Sender<RuntimeCommand>,
17    /// Broadcast sender used to create lifecycle event subscriptions.
18    event_sender: broadcast::Sender<String>,
19}
20
21impl SupervisorHandle {
22    /// Creates a runtime handle from channel senders.
23    ///
24    /// # Arguments
25    ///
26    /// - `command_sender`: Sender used to submit runtime commands.
27    /// - `event_sender`: Sender used to subscribe to lifecycle events.
28    ///
29    /// # Returns
30    ///
31    /// Returns a [`SupervisorHandle`].
32    pub fn new(
33        command_sender: mpsc::Sender<RuntimeCommand>,
34        event_sender: broadcast::Sender<String>,
35    ) -> Self {
36        Self {
37            command_sender,
38            event_sender,
39        }
40    }
41
42    /// Adds a child manifest under a supervisor path.
43    ///
44    /// # Arguments
45    ///
46    /// - `target`: Supervisor path that should receive the child.
47    /// - `child_manifest`: Child manifest text supplied by the caller.
48    /// - `requested_by`: Actor that requested the command.
49    /// - `reason`: Human-readable command reason.
50    ///
51    /// # Returns
52    ///
53    /// Returns a [`CommandResult`] after the runtime accepts the command.
54    pub async fn add_child(
55        &self,
56        target: SupervisorPath,
57        child_manifest: impl Into<String>,
58        requested_by: impl Into<String>,
59        reason: impl Into<String>,
60    ) -> Result<CommandResult, SupervisorError> {
61        self.send(ControlCommand::AddChild {
62            meta: CommandMeta::new(requested_by, reason),
63            target,
64            child_manifest: child_manifest.into(),
65        })
66        .await
67    }
68
69    /// Removes a child from runtime governance.
70    ///
71    /// # Arguments
72    ///
73    /// - `child_id`: Target child identifier.
74    /// - `requested_by`: Actor that requested the command.
75    /// - `reason`: Human-readable command reason.
76    ///
77    /// # Returns
78    ///
79    /// Returns a [`CommandResult`] after removal or idempotent reuse.
80    pub async fn remove_child(
81        &self,
82        child_id: ChildId,
83        requested_by: impl Into<String>,
84        reason: impl Into<String>,
85    ) -> Result<CommandResult, SupervisorError> {
86        self.child_command(child_id, requested_by, reason, |meta, child_id| {
87            ControlCommand::RemoveChild { meta, child_id }
88        })
89        .await
90    }
91
92    /// Restarts a child explicitly.
93    ///
94    /// # Arguments
95    ///
96    /// - `child_id`: Target child identifier.
97    /// - `requested_by`: Actor that requested the command.
98    /// - `reason`: Human-readable command reason.
99    ///
100    /// # Returns
101    ///
102    /// Returns a [`CommandResult`] after restart dispatch.
103    pub async fn restart_child(
104        &self,
105        child_id: ChildId,
106        requested_by: impl Into<String>,
107        reason: impl Into<String>,
108    ) -> Result<CommandResult, SupervisorError> {
109        self.child_command(child_id, requested_by, reason, |meta, child_id| {
110            ControlCommand::RestartChild { meta, child_id }
111        })
112        .await
113    }
114
115    /// Pauses a child idempotently.
116    ///
117    /// # Arguments
118    ///
119    /// - `child_id`: Target child identifier.
120    /// - `requested_by`: Actor that requested the command.
121    /// - `reason`: Human-readable command reason.
122    ///
123    /// # Returns
124    ///
125    /// Returns the current child state after the command.
126    pub async fn pause_child(
127        &self,
128        child_id: ChildId,
129        requested_by: impl Into<String>,
130        reason: impl Into<String>,
131    ) -> Result<CommandResult, SupervisorError> {
132        self.child_command(child_id, requested_by, reason, |meta, child_id| {
133            ControlCommand::PauseChild { meta, child_id }
134        })
135        .await
136    }
137
138    /// Resumes a child idempotently.
139    ///
140    /// # Arguments
141    ///
142    /// - `child_id`: Target child identifier.
143    /// - `requested_by`: Actor that requested the command.
144    /// - `reason`: Human-readable command reason.
145    ///
146    /// # Returns
147    ///
148    /// Returns the current child state after the command.
149    pub async fn resume_child(
150        &self,
151        child_id: ChildId,
152        requested_by: impl Into<String>,
153        reason: impl Into<String>,
154    ) -> Result<CommandResult, SupervisorError> {
155        self.child_command(child_id, requested_by, reason, |meta, child_id| {
156            ControlCommand::ResumeChild { meta, child_id }
157        })
158        .await
159    }
160
161    /// Quarantines a child idempotently.
162    ///
163    /// # Arguments
164    ///
165    /// - `child_id`: Target child identifier.
166    /// - `requested_by`: Actor that requested the command.
167    /// - `reason`: Human-readable command reason.
168    ///
169    /// # Returns
170    ///
171    /// Returns the current child state after the command.
172    pub async fn quarantine_child(
173        &self,
174        child_id: ChildId,
175        requested_by: impl Into<String>,
176        reason: impl Into<String>,
177    ) -> Result<CommandResult, SupervisorError> {
178        self.child_command(child_id, requested_by, reason, |meta, child_id| {
179            ControlCommand::QuarantineChild { meta, child_id }
180        })
181        .await
182    }
183
184    /// Shuts down the supervisor tree idempotently.
185    ///
186    /// # Arguments
187    ///
188    /// - `requested_by`: Actor that requested shutdown.
189    /// - `reason`: Human-readable shutdown reason.
190    ///
191    /// # Returns
192    ///
193    /// Returns the current shutdown result.
194    pub async fn shutdown_tree(
195        &self,
196        requested_by: impl Into<String>,
197        reason: impl Into<String>,
198    ) -> Result<CommandResult, SupervisorError> {
199        self.send(ControlCommand::ShutdownTree {
200            meta: CommandMeta::new(requested_by, reason),
201        })
202        .await
203    }
204
205    /// Queries the current runtime state.
206    ///
207    /// # Arguments
208    ///
209    /// This function has no arguments.
210    ///
211    /// # Returns
212    ///
213    /// Returns a [`CommandResult::CurrentState`] value.
214    pub async fn current_state(&self) -> Result<CommandResult, SupervisorError> {
215        self.send(ControlCommand::CurrentState {
216            meta: CommandMeta::new("system", "current_state"),
217        })
218        .await
219    }
220
221    /// Subscribes to runtime event text emitted by the control loop.
222    ///
223    /// # Arguments
224    ///
225    /// This function has no arguments.
226    ///
227    /// # Returns
228    ///
229    /// Returns a broadcast receiver for event text.
230    pub fn subscribe_events(&self) -> broadcast::Receiver<String> {
231        self.event_sender.subscribe()
232    }
233
234    /// Sends one control command and waits for the result.
235    ///
236    /// # Arguments
237    ///
238    /// - `command`: Command that should be executed by the runtime loop.
239    ///
240    /// # Returns
241    ///
242    /// Returns a command result or a supervisor error when the runtime is gone.
243    async fn send(&self, command: ControlCommand) -> Result<CommandResult, SupervisorError> {
244        let (reply_sender, reply_receiver) = oneshot::channel();
245        self.command_sender
246            .send(RuntimeCommand::Control {
247                command,
248                reply_sender,
249            })
250            .await
251            .map_err(|_| SupervisorError::InvalidTransition {
252                message: "runtime control loop is closed".to_owned(),
253            })?;
254        reply_receiver
255            .await
256            .map_err(|_| SupervisorError::InvalidTransition {
257                message: "runtime control loop dropped command reply".to_owned(),
258            })?
259    }
260
261    /// Builds and sends a child-targeted command.
262    ///
263    /// # Arguments
264    ///
265    /// - `child_id`: Target child identifier.
266    /// - `requested_by`: Actor that requested the command.
267    /// - `reason`: Human-readable command reason.
268    /// - `builder`: Command builder for the child operation.
269    ///
270    /// # Returns
271    ///
272    /// Returns a command result from the runtime loop.
273    async fn child_command<F>(
274        &self,
275        child_id: ChildId,
276        requested_by: impl Into<String>,
277        reason: impl Into<String>,
278        builder: F,
279    ) -> Result<CommandResult, SupervisorError>
280    where
281        F: FnOnce(CommandMeta, ChildId) -> ControlCommand,
282    {
283        let meta = CommandMeta::new(requested_by, reason);
284        self.send(builder(meta, child_id)).await
285    }
286}