GuillaumeGomez / sysinfo

Cross-platform library to fetch system information
MIT License
2.12k stars 318 forks source link

Processes started after program using `sysinfo` aren't added to processes list, sometimes #1339

Open valentinegb opened 3 months ago

valentinegb commented 3 months ago

Describe the bug

It seems that if a program is using sysinfo to check the processes currently running and a new process starts later, it won't be added to the list of processes despite refreshing. Inexplicably, though, while this happens most of the time, there are some times where a newly launched program will be detected.

To Reproduce

let mut sys = sysinfo::System::new();

loop {
    sys.refresh_processes_specifics(ProcessesToUpdate::All, ProcessRefreshKind::new());

    let discord_is_open = sys.processes_by_name("Discord".as_ref()).next().is_some();
    let apple_music_is_open = sys.processes_by_name("Music".as_ref()).next().is_some();

    // Code reacting to the values of these variables
}
GuillaumeGomez commented 3 months ago

Did you give a try to process-viewer and does it have the same issue? Your code looks correct to me. So maybe the issue is the process name you're using to filter?

valentinegb commented 3 months ago

process-viewer doesn't seem to have the same problem. Oddly, searching for the process name exactly wouldn't show it, but searching for the process name in all lowercase always did when it was open. In my code I changed instances of processes_by_name() to processes_by_exact_name(), since I do have the exact names of the processes I'm looking for, but regardless the issue persists.

GuillaumeGomez commented 3 months ago

Weird. And when you list all processes, do you see the ones you're looking for?

valentinegb commented 3 months ago

Nope, not unless they were open before I started checking

(Just did this and then searched in my terminal)

for (_pid, process) in sys.processes() {
    trace!("{}", process.name().to_str().unwrap());
}
GuillaumeGomez commented 3 months ago

Your issue is very weird. It is tested in different tests like here: the binary is run then only we refresh processes and it's working on mac as well. I'm really confused about what's going wrong on your side...

valentinegb commented 3 months ago

