How do I spawn a detached command/process in Rust?
Asked Answered
R

1

7

I have written a small executable to start a WebSocket server for a Node development environment.

In a separate NodeJS application (running in Electron), I am using child_process.spawn() to run the binary and start the server on application start up.

This server child process hijacks the node child_process spawned command and doesn't exit, so I can't capture the exit code or handle failures. I just need to know that it started or didn't.

How do I start this process in Rust so that I can exit (on success, failure etc.) and the child keeps running?

The relevant parts of my main.rs:

extern crate ws;

use std::env;
use std::process::Command;
use ws::{connect, CloseCode};

fn main() {
    let args: Vec<String> = env::args().collect();
    let command = &args[1];
    if command == "activate" {
        println!("Activating");
        stop_server_process();
        start_server_process();
    }
}

fn start_server_process() {
    let mut command;
    if cfg!(windows) {
        command = Command::new(".\\node_modules\\.bin\\ws.cmd");
    } else {
        command = Command::new("./node_modules/.bin/ws");
    }
    command.arg("--websocket");
    command.arg("test/mock-api/ws-server.js");
    command.arg("-p");
    command.arg("9401");
    match command.spawn() {
        Ok(child) => println!("Server process id is {}", child.id()),
        Err(e) => {
            println!("Server didn't start: {}", e);
            std::process::exit(1);
        },
    }
    std::process::exit(0);
}

fn stop_server_process() {
    connect("ws://localhost:9401", |out| {
        out.send("server-stop").unwrap();
        move |msg| {
            println!("Got message: {}", msg);
            out.close(CloseCode::Normal)
        }
    }).unwrap()
}

I'm running the binary from my Electron app like so:

/**
 * @return {Promise<Boolean>}
 */
static activate() {
  return new Promise((resolve, reject) => {
    const command = spawn(process.env.SERVER_LOCAL_PATH, ['activate']);
    command.stdout.on('data', (data) => {
      console.log(`stdout: ${data}`);
    });
    command.stderr.on('data', (data) => {
      console.log(`stderr: ${data}`);
    });
    command.on('close', code => resolve(code === 0));
    command.on('error', error => reject(error));
  });
}

This never resolves as the ws websocket starts running and the spawned process is now picking up events from that. My console output is the following:

stdout: Activating <--- Rust binary output
Server process id is 78057 <--- Rust binary output
stderr: Serving at [various URL variations] <--- ws binary output
stdout: Client Connected <--- ws binary output

The WebSocket server is based on examples and code from this library. Here is a reproducable version of it:

module.exports = SocketBase => class Socket extends SocketBase {
  websocket(wss) {
    wss.on('connection', (ws) => {
      console.log('Client Connected');
      ws.on('message', (data) => {
        if (data === 'server-stop') {
          console.log('Stopping...');
          process.exit(0);
        }
        // do some faked JSON response data
      });
      ws.on('close', () => {
        console.log('Client Disconnected');
      });
      ws.on('error', (error) => {
        console.log(error.toString());
      });
    });
  }
};
Roderickroderigo answered 24/5, 2018 at 16:23 Comment(5)
The child should keep running by default according to the docs. doc.rust-lang.org/std/process/struct.Child.html When you say "this never resolves", can you go into more detail?Frankpledge
@Frankpledge The process never exits - so the activation promise doesn't resolve (on close event). The child process (Rust binary) starts another child process and doesn't exit - I'd like it to start the child process and exit with a 0 code.Roderickroderigo
From my reading, and quick test of your rust code outside of electron from the cmd line, it works as intended. It seems like some interaction with the electron call. What platform is this on? On windows iiirc, one can call a create process with flags preventing child detached processes.Frankpledge
@Frankpledge - thanks for looking into this. It's being run on both Windows & Mac dev environments. Same behaviour on both. I've added the WebSocket server code if it helps.Roderickroderigo
This question is somewhat related and may help readers of this one.Volcanology
C
0

After hours of searching, in case if anyone wonders how to detach a process from windows console, there's a console api which can be called via win32console.

The trick is to call freeconsole() method in the child process:

// Start the process from parent with Command::new()
// Send markings. For instance arguments to that process via args() associated function, in this case "hidden"
use win32console::console::WinConsole;
if self.args[1] == "hidden" {
   WinConsole::free_console().unwrap();
}

Ceraceous answered 16/9 at 11:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.