GuillaumeGomez / sysinfo

Cross-platform library to fetch system information
MIT License
2.06k stars 307 forks source link

Windows System.refresh_process(pid) doesn't load new processes #771

Closed MarkRx closed 2 years ago

MarkRx commented 2 years ago

The System.refresh_process(pid) function does not load a newly launched process in Windows. According to the documentation it should be added to the list of in-memory process information if it hasn't been added yet.

Steps to reproduce

  1. Run sample code below
  2. Launch a long running process (such as "python")
  3. Enter process id of launched process (obtain from task manager)
  4. Observe that refresh_process(pid) fails to load the process information while represh_processes() works

Code

use std::io;

use sysinfo::{SystemExt, System, Pid};

fn main() {
    let mut sys = System::new_all();
    sys.refresh_processes();

    let mut input_text = String::new();
    println!("Enter pid: ");
    io::stdin()
        .read_line(&mut input_text)
        .expect("failed to read from stdin");

    let pid = Pid::from(input_text.trim().parse::<usize>().unwrap());

    println!("1: Refreshing individual process information for {}", pid);
    println!("1: Refresh result: {}", sys.refresh_process(pid));
    if let Some(process) = sys.process(pid) {
        println!("1: Loaded process info for {}. Data: {:?}", pid, process);
    } else {
        println!("1: Failed to load process info for {}", pid);
    }

    println!("2: Refreshing all processes");
    sys.refresh_processes();
    if let Some(process) = sys.process(pid) {
        println!("2: Loaded process info for {}. Data: {:?}", pid, process);
    } else {
        println!("2: Failed to load process info for {}", pid);
    }
}

Expected Result

The System.refresh_process(pid) call loads information about an individual process.

Enter pid: 2364 1: Refreshing individual process information for 2364 1: Refresh result: true 1: Loaded process info for 2364. Data: Process { pid: Pid(2364), parent: Some(Pid(20192)), name: "python.exe", environ: [], command: [], executable path: "", current working directory: "", memory usage: 11169, virtual memory usage: 38350, CPU usage: 0.0, status: Run, root: "", disk_usage: DiskUsage { total_written_bytes: 0, written_bytes: 0, total_read_bytes: 0, read_bytes: 0 } } 2: Refreshing all processes 2: Loaded process info for 2364. Data: Process { pid: Pid(2364), parent: Some(Pid(20192)), name: "python.exe", environ: [], command: [], executable path: "", current working directory: "", memory usage: 11169, virtual memory usage: 38350, CPU usage: 0.0, status: Run, root: "", disk_usage: DiskUsage { total_written_bytes: 0, written_bytes: 0, total_read_bytes: 0, read_bytes: 0 } }

Actual Results

The System.refresh_process(pid) call fails.

Enter pid: 2364 1: Refreshing individual process information for 2364 1: Refresh result: false 1: Failed to load process info for 2364 2: Refreshing all processes 2: Loaded process info for 2364. Data: Process { pid: Pid(2364), parent: Some(Pid(20192)), name: "python.exe", environ: [], command: [], executable path: "", current working directory: "", memory usage: 11169, virtual memory usage: 38350, CPU usage: 0.0, status: Run, root: "", disk_usage: DiskUsage { total_written_bytes: 0, written_bytes: 0, total_read_bytes: 0, read_bytes: 0 } }

GuillaumeGomez commented 2 years ago

It seems surprising since this behaviour is tested in this test: https://github.com/GuillaumeGomez/sysinfo/blob/master/src/system.rs#L24-L40

In some cases, windows needs a small delay to be able to get a process information. Can you try with adding a small delay maybe (even though, since you need to enter the PID by hand, I expect there is a "human" delay already) please?

MarkRx commented 2 years ago

I ran it in the debugger and it seems to be because OpenProcess used by refresh_process(pid) is returning NULL when in a non-elevated shell due to a swallowed Access Denied error. Curiously the NtQureySystemInformation call used by refresh_processes doesn't seem to have the same access restrictions. Hence the inconsistent behavior between the two functions.

Oddly enough refresh_process works inconsistently in the situation where the process is launched before the rust process starts. Here is what I found:

Perhaps the code needs to revert to using PROCESS_QUERY_LIMITED_INFORMATION here in the case that PROCESS_QUERY_INFORMATION fails due to an access restriction?

GuillaumeGomez commented 2 years ago

In case the first call returns NULL, we can always get the handle with PROCESS_QUERY_LIMITED_INFORMATION as you suggested. I think all functions are checked so it would be just changing that. Want to send a PR?