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

Improve file descriptor closing loops #14213

Merged
merged 5 commits into from
Jan 19, 2023
Merged

Conversation

Dim-P
Copy link
Contributor

@Dim-P Dim-P commented Jan 5, 2023

Summary

Fixes #14177 , fixes #14062 .

There are 3 places in the agent where we either try to close all open file descriptors or mark them to be closed by the exec() stage of posix_spawn(). This is done in a bruteforce way, by looping through all FD IDs up to _SC_OPEN_MAX.

While this is a valid approach, it creates issues on platforms that (incorrectly) set the maximum FD limit to infinity (in practice, it can be as high as 1073741816).

This PR instead of bruteforcing its way through all possible FD IDs, it reads /proc/self/fd (if available) to discover which FDs are actually open and closes (or marks to be closed) only them (unless STDIN, STDOUT and/or STDERR are to be excluded).

This should also speed up slightly the agent initialisation time (by how much, it depends on the _SC_OPEN_MAX value) .

Test Plan

Test on a system with a very high ulimit -n such as 1073741816. The agent should start up in reasonable time instead of taking >10min and consuming 100% CPU in the meantime.

TODO: Revert #14178

@Dim-P Dim-P requested a review from a team January 5, 2023 15:54
@Dim-P Dim-P force-pushed the improve-fd-close branch from 22e345d to a088df2 Compare January 5, 2023 16:01
@ilyam8
Copy link
Member

ilyam8 commented Jan 5, 2023

TODO: Revert #14178

We can't do that until a new stable version is released.

@Dim-P
Copy link
Contributor Author

Dim-P commented Jan 5, 2023

TODO: Revert #14178

We can't do that until a new stable version is released.

That's correct, that's why I didn't include it in this PR, but added it as TODO so that we don't forget about it.

libnetdata/libnetdata.c Outdated Show resolved Hide resolved
libnetdata/libnetdata.c Outdated Show resolved Hide resolved
@ilyam8 ilyam8 closed this Jan 10, 2023
@ilyam8 ilyam8 reopened this Jan 10, 2023
@ktsaou
Copy link
Member

ktsaou commented Jan 11, 2023

Various operating systems have different methods for massively closing file descriptors.

  1. Linux has close_range().
  2. BSDs have closefrom().

I think the /proc/PID/fd traversal is slow and should be a fallback when the above are not available.

So, I think the process should be like this:

  1. Use close_range() when it is available.
  2. If not available, use closefrom() when it is available.
  3. If not available, use /proc/PID/fd traversal when it is available.
  4. If not available, use sysconf() like we currently do.

To use close_range() or closefrom() we need to test for them with automake.

By adding this line to configure.ac (I think between lines 254 and 255):

AC_CHECK_FUNCS([close_range closefrom])

We are going to have in config.h the following:

/* Define to 1 if you have the `closefrom' function. */
#define HAVE_CLOSEFROM 1

/* Define to 1 if you have the `close_range' function. */
#define HAVE_CLOSE_RANGE 1

After this, we can do:

#if defined(HAVE_CLOSE_RANGE)
   // use close_range() to close the open file descriptors
#elif defined(HAVE_CLOSEFROM)
   // use closefrom() to close the open file descriptors
#else
   // try to use /proc/PID/fd to close the open file descriptors
   // if the above fails, try to use getrlimit()
   // if the above fails, fallback to sysconf(_SC_OPEN_MAX)
#endif

@ktsaou
Copy link
Member

ktsaou commented Jan 11, 2023

I see in the code that there are 2 possible actions: close and mark with FD_CLOEXEC.

I think the later in popene_internal() is an attempt to use the FD_CLOEXEC flag to let posix_spawn() of the file descriptors to keep open and the ones to close.

Generally speaking, doing this loop at popene_internal() is kind of a waste. All the descriptors should be marked with FD_CLOEXEC when they are first opened, not before forking.

If we can't mark them like that when they are opened, I suggest to drop the marking entirely and once to fork is done by posix_spawn() to close them while running as the child process.

This means that we are going to need only one action for for_each_open_fd(), to close the open files, so we can also rename it to close_all_open_fds() given the exception list.

@ktsaou
Copy link
Member

ktsaou commented Jan 11, 2023

@Dim-P please also use enum not just int, to have better understanding of the type of values accepted. We make a lot of changes to the code base and having enum improves maintainability tremendously.

@github-actions github-actions bot added the area/build Build system (autotools and cmake). label Jan 12, 2023
@Dim-P
Copy link
Contributor Author

Dim-P commented Jan 12, 2023

Thank you for the comments @ktsaou .

  1. close_range() is a good call, I've used it as the first candidate to close the fds. It exists on FreeBSD as well.
  2. re. FD_CLOEXEC, I had a look at the code and you are right, it can probably be removed altogether, but this required more changes than just the plain fixes this PR is meant to do, so it can be done in a future PR. Maybe it's simpler than I thought, but need to check all netdata_popen() occurrences.
  3. I didn't use closefrom() since it doesn't support FD_CLOEXEC, but I don't think it was going to be of much use anyway.
  4. I replaced int with enum where applicable.

Tested on Ubuntu 22.04, Fedora 36, FreeBSD 13.1, Alpine 3.17.

@Dim-P Dim-P requested review from ktsaou, ilyam8 and thiagoftsm January 12, 2023 18:39
@vkalintiris
Copy link
Contributor

FWIW, I think we are overthinking this. The original reason to close open FDs was lxc-attach, see #1775.

There are 3 places in the agent where we either try to close all open file descriptors or mark them to be closed by the exec() stage of posix_spawn().

Skimming through the code, this is (or should be) actually 2 places:

  1. When the agent starts,
  2. When the agent spawns the spawn server,
  3. Inside libnetdata's popen.

We should coalesce 1 and 2. That is, we should spawn the spawn server only after closing all open FDs.

This is done in a bruteforce way, by looping through all FD IDs up to _SC_OPEN_MAX.

The original issue was lxc-attach leaving an open pseudoterminal device open. I'd bet that closing just a small, fixed amount of open FDs (let's say 64-128) would be more than safe. We can handle then the opening of our own FDs with the proper flag to close on exec.

Copy link
Contributor

@thiagoftsm thiagoftsm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we discussed internally, I am approving this PR expecting that suggestions from @vkalintiris will be address in another PR. LGTM!

@Dim-P
Copy link
Contributor Author

Dim-P commented Jan 19, 2023

Thanks guys, @vkalintiris I will merge this now to fix the issue before the upcoming stable release and we can make a new PR to improve the code using your suggestions.

@Dim-P Dim-P merged commit 8ee7e8b into netdata:master Jan 19, 2023
@Dim-P Dim-P deleted the improve-fd-close branch January 19, 2023 12:01
@ilyam8
Copy link
Member

ilyam8 commented Feb 7, 2023

TODO: Revert #14178

@Dim-P it is time now!

Dim-P added a commit that referenced this pull request Feb 9, 2023
Fixes the issue introduced as a result of #14213, where the agent fails to build successfully on FreeBSD < 13.1 and on environments with Linux kernel version < 5.11, due to missing 'CLOSE_RANGE_CLOEXEC' .
Ferroin pushed a commit that referenced this pull request Feb 9, 2023
Fixes the issue introduced as a result of #14213, where the agent fails to build successfully on FreeBSD < 13.1 and on environments with Linux kernel version < 5.11, due to missing 'CLOSE_RANGE_CLOEXEC' .
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/build Build system (autotools and cmake). area/daemon
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Bug]: 100% CPU usage when the open FD limit is huge [Bug]: Netdata in docker hangs/high CPU load on Fedora
6 participants