Skip to main content

behavior_actors/atomic/dynamic_supervisor/
protocol.rs

1//! Commands and immediate replies for one dynamic supervisor.
2
3use behavior::{Behavior, BehaviorAddr, EndpointAddress, MessageProtocol, Protocol};
4
5use crate::{ReplyRoute, WorkerSubmission};
6
7/// Affine authority to cancel one accepted start or replacement.
8///
9/// ```compile_fail,E0382
10/// fn duplicate<Key>(authority: behavior_actors::atomic::CancelAuthority<Key>) {
11///     let first = authority;
12///     let second = authority;
13/// }
14/// ```
15///
16/// Applications cannot forge an operation authority from a key and number:
17///
18/// ```compile_fail,E0451
19/// let _authority = behavior_actors::atomic::CancelAuthority {
20///     key: "search",
21///     operation: 1,
22/// };
23/// ```
24#[must_use = "cancellation authority must be used or deliberately retained"]
25pub struct CancelAuthority<Key> {
26    key: Key,
27    operation: u64,
28}
29
30impl<Key> CancelAuthority<Key> {
31    pub(super) const fn issued(key: Key, operation: u64) -> Self {
32        Self { key, operation }
33    }
34
35    /// Inspect the application key governed by this authority.
36    #[must_use]
37    pub const fn key(&self) -> &Key {
38        &self.key
39    }
40
41    pub(super) const fn operation(&self) -> u64 {
42        self.operation
43    }
44}
45
46/// Immediate proof that one worker change was accepted.
47#[must_use = "accepted worker-change authority must be retained"]
48pub struct WorkerChangeReceipt<Key> {
49    /// Equal application key retained for the requesting actor.
50    pub key: Key,
51    /// Affine authority for this exact accepted operation.
52    pub cancel: CancelAuthority<Key>,
53}
54
55/// Complete worker change returned when admission is rejected.
56#[derive(Debug, Eq, PartialEq)]
57pub struct WorkerChangeRejection<Key, Worker, Plan, Reason> {
58    /// Submitted application key.
59    pub key: Key,
60    /// Submitted worker and its exact activation work.
61    pub submission: WorkerSubmission<Worker, Plan>,
62    /// Operation-specific rejection reason.
63    pub reason: Reason,
64}
65
66/// Why a new keyed service was not admitted.
67#[derive(Clone, Copy, Debug, Eq, PartialEq)]
68pub enum StartRejection {
69    /// The key already belongs to a retained entry.
70    AlreadyExists,
71    /// The configured entry maximum is occupied.
72    AtCapacity,
73    /// Global shutdown has closed mutation.
74    ShuttingDown,
75    /// No fresh entry generation can be issued.
76    EntryGenerationExhausted,
77    /// No fresh ordered management operation can be issued.
78    OperationExhausted,
79    /// No fresh proxy creation ID can be issued.
80    ProxyCreationExhausted,
81}
82
83/// Why a replacement was not admitted.
84#[derive(Clone, Copy, Debug, Eq, PartialEq)]
85pub enum ReplaceRejection {
86    /// The key is not retained.
87    Unknown,
88    /// The current entry phase cannot accept replacement.
89    Unavailable,
90    /// Global shutdown has closed mutation.
91    ShuttingDown,
92    /// No fresh ordered management operation can be issued.
93    OperationExhausted,
94}
95
96/// Complete keyed rejection of an explicit stop.
97#[derive(Clone, Debug, Eq, PartialEq)]
98pub enum StopRejection<Key> {
99    /// The submitted key is not retained.
100    Unknown { key: Key },
101    /// The entry cannot begin an explicit stop in its current phase.
102    Unavailable { key: Key },
103    /// The entry is already stopping.
104    AlreadyStopping { key: Key },
105    /// Global shutdown has closed mutation.
106    ShuttingDown { key: Key },
107    /// No fresh ordered management operation can be issued.
108    OperationExhausted { key: Key },
109}
110
111/// Application-visible phase of one retained keyed service.
112pub enum DynamicStatus<Service>
113where
114    Service: Protocol,
115    Service::Addr: EndpointAddress,
116{
117    CreatingProxy,
118    WaitingForActivation,
119    AwaitingProxy,
120    Ready {
121        /// Stable service capability; never a direct worker capability.
122        proxy: behavior::EstablishedRecipient<Service>,
123    },
124    Empty,
125    Replacing,
126    Stopping,
127    Cancelling,
128    Retiring,
129    /// The supervisor has closed management and is draining this entry.
130    Draining,
131}
132
133/// Immediate reply to a read-only keyed query.
134pub enum QueryReply<Key, Service>
135where
136    Service: Protocol,
137    Service::Addr: EndpointAddress,
138{
139    Unknown {
140        key: Key,
141    },
142    Known {
143        key: Key,
144        status: DynamicStatus<Service>,
145    },
146}
147
148/// Complete immediate result of one cancellation request.
149pub enum CancellationReceipt<Key, Worker, Plan>
150where
151    Worker: Behavior,
152    <Worker::Protocol as Protocol>::Addr: EndpointAddress,
153{
154    Returned {
155        authority: CancelAuthority<Key>,
156        submission: WorkerSubmission<Worker, Plan>,
157    },
158    Pending {
159        authority: CancelAuthority<Key>,
160        phase: DynamicStatus<Worker::Protocol>,
161    },
162    Committed {
163        authority: CancelAuthority<Key>,
164        resulting_phase: DynamicStatus<Worker::Protocol>,
165    },
166    Cancelled {
167        authority: CancelAuthority<Key>,
168    },
169    Stale {
170        authority: CancelAuthority<Key>,
171    },
172    /// Global shutdown owns the operation and returns its affine authority.
173    Draining {
174        authority: CancelAuthority<Key>,
175    },
176}
177
178/// Five application operations accepted by one dynamic supervisor.
179pub enum DynamicCommand<Key, Worker, Plan>
180where
181    Worker: Behavior,
182    BehaviorAddr<Worker>: EndpointAddress,
183{
184    Start {
185        key: Key,
186        submission: WorkerSubmission<Worker, Plan>,
187        reply_to: ReplyRoute<
188            MessageProtocol<
189                BehaviorAddr<Worker>,
190                Result<
191                    WorkerChangeReceipt<Key>,
192                    WorkerChangeRejection<Key, Worker, Plan, StartRejection>,
193                >,
194            >,
195        >,
196    },
197    Replace {
198        key: Key,
199        submission: WorkerSubmission<Worker, Plan>,
200        reply_to: ReplyRoute<
201            MessageProtocol<
202                BehaviorAddr<Worker>,
203                Result<
204                    WorkerChangeReceipt<Key>,
205                    WorkerChangeRejection<Key, Worker, Plan, ReplaceRejection>,
206                >,
207            >,
208        >,
209    },
210    Stop {
211        key: Key,
212        reply_to:
213            ReplyRoute<MessageProtocol<BehaviorAddr<Worker>, Result<Key, StopRejection<Key>>>>,
214    },
215    Query {
216        key: Key,
217        reply_to:
218            ReplyRoute<MessageProtocol<BehaviorAddr<Worker>, QueryReply<Key, Worker::Protocol>>>,
219    },
220    Cancel {
221        authority: CancelAuthority<Key>,
222        reply_to: ReplyRoute<
223            MessageProtocol<BehaviorAddr<Worker>, CancellationReceipt<Key, Worker, Plan>>,
224        >,
225    },
226}
227
228#[cfg(test)]
229mod tests {
230    use super::CancelAuthority;
231
232    #[expect(
233        dead_code,
234        reason = "compile-only proof of one cancellation operation correlation"
235    )]
236    fn cancellation_authority_names_key_and_operation() {
237        let _authority = CancelAuthority::issued("search", 1);
238    }
239}