@@ -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) ]
266269pub 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+
339460pub fn cleanup ( config_path : Option < PathBuf > ) -> Result < ( ) > {
340461 let paths = resolve_paths ( config_path) ?;
341462 log_info ( & paths, "cleanup" , "收到彻底恢复请求,开始恢复系统修改" ) ;
0 commit comments