Rust使用daemonize守护化后Unix socket无法接受连接
问题根因
故障本质是守护进程fork操作和Tokio运行时的初始化顺序冲突:
- 你当前代码用
#[tokio::main]宏标记异步main,程序启动时会第一时间初始化Tokio运行时,完成IO驱动(epoll/kqueue)的创建、事件监听注册等操作。 - 你在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
相关产品推荐
相关产品推荐

