Line data Source code
1 : /// Reasons for downloads or listings to fail.
2 : #[derive(Debug)]
3 : pub enum DownloadError {
4 : /// Validation or other error happened due to user input.
5 : BadInput(anyhow::Error),
6 : /// The file was not found in the remote storage.
7 : NotFound,
8 : /// The caller provided an ETag, and the file was not modified.
9 : Unmodified,
10 : /// A cancellation token aborted the download, typically during
11 : /// tenant detach or process shutdown.
12 : Cancelled,
13 : /// A timeout happened while executing the request. Possible reasons:
14 : /// - stuck tcp connection
15 : ///
16 : /// Concurrency control is not timed within timeout.
17 : Timeout,
18 : /// The file was found in the remote storage, but the download failed.
19 : Other(anyhow::Error),
20 : }
21 :
22 : impl std::fmt::Display for DownloadError {
23 1 : fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 1 : match self {
25 0 : DownloadError::BadInput(e) => {
26 0 : write!(f, "Failed to download a remote file due to user input: {e}")
27 : }
28 0 : DownloadError::NotFound => write!(f, "No file found for the remote object id given"),
29 0 : DownloadError::Unmodified => write!(f, "File was not modified"),
30 0 : DownloadError::Cancelled => write!(f, "Cancelled, shutting down"),
31 0 : DownloadError::Timeout => write!(f, "timeout"),
32 1 : DownloadError::Other(e) => write!(f, "Failed to download a remote file: {e:?}"),
33 : }
34 1 : }
35 : }
36 :
37 : impl std::error::Error for DownloadError {}
38 :
39 : impl DownloadError {
40 : /// Returns true if the error should not be retried with backoff
41 14 : pub fn is_permanent(&self) -> bool {
42 : use DownloadError::*;
43 14 : match self {
44 14 : BadInput(_) | NotFound | Unmodified | Cancelled => true,
45 0 : Timeout | Other(_) => false,
46 : }
47 14 : }
48 :
49 0 : pub fn is_cancelled(&self) -> bool {
50 0 : matches!(self, DownloadError::Cancelled)
51 0 : }
52 : }
53 :
54 : impl From<std::io::Error> for DownloadError {
55 8 : fn from(value: std::io::Error) -> Self {
56 8 : let needs_unwrap = value.kind() == std::io::ErrorKind::Other
57 8 : && value
58 8 : .get_ref()
59 8 : .and_then(|x| x.downcast_ref::<DownloadError>())
60 8 : .is_some();
61 :
62 8 : if needs_unwrap {
63 8 : *value
64 8 : .into_inner()
65 8 : .expect("just checked")
66 8 : .downcast::<DownloadError>()
67 8 : .expect("just checked")
68 : } else {
69 0 : DownloadError::Other(value.into())
70 : }
71 8 : }
72 : }
73 :
74 : #[derive(Debug)]
75 : pub enum TimeTravelError {
76 : /// Validation or other error happened due to user input.
77 : BadInput(anyhow::Error),
78 : /// The used remote storage does not have time travel recovery implemented
79 : Unimplemented,
80 : /// The number of versions/deletion markers is above our limit.
81 : TooManyVersions,
82 : /// A cancellation token aborted the process, typically during
83 : /// request closure or process shutdown.
84 : Cancelled,
85 : /// Other errors
86 : Other(anyhow::Error),
87 : }
88 :
89 : impl std::fmt::Display for TimeTravelError {
90 0 : fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 0 : match self {
92 0 : TimeTravelError::BadInput(e) => {
93 0 : write!(
94 0 : f,
95 0 : "Failed to time travel recover a prefix due to user input: {e}"
96 0 : )
97 : }
98 0 : TimeTravelError::Unimplemented => write!(
99 0 : f,
100 0 : "time travel recovery is not implemented for the current storage backend"
101 0 : ),
102 0 : TimeTravelError::Cancelled => write!(f, "Cancelled, shutting down"),
103 : TimeTravelError::TooManyVersions => {
104 0 : write!(f, "Number of versions/delete markers above limit")
105 : }
106 0 : TimeTravelError::Other(e) => write!(f, "Failed to time travel recover a prefix: {e:?}"),
107 : }
108 0 : }
109 : }
110 :
111 : impl std::error::Error for TimeTravelError {}
112 :
113 : /// Plain cancelled error.
114 : ///
115 : /// By design this type does not not implement `std::error::Error` so it cannot be put as the root
116 : /// cause of `std::io::Error` or `anyhow::Error`. It should never need to be exposed out of this
117 : /// crate.
118 : ///
119 : /// It exists to implement permit acquiring in `{Download,TimeTravel}Error` and `anyhow::Error` returning
120 : /// operations and ensuring that those get converted to proper versions with just `?`.
121 : #[derive(Debug)]
122 : pub(crate) struct Cancelled;
123 :
124 : impl From<Cancelled> for anyhow::Error {
125 0 : fn from(_: Cancelled) -> Self {
126 0 : anyhow::Error::new(TimeoutOrCancel::Cancel)
127 0 : }
128 : }
129 :
130 : impl From<Cancelled> for TimeTravelError {
131 0 : fn from(_: Cancelled) -> Self {
132 0 : TimeTravelError::Cancelled
133 0 : }
134 : }
135 :
136 : impl From<Cancelled> for TimeoutOrCancel {
137 0 : fn from(_: Cancelled) -> Self {
138 0 : TimeoutOrCancel::Cancel
139 0 : }
140 : }
141 :
142 : impl From<Cancelled> for DownloadError {
143 0 : fn from(_: Cancelled) -> Self {
144 0 : DownloadError::Cancelled
145 0 : }
146 : }
147 :
148 : /// This type is used at as the root cause for timeouts and cancellations with `anyhow::Error` returning
149 : /// RemoteStorage methods.
150 : ///
151 : /// For use with `utils::backoff::retry` and `anyhow::Error` returning operations there is
152 : /// `TimeoutOrCancel::caused_by_cancel` method to query "proper form" errors.
153 : #[derive(Debug)]
154 : pub enum TimeoutOrCancel {
155 : Timeout,
156 : Cancel,
157 : }
158 :
159 : impl std::fmt::Display for TimeoutOrCancel {
160 0 : fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 : use TimeoutOrCancel::*;
162 0 : match self {
163 0 : Timeout => write!(f, "timeout"),
164 0 : Cancel => write!(f, "cancel"),
165 : }
166 0 : }
167 : }
168 :
169 : impl std::error::Error for TimeoutOrCancel {}
170 :
171 : impl TimeoutOrCancel {
172 : /// Returns true if the error was caused by [`TimeoutOrCancel::Cancel`].
173 15 : pub fn caused_by_cancel(error: &anyhow::Error) -> bool {
174 15 : error
175 15 : .root_cause()
176 15 : .downcast_ref::<Self>()
177 15 : .is_some_and(Self::is_cancel)
178 15 : }
179 :
180 3 : pub fn is_cancel(&self) -> bool {
181 3 : matches!(self, TimeoutOrCancel::Cancel)
182 3 : }
183 :
184 0 : pub fn is_timeout(&self) -> bool {
185 0 : matches!(self, TimeoutOrCancel::Timeout)
186 0 : }
187 : }
188 :
189 : /// This conversion is used when [`crate::support::DownloadStream`] notices a cancellation or
190 : /// timeout to wrap it in an `std::io::Error`.
191 : impl From<TimeoutOrCancel> for std::io::Error {
192 13 : fn from(value: TimeoutOrCancel) -> Self {
193 13 : let e = DownloadError::from(value);
194 13 : std::io::Error::other(e)
195 13 : }
196 : }
197 :
198 : impl From<TimeoutOrCancel> for DownloadError {
199 13 : fn from(value: TimeoutOrCancel) -> Self {
200 : use TimeoutOrCancel::*;
201 :
202 13 : match value {
203 5 : Timeout => DownloadError::Timeout,
204 8 : Cancel => DownloadError::Cancelled,
205 : }
206 13 : }
207 : }
|