Skip to content

Commit 180e5e9

Browse files
committed
Merge helper and daemon into single process — no more zombie issues
1 parent 44936a9 commit 180e5e9

1 file changed

Lines changed: 177 additions & 56 deletions

File tree

src/service.rs

Lines changed: 177 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -262,16 +262,14 @@ pub fn helper_stop(config_path: Option<PathBuf>) -> Result<()> {
262262
/// Run the privileged helper daemon that listens on a Unix domain socket.
263263
/// This runs as root (via LaunchDaemon) and handles start/stop/status commands
264264
/// from the GUI without requiring a password each time.
265+
///
266+
/// The helper also directly runs the proxy — no separate daemon process is
267+
/// spawned, which avoids zombie-process issues entirely.
265268
#[cfg(unix)]
266269
pub fn run_privileged_helper(config_path: Option<PathBuf>) -> Result<()> {
267-
use crate::helper_ipc::{self, HelperRequest, HelperResponse};
270+
use std::sync::Mutex;
268271

269-
// Automatically reap zombie child processes (daemon exits become zombies
270-
// because the helper is their parent and doesn't explicitly wait() on them).
271-
#[cfg(unix)]
272-
unsafe {
273-
libc::signal(libc::SIGCHLD, libc::SIG_IGN);
274-
}
272+
use crate::helper_ipc::{self, HelperRequest, HelperResponse};
275273

276274
let paths = resolve_paths(config_path.clone())?;
277275
log_info(
@@ -280,62 +278,185 @@ pub fn run_privileged_helper(config_path: Option<PathBuf>) -> Result<()> {
280278
"特权辅助守护进程启动,开始监听 socket",
281279
);
282280

281+
// Shared state: holds the running proxy's shutdown channel and join handle.
282+
let proxy_state: Arc<Mutex<Option<ProxyHandle>>> = Arc::new(Mutex::new(None));
283283
let socket = helper_ipc::socket_path();
284-
let config_path = paths.config_path.clone();
285-
286-
helper_ipc::run_server(&socket, |request| match request {
287-
HelperRequest::Start { config_path } => {
288-
let resp = match helper_start(Some(config_path.clone())) {
289-
Ok(()) => HelperResponse {
290-
success: true,
291-
message: "加速服务已启动".to_string(),
292-
status: None,
293-
},
294-
Err(e) => HelperResponse {
295-
success: false,
296-
message: format!("{e:#}"),
297-
status: None,
298-
},
299-
};
300-
resp
301-
}
302-
HelperRequest::Stop { config_path } => {
303-
let resp = match helper_stop(Some(config_path.clone())) {
304-
Ok(()) => HelperResponse {
305-
success: true,
306-
message: "加速服务已停止".to_string(),
307-
status: None,
308-
},
309-
Err(e) => HelperResponse {
310-
success: false,
311-
message: format!("{e:#}"),
312-
status: None,
313-
},
314-
};
315-
resp
316-
}
317-
HelperRequest::Status { config_path } => {
318-
let resp = match status(Some(config_path.clone())) {
319-
Ok(s) => HelperResponse {
320-
success: true,
321-
message: "ok".to_string(),
322-
status: Some(s),
323-
},
324-
Err(e) => HelperResponse {
325-
success: false,
326-
message: format!("{e:#}"),
327-
status: None,
328-
},
329-
};
330-
resp
331-
}
284+
285+
let ps = proxy_state.clone();
286+
helper_ipc::run_server(&socket, move |request| match request {
287+
HelperRequest::Start { config_path } => handle_helper_start(config_path, &ps),
288+
HelperRequest::Stop { config_path } => handle_helper_stop(config_path, &ps),
289+
HelperRequest::Status { config_path } => handle_helper_status(config_path, &ps),
332290
})?;
333291

334-
// Cleanup socket on exit.
292+
// Cleanup socket on exit (unreachable in practice since run_server loops forever).
335293
let _ = std::fs::remove_file(&socket);
336294
Ok(())
337295
}
338296

297+
/// Handle for a running proxy instance.
298+
#[cfg(unix)]
299+
struct ProxyHandle {
300+
shutdown_tx: tokio::sync::watch::Sender<bool>,
301+
join: std::thread::JoinHandle<()>,
302+
}
303+
304+
#[cfg(unix)]
305+
fn handle_helper_start(
306+
config_path: &std::path::Path,
307+
proxy_state: &Arc<Mutex<Option<ProxyHandle>>>,
308+
) -> HelperResponse {
309+
use crate::helper_ipc::HelperResponse;
310+
311+
let config_path = config_path.to_path_buf();
312+
313+
// If proxy is already running, stop it first.
314+
{
315+
let mut state = proxy_state.lock().unwrap();
316+
if let Some(handle) = state.take() {
317+
let _ = handle.shutdown_tx.send(true);
318+
let _ = handle.join.join();
319+
}
320+
}
321+
322+
let result = (|| -> Result<()> {
323+
let paths = resolve_paths(Some(config_path.clone()))?;
324+
let _ = AppConfig::migrate_config_if_needed(&paths.config_path)?;
325+
let config = AppConfig::load_or_create(&paths.config_path)?;
326+
327+
ensure_loopback_alias(&config)?;
328+
329+
let bundle = ensure_bundle(&config, &paths.cert_dir)?;
330+
331+
apply_hosts(&config, &paths)?;
332+
let _ = flush_dns_cache();
333+
334+
let pid = std::process::id();
335+
state::write_pid(&paths, pid)?;
336+
state::mark_running(&paths, pid)?;
337+
338+
let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false);
339+
let ps = proxy_state.clone();
340+
let cfg = config.clone();
341+
let p = paths.clone();
342+
343+
let join = std::thread::spawn(move || {
344+
let rt = tokio::runtime::Runtime::new().expect("failed to create tokio runtime");
345+
rt.block_on(async move {
346+
let result = run_proxy(cfg, p.clone(), bundle, shutdown_rx).await;
347+
let _ = state::clear_pid_if_matches(&p, pid);
348+
match &result {
349+
Ok(_) => {
350+
let _ = state::mark_stopped(&p, "加速服务已退出");
351+
}
352+
Err(error) => {
353+
let _ = state::mark_error(&p, &error.to_string());
354+
}
355+
}
356+
});
357+
// Remove handle from shared state when proxy exits.
358+
let mut s = ps.lock().unwrap();
359+
*s = None;
360+
});
361+
362+
*proxy_state.lock().unwrap() = Some(ProxyHandle {
363+
shutdown_tx,
364+
join,
365+
});
366+
367+
Ok(())
368+
})();
369+
370+
match result {
371+
Ok(()) => HelperResponse {
372+
success: true,
373+
message: "加速服务已启动".to_string(),
374+
status: None,
375+
},
376+
Err(e) => HelperResponse {
377+
success: false,
378+
message: format!("{e:#}"),
379+
status: None,
380+
},
381+
}
382+
}
383+
384+
#[cfg(unix)]
385+
fn handle_helper_stop(
386+
config_path: &std::path::Path,
387+
proxy_state: &Arc<Mutex<Option<ProxyHandle>>>,
388+
) -> HelperResponse {
389+
use crate::helper_ipc::HelperResponse;
390+
391+
let config_path = config_path.to_path_buf();
392+
393+
let result = (|| -> Result<()> {
394+
let paths = resolve_paths(Some(config_path.clone()))?;
395+
let config = AppConfig::load_or_create(&paths.config_path)?;
396+
397+
// Signal proxy to shut down.
398+
let handle = {
399+
let mut state = proxy_state.lock().unwrap();
400+
state.take()
401+
};
402+
403+
if let Some(handle) = handle {
404+
let _ = handle.shutdown_tx.send(true);
405+
// Wait for proxy thread to finish (with timeout).
406+
let start = std::time::Instant::now();
407+
while !handle.join.is_finished() {
408+
if start.elapsed() > std::time::Duration::from_secs(10) {
409+
break;
410+
}
411+
std::thread::sleep(std::time::Duration::from_millis(100));
412+
}
413+
}
414+
415+
// Clean up hosts, loopback, etc.
416+
let _ = restore_hosts_after_stop(&paths);
417+
let _ = remove_loopback_alias(&config);
418+
let _ = state::clear_pid(&paths);
419+
let _ = flush_dns_cache();
420+
let _ = state::mark_stopped(&paths, "已停止");
421+
422+
Ok(())
423+
})();
424+
425+
match result {
426+
Ok(()) => HelperResponse {
427+
success: true,
428+
message: "加速服务已停止".to_string(),
429+
status: None,
430+
},
431+
Err(e) => HelperResponse {
432+
success: false,
433+
message: format!("{e:#}"),
434+
status: None,
435+
},
436+
}
437+
}
438+
439+
#[cfg(unix)]
440+
fn handle_helper_status(
441+
config_path: &std::path::Path,
442+
proxy_state: &Arc<Mutex<Option<ProxyHandle>>>,
443+
) -> HelperResponse {
444+
use crate::helper_ipc::HelperResponse;
445+
446+
match status(Some(config_path.to_path_buf())) {
447+
Ok(s) => HelperResponse {
448+
success: true,
449+
message: "ok".to_string(),
450+
status: Some(s),
451+
},
452+
Err(e) => HelperResponse {
453+
success: false,
454+
message: format!("{e:#}"),
455+
status: None,
456+
},
457+
}
458+
}
459+
339460
pub fn cleanup(config_path: Option<PathBuf>) -> Result<()> {
340461
let paths = resolve_paths(config_path)?;
341462
log_info(&paths, "cleanup", "收到彻底恢复请求,开始恢复系统修改");

0 commit comments

Comments
 (0)