You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust使用daemonize守护化后Unix socket无法接受连接

问题根因

故障本质是守护进程fork操作和Tokio运行时的初始化顺序冲突:

  1. 你当前代码用#[tokio::main]宏标记异步main,程序启动时会第一时间初始化Tokio运行时,完成IO驱动(epoll/kqueue)的创建、事件监听注册等操作。
  2. 你在Tokio运行时启动后才调用daemonize.start()执行fork,fork生成的守护子进程只会继承父进程的文件描述符,不会继承父进程Tokio运行时的epoll事件监听上下文,导致后续子进程里创建的UnixListener虽然绑定端口成功,但IO事件通知完全失效,accept().await会永久阻塞,既拿不到新连接也不会抛错。

前台模式下没有fork操作,Tokio运行时从启动到结束都在同一个进程里,IO事件监听正常,所以功能完全正常。

修复方案
  • 调整执行顺序:移除#[tokio::main]宏,改用同步main函数作为入口,先完成所有守护化fork操作,确认进入守护子进程后再手动初始化Tokio运行时,执行所有异步逻辑。参考实现代码:
fn main() -> Result<(), std::io::Error> {
    Logger::init();
    let args = Args::parse();

    // 所有守护化逻辑全部放在Tokio启动前的同步阶段执行
    if !args.foreground {
        let pid_file = args.pid_file.unwrap_or("/tmp/vnsd.pid".to_owned());
        if Path::new(&pid_file).exists() {
            std::fs::remove_file(&pid_file)
                .map_err(|e| error!("{e}"))
                .unwrap();
        }

        let stdout = File::create("/tmp/vnsd.out").unwrap();
        let stderr = File::create("/tmp/vnsd.err").unwrap();
        let daemonize = Daemonize::new()
            .pid_file(pid_file)
            .chown_pid_file(false)
            .working_directory("/tmp")
            .user(args.user.unwrap_or("nobody".to_owned()).as_str())
            .group(args.group.unwrap_or("nobody".to_owned()).as_str())
            .umask(0o011)
            .stdout(stdout)
            .stderr(stderr)
            .privileged_action(|| println!("Privileged action!"));

        match daemonize.start() {
            Ok(_) => info!("Daemonization starting"),
            Err(e) => {
                error!("Cannot daemonization: {e}");
                std::process::exit(1);
            }
        }
    }

    // 守护化完成后再启动全新的Tokio运行时
    tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .unwrap()
        .block_on(async move {
            // 原有所有异步逻辑(绑定Unix socket、启动HTTP服务、处理连接等)全部移到这里
            let mut listener = UnixSocket::bind("/tmp/vnsd.sock") 
                .map_err(|e| { 
                    error!( 
                        "Cannot bind unix socket server: {}", 
                        e.root_cause().downcast_ref::<std::io::Error>().unwrap() 
                    ) 
                }) 
                .unwrap();

            let _: (_, Result<(), anyhow::Error>) = tokio::join!(
                async {
                    // HTTP服务逻辑
                },
                async {
                    loop {
                        listener.handle().await.map_err(|e| error!("{e}")).unwrap();
                        match listener.receive().await {
                            // 原有消息处理逻辑
                        }
                    }
                }
            );
            Ok(())
        })
}
  • 校验Unix socket文件权限:你设置的umask为0o011,绑定生成的/tmp/vnsd.sock默认权限为0o766,要确认socket文件的属主和你启动时指定的mohamed用户一致,避免客户端连接时出现权限拒绝。
  • 修复UnixSocket封装的并发缺陷:你当前的实现同一时间只能保存一个连接流,处理上一个连接的消息时不会调用accept接收新连接,会导致后续连接请求阻塞。建议accept拿到新的UnixStream后,直接spawn独立的异步任务处理该连接的读写,不要把stream存在UnixSocket结构体中串行处理。

内容的提问来源于stack exchange,提问作者Mohamed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 21:48:48