From b2d5aa86255a569dcccad70d7f0fdce609ae63c7 Mon Sep 17 00:00:00 2001 From: Jacob Taylor Date: Wed, 11 Nov 2026 10:09:20 -0800 Subject: [PATCH] feat: 410 Gone Handling --- changelog.d/2355.feature.md | 1 + src/service/federation/execute.rs | 19 ++++++++++++++++--- src/service/federation/mod.rs | 21 +++++++++++---------- src/service/sending/sender.rs | 4 ++-- 4 files changed, 30 insertions(+), 15 deletions(-) create mode 100644 changelog.d/2355.feature.md diff --git a/changelog.d/2355.feature.md b/changelog.d/2355.feature.md new file mode 100644 index 000000000..f76ef7211 --- /dev/null +++ b/changelog.d/2355.feature.md @@ -0,0 +1 @@ +Added support for servers returning 410 gone to indicate they are no longer serving a Matrix server (MSC4188). Contributed by @nex and @silversys diff --git a/src/service/federation/execute.rs b/src/service/federation/execute.rs index b32174c22..152433cb7 100644 --- a/src/service/federation/execute.rs +++ b/src/service/federation/execute.rs @@ -1,4 +1,9 @@ -use std::{borrow::Cow, fmt::Debug, mem, time::Instant}; +use std::{ + borrow::Cow, + fmt::Debug, + mem, + time::{Duration, Instant}, +}; use conduwuit::{ Err, Error, Result, debug, debug_error, debug_info, debug_warn, err, metrics::KeyValue, @@ -133,12 +138,20 @@ impl super::Service { self.ensure_remote_is_healthy(dest)?; + /// How long to mark a server as unhealthy before attempting + /// re-resolution + const UNHEALTHY_TIMEOUT: Duration = Duration::from_hours(24 * 90); + let actual = self .services .client .matrix_resolver .resolve_server(dest.as_str()) - .await?; + .await + .inspect_err(|_| { + self.hit_unhealthy(dest.to_owned(), Some(UNHEALTHY_TIMEOUT)); + self.mark_destination_stale(dest); + })?; let request = Request::try_from(request.try_into_http_request::>( actual.base_url().as_str(), @@ -210,7 +223,7 @@ impl super::Service { if self.should_mark_stale(&wrapped) { debug_info!("{dest} is unhealthy & stale due to a connect error"); self.mark_destination_stale(dest); - self.hit_unhealthy(dest.to_owned()); + self.hit_unhealthy(dest.to_owned(), None); } let Error::Reqwest(unwrapped) = wrapped else { unreachable!("wrapped reqwest error must unwrap to a reqwest error"); diff --git a/src/service/federation/mod.rs b/src/service/federation/mod.rs index 76d0e124a..0b09c56bf 100644 --- a/src/service/federation/mod.rs +++ b/src/service/federation/mod.rs @@ -92,7 +92,7 @@ impl Service { /// retry count is incremented and next retry window is updated. /// /// Does not update the marker if the backoff period is already in effect. - pub fn hit_unhealthy(&self, server_name: OwnedServerName) { + pub fn hit_unhealthy(&self, server_name: OwnedServerName, next_attempt: Option) { if server_name == self.services.server.name { return; } @@ -109,7 +109,8 @@ impl Service { } *retries = retries.saturating_add(1); - let next_interval = self.services.server.config.sender_retry_backoff(*retries); + let next_interval = next_attempt + .unwrap_or_else(|| self.services.server.config.sender_retry_backoff(*retries)); *next_retry = millis_from_now(next_interval); debug!( "{} is (now) unhealthy ({} retries, blocked until: {})", @@ -200,14 +201,14 @@ impl Service { } match error.status_code() { - // Some special servers account for this specifically - | StatusCode::MISDIRECTED_REQUEST - // Common error codes observed for misdirected requests - // | StatusCode::NOT_FOUND This one can be encountered naturally - | StatusCode::METHOD_NOT_ALLOWED - | StatusCode::IM_A_TEAPOT => true, - _ => false, - } + // Some special servers account for this specifically + | StatusCode::MISDIRECTED_REQUEST + // Common error codes observed for misdirected requests + // | StatusCode::NOT_FOUND This one can be encountered naturally + | StatusCode::METHOD_NOT_ALLOWED + | StatusCode::IM_A_TEAPOT => true, + _ => false, + } } /// Returns a clone of the internal remote health tracking map. diff --git a/src/service/sending/sender.rs b/src/service/sending/sender.rs index 5467b36f4..03be9e2ce 100644 --- a/src/service/sending/sender.rs +++ b/src/service/sending/sender.rs @@ -149,10 +149,10 @@ impl Service { { debug!("{dest} is now unhealthy & stale due to a connect error: {e:?}"); self.services.federation.mark_destination_stale(dest); - self.services.federation.hit_unhealthy(dest.clone()); + self.services.federation.hit_unhealthy(dest.clone(), None); } else if e.status_code() != StatusCode::OK { debug!("{dest} is now unhealthy due to error response: {e:?}"); - self.services.federation.hit_unhealthy(dest.clone()); + self.services.federation.hit_unhealthy(dest.clone(), None); } } Self::handle_response_err(dest, statuses, &e);