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