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

Rust中Tokio与daemonize协同工作异常问题及解决方案

Tokio + daemonize 后台运行时TCP连接日志无响应问题修复

背景

我编写了一个基于Tokio和fern的TCP服务程序,功能是接收TCP输入并将日志同时输出到标准输出与文件。为了让程序在远程服务器后台持续运行(即使断开终端连接也能记录日志),选用了daemonize crate实现守护进程化。

初始代码

use std::fs::File;
use std::str;
use std::time::SystemTime;

use fern::colors::{Color, ColoredLevelConfig};

use log::{debug, info, trace, warn};

use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;

use daemonize::Daemonize;

fn setup_logging(verbosity: u64) -> Result<(), fern::InitError> {
    let mut base_config = fern::Dispatch::new();

    base_config = match verbosity {
        0 => {
            // Let's say we depend on something which whose "info" level messages are too
            // verbose to include in end-user output. If we don't need them,
            // let's not include them.
            base_config
                .level(log::LevelFilter::Info)
                .level_for("overly-verbose-target", log::LevelFilter::Warn)
        }
        1 => base_config
            .level(log::LevelFilter::Debug)
            .level_for("overly-verbose-target", log::LevelFilter::Info),
        2 => base_config.level(log::LevelFilter::Debug),
        _3_or_more => base_config.level(log::LevelFilter::Trace),
    };

    // Separate file config so we can include year, month and day in file logs
    let file_config = fern::Dispatch::new()
        .format(|out, message, record| {
            let colors = ColoredLevelConfig::new().debug(Color::Magenta);
            out.finish(format_args!(
                "[{} {} {}] {}",
                humantime::format_rfc3339_seconds(SystemTime::now()),
                colors.color(record.level()),
                //record.level(),
                record.target(),
                message
            ))
        })
        .chain(fern::log_file("program.log")?);

    let stdout_config = fern::Dispatch::new()
        .format(|out, message, record| {
            let colors = ColoredLevelConfig::new().debug(Color::Magenta);
            // special format for debug messages coming from our own crate.
            if record.level() > log::LevelFilter::Info && record.target() == "cmd_program" {
                out.finish(format_args!(
                    "DEBUG @ {}: {}",
                    humantime::format_rfc3339_seconds(SystemTime::now()),
                    message
                ))
            } else {
                out.finish(format_args!(
                    "[{} {} {}] {}",
                    humantime::format_rfc3339_seconds(SystemTime::now()),
                    colors.color(record.level()),
                    //record.level(),
                    record.target(),
                    message
                ))
            }
        })
        .chain(std::io::stdout());

    base_config
        .chain(file_config)
        .chain(stdout_config)
        .apply()?;

    Ok(())
}

#[tokio::main]
async fn main() -> io::Result<()> {
    let stdout = File::create("/tmp/daemon.out").unwrap();
    let stderr = File::create("/tmp/daemon.err").unwrap();

    let daemonize = Daemonize::new()
        .pid_file("/tmp/test.pid") // Every method except `new` and `start`
        .chown_pid_file(true) // is optional, see `Daemonize` documentation
        .working_directory("/tmp") // for default behaviour.
        .user("nobody")
        .group("daemon") // Group name
        .group(2) // or group id.
        .umask(0o777) // Set umask, `0o027` by default.
        .stdout(stdout) // Redirect stdout to `/tmp/daemon.out`.
        .stderr(stderr) // Redirect stderr to `/tmp/daemon.err`.
        .privileged_action(|| "Executed before drop privileges");

    match daemonize.start() {
        Ok(_) => println!("Success, daemonized"),
        Err(e) => eprintln!("Error, {}", e),
    }

    setup_logging(3).expect("failed to initialize logging.");
    info!("Server starting...");
    let listener = TcpListener::bind("###IP:port###").await?;
    info!("TcpListener bound to ###IP:port###");
    info!("{:?}", listener.local_addr());
    loop {
        println!("Listening for connection.");
        let (mut socket, _) = listener.accept().await?;
        info!("Connection made from client: {:?}", socket.peer_addr());
        println!("Connection made from client: {:?}", socket.peer_addr());

        tokio::spawn(async move {
            let mut buf = vec![0; 1024];

            loop {
                match socket.read(&mut buf).await {
                    // Return value of `Ok(0)` signifies that the remote has
                    // closed
                    Ok(0) => {
                        info!("Client ({:?}) closed the connection", socket.peer_addr());
                        println!("Client ({:?}) closed the connection", socket.peer_addr());
                        return;
                    }
                    Ok(n) => {
                        let s = match str::from_utf8(buf.as_slice()) {
                            Ok(v) => v,
                            Err(e) => {
                                trace!("Invalid UTF-8 sequence: {}", e);
                                return;
                            }
                        };
                        print!("{}", s);
                        info!("{}", s);
                        // Copy the data back to socket
                        // if socket.write_all(&buf[..n]).await.is_err() {
                        //     // Unexpected socket error. There isn't much we can
                        //     // do here so just stop processing.
                        //     return;
                        // }
                    }
                    Err(_) => {
                        debug!("Something went wrong. Socket Error.");
                        // Unexpected socket error. There isn't much we can do
                        // here so just stop processing.
                        return;
                    }
                }
            }
        });
    }
    debug!("Program outside of loop and exiting. Did something go wrong?");
}

