feat: try fasted relays to attempt first configure on

This commit is contained in:
holger krekel
2026-09-19 23:13:39 +02:00
parent 72e711e59a
commit 0eddb98354
5 changed files with 33 additions and 9 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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.
@@ -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
/// and configured in the order in which the connections complete,
/// 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(
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, &param, skip_network).await {
Ok(()) => {
info!(context, "Added a transport on relay {host}.");

View File

@@ -56,7 +56,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();

View File

@@ -189,7 +189,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