Throttle our retries when attempting cross-signing reset, and give up after a time limit (#6325)

Related to https://github.com/element-hq/element-x-android/pull/6420

Part of https://github.com/element-hq/element-x-android/issues/5075

I noticed when investigating a bug about resetting your identity when
using a MAS login that we poll the server checking whether the user has
given us permission with no limit on how fast we poll, and with no
ability to give up if it's not working.

This change causes us to retry only twice per second, and give up after
2 minutes. These are guesses as to the right values and I am open to
discussion.
This commit is contained in:
Andy Balaam
2026-03-20 10:13:44 +00:00
committed by GitHub
parent cbfecf520c
commit ec975094e2
3 changed files with 47 additions and 15 deletions

View File

@@ -8,6 +8,9 @@ All notable changes to this project will be documented in this file.
### Features
- [**breaking**] `matrix_sdk::error::Error` has a new variant `Timeout` which occurs when
a cross-signing reset does not succeed after some period of time.
([#6325](https://github.com/matrix-org/matrix-rust-sdk/pull/6325))
- The `beacon_info` start event ([MSC3672](https://github.com/matrix-org/matrix-spec-proposals/pull/3672))
is now included when computing the latest event for a room, so live location sharing
sessions can be surfaced as a room's most recent activity.

View File

@@ -25,6 +25,7 @@ use std::{
path::PathBuf,
str::FromStr,
sync::Arc,
time::Duration,
};
use eyeball::{SharedObservable, Subscriber};
@@ -48,6 +49,7 @@ use matrix_sdk_base::{
},
},
},
sleep::sleep,
};
use matrix_sdk_common::{executor::spawn, locks::Mutex as StdMutex};
use ruma::{
@@ -298,27 +300,50 @@ impl CrossSigningResetHandle {
/// authentication to be done on the side of the OAuth 2.0 server or by
/// providing additional [`AuthData`] the homeserver requires.
pub async fn auth(&self, auth: Option<AuthData>) -> Result<()> {
let mut upload_request = self.upload_request.clone();
upload_request.auth = auth;
// Poll to see whether the reset has been authorized twice per second.
const RETRY_EVERY: Duration = Duration::from_millis(500);
while let Err(e) = self.client.send(upload_request.clone()).await {
if *self.is_cancelled.lock().await {
return Ok(());
}
// Give up after two minutes of polling.
const TIMEOUT: Duration = Duration::from_mins(2);
match e.as_uiaa_response() {
Some(uiaa_info) => {
if uiaa_info.auth_error.is_some() {
return Err(e.into());
}
tokio::time::timeout(TIMEOUT, async {
let mut upload_request = self.upload_request.clone();
upload_request.auth = auth;
debug!(
"Repeatedly PUTting to keys/device_signing/upload until it works \
or we hit a permanent failure."
);
while let Err(e) = self.client.send(upload_request.clone()).await {
if *self.is_cancelled.lock().await {
return Ok(());
}
None => return Err(e.into()),
match e.as_uiaa_response() {
Some(uiaa_info) => {
if uiaa_info.auth_error.is_some() {
return Err(e.into());
}
}
None => return Err(e.into()),
}
debug!(
"PUT to keys/device_signing/upload failed with 401. Retrying after \
a short delay."
);
sleep(RETRY_EVERY).await;
}
}
self.client.send(self.signatures_request.clone()).await?;
self.client.send(self.signatures_request.clone()).await?;
Ok(())
Ok(())
})
.await
.unwrap_or_else(|_| {
warn!("Timed out waiting for keys/device_signing/upload to succeed.");
Err(Error::Timeout)
})
}
/// Cancel the ongoing identity reset process

View File

@@ -417,6 +417,10 @@ pub enum Error {
/// An error happened while attempting to change power levels.
#[error("power levels error: {0}")]
PowerLevels(#[from] PowerLevelsError),
/// We timed out attempting to complete an operation.
#[error("timed out")]
Timeout,
}
#[rustfmt::skip] // stop rustfmt breaking the `<code>` in docs across multiple lines