I cloned sysinfo and ran all the tests, all passed except test_environ (not sure if that's related)

---- test_environ stdout ----
thread 'test_environ' panicked at tests/process.rs:158:9:
assertion failed: proc_.environ().iter().any(|e| *e == *env)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

It may be worth noting that I'm on macOS 15.1 Beta (24B5024e), though I read through the release notes and didn't see any indication that this is a known issue

GuillaumeGomez commented 3 months ago

This one is flaky (on mac). For some reasons, sometimes it cannot retrieve process environment. No clue why. It's part of mac's "magic". ^^'

But if all tests pass, it seems to mean that the problem might not be directly in sysinfo. Maybe there is something slightly different you do which triggers this result? If we can find out what it is, it'd be awesome so that it can be either be fixed directly in sysinfo or at least be mentioned in the docs that this one thing should be done differently.

valentinegb commented 3 months ago

Here's pretty much all the code that currently is actually used in my program:

// main.rs
use std::{error::Error, process::ExitCode};

use discord_rich_presence::{DiscordIpc, DiscordIpcClient};
use log::{debug, error, trace};
use sysinfo::{ProcessRefreshKind, ProcessesToUpdate};

const CLIENT_ID: &str = "1273805325954187386";

fn try_main() -> Result<(), Box<dyn Error>> {
    env_logger::init();
    debug!("Initialized logging");

    if !sysinfo::IS_SUPPORTED_SYSTEM {
        return Err("This system is not supported by sysinfo, a crucial dependency".into());
    }

    let mut sys = sysinfo::System::new();
    let mut discord_client = None;

    loop {
        sys.refresh_processes_specifics(ProcessesToUpdate::All, ProcessRefreshKind::new());

        let discord_is_open = sys
            .processes_by_exact_name("Discord".as_ref())
            .next()
            .is_some();
        let apple_music_is_open = sys
            .processes_by_exact_name("Music".as_ref())
            .next()
            .is_some();

        if discord_is_open && apple_music_is_open {
            trace!("Time to work!");

            if discord_client.is_none() {
                discord_client = Some(DiscordIpcClient::new(CLIENT_ID)?);

                discord_client.as_mut().unwrap().connect()?;
                debug!("Connected Discord client");
            }

            // TODO
        } else {
            trace!("Idling...");
        }
    }
}

fn main() -> ExitCode {
    if let Err(err) = try_main() {
        error!("{err}");

        return ExitCode::FAILURE;
    }

    ExitCode::SUCCESS // Impossible to reach?
}
# Cargo.toml
[package]
name = "apple-music-rich-presence"
version = "0.1.0"
edition = "2021"

[dependencies]
discord-rich-presence = { git = "https://github.com/vionya/discord-rich-presence.git", rev = "5620e8901566290a583f9354205686b18628ba1b", version = "0.2.4" }
env_logger = "0.11.5"
log = "0.4.22"
sysinfo = { version = "0.31.2", default-features = false, features = ["system"] }

Some stuff was omitted since it's old and not actually used

Sorry for the massive comment! You would think most of this couldn't be causing this issue but, who knows

GuillaumeGomez commented 3 months ago

Your code looks good. Then let's try taking a different approach: if you ask for the PIDs when you start the program and then see what information sysinfo has for both processes maybe? I'm curious to see if an information in particular is badly retrieved.

valentinegb commented 3 months ago

I switched out refresh_processes_specifics() for refresh_processes() and added this to the loop:

for (pid, process) in sys.processes() {
    trace!("{pid}: {process:?}");
}

Launched Discord and Apple Music then the program. Found those two in the logs:

[2024-08-17T19:58:24Z TRACE apple_music_rich_presence] 11848: Process { pid: Pid(11848), parent: Some(Pid(1)), name: "Discord", environ: [], command: [], executable path: Some("/Applications/Discord.app/Contents/MacOS/Discord"), current working directory: None, memory usage: 48381952, virtual memory usage: 1635724623872, CPU usage: 0.0, status: Run, root: None, disk_usage: DiskUsage { total_written_bytes: 13795328, written_bytes: 0, total_read_bytes: 245784576, read_bytes: 0 }, user_id: Some(Uid(501)), effective_user_id: Some(Uid(501)) }
[2024-08-17T19:58:24Z TRACE apple_music_rich_presence] 15340: Process { pid: Pid(15340), parent: Some(Pid(1)), name: "Music", environ: [], command: [], executable path: Some("/System/Applications/Music.app/Contents/MacOS/Music"), current working directory: None, memory usage: 141099008, virtual memory usage: 426729963520, CPU usage: 0.0, status: Run, root: None, disk_usage: DiskUsage { total_written_bytes: 630784, written_bytes: 0, total_read_bytes: 93708288, read_bytes: 0 }, user_id: Some(Uid(501)), effective_user_id: Some(Uid(501)) }

Another very strange thing I noticed is that programs (such as "VisualizerService") which are launched alongside Apple Music, are detected by sysinfo when Apple Music is launched afterwards, even though Apple Music itself is not. Here's the logging of VisualizerService, too:

[2024-08-17T19:58:24Z TRACE apple_music_rich_presence] 15341: Process { pid: Pid(15341), parent: Some(Pid(1)), name: "VisualizerService", environ: [], command: [], executable path: Some("/System/Applications/Music.app/Contents/XPCServices/VisualizerService.xpc/Contents/MacOS/VisualizerService"), current working directory: None, memory usage: 15843328, virtual memory usage: 420849057792, CPU usage: 0.0, status: Run, root: None, disk_usage: DiskUsage { total_written_bytes: 0, written_bytes: 0, total_read_bytes: 348160, read_bytes: 0 }, user_id: Some(Uid(501)), effective_user_id: Some(Uid(501)) }
GuillaumeGomez commented 3 months ago

Definitely strange. refresh_processes calls refresh_processes_specifics as you can see here. So there is definitely something wrong in sysinfo. Since I can't replicate the bug locally, do you mind trying to find out what's wrong in the source code please? ^^'

Don't hesitate to ask if you have any question. But I'm glad that a new bug was found. :D

valentinegb commented 3 months ago

A revelation! This code fixed the issue:

sleep(Duration::from_secs(5));

... So the problem is that the loop is too fast when not limited?

GuillaumeGomez commented 3 months ago

Wait really? If true then it's really not great. T_T

sysinfo uses proc_listallpids to retrieve all PIDs and then iterate through them. It'd be super weird for this API to take multiple seconds to realize a process got added...

valentinegb commented 3 months ago

Did a little bit of testing, I was able to get as low as 1 second to consistently detect processes correctly, and as low as 100 milliseconds to inconsistently detect processes correctly

GuillaumeGomez commented 3 months ago

But no process with the given PID you're looking for is there right? If so, I suppose the proc_listallpids function is not working correctly...

valentinegb commented 3 months ago

But no process with the given PID you're looking for is there right?

If there's some time between refreshes, the processes I'm looking for do appear.

GuillaumeGomez commented 3 months ago

So it seems like the system API is not refreshing very quickly. How strange.