mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-10-26 01:34:51 +08:00
fix(proxy): roll back the saved listen address when the restart fails to bind
When the new address or port could not be bound, update_config had already stopped the old server and written the new config, so the service stayed down and the next save returned Ok(false) because nothing was running: the UI reported success while clients still pointed at the old port. On a failed restart write the previous config back (global four fields or the legacy full row) and start the server on it again, so a failed save changes nothing.
This commit is contained in:
1 parent
edc8c2b966
commit
55fa8e852e
1 file changed
+105
-14
+105
-14
@@ -25,6 +25,22 @@ pub struct ProxyService {
|
||||
switch_locks: SwitchLockManager,
|
||||
}
|
||||
|
||||
/// 重启失败时写回库的那份配置:设置页只动四个全局字段,旧接口是整份七个字段。
|
||||
enum SavedProxyConfig {
|
||||
Global(GlobalProxyConfig),
|
||||
Legacy(ProxyConfig),
|
||||
}
|
||||
|
||||
impl SavedProxyConfig {
|
||||
async fn write(&self, db: &Database) -> Result<(), String> {
|
||||
match self {
|
||||
Self::Global(config) => db.update_global_proxy_config(config.clone()).await,
|
||||
Self::Legacy(config) => db.update_proxy_config(config.clone()).await,
|
||||
}
|
||||
.map_err(|e| format!("恢复原代理配置失败: {e}"))
|
||||
}
|
||||
}
|
||||
|
||||
/// 客户端连接代理用的地址。`listen_address` 可能是 `0.0.0.0` / `::`(监听所有网卡),
|
||||
/// 客户端连不上这个地址,改用本机回环;IPv6 加方括号。
|
||||
pub(crate) fn proxy_origin(listen_address: &str, listen_port: u16) -> String {
|
||||
@@ -468,7 +484,8 @@ impl ProxyService {
|
||||
// 判断是否需要重启(地址或端口变更)
|
||||
let require_restart = new_config.listen_address != previous.listen_address
|
||||
|| new_config.listen_port != previous.listen_port;
|
||||
self.apply_saved_config(require_restart).await
|
||||
self.apply_saved_config(require_restart, SavedProxyConfig::Legacy(previous))
|
||||
.await
|
||||
}
|
||||
|
||||
/// 设置页「保存并重启服务」:只写四个全局字段。各应用自己的重试次数和超时不碰——
|
||||
@@ -488,12 +505,19 @@ impl ProxyService {
|
||||
|
||||
let require_restart = config.listen_address != previous.listen_address
|
||||
|| config.listen_port != previous.listen_port;
|
||||
self.apply_saved_config(require_restart).await
|
||||
self.apply_saved_config(require_restart, SavedProxyConfig::Global(previous))
|
||||
.await
|
||||
}
|
||||
|
||||
/// 让运行中的服务用上刚写进库的配置:地址或端口变了就重启(返回 true),否则实时应用。
|
||||
/// 服务没在跑时什么都不做。
|
||||
async fn apply_saved_config(&self, require_restart: bool) -> Result<bool, String> {
|
||||
/// 服务没在跑时什么都不做。新地址绑不上就把 `previous` 写回库、按旧地址重新拉起,让
|
||||
/// 「保存失败」就是什么都没变:服务停着不管的话,下一次保存会因为没在跑而假成功,
|
||||
/// 客户端也还指着旧地址。
|
||||
async fn apply_saved_config(
|
||||
&self,
|
||||
require_restart: bool,
|
||||
previous: SavedProxyConfig,
|
||||
) -> Result<bool, String> {
|
||||
let mut server_guard = self.server.write().await;
|
||||
if server_guard.is_none() {
|
||||
return Ok(false);
|
||||
@@ -519,23 +543,48 @@ impl ProxyService {
|
||||
.map_err(|e| format!("重启前停止代理服务器失败: {e}"))?;
|
||||
}
|
||||
|
||||
let error = match self.start_with(&new_config).await {
|
||||
Ok(server) => {
|
||||
*server_guard = Some(server);
|
||||
log::info!("代理配置已更新,服务器已自动重启应用最新配置");
|
||||
return Ok(true);
|
||||
}
|
||||
Err(error) => format!("重启代理服务器失败: {error}"),
|
||||
};
|
||||
|
||||
log::warn!("{error},恢复原配置并重新启动");
|
||||
match self.restore_and_start(previous).await {
|
||||
Ok(server) => {
|
||||
*server_guard = Some(server);
|
||||
Err(format!("{error};已按原配置重新启动"))
|
||||
}
|
||||
Err(restore_error) => Err(format!("{error};恢复原配置也失败: {restore_error}")),
|
||||
}
|
||||
}
|
||||
|
||||
/// 按 `config` 起一个新服务,动态端口回写进库。
|
||||
async fn start_with(&self, config: &ProxyConfig) -> Result<ProxyServer, String> {
|
||||
let app_handle = self.app_handle.read().await.clone();
|
||||
let new_server = ProxyServer::new(new_config.clone(), self.db.clone(), app_handle);
|
||||
let info = new_server
|
||||
.start()
|
||||
.await
|
||||
.map_err(|e| format!("重启代理服务器失败: {e}"))?;
|
||||
let server = ProxyServer::new(config.clone(), self.db.clone(), app_handle);
|
||||
let info = server.start().await.map_err(|e| e.to_string())?;
|
||||
if let Err(e) = self
|
||||
.persist_ephemeral_listen_port_if_needed(&new_config, info.port)
|
||||
.persist_ephemeral_listen_port_if_needed(config, info.port)
|
||||
.await
|
||||
{
|
||||
let _ = new_server.stop().await;
|
||||
let _ = server.stop().await;
|
||||
return Err(e);
|
||||
}
|
||||
Ok(server)
|
||||
}
|
||||
|
||||
*server_guard = Some(new_server);
|
||||
log::info!("代理配置已更新,服务器已自动重启应用最新配置");
|
||||
Ok(true)
|
||||
async fn restore_and_start(&self, previous: SavedProxyConfig) -> Result<ProxyServer, String> {
|
||||
previous.write(&self.db).await?;
|
||||
let config = self
|
||||
.db
|
||||
.get_proxy_config()
|
||||
.await
|
||||
.map_err(|e| format!("获取代理配置失败: {e}"))?;
|
||||
self.start_with(&config).await
|
||||
}
|
||||
|
||||
/// 检查服务器是否正在运行
|
||||
@@ -679,4 +728,46 @@ mod tests {
|
||||
serde_json::to_value(global).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
/// 新端口绑不上时「保存失败」要等于什么都没变:库里还是旧端口、服务还在旧端口上跑。
|
||||
/// 服务停着不管的话,下一次保存会因为「没在跑」直接假成功。
|
||||
#[tokio::test]
|
||||
async fn failed_restart_restores_previous_config_and_server() {
|
||||
let db = Arc::new(Database::memory().unwrap());
|
||||
let service = ProxyService::new(db.clone());
|
||||
let mut global = db.get_global_proxy_config().await.unwrap();
|
||||
global.listen_address = "127.0.0.1".to_string();
|
||||
global.listen_port = 0;
|
||||
db.update_global_proxy_config(global).await.unwrap();
|
||||
let running_port = service.start().await.unwrap().port;
|
||||
assert_ne!(running_port, 0);
|
||||
|
||||
// 占住另一个端口,让重启时绑定失败
|
||||
let blocker = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let taken_port = blocker.local_addr().unwrap().port();
|
||||
let mut attempt = db.get_global_proxy_config().await.unwrap();
|
||||
attempt.listen_port = taken_port;
|
||||
|
||||
let error = service
|
||||
.update_global_config(&attempt)
|
||||
.await
|
||||
.expect_err("binding a taken port must fail");
|
||||
assert!(error.contains("重启代理服务器失败"), "{error}");
|
||||
|
||||
assert_eq!(
|
||||
db.get_global_proxy_config().await.unwrap().listen_port,
|
||||
running_port,
|
||||
"库里应回到原端口"
|
||||
);
|
||||
let status = service.get_status().await.unwrap();
|
||||
assert!(status.running, "服务应按原配置重新拉起");
|
||||
assert_eq!(status.port, running_port);
|
||||
|
||||
// 再保存一次不再假成功:服务在跑,配置没变就实时应用
|
||||
let again = db.get_global_proxy_config().await.unwrap();
|
||||
assert!(!service.update_global_config(&again).await.unwrap());
|
||||
assert!(service.is_running().await);
|
||||
service.stop().await.unwrap();
|
||||
drop(blocker);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user