fix(web-client): keep retrying unreachable config server (#2140)

Defer config-server connector creation into the web client retry loop so
service startup does not fail when network or DNS is unavailable.
This commit is contained in:
fanyang
2026-05-02 00:09:48 +08:00
committed by GitHub
parent 1b48029bdc
commit 4eba9b07b6
+63 -5
View File
@@ -2,13 +2,17 @@ use std::sync::Arc;
use crate::{ use crate::{
common::{ common::{
config::TomlConfigLoader, global_ctx::GlobalCtx, log, os_info::collect_device_os_info, config::TomlConfigLoader,
set_default_machine_id, stun::MockStunInfoCollector, global_ctx::{ArcGlobalCtx, GlobalCtx},
log,
os_info::collect_device_os_info,
set_default_machine_id,
stun::MockStunInfoCollector,
}, },
connector::create_connector_by_url, connector::create_connector_by_url,
instance_manager::{DaemonGuard, NetworkInstanceManager}, instance_manager::{DaemonGuard, NetworkInstanceManager},
proto::common::NatType, proto::common::NatType,
tunnel::{IpVersion, TunnelConnector}, tunnel::{IpVersion, Tunnel, TunnelConnector, TunnelError, TunnelScheme},
}; };
use anyhow::{Context as _, Result}; use anyhow::{Context as _, Result};
use async_trait::async_trait; use async_trait::async_trait;
@@ -49,6 +53,30 @@ pub struct WebClient {
connected: Arc<AtomicBool>, connected: Arc<AtomicBool>,
} }
struct ConfigServerConnector {
url: Url,
global_ctx: ArcGlobalCtx,
}
#[async_trait]
impl TunnelConnector for ConfigServerConnector {
async fn connect(&mut self) -> std::result::Result<Box<dyn Tunnel>, TunnelError> {
let mut connector =
create_connector_by_url(self.url.as_str(), &self.global_ctx, IpVersion::Both)
.await
.map_err(|err| match err {
crate::common::error::Error::TunnelError(err) => err,
err => TunnelError::Anyhow(err.into()),
})?;
connector.connect().await
}
fn remote_url(&self) -> Url {
self.url.clone()
}
}
impl WebClient { impl WebClient {
pub fn new<T: TunnelConnector + 'static, S: ToString, H: ToString>( pub fn new<T: TunnelConnector + 'static, S: ToString, H: ToString>(
connector: T, connector: T,
@@ -218,6 +246,13 @@ pub async fn run_web_client(
.with_context(|| "failed to parse config server URL")?, .with_context(|| "failed to parse config server URL")?,
}; };
TunnelScheme::try_from(&config_server_url).map_err(|_| {
anyhow::anyhow!(
"unsupported config server scheme: {}",
config_server_url.scheme()
)
})?;
let mut c_url = config_server_url.clone(); let mut c_url = config_server_url.clone();
if !matches!(c_url.scheme(), "ws" | "wss") { if !matches!(c_url.scheme(), "ws" | "wss") {
c_url.set_path(""); c_url.set_path("");
@@ -243,16 +278,20 @@ pub async fn run_web_client(
let mut flags = global_ctx.get_flags(); let mut flags = global_ctx.get_flags();
flags.bind_device = false; flags.bind_device = false;
global_ctx.set_flags(flags); global_ctx.set_flags(flags);
let hostname = match hostname { let hostname = match hostname {
None => gethostname::gethostname().to_string_lossy().to_string(), None => gethostname::gethostname().to_string_lossy().to_string(),
Some(hostname) => hostname, Some(hostname) => hostname,
}; };
Ok(WebClient::new( Ok(WebClient::new(
create_connector_by_url(c_url.as_str(), &global_ctx, IpVersion::Both).await?, ConfigServerConnector {
url: c_url,
global_ctx,
},
token.to_string(), token.to_string(),
hostname, hostname,
secure_mode, secure_mode,
manager.clone(), manager,
hooks, hooks,
)) ))
} }
@@ -292,4 +331,23 @@ mod tests {
assert!(sleep_finish.load(std::sync::atomic::Ordering::Relaxed)); assert!(sleep_finish.load(std::sync::atomic::Ordering::Relaxed));
println!("Manager stopped."); println!("Manager stopped.");
} }
#[tokio::test]
async fn test_run_web_client_with_unreachable_config_server() {
let manager = Arc::new(NetworkInstanceManager::new());
let client = super::run_web_client(
"udp://config-server.invalid:22020/test",
None,
None,
false,
manager,
None,
)
.await
.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
assert!(!client.is_connected());
drop(client);
}
} }