Repository: GuillaumeGomez/sysinfo (Rust)

Issue:
[Feature Request] waitpid()
Would it make sense to have a `waitpid()`  method available in `sysinfo::ProcessExt`?

Currently if we are dealing with process creation and using `sysinfo` to kill it, we also have to wait for it. We can directly use `libc::waitpid()` but I was wondering if it would also makes sense to have it in `sysinfo`.

Currently we can do this:
```rust
use sysinfo::{SystemExt, Pid, ProcessExt};

fn _start_and_get_dummy_process(ttl: i32) -> std::process::Child {
    std::process::Command::new("sleep")
         .arg(ttl.to_string())
         .spawn()
         .unwrap()
}

fn main() {
    let mut dummy_process = _start_and_get_dummy_process(10);
    let pid = Pid::from(dummy_process.id() as i32);
    let mut system = sysinfo::System::new();
    system.refresh_processes();

    let p = system.process(pid).unwrap();
    println!("Attempting to send signal");
    if p.kill_with(sysinfo::Signal::Kill).is_some() {
        println!("Signal sent!");
    } else {
        println!("Problem sending signal!");
    }

    // Manually wait
    let _ = dummy_process.wait();

    // or
    let mut status = 0 as libc::c_int;
    unsafe {
        libc::waitpid(dummy_process.id() as i32, &mut status, 0);
    }

    system.refresh_processes();
    
    if system.process(pid).is_some() {
        println!("Process still around!");
    } else {
        println!("Process not around");
    }
}

```

which will output (only if wait() is called):
```bash
Attempting to send signal
Signal sent!
Process not around
```

without the manual wait:
```bash
Attempting to send signal
Signal sent!
Process still around!
```

Having a method would allow us to do:
```rust
p.wait() // maybe?
```

I'm not sure if we need another way to wait, but anyway thought of creating this issue.


Discussion:
Adding a `wait` method on `Process` sounds like a good suggestion. However, its implementation will not be so straightforward since the `wait*` commands don't allow to wait for a process that is not a child. We could loop around a `kill` call I suppose and wait like 10ms between each check.

For windows, I'm not sure but I think it would be something similar.

Do you want to give it a try?
Cool, I'll give it a try and create a PR and maybe we could discuss more there.
Sure! Good luck and thanks.



Patch:
diff --git a/src/apple/app_store/process.rs b/src/apple/app_store/process.rs
index 8c3348ee9..7c43697a6 100644
--- a/src/apple/app_store/process.rs
+++ b/src/apple/app_store/process.rs
@@ -79,4 +79,6 @@ impl ProcessExt for Process {
     fn group_id(&self) -> Option<Gid> {
         None
     }
+
+    fn wait(&self) {}
 }
diff --git a/src/apple/macos/process.rs b/src/apple/macos/process.rs
index 37282697a..fff9c1f71 100644
--- a/src/apple/macos/process.rs
+++ b/src/apple/macos/process.rs
@@ -184,6 +184,20 @@ impl ProcessExt for Process {
     fn group_id(&self) -> Option<Gid> {
         self.group_id
     }
+
+    fn wait(&self) {
+        let mut status = 0;
+        // attempt waiting
+        unsafe {
+            if libc::waitpid(self.pid.0, &mut status, 0) < 0 {
+                // attempt failed (non-child process) so loop until process ends
+                let duration = std::time::Duration::from_millis(10);
+                while kill(self.pid.0, 0) == 0 {
+                    std::thread::sleep(duration);
+                }
+            }
+        }
+    }
 }
 
 #[allow(deprecated)] // Because of libc::mach_absolute_time.
diff --git a/src/freebsd/process.rs b/src/freebsd/process.rs
index 9dabaa07c..b0dda0f76 100644
--- a/src/freebsd/process.rs
+++ b/src/freebsd/process.rs
@@ -5,6 +5,8 @@ use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus,
 use std::fmt;
 use std::path::{Path, PathBuf};
 
+use libc::kill;
+
 use super::utils::{get_sys_value_str, WrapMap};
 
 #[doc(hidden)]
