Skip to content

Commit 8988094

Browse files
committed
sdk: introduce RetryKind next to the HttpError to determine errors that should be retried
retry kind: make `characterize_retry_kind` a method of `HttpError` --- retry kind: mark all network errors as transient This should cause more backoff in general, if the client is disconnected of the grid, or the remote server is, which is good behavior to have in general. --- http error: introduce another rustfmt-formated impl block for HttpError The previous `impl` block was marked as skipping rustfmt, which led to weird formatting behavior. Changes are formatting only.
1 parent f93df8c commit 8988094

File tree

4 files changed

+71
-92
lines changed

4 files changed

+71
-92
lines changed

crates/matrix-sdk/src/error.rs

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
//! Error conditions.
1616
17-
use std::{io::Error as IoError, sync::Arc};
17+
use std::{io::Error as IoError, sync::Arc, time::Duration};
1818

1919
use as_variant::as_variant;
2020
#[cfg(feature = "qrcode")]
@@ -122,13 +122,15 @@ impl HttpError {
122122
pub fn as_client_api_error(&self) -> Option<&ruma::api::client::Error> {
123123
self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error)
124124
}
125+
}
125126

127+
// Another impl block that's formatted with rustfmt.
128+
impl HttpError {
126129
/// If `self` is a server error in the `errcode` + `error` format expected
127130
/// for client-API endpoints, returns the error kind (`errcode`).
128131
pub fn client_api_error_kind(&self) -> Option<&ErrorKind> {
129-
self.as_client_api_error().and_then(|e| {
130-
as_variant!(&e.body, ErrorBody::Standard { kind, .. } => kind)
131-
})
132+
self.as_client_api_error()
133+
.and_then(|e| as_variant!(&e.body, ErrorBody::Standard { kind, .. } => kind))
132134
}
133135

134136
/// Try to destructure the error into an universal interactive auth info.
@@ -145,6 +147,59 @@ impl HttpError {
145147
pub fn as_uiaa_response(&self) -> Option<&UiaaInfo> {
146148
self.as_ruma_api_error().and_then(as_variant!(RumaApiError::Uiaa))
147149
}
150+
151+
/// Returns whether an HTTP error response should be qualified as transient
152+
/// or permanent.
153+
pub(crate) fn retry_kind(&self) -> RetryKind {
154+
use ruma::api::client::error::{ErrorBody, ErrorKind, RetryAfter};
155+
156+
use crate::RumaApiError;
157+
158+
// If it was a plain network error, it's either that we're disconnected from the
159+
// internet, or that the remote is, so retry a few times.
160+
if matches!(self, Self::Reqwest(..)) {
161+
return RetryKind::Transient { retry_after: None };
162+
}
163+
164+
if let Some(api_error) = self.as_ruma_api_error() {
165+
let status_code = match api_error {
166+
RumaApiError::ClientApi(e) => match e.body {
167+
ErrorBody::Standard {
168+
kind: ErrorKind::LimitExceeded { retry_after }, ..
169+
} => {
170+
let retry_after = retry_after.and_then(|retry_after| match retry_after {
171+
RetryAfter::Delay(d) => Some(d),
172+
RetryAfter::DateTime(_) => None,
173+
});
174+
return RetryKind::Transient { retry_after };
175+
}
176+
_ => Some(e.status_code),
177+
},
178+
RumaApiError::Uiaa(_) => None,
179+
RumaApiError::Other(e) => Some(e.status_code),
180+
};
181+
182+
if let Some(status_code) = status_code {
183+
if status_code.is_server_error() {
184+
return RetryKind::Transient { retry_after: None };
185+
}
186+
}
187+
}
188+
189+
RetryKind::Permanent
190+
}
191+
}
192+
193+
/// How should we behave with respect to retry behavior after an `HttpError`
194+
/// happened?
195+
pub(crate) enum RetryKind {
196+
Transient {
197+
// This is used only for attempts to retry, so on non-wasm32 code (in the `native` module).
198+
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
199+
retry_after: Option<Duration>,
200+
},
201+
202+
Permanent,
148203
}
149204

150205
/// Internal representation of errors.

crates/matrix-sdk/src/http_client/mod.rs

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -259,56 +259,3 @@ impl tower::Service<http_old::Request<Bytes>> for HttpClient {
259259
Box::pin(fut)
260260
}
261261
}
262-
263-
pub(crate) enum RetryKind {
264-
Transient {
265-
err: HttpError,
266-
// This is used only for attempts to retry, so on non-wasm32 code (in the `native` module).
267-
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
268-
retry_after: Option<Duration>,
269-
},
270-
Permanent(HttpError),
271-
}
272-
273-
impl RetryKind {
274-
pub fn error(self) -> HttpError {
275-
match self {
276-
RetryKind::Transient { err, .. } | RetryKind::Permanent(err) => err,
277-
}
278-
}
279-
}
280-
281-
/// Returns whether an HTTP error response should be qualified as transient or
282-
/// permanent.
283-
///
284-
/// This is what decides whether to retry requests, in the [`native`] module.
285-
pub(crate) fn characterize_retry_kind(err: HttpError) -> RetryKind {
286-
use ruma::api::client::error::{ErrorBody, ErrorKind, RetryAfter};
287-
288-
use crate::RumaApiError;
289-
290-
if let Some(api_error) = err.as_ruma_api_error() {
291-
let status_code = match api_error {
292-
RumaApiError::ClientApi(e) => match e.body {
293-
ErrorBody::Standard { kind: ErrorKind::LimitExceeded { retry_after }, .. } => {
294-
let retry_after = retry_after.and_then(|retry_after| match retry_after {
295-
RetryAfter::Delay(d) => Some(d),
296-
RetryAfter::DateTime(_) => None,
297-
});
298-
return RetryKind::Transient { err, retry_after };
299-
}
300-
_ => Some(e.status_code),
301-
},
302-
RumaApiError::Uiaa(_) => None,
303-
RumaApiError::Other(e) => Some(e.status_code),
304-
};
305-
306-
if let Some(status_code) = status_code {
307-
if status_code.is_server_error() {
308-
return RetryKind::Transient { err, retry_after: None };
309-
}
310-
}
311-
}
312-
313-
RetryKind::Permanent(err)
314-
}

