Spawn process within the timer is becoming defunc

Hello Admin,

I have a tokio-timer which spins of every two seconds and calls health_monitor function. I am trying to monitor the spawned process, wpa_supplicant here. If wpa_supplicant is not running, the process is spawned, and the function exits. However the spawned wpa_supplicant becomes defunct

image

extern crate tokio_timer;
extern crate futures;
extern crate tokio_core;

use std::error::Error;
use std::io::prelude::*;
use std::process::{Command, Stdio};

use std::time::*;
use futures::*;
use tokio_core::reactor::Core;
use tokio_timer::*;

fn health_monitor(_: ()) -> futures::future::FutureResult<(), tokio_timer::TimerError> {
		println!("Health Monitoring");
		
	let process = match Command::new("pidof")
								.arg("wpa_supplicant")
								.stdin(Stdio::piped())
								.stdout(Stdio::piped())
								.spawn() {
		Err(why) => panic!("couldn't spawn wc: {}", why.description()),
		Ok(process) => process,
	};
	
	let mut s = String::new();
	 match process.stdout.unwrap().read_to_string(&mut s) {
		Err(why) => panic!("couldn't read wc stdout: {}",
						   why.description()),
		Ok(_) => print!("wc responded with:\n{}", s),
	}
	
	 if s.to_string().is_empty() {
		 let process = match Command::new("wpa_supplicant")
								.arg("-iwlx002369e09f0b")
								.arg("-c/home/vinay245/test3.conf")
								.stdin(Stdio::piped())
								.stdout(Stdio::piped())
								.spawn() {
		Err(why) => panic!("couldn't spawn wc: {}", why.description()),
		Ok(process) => process,
	 };
	 }
	futures::future::ok(())
	}

fn main() {
   let process = match Command::new("wpa_supplicant")
								.arg("-iwlx002369e09f0b")
								.arg("-c/home/vinay245/test3.conf")
								.stdin(Stdio::piped())
								.stdout(Stdio::piped())
								.spawn() {
		Err(why) => panic!("couldn't spawn wc: {}", why.description()),
		Ok(process) => process,
	};

	 let pid = process.id();
	 let health_str = tPid {wpa_pid: &pid};
	 
	 let timer = Timer::default();
	 let duration = Duration::new(1, 0); // 2 seconds
	 let wakeups = timer.interval(duration);
	 
	 let health_monitor = wakeups.for_each(health_monitor);
	 
	 let mut core = Core::new().unwrap();
	 
	 core.run(health_monitor).unwrap();

}

How to avoid the defunct behaviour or is there a better way of health_monitoring ?

Thanks in Advance

You need to keep the Child and call wait when you know it is dead.

Saving it in thread_local maybe easy lazy option.

Running this with tokio-process will provide better facilities to track child’s exit (no need to stash it anywhere - the child is a future that resolves when it exits). You can also read its pipes asynchronously, although that’s maybe not that crucial here.

tokio-process, when a process is spawned it blocks until it exits as mentioned by you. I tried to use this with other futurss and intergrated with main core.run(), process future is blocking and other functionalities wont get a chance to execute. How to avoid this scenario ?
The functionality what I am trying to achieve is to check for the spawned process from the main loop , say every two second and if any of the spawned process are not running, just spawn them. I am not sure if this is even possible to achieve, as the function spawned by timer.interval would end up in defunct state

Defunct state simply means the process has exited but no one has collected its status. So, something is probably wrong with the way you are spawning the process - wrong parameters, permissions, etc...

i used child.wait to resolve the defunct problem. Thank you all for the help.

Hmm, not sure I entirely understand what you mean. Here's a quick snippet (with a few comments) of how you'd schedule the process to run on the reactor for each timer tick:

let timer = Timer::default();
    let duration = Duration::new(1, 0); // 2 seconds
    let wakeups = timer.interval(duration);

    let mut core = Core::new().unwrap();
    let handle = core.handle();

    let health_monitor = wakeups.map_err(|e| std::io::Error::from(e)).for_each(|_| {
        // this spawns the child asynchronously, wires up its stdout/stderr pipes to be redirected so its output can be captured
        // the future resolves when the process exits
        let output = Command::new("echo").arg("hello").arg("world").output_async(&handle);
        // Chain a continuation to be called when the process is done.  Note that this
        // does not block or wait for the process to exit.  The child is dropped by this point, but its status
        // was collected (i.e. it was wait()'d on upon completion) so it should not be defunc
        output.and_then(|o| {
            println!("Child exited with status {}", o.status);
            println!("Child stdout: {}", String::from_utf8(o.stdout).unwrap());
            Ok(())
        })
    });
    core.run(health_monitor).unwrap();
3 Likes

I got the point here. Thank you again @vitalyd