1use behavior::{
4 ActionItemResult, Behavior, BehaviorAddr, EndpointAddress, InterpreterFault, Protocol,
5};
6
7use crate::{
8 ActivationPlan, ChildStopped, ProxyOperation, ProxyOutcome, ProxyPhase, ScheduleAfter,
9 ScheduleAfterRejection, StableProxy, WorkerSubmission,
10};
11
12use behavior::ChildInputReason;
13
14use super::super::{PreparedWorker, RoleName, WorkerSource};
15use super::restart::RecoveryDenialReason;
16use super::{FixedSupervisorEvent, PrepareWorkers, WorkerPreparation};
17
18pub enum FixedDiagnostic<Role, Worker, Plan, Source>
20where
21 Role: Send + Sync,
22 Worker: Behavior + Send,
23 Plan: ActivationPlan,
24 Source: WorkerSource<Role, Worker, Plan>,
25 BehaviorAddr<Worker>: EndpointAddress,
26 StableProxy<Worker, Plan>: Behavior<Protocol = Worker::Protocol>,
27{
28 ProxyOutcomeFailed(ProxyOutcomeFailure<Role, Worker, Plan>),
30 ProxyInputRejected(ProxyInputFailure<Role, Worker, Plan>),
32 WorkerPreparationFailed(
34 WorkerPreparationFailure<
35 Role,
36 Worker,
37 Plan,
38 Source::WorkerRejection,
39 Source::SourceRejection,
40 >,
41 ),
42 RecoveryDenied(RecoveryDenied<Role, Worker>),
44 RestartScheduleFailed(RestartScheduleFailure<Role>),
46 WorkerUnavailable(WorkerUnavailable<Role, Worker>),
48 UnexpectedInput {
50 input: FixedSupervisorEvent<
52 Role,
53 Worker,
54 Plan,
55 ActionItemResult<PrepareWorkers<Source, Role, Worker, Plan>>,
56 WorkerPreparation<Source, Role, Worker, Plan>,
57 >,
58 },
59}
60
61pub struct WorkerUnavailable<Role, Worker>
63where
64 Worker: Behavior,
65{
66 role: RoleName<Role>,
67 sender: BehaviorAddr<Worker>,
68 phase: ProxyPhase,
69 command: <Worker::Protocol as Protocol>::Msg,
70}
71
72impl<Role, Worker> WorkerUnavailable<Role, Worker>
73where
74 Worker: Behavior,
75{
76 pub(super) const fn new(
77 role: RoleName<Role>,
78 sender: BehaviorAddr<Worker>,
79 phase: ProxyPhase,
80 command: <Worker::Protocol as Protocol>::Msg,
81 ) -> Self {
82 Self {
83 role,
84 sender,
85 phase,
86 command,
87 }
88 }
89
90 pub(super) fn into_parts(
91 self,
92 ) -> (
93 RoleName<Role>,
94 BehaviorAddr<Worker>,
95 ProxyPhase,
96 <Worker::Protocol as Protocol>::Msg,
97 ) {
98 (self.role, self.sender, self.phase, self.command)
99 }
100
101 #[must_use]
103 pub fn role(&self) -> &Role {
104 self.role.role()
105 }
106
107 #[must_use]
109 pub const fn sender(&self) -> &BehaviorAddr<Worker> {
110 &self.sender
111 }
112
113 #[must_use]
115 pub const fn phase(&self) -> ProxyPhase {
116 self.phase
117 }
118
119 #[must_use]
121 pub const fn command(&self) -> &<Worker::Protocol as Protocol>::Msg {
122 &self.command
123 }
124}
125
126pub struct ProxyInputFailure<Role, Worker, Plan>
128where
129 Worker: Behavior,
130 Plan: ActivationPlan,
131 BehaviorAddr<Worker>: EndpointAddress,
132 StableProxy<Worker, Plan>: Behavior<Protocol = Worker::Protocol>,
133{
134 role: RoleName<Role>,
135 operation: ProxyOperation<behavior::Here, Worker, Plan>,
136 reason: ChildInputReason,
137}
138
139impl<Role, Worker, Plan> ProxyInputFailure<Role, Worker, Plan>
140where
141 Worker: Behavior,
142 Plan: ActivationPlan,
143 BehaviorAddr<Worker>: EndpointAddress,
144 StableProxy<Worker, Plan>: Behavior<Protocol = Worker::Protocol>,
145{
146 pub(super) const fn new(
147 role: RoleName<Role>,
148 operation: ProxyOperation<behavior::Here, Worker, Plan>,
149 reason: ChildInputReason,
150 ) -> Self {
151 Self {
152 role,
153 operation,
154 reason,
155 }
156 }
157
158 #[must_use]
160 pub fn role(&self) -> &Role {
161 self.role.role()
162 }
163
164 #[must_use]
166 pub const fn operation(&self) -> &ProxyOperation<behavior::Here, Worker, Plan> {
167 &self.operation
168 }
169
170 #[must_use]
172 pub const fn reason(&self) -> ChildInputReason {
173 self.reason
174 }
175}
176
177pub struct RestartScheduleFailure<Role> {
179 trigger: RoleName<Role>,
180 request: ScheduleAfter,
181 reason: ScheduleAfterRejection,
182}
183
184impl<Role> RestartScheduleFailure<Role> {
185 pub(super) const fn new(
186 trigger: RoleName<Role>,
187 request: ScheduleAfter,
188 reason: ScheduleAfterRejection,
189 ) -> Self {
190 Self {
191 trigger,
192 request,
193 reason,
194 }
195 }
196
197 #[must_use]
199 pub fn role(&self) -> &Role {
200 self.trigger.role()
201 }
202
203 #[must_use]
205 pub const fn request(&self) -> ScheduleAfter {
206 self.request
207 }
208
209 #[must_use]
211 pub const fn reason(&self) -> ScheduleAfterRejection {
212 self.reason
213 }
214}
215
216pub struct RecoveryDenied<Role, Worker>
222where
223 Worker: Behavior,
224{
225 trigger: RoleName<Role>,
226 stopped: ChildStopped<BehaviorAddr<Worker>>,
227 reason: RecoveryDenialReason,
228}
229
230impl<Role, Worker> RecoveryDenied<Role, Worker>
231where
232 Worker: Behavior,
233{
234 pub(super) const fn new(
235 trigger: RoleName<Role>,
236 stopped: ChildStopped<BehaviorAddr<Worker>>,
237 reason: RecoveryDenialReason,
238 ) -> Self {
239 Self {
240 trigger,
241 stopped,
242 reason,
243 }
244 }
245
246 #[must_use]
248 pub fn role(&self) -> &Role {
249 self.trigger.role()
250 }
251
252 #[must_use]
254 pub const fn stopped(&self) -> &ChildStopped<BehaviorAddr<Worker>> {
255 &self.stopped
256 }
257
258 #[must_use]
260 pub const fn reason(&self) -> &RecoveryDenialReason {
261 &self.reason
262 }
263}
264
265enum PreparationFailureState<Role, Worker, Plan, WorkerRejection, SourceRejection> {
266 WorkerRejected {
267 prepared: Vec<PreparedWorker<RoleName<Role>, Worker, Plan>>,
268 failed_role: RoleName<Role>,
269 reason: WorkerRejection,
270 remaining: Vec<RoleName<Role>>,
271 },
272 SourceRejected {
273 reason: SourceRejection,
274 remaining: Vec<RoleName<Role>>,
275 },
276 InterpreterFault {
277 fault: InterpreterFault,
278 remaining: Vec<RoleName<Role>>,
279 },
280 Unattempted(Vec<RoleName<Role>>),
281}
282
283pub struct WorkerPreparationFailure<Role, Worker, Plan, WorkerRejection, SourceRejection> {
289 trigger: RoleName<Role>,
290 state: PreparationFailureState<Role, Worker, Plan, WorkerRejection, SourceRejection>,
291}
292
293pub enum WorkerPreparationFailureReason<'a, Role, WorkerRejection, SourceRejection> {
295 WorkerRejected {
297 role: &'a Role,
299 reason: &'a WorkerRejection,
301 },
302 SourceRejected(&'a SourceRejection),
304 InterpreterFault(InterpreterFault),
306 Unattempted,
308}
309
310impl<Role, Worker, Plan, WorkerRejection, SourceRejection>
311 WorkerPreparationFailure<Role, Worker, Plan, WorkerRejection, SourceRejection>
312{
313 pub(super) fn worker_rejected(
314 trigger: RoleName<Role>,
315 prepared: Vec<PreparedWorker<RoleName<Role>, Worker, Plan>>,
316 failed_role: RoleName<Role>,
317 reason: WorkerRejection,
318 remaining: Vec<RoleName<Role>>,
319 ) -> Self {
320 Self {
321 trigger,
322 state: PreparationFailureState::WorkerRejected {
323 prepared,
324 failed_role,
325 reason,
326 remaining,
327 },
328 }
329 }
330
331 pub(super) fn source_rejected(
332 trigger: RoleName<Role>,
333 reason: SourceRejection,
334 remaining: Vec<RoleName<Role>>,
335 ) -> Self {
336 Self {
337 trigger,
338 state: PreparationFailureState::SourceRejected { reason, remaining },
339 }
340 }
341
342 pub(super) fn interpreter_fault(
343 trigger: RoleName<Role>,
344 fault: InterpreterFault,
345 remaining: Vec<RoleName<Role>>,
346 ) -> Self {
347 Self {
348 trigger,
349 state: PreparationFailureState::InterpreterFault { fault, remaining },
350 }
351 }
352
353 pub(super) fn unattempted(trigger: RoleName<Role>, remaining: Vec<RoleName<Role>>) -> Self {
354 Self {
355 trigger,
356 state: PreparationFailureState::Unattempted(remaining),
357 }
358 }
359
360 #[must_use]
362 pub fn role(&self) -> &Role {
363 self.trigger.role()
364 }
365
366 #[must_use]
368 pub fn reason(
369 &self,
370 ) -> WorkerPreparationFailureReason<'_, Role, WorkerRejection, SourceRejection> {
371 match &self.state {
372 PreparationFailureState::WorkerRejected {
373 failed_role,
374 reason,
375 ..
376 } => WorkerPreparationFailureReason::WorkerRejected {
377 role: failed_role.role(),
378 reason,
379 },
380 PreparationFailureState::SourceRejected { reason, .. } => {
381 WorkerPreparationFailureReason::SourceRejected(reason)
382 }
383 PreparationFailureState::InterpreterFault { fault, .. } => {
384 WorkerPreparationFailureReason::InterpreterFault(*fault)
385 }
386 PreparationFailureState::Unattempted(_) => WorkerPreparationFailureReason::Unattempted,
387 }
388 }
389
390 pub fn prepared(
392 &self,
393 ) -> impl ExactSizeIterator<Item = (&Role, &WorkerSubmission<Worker, Plan>)> {
394 let prepared = match &self.state {
395 PreparationFailureState::WorkerRejected { prepared, .. } => prepared.as_slice(),
396 PreparationFailureState::SourceRejected { .. }
397 | PreparationFailureState::InterpreterFault { .. }
398 | PreparationFailureState::Unattempted(_) => &[],
399 };
400 prepared
401 .iter()
402 .map(|prepared| (prepared.role.role(), &prepared.submission))
403 }
404
405 pub fn remaining_roles(&self) -> impl ExactSizeIterator<Item = &Role> {
407 let remaining = match &self.state {
408 PreparationFailureState::WorkerRejected { remaining, .. }
409 | PreparationFailureState::SourceRejected { remaining, .. }
410 | PreparationFailureState::InterpreterFault { remaining, .. } => remaining,
411 PreparationFailureState::Unattempted(remaining) => remaining,
412 };
413 remaining.iter().map(RoleName::role)
414 }
415}
416
417pub struct ProxyOutcomeFailure<Role, Worker, Plan>
419where
420 Worker: Behavior,
421 Plan: ActivationPlan,
422 BehaviorAddr<Worker>: EndpointAddress,
423 StableProxy<Worker, Plan>: Behavior<Protocol = Worker::Protocol>,
424{
425 role: RoleName<Role>,
426 outcome: ProxyOutcome<Worker, Plan>,
427}
428
429impl<Role, Worker, Plan> ProxyOutcomeFailure<Role, Worker, Plan>
430where
431 Worker: Behavior,
432 Plan: ActivationPlan,
433 BehaviorAddr<Worker>: EndpointAddress,
434 StableProxy<Worker, Plan>: Behavior<Protocol = Worker::Protocol>,
435{
436 pub(super) const fn new(role: RoleName<Role>, outcome: ProxyOutcome<Worker, Plan>) -> Self {
437 Self { role, outcome }
438 }
439
440 #[must_use]
442 pub fn role(&self) -> &Role {
443 self.role.role()
444 }
445
446 #[must_use]
448 pub const fn outcome(&self) -> &ProxyOutcome<Worker, Plan> {
449 &self.outcome
450 }
451}
452
453#[cfg(test)]
454mod tests {
455 use super::{WorkerPreparationFailure, WorkerPreparationFailureReason};
456 use crate::WorkerSubmission;
457 use crate::atomic::{PreparedWorker, RoleName};
458 use behavior::Never;
459
460 fn submission(worker: u8) -> WorkerSubmission<u8, u16> {
461 WorkerSubmission::activated(worker, u16::from(worker))
462 }
463
464 #[test]
465 fn worker_rejection_preserves_prepared_order_and_untouched_roles() {
466 let trigger = RoleName::new(1_u8);
467 let failure: WorkerPreparationFailure<u8, u8, u16, u32, Never> =
468 WorkerPreparationFailure::worker_rejected(
469 trigger.clone(),
470 vec![
471 PreparedWorker {
472 role: RoleName::new(2),
473 submission: submission(31),
474 },
475 PreparedWorker {
476 role: RoleName::new(3),
477 submission: submission(37),
478 },
479 ],
480 RoleName::new(4),
481 41,
482 vec![RoleName::new(5)],
483 );
484
485 assert_eq!(failure.role(), trigger.role());
486 let prepared: Vec<_> = failure.prepared().collect();
487 assert_eq!(prepared.len(), 2);
488 assert_eq!(prepared[0], (&2, &submission(31)));
489 assert_eq!(prepared[1], (&3, &submission(37)));
490 let WorkerPreparationFailureReason::WorkerRejected { role, reason } = failure.reason()
491 else {
492 panic!("worker rejection retains its role and reason");
493 };
494 assert_eq!(role, &4);
495 assert_eq!(reason, &41);
496 assert_eq!(failure.remaining_roles().collect::<Vec<_>>(), [&5]);
497 }
498}