Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sysinfo System::new opens too many files #242

Closed
ailisp opened this issue Jan 14, 2020 · 17 comments · Fixed by #243
Closed

sysinfo System::new opens too many files #242

ailisp opened this issue Jan 14, 2020 · 17 comments · Fixed by #243

Comments

@ailisp
Copy link

ailisp commented Jan 14, 2020

Giving this snippet:

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

pub fn show_lsof(prefix: &str) {
    let id = std::process::id();
    let out = std::process::Command::new("bash")
        .arg("-c")
        .arg(format!("lsof -p {} | wc -l", id))
        .output()
        .expect("failed to execute process");
    println!("{} {:?}", prefix, out);
}

fn main() {
    show_lsof("before system::new");
    let mut sys = System::new();
    show_lsof("after system::new");
    println!("Hello, world!");
}

Run it:

ulimit -n unlimited
cargo run

it's showing:

before system::new Output { status: ExitStatus(ExitStatus(0)), stdout: "84\n", stderr: "" }
after system::new Output { status: ExitStatus(ExitStatus(0)), stdout: "2318\n", stderr: "" }
Hello, world!

Checking output of lsof manually, it seems to open 2000+ files of /proc/*/task/*/stat. In our use case we have to keep a reference of sysinfo::System, to log mem and cpu usage every ten seconds; but it opens many files, lead to rust panic: too many open files (if do not increase system default ulimit)
@ailisp
Copy link
Author

ailisp commented Jan 14, 2020

Also this only happens on sysinfo 0.10, 0.9 was good

@GuillaumeGomez
Copy link
Owner

This is something I was afraid that would happen... I added it in this commit. The issue here is that it takes a lot of time opening those specific files, so keeping them around open allowed me to improve the performance on linux.

So after doing this, I opened #231 and fixed it in this commit. As you can see, I limit its usage at 90% of the total number of available fds. But from this issue, it appears to be too much.

So in here we have three solutions:

  1. Remove this optimization completely.
  2. Reduce the limit (let's say to 50% of total number of fds?).
  3. Allow the user to override this limit.

What do you think would be better?

@ailisp
Copy link
Author

ailisp commented Jan 14, 2020

Thanks for quick response! 3 sounds most appealing to me, so user can make it 0% (option 1), 50% (option 2), and leave it default (90%). Btw i feel 90% is a little much, if program is opening other files or listen for sockets.

@GuillaumeGomez
Copy link
Owner

I opened #243. Could you confirm it works for you please?

@ailisp
Copy link
Author

ailisp commented Jan 15, 2020

Hmm, seems not work for me? My cargo.toml:

[package]
name = "sysinfotest"
version = "0.1.0"
authors = ["Bo Yao <bo@nearprotocol.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
sysinfo = {git = "https://github.com/GuillaumeGomez/sysinfo", branch="fds-limit"}

And with default ulimit -n 1024, and my set of ulimit -n 2048 it

thread 'main' panicked at 'failed to execute process: Os { code: 24, kind: Other, message: "Too many open files" }', src/libcore/result.rs:1187:5

if i set ulimit to 4000, it gives:

before system::new Output { status: ExitStatus(ExitStatus(0)), stdout: "77\n", stderr: "" }
after system::new Output { status: ExitStatus(ExitStatus(0)), stdout: "2617\n", stderr: "" }

This looks 50% limit was not preserved?

@GuillaumeGomez
Copy link
Owner

Maybe I don't get the system's limit correctly... What linux are you using? Also, can you run this code and show what you get:

#[cfg(target_os = "android")]
{
    // The constant "RLIMIT_NOFILE" doesn't exist on Android so we have to return a value.
    // The default value seems to be 1024 so let's return 50% of it...
    println!("new limit1: {}", 1024 - 1024 / 2);
}
#[cfg(not(target_os = "android"))]
unsafe {
    let mut limits = libc::rlimit {
        rlim_cur: 0,
        rlim_max: 0,
    };
    if libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) != 0 {
        // Most linux system now defaults to 1024.
        println!("new limit2: {}", 1024 - 1024 / 2);
        return;
    }
    // We save the value in case the update fails.
    let current = limits.rlim_cur;

    // The set the soft limit to the hard one.
    limits.rlim_cur = limits.rlim_max;
    // In this part, we leave minimum 50% of the available file descriptors to the process
    // using sysinfo.
    println!("new limit3: {}",
        if libc::setrlimit(libc::RLIMIT_NOFILE, &limits) == 0 {
            limits.rlim_cur - limits.rlim_cur / 2
        } else {
            current - current / 2
        } as _);
    ))
}

@GuillaumeGomez
Copy link
Owner

I just realized that my code was incorrect: when opening a file, I was increasing the count instead of decreasing it. Please try again.

@ailisp
Copy link
Author

ailisp commented Jan 15, 2020 via email

@GuillaumeGomez
Copy link
Owner

This is surprisingly high... But I have the same system and it works as expected on mine... Did you try with my last change on the PR?

@ailisp
Copy link
Author

ailisp commented Jan 16, 2020 via email

@GuillaumeGomez
Copy link
Owner

Yes, it's raising the maximum so more FDs are available. In any case, you can still limit it to 0 by using the function set_open_files_limit(0) if you want.

@ailisp
Copy link
Author

ailisp commented Jan 17, 2020

@GuillaumeGomez set_open_files_limit(0) sounds great. however it seems does not have effect on my machine:


