feat: try fasted relays to attempt first configure on

This commit is contained in:
holger krekel
2026-09-21 18:51:36 +02:00
parent 72e711e59a
commit 0eddb98354
5 changed files with 33 additions and 9 deletions
+1 -1
View File
@@ -546,7 +546,7 @@ impl CommandApi {
ctx.add_transport_from_qr(&qr).await 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. /// and lets the profile add further ones in the background.
/// ///
/// A `DCACCOUNT:` or `DCLOGIN:` `qr` code adds a single transport /// A `DCACCOUNT:` or `DCLOGIN:` `qr` code adds a single transport
@@ -141,7 +141,7 @@ class Account:
@futuremethod @futuremethod
def init_transports(self, qr: Optional[str] = None): 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. The profile then adds further ones in the background.
A ``DCACCOUNT:`` or ``DCLOGIN:`` ``qr`` code adds a single transport A ``DCACCOUNT:`` or ``DCLOGIN:`` ``qr`` code adds a single transport
+29 -5
View File
@@ -11,11 +11,13 @@ use deltachat_contact_tools::addr_normalize;
use rand::distr::{Alphanumeric, SampleString}; use rand::distr::{Alphanumeric, SampleString};
use rand::seq::{IndexedRandom, SliceRandom}; use rand::seq::{IndexedRandom, SliceRandom};
use rusqlite::Transaction; use rusqlite::Transaction;
use tokio::task::JoinSet;
use crate::config::{self, Config}; use crate::config::{self, Config};
use crate::configure::{EnteredLoginParam, configure}; use crate::configure::{EnteredLoginParam, configure};
use crate::log::{LogExt, warn}; use crate::log::{LogExt, warn};
use crate::login_param::{EnteredCertificateChecks, EnteredImapLoginParam}; use crate::login_param::{EnteredCertificateChecks, EnteredImapLoginParam};
use crate::net::{connect_tcp, proxy::ProxyConfig};
use crate::{context::Context, tools::time}; use crate::{context::Context, tools::time};
/// The target number of transports. /// The target number of transports.
@@ -57,19 +59,41 @@ pub(crate) async fn add_relay_candidates(context: &Context, addrs: &[String]) ->
/// All candidates are probed at once with a TCP connection to their HTTPS port /// All candidates are probed at once with a TCP connection to their HTTPS port
/// and configured in the order in which the connections complete, /// and configured in the order in which the connections complete,
/// stopping at the first success. Candidates that fail the probe are skipped. /// stopping at the first success. Candidates that fail the probe are skipped.
/// Answering TCP fastest is used as a network proximity measure,
/// which keeps latency low for initial onboarding,
/// and it avoids relays that are down or black-holing traffic.
pub(crate) async fn add_transport_from_candidates( pub(crate) async fn add_transport_from_candidates(
context: &Context, context: &Context,
skip_network: bool, skip_network: bool,
) -> Result<()> { ) -> Result<()> {
let mut candidates = triable_relay_candidates(context, time()).await?; let mut candidates = triable_relay_candidates(context, time()).await?;
candidates.shuffle(&mut rand::rng()); 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 let mut last_err = format_err!("No relay candidates");
// if many hosts are unreachable but eventually succeeds if one candidate works.
let mark_as_autorelay = true; let mark_as_autorelay = true;
for host in &candidates { while let Some(res) = probes.join_next().await {
let param = login_param_from_host(host, mark_as_autorelay); 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, &param, skip_network).await { match configure(context, &param, skip_network).await {
Ok(()) => { Ok(()) => {
info!(context, "Added a transport on relay {host}."); info!(context, "Added a transport on relay {host}.");
+1 -1
View File
@@ -56,7 +56,7 @@ async fn test_add_transport_from_candidates_failure() -> Result<()> {
mark_defaults_tried(t, time()).await?; mark_defaults_tried(t, time()).await?;
save_relay_candidates(t, &["bad host", "worse host"], 0).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) let err = add_transport_from_candidates(t, skip_network)
.await .await
.unwrap_err(); .unwrap_err();
+1 -1
View File
@@ -189,7 +189,7 @@ impl Context {
Ok(()) 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. /// and lets the profile add further ones in the background.
/// ///
/// A `DCACCOUNT:` or `DCLOGIN:` `qr` code adds a single transport /// A `DCACCOUNT:` or `DCLOGIN:` `qr` code adds a single transport