写点什么

文盘 Rust -- 把程序作为守护进程启动

  • 2022-11-04
    北京
  • 本文字数:2692 字

    阅读完需:约 9 分钟

原文来源:https://tidb.net/blog/64d45df0


notice”Rust is a trademark of the Mozilla Foundation in the US and other countries.”



当我们写完一个服务端程序,需要上线部署的时候,或多或少都会和操作系统的守护进程打交道,毕竟谁也不希望 shell 关闭既停服。今天我们就来聊聊这个事儿。最早大家部署应用的通常操作是 “nohup xxxx &”,别说像 weblogic 或者其他 java 容器有启动脚本,里面其实也差不多;很喜欢 nginx 的 -d 参数,或者像 redis 配置文件里可以指定是否以守护进程启动。看起来很优雅。那么,使用 rust 写一个服务端程序能不能优雅的使用一个参数指定应用 daemon 模式启动,同时使用 stop 方式优雅的停机呢?我们通过一个例子来说说基本的实现方式。实例代码依然集成在interactcli-rs 工程中。


首先来模拟一个启动的服务进程 /src/server/server.rs


pub fn start(prefix: String) {    for i in 0..1000 {        println!("{}", prefix.clone() + &i.to_string());        thread::sleep(Duration::from_secs(1));    }}
复制代码


程序每秒输出一个字符串,持续 999 秒,这个时长足够验证实验结果了。


后台启动有两个实现,分别是利用 fork  或 daemonize , 这两个 crate 实现原理类似,但在使用上稍有不同。


/src/cmd/cmdserver.rs, 构建了两个启动子命令,分别来调用 fork 和 daemonize 的守护进程启动实现。


pub fn new_server_cmd() -> Command {    clap::Command::new("server")        .about("server")        .subcommand(server_start_byfork())        .subcommand(server_start_bydaemonize())}
pub fn server_start_byfork() -> Command { clap::Command::new("byfork") .about("start daemon by fork crate") .arg( Arg::new("daemon") .short('d') .long("daemon") .action(ArgAction::SetTrue) .help("start as daemon") .required(false), )}pub fn server_start_bydaemonize() -> Command { clap::Command::new("bydaemonize") .about("start daemon by daemonize crate") .arg( Arg::new("daemon") .short('d') .long("daemon") .action(ArgAction::SetTrue) .help("start as daemon") .required(false), )}
复制代码


server 的子命令 byfork 启动 通过 fork 实现的功能,bydaemonize 则调用通过 daemonize 的功能实现。命令解析的代码在 /src/cmd/rootcmd.rs 文件中。先来看看基于 fork 的实现


if let Some(startbyfork) = server.subcommand_matches("byfork") {    println!("start by fork");    if startbyfork.get_flag("daemon") {        let args: Vec<String> = env::args().collect();        if let Ok(Fork::Child) = daemon(true, false) {            // 启动子进程            let mut cmd = Command::new(&args[0])            for idx in 1..args.len() {                let arg = args.get(idx).expect("get cmd arg error!");                // 去除后台启动参数,避免重复启动                if arg.eq("-d") || arg.eq("-daemon") {                    continue;                }                cmd.arg(arg);                        let child = cmd.spawn().expect("Child process failed to start.");            fs::write("pid", child.id().to_string()).unwrap();            println!("process id is:{}", std::process::id());            println!("child id is:{}", child.id());        }        println!("{}", "daemon mod");        process::exit(0);    }    start("by_fork:".to_string());}
复制代码


首先,通过 Fork::daemon 函数派生出一个子进程;然后解析一下当前命令,去掉 -d 参数,构建一个启动命令,子命令启动,退出父进程。这基本符合操作系统创建守护进程的过程 – 两次 fork。


再来看看基于 daemonize 的实现。


if let Some(startbydaemonize) = server.subcommand_matches("bydaemonize") {            println!("start by daemonize");            let base_dir = env::current_dir().unwrap();            if startbydaemonize.get_flag("daemon") {                let stdout = File::create("/tmp/daemon.out").unwrap();                let stderr = File::create("/tmp/daemon.err").unwrap();
println!("{:?}", base_dir);
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(base_dir.as_path()) // for default behaviour. .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), } } println!("pid is:{}", std::process::id()); fs::write("pid", process::id().to_string()).unwrap(); start("by_daemonize:".to_string()); }
复制代码


首先获取当前的工作目录,默认情况下 daemonize 会将工作目录设置为 “/”, 为了避免权限问题,我们获取当前目录作为守护进程的工作目录。不知道是什么原因,在配置了 pid_file 后,启动守护进程时并没在文件中有记录 pid。不过也没关系,我们可以在外部获取并记录守护进程的 pid。两种方式启动的守护进程均可在关闭 shell 的情况下维持进程运行。从实现上来讲,不论是 fork 还是 daemonize 都是 通过 unsafe 方式调用了 libc api,类 unix 系统大多跑起来没问题,windows 系统作者没有验证。本期关于守护进程的话题就聊到这儿。


咱们下期见。


发布于: 刚刚阅读数: 3
用户头像

TiDB 社区官网:https://tidb.net/ 2021-12-15 加入

TiDB 社区干货传送门是由 TiDB 社区中布道师组委会自发组织的 TiDB 社区优质内容对外宣布的栏目,旨在加深 TiDBer 之间的交流和学习。一起构建有爱、互助、共创共建的 TiDB 社区 https://tidb.net/

评论

发布
暂无评论
文盘Rust --  把程序作为守护进程启动_开发语言_TiDB 社区干货传送门_InfoQ写作社区