fn main() {
    show_lsof("before system::new");
    assert!(sysinfo::set_open_files_limit(0));
    let mut sys = System::new();
    show_lsof("after system::new");
}

shows:
before system::new Output { status: ExitStatus(ExitStatus(0)), stdout: "63\n", stderr: "" }
after system::new Output { status: ExitStatus(ExitStatus(0)), stdout: "5035\n", stderr: "" }

@GuillaumeGomez
Copy link
Owner

Indeed, I made two mistakes:

  1. I completely overlooked the instanciation of the Process type, which is where I didn't check for the file limit and was keeping it around.
  2. I failed the computation of the already used files in set_open_files_limit.

Both should be fixed now. Can you give it another try please? Really sorry for all the mistakes on my side...

@ailisp
Copy link
Author

ailisp commented Jan 17, 2020

@GuillaumeGomez No worries, I'm very appreciate for your help. Seems still doesn't work for me:
before system::new Output { status: ExitStatus(ExitStatus(0)), stdout: "108\n", stderr: "" }
after system::new Output { status: ExitStatus(ExitStatus(0)), stdout: "5175\n", stderr: "" }\

(in case it was cargo cache issue i did cargo clean and remove sysinfo from ~/.cargo/git, jump to definition also jumps to your latest version)

What is the result running on your machine?

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

pub fn show_lsof(prefix: &str) {
    let id = std::process::id();
    let out = std::process::Command::new("bash")
        .arg("-c")
        .arg(format!("lsof -p {} | wc -l", id))
        .output()
        .expect("failed to execute process");
    println!("{} {:?}", prefix, out);
}

fn main() {
    show_lsof("before system::new");
    assert!(sysinfo::set_open_files_limit(0));
    let mut sys = System::new();
    show_lsof("after system::new");
}

@GuillaumeGomez
Copy link
Owner

I get:

cargo run
    Updating crates.io index
   Compiling autocfg v0.1.7
   Compiling semver-parser v0.7.0
   Compiling cfg-if v0.1.10
   Compiling lazy_static v1.4.0
   Compiling libc v0.2.66
   Compiling scopeguard v1.0.0
   Compiling rayon-core v1.7.0
   Compiling doc-comment v0.3.1
   Compiling sysinfo v0.10.5 (/home/imperio/rust/sysinfo)
   Compiling either v1.5.3
   Compiling once_cell v1.3.0
   Compiling semver v0.9.0
   Compiling crossbeam-utils v0.7.0
   Compiling crossbeam-epoch v0.8.0
   Compiling rustc_version v0.2.3
   Compiling memoffset v0.5.3
   Compiling crossbeam-queue v0.2.1
   Compiling num_cpus v1.12.0
   Compiling crossbeam-deque v0.7.2
   Compiling rayon v1.3.0
warning: use of deprecated item 'std::error::Error::description': use the Display impl or to_string()
   --> /home/imperio/rust/sysinfo/src/linux/system.rs:738:68
    |
738 |         .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e.description()))?)
    |                                                                    ^^^^^^^^^^^
    |
    = note: `#[warn(deprecated)]` on by default

   Compiling test-sysinfo v0.1.0 (/home/imperio/rust/test-sysinfo)
warning: unused imports: `Pid`, `get_current_pid`
 --> src/main.rs:1:15
  |
1 | use sysinfo::{get_current_pid, Pid, ProcessExt, System, SystemExt};
  |               ^^^^^^^^^^^^^^^  ^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: unused import: `ProcessExt`
 --> src/main.rs:1:37
  |
1 | use sysinfo::{get_current_pid, Pid, ProcessExt, System, SystemExt};
  |                                     ^^^^^^^^^^

warning: unused variable: `sys`
  --> src/main.rs:16:13
   |
16 |     let mut sys = System::new();
   |             ^^^ help: consider prefixing with an underscore: `_sys`
   |
   = note: `#[warn(unused_variables)]` on by default

warning: variable does not need to be mutable
  --> src/main.rs:16:9
   |
16 |     let mut sys = System::new();
   |         ----^^^
   |         |
   |         help: remove this `mut`
   |
   = note: `#[warn(unused_mut)]` on by default

    Finished dev [unoptimized + debuginfo] target(s) in 11.15s
     Running `target/debug/test-sysinfo`
before system::new Output { status: ExitStatus(ExitStatus(0)), stdout: "14\n", stderr: "" }
after system::new Output { status: ExitStatus(ExitStatus(0)), stdout: "14\n", stderr: "" }

But I link to my local sysinfo folder. My Cargo.toml file looks like this:

[package]
name = "test-sysinfo"
version = "0.1.0"
authors = ["Guillaume Gomez <guillaume1.gomez@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
sysinfo = { path = "../sysinfo" }

Just to be sure, the last commit hash of sysinfo I'm using is: 34bba033fe8dc23410fdc3bec02e6e95f3e11132, which seems to be the same as the last one in #243. So I'm a bit surprised...

@ailisp
Copy link
Author

ailisp commented Jan 17, 2020

@GuillaumeGomez sorry my fault, seems i didn't clean up thoroughly. after nuke ~/.cargo/git and ~/.cargo/registry it works. clone sysinfo to a local path, checkout to fds-limit branch as you did above also works. Thank you a lot!

@GuillaumeGomez
Copy link
Owner

Perfect! I'll make a new minor release quickly then. Thanks a lot for your help through this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants