Spawn process within the timer is becoming defunc


#1

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


#2

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

Saving it in thread_local maybe easy lazy option.


#3

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.


#4

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


#5

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…


#6

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


#7

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();

#8

I got the point here. Thank you again @vitalyd