crates/matrix-sdk/src/http_client/native.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ use reqwest::Certificate;
2828
use ruma::api::{error::FromHttpResponseError, IncomingResponse, OutgoingRequest};
2929
use tracing::{info, warn};
3030

31-
use super::{
32-
characterize_retry_kind, response_to_http_response, HttpClient, RetryKind,
33-
TransmissionProgress, DEFAULT_REQUEST_TIMEOUT,
31+
use super::{response_to_http_response, HttpClient, TransmissionProgress, DEFAULT_REQUEST_TIMEOUT};
32+
use crate::{
33+
config::RequestConfig,
34+
error::{HttpError, RetryKind},
3435
};
35-
use crate::{config::RequestConfig, error::HttpError};
3636

3737
impl HttpClient {
3838
pub(super) async fn send_request<R>(
@@ -62,11 +62,11 @@ impl HttpClient {
6262
let error_type = if stop {
6363
RetryError::Permanent
6464
} else {
65-
|err: HttpError| match characterize_retry_kind(err) {
66-
RetryKind::Transient { err, retry_after } => {
65+
|err: HttpError| match err.retry_kind() {
66+
RetryKind::Transient { retry_after } => {
6767
RetryError::Transient { err, retry_after }
6868
}
69-
RetryKind::Permanent(err) => RetryError::Permanent(err),
69+
RetryKind::Permanent => RetryError::Permanent(err),
7070
}
7171
};
7272

crates/matrix-sdk/src/send_queue.rs

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,7 @@ use tokio::sync::{broadcast, Notify, RwLock};
3232
use tracing::{debug, error, info, instrument, trace, warn};
3333

3434
use crate::{
35-
client::WeakClient,
36-
config::RequestConfig,
37-
http_client::{characterize_retry_kind, RetryKind},
38-
room::WeakRoom,
39-
Client, Room,
35+
client::WeakClient, config::RequestConfig, error::RetryKind, room::WeakRoom, Client, Room,
4036
};
4137

4238
/// A client-wide send queue, for all the rooms known by a client.
@@ -366,30 +362,11 @@ impl RoomSendQueue {
366362
}
367363

368364
Err(err) => {
369-
let (err, is_recoverable) = if let crate::Error::Http(http_err) = err {
370-
// A small trick to confuse borrowck (and humans too).
371-
//
372-
// `is_transient_error` really wants ownership of the error, so we can't
373-
// match above on `&err`, and we have to pass the error around by ownership
374-
// all over the place. We give ownership in `is_transient_error`, and then
375-
// get it back with the below call to `.error()`. Oh well.
376-
let retry_kind = characterize_retry_kind(http_err);
377-
378-
let mut is_recoverable = matches!(retry_kind, RetryKind::Transient { .. });
379-
380-
let http_err = retry_kind.error();
381-
382-
if !is_recoverable {
383-
// Also flag all HTTP errors as recoverable: they might indicate that
384-
// the server was unreachable, or that the device is entirely
385-
// disconnected.
386-
is_recoverable =
387-
matches!(http_err, crate::error::HttpError::Reqwest(..));
388-
}
389-
390-
(crate::Error::Http(http_err), is_recoverable)
365+
let is_recoverable = if let crate::Error::Http(ref http_err) = err {
366+
// All transient errors are recoverable.
367+
matches!(http_err.retry_kind(), RetryKind::Transient { .. })
391368
} else {
392-
(err, false)
369+
false
393370
};
394371

395372
if is_recoverable {

0 commit comments

Comments
 (0)