Rust Fork Process errors

Hi! I'm some weird issues which I can't explain.
Short story: There is some issue with my Linux setup that keeps resetting my keyboard repeat rate randomly once in a few minutes. I don't have time for dealing with this, so I decided to fix it bluntly with this Rust code.
As you can see it just sits in the background and spawns xset every N seconds.

use nix::unistd::{fork, ForkResult};
use std::time::Duration;

fn main() {
    match fork() {
        Ok(ForkResult::Parent { child: _, .. }) => {
            println!("starting xset_daemon");
        }
        Ok(ForkResult::Child) => {
            println!("xset_daemon started");
            loop {
                std::thread::sleep(Duration::from_secs(10));
                match std::process::Command::new("xset")
                    .args(&["r", "rate", "250", "50"]).spawn() {
                    Err(e) => eprintln!("xset_daemon error: {}", e),
                    _ => ()
                }
            }
        }
        Err(e) => eprintln!("Could not fork: {}", e)
    }
}

It works perfectly fine for a while, but after some time (usually one day) I'm starting seeing errors in other places in my system, errors like:

Unable to create native thread: possibly out of memory or process/resource limits reached

At the same time, the daemon itself begins printing:
xset_daemon error: Resource temporarily unavailable (os error 11)

I'm relatively new to Rust and must be doing something wrong, do you have any ideas what's wrong with the code? Could it be an issue with the nix create and how it creates a fork?

Looks like the Command struct doesn't get dropped in the loop, I guess I should create it just once before the loop.

My best guess is that the child processes aren’t ending and you’re running out of process ids. Do they show up in ps?

There was only one short-living xset process every 10 seconds when I was monitoring it with ps

1 Like

I changed the code to:

...
let mut proc = Command::new("xset");
loop {
    std::thread::sleep(Duration::from_secs(10));
    match proc.args(&["r", "rate", "250", "50"]).spawn() {
               Err(e) => eprintln!("xset_daemon error: {}", e),
               _ => ()
    }
}

I'll see if it helps

Try .output() instead of .spawn() if it’s a short lived process

Edit:
If the spawned process is short lived that is. That should clean up the child process I think.

1 Like

Scanning throught the list of ulimit options, the only others that seem suspicious are the number of open file descriptors (-n) and the number of threads (-T).

File descriptors could especially be a problem if some I/O pipes aren’t being closed properly when the subprocess exits.

From the docs of std::process::Child which is the result of spawn

On some system, calling wait or similar is necessary for the OS to release resources. A process that terminated but has not been waited on is still around as a "zombie". Leaving too many zombies around may exhaust global resources (for example process IDs).

5 Likes

Ah that explains everything! I'll change the call to status() instead of spawn() Thank you.