Creating a simple Rust daemon that listens to a port
Asked Answered
M

2

18

I've been trying to make a simple daemon in Rust that will listen to a port using tcp_stream and print the message. However, I'm running into two problems:

1) If my daemon uses println!, it crashes. If I remove all mentions of println!, the daemon works. How does stdout/stdin work when making a daemon?

One source I found on the Rust mailing list says "With modern init systems, such as systemd or launchctl, this works very nicely and application developer doesn't have to care about daemonisation and logging is also done simply via stdout." What do they mean by this?

2) When I run the code below in non-daemon mode, curls don't immediately return (running something like $ curl -XPOST localhost:9337 -d 'hi'). I have to kill curl for the server to print something. Doesn't curl close the connection automatically? Shouldn't the sent bytes be available to the server after they are sent, not after the connection is closed?

extern crate getopts;
use getopts::{optflag,getopts};
use std::io::Command;
use std::io::net::tcp::{TcpListener};
use std::io::{Acceptor,Listener};
use std::os;

fn main() {
    let args: Vec<String> = os::args();
    let opts = [
        optflag("d", "daemon", "conver this into a daemon"),
    ];
    let matches = match getopts(args.tail(), opts) {
        Ok(m) => { m },
        Err(f) => { fail!(f.to_string()) }
    };

    // Create a daemon? if necessary
    if matches.opt_present("d") {
        let child = Command::new(args[0].as_slice())
                            .detached().spawn().unwrap();
        println!("Created child: {}", child.id());

        // Do I wrap this in unsafe?
        child.forget();
        return;
    }

    let listener = TcpListener::bind("127.0.0.1", 9337u16).ok().expect("Failed to bind");
    let mut acceptor = listener.listen().ok().expect("Could not listen");

    loop {
        let mut tcp_stream = acceptor.accept().ok().expect("Could not accept connection");
        println!("Accepted new connection");

        let message = tcp_stream.read_to_string().unwrap();
        println!("Received message {}", message);
    }
}
Messiah answered 14/10, 2014 at 6:58 Comment(0)
A
17

What do they mean by this?

They mean that you shouldn't do anything fancy like forking to create a daemon program. Your program should just work, logging its operations directly into stdout, and init systems like systemd or launchctl will automatically handle everything else, including startup, shutdown, logging redirection, lifecycle management etc. Seriously consider this approach because it would make your program much simpler.

Creating a daemon process properly, though, is not simple. You have to fork the process, close and set up new file descriptors, tweak process groups, add signals handlers and more. Googling for something like "fork daemon" gives a lot of articles on how to create a daemon, and you can see that this is not an easy task. Certainly, you can do something like this in Rust, because it exposes all of the necessary system calls through libc crate. There can be some caveats, though: for example, I'm not sure how Rust runtime would react on fork() system call.

As for why your "daemon" fails when you use println!(), I suspect it happens because you detach from your child process and its stdio handles are automatically closed, and Rust I/O routines are not happy with that and trigger a task failure.

Akron answered 14/10, 2014 at 17:39 Comment(1)
While systemd and launchctl might handle fork()+exec() for you, neither traditional init systems nor all modern ones do so. On illumos, svc.startd still expects classical daemon behaviour, as spelled out in the manual page for smf_method(5). So when targeting other platforms than Linux and MacOS, daemonizing is still a thing.Installation
I
10

Use https://docs.rs/daemonize/0.4.1/daemonize/ crate that takes care of most of this tricky stuff in creating a daemon for you?

Inner answered 11/9, 2019 at 9:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.