Merge branch 'cv-main'

This commit is contained in:
xintaofei
2026-04-24 16:02:30 +08:00
3 changed files with 42 additions and 9 deletions

View File

@@ -409,7 +409,7 @@ mod tauri_app {
if let tauri::RunEvent::ExitRequested { .. } = event {
APP_QUITTING.store(true, Ordering::Relaxed);
if let Some(ws) = app.try_state::<web::WebServerState>() {
web::do_stop_web_server(&ws);
tauri::async_runtime::block_on(web::do_stop_web_server(&ws));
}
if let Some(tm) = app.try_state::<TerminalManager>() {
tm.kill_all();

View File

@@ -42,7 +42,7 @@ pub async fn start_web_server(
pub async fn stop_web_server(
Extension(state): Extension<Arc<AppState>>,
) -> Result<Json<()>, AppCommandError> {
do_stop_web_server(&state.web_server_state);
do_stop_web_server(&state.web_server_state).await;
Ok(Json(()))
}

View File

@@ -22,6 +22,7 @@ pub const DEFAULT_WEB_SERVICE_PORT: u16 = 3080;
pub struct WebServerState {
handle: Mutex<Option<tokio::task::JoinHandle<()>>>,
shutdown_tx: Mutex<Option<tokio::sync::oneshot::Sender<()>>>,
port: AtomicU16,
token: Mutex<String>,
running: std::sync::atomic::AtomicBool,
@@ -37,6 +38,7 @@ impl WebServerState {
pub fn new() -> Self {
Self {
handle: Mutex::new(None),
shutdown_tx: Mutex::new(None),
port: AtomicU16::new(0),
token: Mutex::new(String::new()),
running: std::sync::atomic::AtomicBool::new(false),
@@ -323,13 +325,18 @@ pub(crate) async fn do_start_web_server_with_state(
let actual_port = listener.local_addr().map(|a| a.port()).unwrap_or(port);
eprintln!("[WEB] Starting web server on {}", addr);
let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>();
let handle = tokio::spawn(async move {
if let Err(e) = axum::serve(listener, router).await {
let serve = axum::serve(listener, router).with_graceful_shutdown(async move {
let _ = shutdown_rx.await;
});
if let Err(e) = serve.await {
eprintln!("[WEB] Server error: {}", e);
}
});
*ws.handle.lock().unwrap() = Some(handle);
*ws.shutdown_tx.lock().unwrap() = Some(shutdown_tx);
ws.port.store(actual_port, Ordering::Relaxed);
*ws.token.lock().unwrap() = token.clone();
// running already true from compare_exchange; disarm guard so it doesn't flip back.
@@ -343,13 +350,34 @@ pub(crate) async fn do_start_web_server_with_state(
})
}
pub(crate) fn do_stop_web_server(state: &WebServerState) {
if let Some(handle) = state.handle.lock().unwrap().take() {
handle.abort();
pub(crate) async fn do_stop_web_server(state: &WebServerState) {
let handle_opt = state.handle.lock().unwrap().take();
let shutdown_tx = state.shutdown_tx.lock().unwrap().take();
// Signal graceful shutdown so axum stops accepting new connections
// and drops the listening socket once the serve future resolves.
if let Some(tx) = shutdown_tx {
let _ = tx.send(());
}
state.running.store(false, Ordering::Relaxed);
// Await the serve task so the OS socket is guaranteed released before we return.
// A live WebSocket/keep-alive connection can block graceful drain; after a
// short grace period, force-abort and await the cancellation to complete.
if let Some(mut handle) = handle_opt {
if tokio::time::timeout(std::time::Duration::from_secs(2), &mut handle)
.await
.is_err()
{
handle.abort();
let _ = handle.await;
}
}
// Only release the running flag after the listener is guaranteed dropped,
// so a concurrent start() cannot race into a bind() while the old socket lingers.
state.port.store(0, Ordering::Relaxed);
*state.token.lock().unwrap() = String::new();
state.running.store(false, Ordering::Release);
eprintln!("[WEB] Web server stopped");
}
@@ -438,13 +466,18 @@ pub async fn start_web_server(
let actual_port = listener.local_addr().map(|a| a.port()).unwrap_or(port_val);
eprintln!("[WEB] Starting web server on {}", addr);
let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>();
let handle = tokio::spawn(async move {
if let Err(e) = axum::serve(listener, router).await {
let serve = axum::serve(listener, router).with_graceful_shutdown(async move {
let _ = shutdown_rx.await;
});
if let Err(e) = serve.await {
eprintln!("[WEB] Server error: {}", e);
}
});
*ws.handle.lock().unwrap() = Some(handle);
*ws.shutdown_tx.lock().unwrap() = Some(shutdown_tx);
ws.port.store(actual_port, Ordering::Relaxed);
*ws.token.lock().unwrap() = token.clone();
// running already true from compare_exchange; disarm guard so it doesn't flip back.
@@ -463,7 +496,7 @@ pub async fn start_web_server(
pub async fn stop_web_server(
state: tauri::State<'_, WebServerState>,
) -> Result<(), AppCommandError> {
do_stop_web_server(&state);
do_stop_web_server(&state).await;
Ok(())
}