Rust Daemon best practices
Asked Answered
L

1

7

I'm having difficulty creating a daemon for a unix socket server in rust. In a language like node.js i'd just spawn a detached process. But rust seems to be a bit more challenging.

(all work is on unix environments)

Here is a quick example. I wrote up:

use std::os::unix::net::{UnixListener, UnixStream};

use std::path::{Path};
use std::io::{Read, Write};
use std::{fs};
use std::thread;


fn handle_stream(mut stream: UnixStream) {
    
    loop {
        let mut read = [0; 1028];
        match stream.read(&mut read) {
            Ok(n) => {
                if n == 0 {
                    // connection was closed
                    println!("connection closed?");
                    break;
                }
                let msg = String::from_utf8(read.to_vec()).unwrap();
                println!("SERVER: {} received from remote client.", msg);

                match stream.write_all(&read[0..n]) {
                    Ok(_) => {}
                    Err(e) => println!("Error writing to socket: {}", e),
                }
            }
            Err(err) => {
                panic!(err);
            }
        }
    }
    println!("SERVER: Ending connection with client.");
}

fn server_start() {
    
    // remove existing sock if exists
    let _did_remove = fs::remove_file("/Users/tom/desktop/app.sock");

    // socket location
    let socket_file = "/Users/tom/desktop/app.sock";
    let socket = Path::new(socket_file);

    // Bind to socket
    let listener = match UnixListener::bind(&socket) {
        Err(_) => panic!("failed to bind socket"),
        Ok(stream) => stream,
    };

    println!("Server started, waiting for clients");

    for conn in listener.incoming() {
        match conn {
            Ok(stream) => {
                // spawn a new thread for each incoming
                thread::spawn(|| handle_stream(stream));
            }
            Err(err) => {
                println!("Error connecting to client: {}", err);
                break;
            }
        }
    }
}

fn main() {

    server_start();
}
Leffler answered 26/4, 2020 at 15:8 Comment(5)
Does this answer your question? Creating a simple Rust daemon that listens to a portBasipetal
I wish, I’ve actually read that post too many times. I guess rust removed the detached method from Command after that post was answered.Leffler
Are you sure you need an actual daemon as opposed to a simple systemd service (assuming you are on Linux)? The latter is way easier and for most purposes works just fine. For example, see medium.com/@benmorel/…Dagmardagna
This looks like the way to go, i'll post an example laterLeffler
I second @kreo's suggestion of not bothering. Systemd documentation discourages self-daemonization: "For developing a new-style daemon, none of the initialization steps recommended for SysV daemons need to be implemented. New-style init systems such as systemd make all of them redundant. Moreover, since some of these steps interfere with process monitoring, file descriptor passing and other functionality of the init system, it is recommended not to execute them when run as new-style service."Tory
L
8

Following the messages in the comments, I decided to use a systemd service rather than creating my own daemon. This seems to be an ideal way to manage background tasks. I've edited the top code so that it makes sense with the answer.

Systemd - linux

You will need to create a .service file and place it in your systemd daemon directory. For example: /etc/systemd/system/test.service

Then update the file rights:

sudo chmod 644 /etc/systemd/system/test.service

To start your service:

sudo systemctl start service_name

Service Code:

[Unit]
Description=Test service
After=network.target
StartLimitIntervalSec=0
[Service]
Type=simple
Restart=always
RestartSec=1
User=username
ExecStart=/usr/bin/env test

[Install]
WantedBy=multi-user.target

Launchctl - macOS

For macOS we need to create a .plist file and place it in the launch daemons directory. For example: /Library/LaunchDaemons/test.plist

Next update the permissions on the file:

sudo chown root:wheel /Library/LaunchDaemons/com.test.daemon.plist

Load the daemon:

launchctl load /Library/LaunchDaemons/com.test.daemon.plist

Start the daemon:

launchctl start /Library/LaunchDaemons/com.test.daemon

Plist code:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
    "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
        <string>com.test.daemon</string>
    <key>ServiceDescription</key>
        <string>Test Server</string>
    <key>ProgramArguments</key>
        <array>             
            <string>/Users/tom/desktop/test/target/debug/test</string>
        </array>
    <key>RunAtLoad</key>
        <false/>
</dict>
</plist>
Leffler answered 27/4, 2020 at 23:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.