From 5162a81f17194eb5da6bc94d165daa120d851c56 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sat, 19 Sep 2026 23:13:39 +0200 Subject: [PATCH] feat: try fasted relays to attempt first configure on --- deltachat-jsonrpc/src/api.rs | 2 +- .../src/deltachat_rpc_client/account.py | 2 +- src/autorelay.rs | 37 ++++++++++++++++--- src/autorelay/autorelay_tests.rs | 2 +- src/configure.rs | 2 +- 5 files changed, 35 insertions(+), 10 deletions(-) diff --git a/deltachat-jsonrpc/src/api.rs b/deltachat-jsonrpc/src/api.rs index 68a4906e1..94b401362 100644 --- a/deltachat-jsonrpc/src/api.rs +++ b/deltachat-jsonrpc/src/api.rs @@ -546,7 +546,7 @@ impl CommandApi { ctx.add_transport_from_qr(&qr).await } - /// Adds an initial transport on a randomly chosen chatmail relay + /// Adds an initial transport on the chatmail relay that answers fastest /// and lets the profile add further ones in the background. /// /// A `DCACCOUNT:` or `DCLOGIN:` `qr` code adds a single transport diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/account.py b/deltachat-rpc-client/src/deltachat_rpc_client/account.py index 42c3f28ea..db0ef9ece 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/account.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/account.py @@ -141,7 +141,7 @@ class Account: @futuremethod def init_transports(self, qr: Optional[str] = None): - """Add an initial transport on a randomly chosen chatmail relay. + """Add an initial transport on the chatmail relay that answers fastest. The profile then adds further ones in the background. A ``DCACCOUNT:`` or ``DCLOGIN:`` ``qr`` code adds a single transport diff --git a/src/autorelay.rs b/src/autorelay.rs index d32a593b2..2bd4ac83a 100644 --- a/src/autorelay.rs +++ b/src/autorelay.rs @@ -11,11 +11,13 @@ use deltachat_contact_tools::addr_normalize; use rand::distr::{Alphanumeric, SampleString}; use rand::seq::{IndexedRandom, SliceRandom}; use rusqlite::Transaction; +use tokio::task::JoinSet; use crate::config::{self, Config}; use crate::configure::{EnteredLoginParam, configure}; use crate::log::{LogExt, warn}; use crate::login_param::{EnteredCertificateChecks, EnteredImapLoginParam}; +use crate::net::{connect_tcp, proxy::ProxyConfig}; use crate::{context::Context, tools::time}; /// The target number of transports. @@ -52,20 +54,43 @@ pub(crate) async fn add_relay_candidates(context: &Context, addrs: &[String]) -> .await } -/// Adds a first transport on a random relay candidate. +/// Adds a first transport on the relay candidate that answers fastest. +/// +/// All candidates are probed at once with a TCP connection to their HTTPS port +/// and configured in the order in which the connections complete, +/// stopping at the first success. Candidates that fail the probe are skipped. pub(crate) async fn add_transport_from_candidates( context: &Context, skip_network: bool, ) -> Result<()> { let mut candidates = triable_relay_candidates(context, time()).await?; candidates.shuffle(&mut rand::rng()); - let mut last_err = format_err!("No relay candidates"); + let mut probes = JoinSet::new(); + let proxy_config = ProxyConfig::load(context).await?; + let load_cache = false; + for host in candidates { + let ctx = context.clone(); + let proxy_config = proxy_config.clone(); + probes.spawn(async move { + let res = match proxy_config { + _ if skip_network => Ok(()), + Some(proxy) => proxy.connect(&ctx, &host, 443, load_cache).await.map(drop), + None => connect_tcp(&ctx, &host, 443, load_cache).await.map(drop), + }; + (host, res) + }); + } - // We patiently try each candidate in turn which might take a while - // if many hosts are unreachable but eventually succeeds if one candidate works. + let mut last_err = format_err!("No relay candidates"); let mark_as_autorelay = true; - for host in &candidates { - let param = login_param_from_host(host, mark_as_autorelay); + while let Some(res) = probes.join_next().await { + let (host, res) = res?; + if let Err(err) = res { + warn!(context, "Failed to connect to relay {host}: {err:#}."); + last_err = err; + continue; + } + let param = login_param_from_host(&host, mark_as_autorelay); match configure(context, ¶m, skip_network).await { Ok(()) => { param.save_legacy(context).await?; diff --git a/src/autorelay/autorelay_tests.rs b/src/autorelay/autorelay_tests.rs index a8a13bb4b..f84b0bc4e 100644 --- a/src/autorelay/autorelay_tests.rs +++ b/src/autorelay/autorelay_tests.rs @@ -60,7 +60,7 @@ async fn test_add_transport_from_candidates_failure() -> Result<()> { mark_defaults_tried(t, time()).await?; save_relay_candidates(t, &["bad host", "worse host"], 0).await?; - let skip_network = false; + let skip_network = true; let err = add_transport_from_candidates(t, skip_network) .await .unwrap_err(); diff --git a/src/configure.rs b/src/configure.rs index efdb4bcb6..6b32d7a4e 100644 --- a/src/configure.rs +++ b/src/configure.rs @@ -190,7 +190,7 @@ impl Context { Ok(()) } - /// Adds an initial transport on a randomly chosen chatmail relay + /// Adds an initial transport on the chatmail relay that answers fastest /// and lets the profile add further ones in the background. /// /// A `DCACCOUNT:` or `DCLOGIN:` `qr` code adds a single transport