fix(web-client): time out stalled config-server dials (#2461)

Co-authored-by: 225284228a-droid <239500008+225284228a-droid@users.noreply.github.com>
This commit is contained in:
刚刚
2026-08-04 14:08:57 +12:00
committed by GitHub
parent df874b85be
commit d114cdd20f
@@ -28,8 +28,19 @@ use super::{
};
const RETRY_INTERVAL: std::time::Duration = std::time::Duration::from_secs(1);
// Keep retry ownership in this loop when transport or protocol handshakes stall.
const CONNECT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(20);
const FEATURE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3);
async fn connect_config_server(
connector: &dyn TunnelDialer,
timeout: std::time::Duration,
) -> anyhow::Result<Box<dyn Tunnel>> {
time::timeout(timeout, connector.connect())
.await
.map_err(|_| anyhow::anyhow!("config-server connection timed out after {timeout:?}"))?
}
/// Normalized config-server endpoint and authentication token.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConfigServerEndpoint {
@@ -164,7 +175,8 @@ where
connector: Box<dyn TunnelDialer>,
) {
loop {
let connection = match connector.connect().await {
let connection = match connect_config_server(connector.as_ref(), CONNECT_TIMEOUT).await
{
Ok(connection) => connection,
Err(error) => {
tracing::warn!(%error, "failed to connect to config server; retrying");
@@ -191,7 +203,9 @@ where
if support_encryption && web_security::web_secure_tunnel_supported() {
drop(session);
let connection = match connector.connect().await {
let connection = match connect_config_server(connector.as_ref(), CONNECT_TIMEOUT)
.await
{
Ok(connection) => connection,
Err(error) => {
connected.store(false, Ordering::Release);
@@ -364,7 +378,52 @@ where
#[cfg(test)]
mod tests {
use std::{
future::pending,
sync::atomic::{AtomicUsize, Ordering},
};
use async_trait::async_trait;
use super::*;
use crate::tunnel::ring::create_ring_tunnel_pair;
struct StalledThenReadyDialer {
attempts: AtomicUsize,
}
#[async_trait]
impl TunnelDialer for StalledThenReadyDialer {
async fn connect(&self) -> anyhow::Result<Box<dyn Tunnel>> {
if self.attempts.fetch_add(1, Ordering::Relaxed) == 0 {
return pending().await;
}
let (tunnel, _peer) = create_ring_tunnel_pair();
Ok(tunnel)
}
fn remote_url(&self) -> Url {
"ring://config-server".parse().unwrap()
}
}
#[tokio::test]
async fn stalled_connection_attempt_times_out_and_allows_redial() {
let connector = StalledThenReadyDialer {
attempts: AtomicUsize::new(0),
};
let error = connect_config_server(&connector, std::time::Duration::from_millis(10))
.await
.unwrap_err();
assert!(error.to_string().contains("connection timed out"));
connect_config_server(&connector, std::time::Duration::from_secs(1))
.await
.unwrap();
assert_eq!(connector.attempts.load(Ordering::Relaxed), 2);
}
#[test]
fn endpoint_normalizes_non_websocket_paths() {