crossflow/
errors.rs

1/*
2 * Copyright (C) 2024 Open Source Robotics Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16*/
17
18use bevy_ecs::prelude::{Entity, Resource};
19
20use backtrace::Backtrace;
21
22use anyhow::Error as Anyhow;
23
24use std::{borrow::Cow, sync::Arc};
25
26use crate::{Broken, Cancel, Disposal, OperationError};
27
28use thiserror::Error as ThisError;
29
30/// This resource stores errors that have occurred that could not be handled
31/// internally or communicated to the user by any other means.
32#[derive(Resource, Default, Clone, Debug)]
33pub struct UnhandledErrors {
34    pub setup: Vec<SetupFailure>,
35    pub cancellations: Vec<CancelFailure>,
36    pub operations: Vec<OperationError>,
37    pub disposals: Vec<DisposalFailure>,
38    pub stop_tasks: Vec<StopTaskFailure>,
39    pub broken: Vec<Broken>,
40    pub unused_targets: Vec<UnusedTargetDrop>,
41    pub connections: Vec<ConnectionFailure>,
42    pub duplicate_streams: Vec<DuplicateStream>,
43    pub flush_warnings: Vec<FlushWarning>,
44    pub miscellaneous: Vec<MiscellaneousFailure>,
45}
46
47impl UnhandledErrors {
48    pub fn is_empty(&self) -> bool {
49        // TODO(@mxgrey): Implement this is a proc macro to avoid maintenance
50        // bugs whenever a new field gets added.
51        self.setup.is_empty()
52            && self.cancellations.is_empty()
53            && self.operations.is_empty()
54            && self.disposals.is_empty()
55            && self.stop_tasks.is_empty()
56            && self.broken.is_empty()
57            && self.unused_targets.is_empty()
58            && self.connections.is_empty()
59            && self.duplicate_streams.is_empty()
60            && self.flush_warnings.is_empty()
61            && self.miscellaneous.is_empty()
62    }
63}
64
65#[derive(Clone, Debug)]
66pub struct SetupFailure {
67    pub broken_node: Entity,
68    pub error: OperationError,
69}
70
71#[derive(Clone, Debug)]
72pub struct CancelFailure {
73    /// The error produced while the cancellation was happening
74    pub error: OperationError,
75    /// The cancellation that was being emitted
76    pub cancel: Cancel,
77}
78
79impl CancelFailure {
80    pub fn new(error: OperationError, cancel: Cancel) -> Self {
81        Self { error, cancel }
82    }
83}
84
85/// When it is impossible for some reason to perform a disposal, the incident
86/// will be logged in this resource. This may happen if a node somehow gets
87/// despawned while its service is attempting to dispose a request.
88#[derive(Clone, Debug)]
89pub struct DisposalFailure {
90    /// The disposal that was attempted
91    pub disposal: Disposal,
92    /// The node which was attempting to report the disposal
93    pub broken_node: Entity,
94    /// The backtrace indicating what led up to the failure
95    pub backtrace: Option<Backtrace>,
96}
97
98/// An error happened, causing the task of a provider to be unable to stop.
99#[derive(Clone, Debug)]
100pub struct StopTaskFailure {
101    /// The task that was unable to be stopped
102    pub task: Entity,
103    /// The backtrace to indicate why it failed
104    pub backtrace: Option<Backtrace>,
105}
106
107/// A series chain was dropped because its final target was unused but `detach()`
108/// was not called on it. This is almost always a usage error, so we report it here.
109#[derive(Clone, Debug)]
110pub struct UnusedTargetDrop {
111    /// Which target was dropped.
112    pub unused_target: Entity,
113    /// Which series were dropped as a consequence of the unused target.
114    pub dropped_series: Vec<Entity>,
115}
116
117/// Something went wrong while trying to connect a target into a source.
118#[derive(Clone, Debug)]
119pub struct ConnectionFailure {
120    pub original_target: Entity,
121    pub new_target: Entity,
122    pub backtrace: Backtrace,
123}
124
125/// Use this for any failures that are not covered by the other categories
126#[derive(Clone, Debug)]
127pub struct MiscellaneousFailure {
128    pub error: Arc<Anyhow>,
129    pub backtrace: Option<Backtrace>,
130}
131
132/// A stream pack has a duplicated stream name or the same stream type appears
133/// multiple times in the anonymous streams.
134///
135/// This can cause unexpected workflow behavior because the duplicate streams
136/// will never output any data.
137#[derive(Clone, Debug)]
138pub struct DuplicateStream {
139    /// The target that will never receive any stream data.
140    pub target: Entity,
141    /// The output type of the stream that's being duplicated.
142    pub type_name: &'static str,
143    /// The name of the stream that was duplicated (if it was an anonymous stream
144    /// this will be [`None`]).
145    pub stream_name: Option<Cow<'static, str>>,
146}
147
148#[derive(Clone, Debug, ThisError)]
149/// A problem occurred while flushing workflow activity. This may indicate that
150/// your system is over capacity.
151pub enum FlushWarning {
152    #[error("exceeded flush loop limit of {limit} with {reached}")]
153    ExceededFlushLoopLimit { limit: usize, reached: usize },
154    #[error(
155        "exceeded poll limit {limit} with {reached} while performing single-threaded execution"
156    )]
157    ExceededSingleThreadedPollLimit { limit: usize, reached: usize },
158    #[error("exceeded channel received limit of {limit} with {reached}")]
159    ExceededChannelReceivedLimit { limit: usize, reached: usize },
160}