Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust Daemon best practices

Tags:

unix

rust

daemon

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();
}
like image 386
lostAstronaut Avatar asked Apr 26 '20 15:04

lostAstronaut


1 Answers

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>
like image 168
lostAstronaut Avatar answered Sep 23 '22 05:09

lostAstronaut