异常现象

程序执行到let (mut socket, _) = listener.accept().await?;后不再输出日志,但TCP连接可正常建立:telnet能连接并发送消息,杀死程序时telnet会提示断开。说明Tokio底层处理了TCP连接,但没有触发accept()返回的Future后续逻辑。

已知Tokio与fork存在兼容性问题,但资料显示在Tokio初始化前执行fork即可正常工作。

修复后的代码

//////////////////////////////////////////////////////////////
// Changes made after answer that got the code working
//////////////////////////////////////////////////////////////

fn main() -> Result<(), Box<dyn std::error::Error>> {
let stdout = File::create("/tmp/daemon.out").unwrap();
let stderr = File::create("/tmp/daemon.err").unwrap();

let daemonize = Daemonize::new()
    .pid_file("/tmp/test.pid") // Every method except `new` and `start`
    .chown_pid_file(true) // is optional, see `Daemonize` documentation
    .working_directory("/tmp") // for default behaviour.
    .user("nobody")
    .group("daemon") // Group name
    .group(2) // or group id.
    .umask(0o777) // Set umask, `0o027` by default.
    .stdout(stdout) // Redirect stdout to `/tmp/daemon.out`.
    .stderr(stderr) // Redirect stderr to `/tmp/daemon.err`.
    .privileged_action(|| "Executed before drop privileges");

match daemonize.start() {
    Ok(_) => println!("Success, daemonized"),
    Err(e) => eprintln!("Error, {}", e),
}

tokio::runtime::Builder::new_multi_thread()
    .enable_all()
    .build()
    .unwrap()
    .block_on(async {
        setup_logging(3).expect("failed to initialize logging.");
        main_functionality().await
    })
//debug!("Program outside of loop and exitting. Did something go wrong?");
}

async fn main_functionality() -> Result<(), Box<dyn 
std::error::Error>> {
info!("Server starting...");
let listener = TcpListener::bind("###IP:port###").await?;
info!("TcpListener bound to ###IP:port###");
info!("{:?}", listener.local_addr());
loop {
    println!("Listening for connection.");
    let (mut socket, _) = listener.accept().await?;
    info!("Connection made from client: {:?}", socket.peer_addr());
    println!("Connection made from client: {:?}", socket.peer_addr());

    tokio::spawn(async move {
        let mut buf = vec![0; 1024];

        loop {
            match socket.read(&mut buf).await {
                // Return value of `Ok(0)` signifies that the remote has
                // closed
                Ok(0) => {
                    info!("Client ({:?}) closed the connection", socket.peer_addr());
                    println!("Client ({:?}) closed the connection", socket.peer_addr());
                    return;
                }
                Ok(n) => {
                    let s = match str::from_utf8(buf.as_slice()) {
                        Ok(v) => v,
                        Err(e) => {
                            trace!("Invalid UTF-8 sequence: {}:[{}]", e, n);
                            return;
                        }
                    };
                    print!("{}", s);
                    info!("{}", s);
                    // Copy the data back to socket
                    // if socket.write_all(&buf[..n]).await.is_err() {
                    //     // Unexpected socket error. There isn't much we can
                    //     // do here so just stop processing.
                    //     return;
                    // }
                }
                Err(_) => {
                    debug!("Something went wrong. Socket Error.");
                    // Unexpected socket error. There isn't much we can do
                    // here so just stop processing.
                    return;
                }
            }
        }
    });
}
}

补充说明

修复后程序已正常运行。另外,除了daemonize crate,也可以使用systemd这类系统服务管理工具来实现后台服务的托管与管理。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 07:07:35