Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

signal handling / graceful shutdown #124

Open
qmx opened this issue Oct 5, 2018 · 1 comment
Open

signal handling / graceful shutdown #124

qmx opened this issue Oct 5, 2018 · 1 comment

Comments

@qmx
Copy link

qmx commented Oct 5, 2018

It'd be nice to have a simple way of handling SIGINT / SIGTERM for gracefully shutting down stuff

@t3hmrman
Copy link

t3hmrman commented Jan 28, 2019

One use case in particular to highlight would be testing.

I just ran into this and worked around the lack of a stop() function by running the executable with std::process:Child. I was relatively certain I might have been able to use serve() in a thread and maybe set up a channel to listen for some shutdown message (you can't just kill pthreads), but making a child process and managing killing it was much easier.

The code isn't the greatest but if anyone runs into this:

/// Setup API server for use in testing
pub fn setup_api_server_process() -> Result<APIServerTestInfo, Error> {
    // Generate configuration for the server
    let cfg = AppConfig::new_randomized()?;
    let cfg_toml_contents = toml::to_string(&cfg).expect("failed to config to TOML");

    // Write config to temp file
    let dir = tempdir().unwrap();
    let cfg_path = String::from(dir.path().join("cfg.toml").to_str().unwrap());
    let mut file: File = File::create(&cfg_path).unwrap();
    write!(file, "{}", cfg_toml_contents).expect("Failed to write temporary file");


    // Spawn a child process (that never returns) for the API server
    let child = Command::new("cargo")
        .arg("run")
        .arg("--")
        .arg("-c")
        .arg(cfg_path)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("failed to spawn child thread for test API server");

    // Wait for the child process something
    // FUTURE: this is fragile, would be better to wait for some sort of "server running" message on stdout
    thread::sleep(time::Duration::from_millis(STARTUP_WAIT));
    
    Ok((cfg, child))
}

I found a way to make the child process a tiny bit less fragile by waiting on output of a very specific message on stdout:

    // It is surprisingly hard to redirect output from a child process:
    // https://stackoverflow.com/questions/42726095/how-to-implement-redirection-of-stdout-of-a-child-process-to-a-file
    // https://stackoverflow.com/questions/43949612/redirect-output-of-child-process-spawned-from-rust
    let output_path = String::from(dir.path().join("output.log").to_str().unwrap());
    let output = File::create(output_path.clone()).unwrap().into_raw_fd();
    let raw_output_fd = unsafe {Stdio::from_raw_fd(output)};

    // Spawn a child process (that never returns) for the API server
    let child = Command::new("cargo")
        .arg("run")
        .arg("--")
        .arg("-c")
        .arg(cfg_path)
        .stdout(raw_output_fd)
        .spawn()
        .expect("failed to spawn child thread for test API server");

    // Read from the output file to which child process stdout is going, as the child process writes it
    let mut output_file = File::open(output_path).unwrap();
    let mut log_contents = String::new();

    // Wait until the server says it's ready
    loop {
        output_file.read_to_string(&mut log_contents)?;
        if log_contents.contains(SERVER_STARTED_MESSAGE) {
            thread::sleep(time::Duration::from_millis(STARTUP_EXTRA_WAIT));
            break;
        }
    }

Still not particularly proud of it, but it does work...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants