Issues daemonzing axim hello-world app

I just finished porting a web server application I had working in actix-web over to axum.

Things went pretty smoothly until I ran into a problem using the daemonize crate so that my web server could run as a background (i.e. daemon) process.

Using daemonize and coding it the way I did with actix-web, the server would start with no errors, but all http client requests. i.e.:

curl http://localhost:3000

would just hang.

I found that I had to create a tokio::Runtime instance manually from within the daemonize wrapper, instead of being able to use the tokio::main attribute.

Any ideas why this is the case?

Here's my Cargo.toml:

[package]
name = "example-hello-world"
version = "0.1.0"
edition = "2018"
publish = false

[dependencies]
axum = "0.4.5"
tokio = { version = "1.0", features = ["full"] }
daemonize = "0.4.1"

Here's the code that I should work but requests hang. It's just the axim hello-world example wrapped in daemonize.

use axum::{response::Html, routing::get, Router};
use std::net::SocketAddr;

use std::fs::File;
use daemonize::Daemonize;

#[tokio::main]
async fn main() {
    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`
        .working_directory("/tmp") // for default behaviour.
        .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(_) => {
            // build our application with a route
            let app = Router::new().route("/", get(handler));

            // run it
            let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
            println!("listening on {}", addr);
            axum::Server::bind(&addr)
                .serve(app.into_make_service())
                .await
                .unwrap();

            println!("Success, daemonized");
        },
        Err(e) => eprintln!("Error, {}", e),
    }
}

async fn handler() -> Html<&'static str> {
    Html("<h1>Hello, World!</h1>")
}

Here's the code I got to work using a manual tokio::Runtime, but I'd rather not have to do it this way because it requires a lot of code refactoring.

use axum::{response::Html, routing::get, Router};
use std::net::SocketAddr;

use std::fs::File;
use daemonize::Daemonize;

use tokio::runtime::Runtime;

fn main() {
    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`
        .working_directory("/tmp") // for default behaviour.
        .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(_) => {
            let rt  = Runtime::new().unwrap();

            rt.block_on(async {
                // build our application with a route
                let app = Router::new().route("/", get(handler));

                // run it
                let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
                println!("listening on {}", addr);
                axum::Server::bind(&addr)
                    .serve(app.into_make_service())
                    .await
                    .unwrap();

                println!("Success, daemonized");
            });
        },
        Err(e) => eprintln!("Error, {}", e),
    }
}

async fn handler() -> Html<&'static str> {
    Html("<h1>Hello, World!</h1>")
}

I’m not sure if it will work in this case but you don’t have to put #[tokio::main] on fn main(). So you could move the code after daemonize into #[tokio::main] async fn start() (or some other better name for that function). And then call it from main as a synchronous function.

Manual demonization in Linux requires fork()ing the process, which doesn't play well with multithreaded program. Have you tried to use systemd service?

2 Likes

Thanks for the reply.

I have not tried a systemd service. For my dev purposes, simply running as a background process launched from the command line is what I really hope to achieve here. Since it seems to work fine using the manual Runtime approach I mentioned, I'm tempted to stay with it however I would like to know specifically what is going wrong with the automatic runtime approach using the tokio::main attribute.

In the past I've had no problem forking for achieving a daemon status and then the remaining (detached) process has no problem multi threading. Granted most that work has been in C/C++.

When you say it doesn't "play nice" where would a forked process run into problems doing it's own multi-threading? I'm apt to assume as long as I stick with the guard rails already present with rust and multi-threading, I shouldn't have a problem doing it in a process regardless of it's having been forked?

Doesn't play well means it may not work due to the process global state, so you need to control every details.

https://man7.org/linux/man-pages/man2/fork.2.html

The child process is created with a single thread—the one that called fork().

This means you should not spawn any threads before the fork.

But the #[tokio::main] creates the multithreaded runtime and run the given async fn on it. This is necessary as async fns can't be executed without runtime. To avoid this ordering you can add #[tokio::main] on another function and call it from your main.

Don't let anyone touch your fn main if you want to do subtle things on it. fork() is that subtle, you may not be able to issue some system calls after it. On the man page linked above:

After a fork() in a multithreaded program, the child can
safely call only async-signal-safe functions (see
signal-safety(7)) until such time as it calls execve(2)

Which links long list of system functions, but it may not contains some constructs which is common in traditional C code.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.