diff options
author | Alexey Izbyshev <izbyshev@ispras.ru> | 2022-05-05 16:46:19 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-05-05 16:46:19 (GMT) |
commit | 58573ffba0e4d3c7d6e6712169578e45d2926dbd (patch) | |
tree | 7f695ae7bd7560b989c62f88425579893c089b61 /Modules | |
parent | e65e587f9320947d73817fbe62c11d2a0fd50dfb (diff) | |
download | cpython-58573ffba0e4d3c7d6e6712169578e45d2926dbd.zip cpython-58573ffba0e4d3c7d6e6712169578e45d2926dbd.tar.gz cpython-58573ffba0e4d3c7d6e6712169578e45d2926dbd.tar.bz2 |
gh-92301: subprocess: Prefer close_range() to procfs-based fd closing (#92303)
#92301: subprocess: Prefer `close_range()` to procfs-based fd closing.
`close_range()` is much faster for large number of file descriptors, e.g.
4 times faster for 1000 descriptors in a Linux 5.16-based environment.
We prefer close_range() only if it's known to be async-signal-safe.
Diffstat (limited to 'Modules')
-rw-r--r-- | Modules/_posixsubprocess.c | 88 |
1 files changed, 70 insertions, 18 deletions
diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c index 2440609..21c2bd1 100644 --- a/Modules/_posixsubprocess.c +++ b/Modules/_posixsubprocess.c @@ -210,18 +210,23 @@ safe_get_max_fd(void) } -/* Close all file descriptors in the range from start_fd and higher - * except for those in py_fds_to_keep. If the range defined by - * [start_fd, safe_get_max_fd()) is large this will take a long - * time as it calls close() on EVERY possible fd. +/* Close all file descriptors in the given range except for those in + * py_fds_to_keep by invoking closer on each subrange. * - * It isn't possible to know for sure what the max fd to go up to - * is for processes with the capability of raising their maximum. + * If end_fd == -1, it's guessed via safe_get_max_fd(), but it isn't + * possible to know for sure what the max fd to go up to is for + * processes with the capability of raising their maximum, or in case + * a process opened a high fd and then lowered its maximum. */ -static void -_close_fds_by_brute_force(long start_fd, PyObject *py_fds_to_keep) +static int +_close_range_except(int start_fd, + int end_fd, + PyObject *py_fds_to_keep, + int (*closer)(int, int)) { - long end_fd = safe_get_max_fd(); + if (end_fd == -1) { + end_fd = Py_MIN(safe_get_max_fd(), INT_MAX); + } Py_ssize_t num_fds_to_keep = PyTuple_GET_SIZE(py_fds_to_keep); Py_ssize_t keep_seq_idx; /* As py_fds_to_keep is sorted we can loop through the list closing @@ -231,15 +236,17 @@ _close_fds_by_brute_force(long start_fd, PyObject *py_fds_to_keep) int keep_fd = PyLong_AsLong(py_keep_fd); if (keep_fd < start_fd) continue; - _Py_closerange(start_fd, keep_fd - 1); + if (closer(start_fd, keep_fd - 1) != 0) + return -1; start_fd = keep_fd + 1; } if (start_fd <= end_fd) { - _Py_closerange(start_fd, end_fd); + if (closer(start_fd, end_fd) != 0) + return -1; } + return 0; } - #if defined(__linux__) && defined(HAVE_SYS_SYSCALL_H) /* It doesn't matter if d_name has room for NAME_MAX chars; we're using this * only to read a directory of short file descriptor number names. The kernel @@ -255,6 +262,16 @@ struct linux_dirent64 { char d_name[256]; /* Filename (null-terminated) */ }; +static int +_brute_force_closer(int first, int last) +{ + for (int i = first; i <= last; i++) { + /* Ignore errors */ + (void)close(i); + } + return 0; +} + /* Close all open file descriptors in the range from start_fd and higher * Do not close any in the sorted py_fds_to_keep list. * @@ -278,7 +295,7 @@ _close_open_fds_safe(int start_fd, PyObject* py_fds_to_keep) fd_dir_fd = _Py_open_noraise(FD_DIR, O_RDONLY); if (fd_dir_fd == -1) { /* No way to get a list of open fds. */ - _close_fds_by_brute_force(start_fd, py_fds_to_keep); + _close_range_except(start_fd, -1, py_fds_to_keep, _brute_force_closer); return; } else { char buffer[sizeof(struct linux_dirent64)]; @@ -306,10 +323,16 @@ _close_open_fds_safe(int start_fd, PyObject* py_fds_to_keep) } } -#define _close_open_fds _close_open_fds_safe +#define _close_open_fds_fallback _close_open_fds_safe #else /* NOT (defined(__linux__) && defined(HAVE_SYS_SYSCALL_H)) */ +static int +_unsafe_closer(int first, int last) +{ + _Py_closerange(first, last); + return 0; +} /* Close all open file descriptors from start_fd and higher. * Do not close any in the sorted py_fds_to_keep tuple. @@ -325,7 +348,7 @@ _close_open_fds_safe(int start_fd, PyObject* py_fds_to_keep) * http://womble.decadent.org.uk/readdir_r-advisory.html */ static void -_close_open_fds_maybe_unsafe(long start_fd, PyObject* py_fds_to_keep) +_close_open_fds_maybe_unsafe(int start_fd, PyObject* py_fds_to_keep) { DIR *proc_fd_dir; #ifndef HAVE_DIRFD @@ -348,7 +371,7 @@ _close_open_fds_maybe_unsafe(long start_fd, PyObject* py_fds_to_keep) proc_fd_dir = opendir(FD_DIR); if (!proc_fd_dir) { /* No way to get a list of open fds. */ - _close_fds_by_brute_force(start_fd, py_fds_to_keep); + _close_range_except(start_fd, -1, py_fds_to_keep, _unsafe_closer); } else { struct dirent *dir_entry; #ifdef HAVE_DIRFD @@ -369,16 +392,45 @@ _close_open_fds_maybe_unsafe(long start_fd, PyObject* py_fds_to_keep) } if (errno) { /* readdir error, revert behavior. Highly Unlikely. */ - _close_fds_by_brute_force(start_fd, py_fds_to_keep); + _close_range_except(start_fd, -1, py_fds_to_keep, _unsafe_closer); } closedir(proc_fd_dir); } } -#define _close_open_fds _close_open_fds_maybe_unsafe +#define _close_open_fds_fallback _close_open_fds_maybe_unsafe #endif /* else NOT (defined(__linux__) && defined(HAVE_SYS_SYSCALL_H)) */ +/* We can use close_range() library function only if it's known to be + * async-signal-safe. + * + * On Linux, glibc explicitly documents it to be a thin wrapper over + * the system call, and other C libraries are likely to follow glibc. + */ +#if defined(HAVE_CLOSE_RANGE) && \ + (defined(__linux__) || defined(__FreeBSD__)) +#define HAVE_ASYNC_SAFE_CLOSE_RANGE + +static int +_close_range_closer(int first, int last) +{ + return close_range(first, last, 0); +} +#endif + +static void +_close_open_fds(int start_fd, PyObject* py_fds_to_keep) +{ +#ifdef HAVE_ASYNC_SAFE_CLOSE_RANGE + if (_close_range_except( + start_fd, INT_MAX, py_fds_to_keep, + _close_range_closer) == 0) { + return; + } +#endif + _close_open_fds_fallback(start_fd, py_fds_to_keep); +} #ifdef VFORK_USABLE /* Reset dispositions for all signals to SIG_DFL except for ignored |