How to make RUST run gracefully in the background and daemonize?
Asked Answered
C

1

8

This is what i want to achieve

root> ./webserver start   // Does not block the terminal after startup, runs in the background and the process is guarded
root> 

My current implementation logic:

Logic running in the background
 use std::process::Command;
use std::thread;
use std::env;
use std::time::Duration;

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() == 2 {
        if &args[1] == "start" {
            // Main process start child process
            let child = Command::new(&args[0])
                .spawn().expect("Child process failed to start.");
            println!("child pid: {}", child.id());
            // Main process exit
        }
    } else {Is there any more elegant approach? Looking forward to your reply
        // Main business logic
        run webserver
    }
}

In this way, rust will run in the background without blocking the terminal, but the information printed by rust in the background will still be displayed on the terminal, and the current rust program will exit when exiting the terminal

Process daemon logic

My idea is to monitor the exit signal of the system and not process the exit request

SIGHUP       1          /* Hangup (POSIX).  */                  
SIGINT       2          /* Interrupt (ANSI).  */                       
SIGQUIT      3          /* Quit (POSIX).  */                        
SIGTERM      15         /* Termination (ANSI).  */               

code:

use signal_hook::{iterator::Signals, SIGHUP,SIGINT,SIGQUIT,SIGTERM};
use std::{thread, time::Duration};
pub fn process_daemon() {
    let signals = match Signals::new(&[SIGHUP,SIGINT,SIGQUIT,SIGTERM]) {
        Ok(t) => t,
        Err(e) => panic!(e),
    };Is there any more elegant approach? Looking forward to your reply

    thread::spawn(move || {
        for sig in signals.forever() {
            println!("Received signal {:?}", sig);
        }
    });

    thread::sleep(Duration::from_secs(2));
}

Is there any more elegant approach? Looking forward to your reply.

Cider answered 8/5, 2020 at 2:28 Comment(4)
If you can use a process manager like systemd instead of trying to solve it in code. That's a lot less painful than trying to deal with fork or child processes yourself.Pebrook
a program that itself would put it as deamon would be instant ban from my system. no one program do that.Halliday
This seems like a task for your shell's native job control capabilities really. Just start your thing with an & suffix (in sh-compatible shells) and it'll run as a background job, or start it normally, then press Control-Z to suspend the process, then resume it in the background using bg. Check out your shell's documentation for other job control facilities. And as @Pebrook recommends, for more "production" tasks than temporary running, use a proper service manager.Pinball
What about daemon(3)?Glenine
A
11

TLDR: if you really want your process to act like a service (and never quit), probably do the work to set up a service manager. Otherwise, just let it be a normal process.

Daemonizing a Process

One thing to notice right off the bat is that most of the considerations about daemonizing have nothing to do with Rust as a language and are more about:

  1. The underlying system your processes are targeted for
  2. The exact behavior of your daemon processes once spawned

By looking at your question, it seems you have realized most of this. Unfortunately to properly answer your question we have to delve a bit into the intricacies of processes and how they are managed. It should be noted that existing 'service' managers are a great solution if you are OK with significant platform dependence in your launching infrastructure.

As you can see, no simple feat if you want to have a simple deployment that just works (provided that it is compiled for the relevant system). These are just the bare metal service managers. If you want to support virtual environments you will have to think about Kubernetes services, dockerizing, etc.

These tools exist because there are many considerations to managing a long-running process on a system:

  • Should my daemon behave like a service and respawn if killed (or if the system is rebooted)? The tools above will allow for this.
  • If a service, should my daemon have status states associated with it to help with maintenance? This can help with managing outages and building tooling to scale horizontally.
  • If the daemon shouldn't be a service (unlikely in your case given your binary's name) there are even more questions: should it be attached to the parent process? Should it be attached to the login process group?

My guess for your process given how complex this can become, simply run the process directly. Don't daemonize at all.

For testing, (if you are in a unix-like environment) you can run your process in the background:

./webserver start &

This will spawn the new process in the background, but attach it to your shell's process list. This can be nice for testing, because if that shell goes away the system will clean up these attached processes along with it.

The above will direct stderr and stdout file descriptors back to your terminal and print them. If you wish to avoid that, you can always redirect the output somewhere else.

Disabling signals to a process like this doesn't seem like the right approach to me. Save these signals to gracefully exit your process once you need to save state or send a termination message to a client. If you do the above, your daemon will only be killable by a kill -9 <pid> or rebooting, or finding some non-standard signal you haven't overridden whose default behavior is to terminate.

Asiaasian answered 8/5, 2020 at 14:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.