@@ -141,6 +143,20 @@ impl ProcessExt for Process {
     fn group_id(&self) -> Option<Gid> {
         Some(self.group_id)
     }
+
+    fn wait(&self) {
+        let mut status = 0;
+        // attempt waiting
+        unsafe {
+            if libc::waitpid(self.pid.0, &mut status, 0) < 0 {
+                // attempt failed (non-child process) so loop until process ends
+                let duration = std::time::Duration::from_millis(10);
+                while kill(self.pid.0, 0) == 0 {
+                    std::thread::sleep(duration);
+                }
+            }
+        }
+    }
 }
 
 pub(crate) unsafe fn get_process_data(
diff --git a/src/linux/process.rs b/src/linux/process.rs
index b71d97c45..d7d61b5bc 100644
--- a/src/linux/process.rs
+++ b/src/linux/process.rs
@@ -219,6 +219,20 @@ impl ProcessExt for Process {
     fn group_id(&self) -> Option<Gid> {
         self.group_id
     }
+
+    fn wait(&self) {
+        let mut status = 0;
+        // attempt waiting
+        unsafe {
+            if libc::waitpid(self.pid.0, &mut status, 0) < 0 {
+                // attempt failed (non-child process) so loop until process ends
+                let duration = std::time::Duration::from_millis(10);
+                while kill(self.pid.0, 0) == 0 {
+                    std::thread::sleep(duration);
+                }
+            }
+        }
+    }
 }
 
 pub(crate) fn compute_cpu_usage(p: &mut Process, total_time: f32, max_value: f32) {
diff --git a/src/traits.rs b/src/traits.rs
index 3ad540238..e2d546de1 100644
--- a/src/traits.rs
+++ b/src/traits.rs
@@ -412,6 +412,21 @@ pub trait ProcessExt: Debug {
     /// }
     /// ```
     fn group_id(&self) -> Option<Gid>;
+
+    /// Wait for process termination.
+    ///
+    ///```no_run
+    /// use sysinfo::{Pid, ProcessExt, System, SystemExt};
+    ///
+    /// let mut s = System::new_all();
+    ///
+    /// if let Some(process) = s.process(Pid::from(1337)) {
+    ///     eprintln!("Waiting for pid 1337");
+    ///     process.wait();
+    ///     eprintln!("Pid 1337 exited");
+    /// }
+    ///````
+    fn wait(&self);
 }
 
 /// Contains all the methods of the [`Cpu`][crate::Cpu] struct.
diff --git a/src/unknown/process.rs b/src/unknown/process.rs
index c4656d3f5..4a12ec829 100644
--- a/src/unknown/process.rs
+++ b/src/unknown/process.rs
@@ -89,4 +89,6 @@ impl ProcessExt for Process {
     fn group_id(&self) -> Option<Gid> {
         None
     }
+
+    fn wait(&self) {}
 }
diff --git a/src/windows/process.rs b/src/windows/process.rs
index 888bc712a..7561c658f 100644
--- a/src/windows/process.rs
+++ b/src/windows/process.rs
@@ -1,5 +1,6 @@
 // Take a look at the license at the top of the repository in the LICENSE file.
 
+use crate::sys::system::is_proc_running;
 use crate::sys::utils::to_str;
 use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid};
 
@@ -547,6 +548,21 @@ impl ProcessExt for Process {
     fn group_id(&self) -> Option<Gid> {
         None
     }
+
+    fn wait(&self) {
+        if let Some(handle) = self.get_handle() {
+            while is_proc_running(handle) {
+                if get_start_time(handle) != self.start_time() {
+                    // PID owner changed so the previous process was finished!
+                    return;
+                }
+                std::thread::sleep(std::time::Duration::from_millis(10));
+            }
+        } else {
+            // In this case, we can't do anything so we just return.
+            sysinfo_debug!("can't wait on this process so returning");
+        }
+    }
 }
 
 #[inline]
diff --git a/src/windows/system.rs b/src/windows/system.rs
index 205c21293..718b6daf0 100644
--- a/src/windows/system.rs
+++ b/src/windows/system.rs
@@ -508,7 +508,7 @@ impl Default for System {
     }
 }
 
-fn is_proc_running(handle: HANDLE) -> bool {
+pub(crate) fn is_proc_running(handle: HANDLE) -> bool {
     let mut exit_code = 0;
     unsafe {
         let ret = GetExitCodeProcess(handle, &mut exit_code);


Test patch:
diff --git a/tests/process.rs b/tests/process.rs
index c200a6097..1e0e74b7c 100644
--- a/tests/process.rs
+++ b/tests/process.rs
@@ -378,3 +378,78 @@ fn test_refresh_process() {
     // Checks that the process is still listed.
     assert!(s.process(pid).is_some());
 }
+
+#[test]
+fn test_wait_child() {
+    if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") {
+        return;
+    }
+    let p = if cfg!(target_os = "windows") {
+        std::process::Command::new("waitfor")
+            .arg("/t")
+            .arg("300")
+            .arg("RefreshProcess")
+            .stdout(std::process::Stdio::null())
+            .spawn()
+            .unwrap()
+    } else {
+        std::process::Command::new("sleep")
+            .arg("300")
+            .stdout(std::process::Stdio::null())
+            .spawn()
+            .unwrap()
+    };
+
+    let before = std::time::Instant::now();
+    let pid = Pid::from_u32(p.id() as u32);
+
+    let mut s = sysinfo::System::new();
+    s.refresh_process(pid);
+    let process = s.process(pid).unwrap();
+
+    // Kill the child process.
+    process.kill();
+    // Wait for child process should work.
+    process.wait();
+
+    // Child process should not be present.
+    assert!(!s.refresh_process(pid));
+    assert!(before.elapsed() < std::time::Duration::from_millis(1000));
+}
+
+#[test]
+fn test_wait_non_child() {
+    if !sysinfo::System::IS_SUPPORTED || cfg!(feature = "apple-sandbox") {
+        return;
+    }
+
+    // spawn non child process.
+    let p = if !cfg!(target_os = "linux") {
+        return;
+    } else {
+        std::process::Command::new("setsid")
+            .arg("-w")
+            .arg("sleep")
+            .arg("2")
+            .stdout(std::process::Stdio::null())
+            .spawn()
+            .unwrap()
+    };
+    let pid = Pid::from_u32(p.id());
+
+    let before = std::time::Instant::now();
+
+    let mut s = sysinfo::System::new();
+    s.refresh_process(pid);
+    let process = s.process(pid).expect("Process not found!");
+
+    // Wait for a non child process.
+    process.wait();
+
+    // Child process should not be present.
+    assert!(!s.refresh_process(pid));
+
+    // should wait for 2s.
+    assert!(before.elapsed() > std::time::Duration::from_millis(2000));
+    assert!(before.elapsed() < std::time::Duration::from_millis(3000));
+}