Rollup merge of #50638 - tbu-:pr_open_cloexec_once, r=nagisa
Don't unconditionally set CLOEXEC twice on every fd we open on Linux Previously, every `open64` was accompanied by a `ioctl(…, FIOCLEX)`, because some old Linux version would ignore the `O_CLOEXEC` flag we pass to the `open64` function. Now, we check whether the `CLOEXEC` flag is set on the first file we open – if it is, we won't do extra syscalls for every opened file. If it is not set, we fall back to the old behavior of unconditionally calling `ioctl(…, FIOCLEX)` on newly opened files. On old Linuxes, this amounts to one extra syscall per process, namely the `fcntl(…, F_GETFD)` call to check the `CLOEXEC` flag. On new Linuxes, this reduces the number of syscalls per opened file by one, except for the first file, where it does the same number of syscalls as before (`fcntl(…, F_GETFD)` to check the flag instead of `ioctl(…, FIOCLEX)` to set it).
This commit is contained in:
commit
d623f45a40
2 changed files with 44 additions and 4 deletions
|
@ -154,6 +154,13 @@ impl FileDesc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub fn get_cloexec(&self) -> io::Result<bool> {
|
||||||
|
unsafe {
|
||||||
|
Ok((cvt(libc::fcntl(self.fd, libc::F_GETFD))? & libc::FD_CLOEXEC) != 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(any(target_env = "newlib",
|
#[cfg(not(any(target_env = "newlib",
|
||||||
target_os = "solaris",
|
target_os = "solaris",
|
||||||
target_os = "emscripten",
|
target_os = "emscripten",
|
||||||
|
|
|
@ -441,15 +441,48 @@ impl File {
|
||||||
|
|
||||||
// Currently the standard library supports Linux 2.6.18 which did not
|
// Currently the standard library supports Linux 2.6.18 which did not
|
||||||
// have the O_CLOEXEC flag (passed above). If we're running on an older
|
// have the O_CLOEXEC flag (passed above). If we're running on an older
|
||||||
// Linux kernel then the flag is just ignored by the OS, so we continue
|
// Linux kernel then the flag is just ignored by the OS. After we open
|
||||||
// to explicitly ask for a CLOEXEC fd here.
|
// the first file, we check whether it has CLOEXEC set. If it doesn't,
|
||||||
|
// we will explicitly ask for a CLOEXEC fd for every further file we
|
||||||
|
// open, if it does, we will skip that step.
|
||||||
//
|
//
|
||||||
// The CLOEXEC flag, however, is supported on versions of macOS/BSD/etc
|
// The CLOEXEC flag, however, is supported on versions of macOS/BSD/etc
|
||||||
// that we support, so we only do this on Linux currently.
|
// that we support, so we only do this on Linux currently.
|
||||||
if cfg!(target_os = "linux") {
|
#[cfg(target_os = "linux")]
|
||||||
fd.set_cloexec()?;
|
fn ensure_cloexec(fd: &FileDesc) -> io::Result<()> {
|
||||||
|
use sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
const OPEN_CLOEXEC_UNKNOWN: usize = 0;
|
||||||
|
const OPEN_CLOEXEC_SUPPORTED: usize = 1;
|
||||||
|
const OPEN_CLOEXEC_NOTSUPPORTED: usize = 2;
|
||||||
|
static OPEN_CLOEXEC: AtomicUsize = AtomicUsize::new(OPEN_CLOEXEC_UNKNOWN);
|
||||||
|
|
||||||
|
let need_to_set;
|
||||||
|
match OPEN_CLOEXEC.load(Ordering::Relaxed) {
|
||||||
|
OPEN_CLOEXEC_UNKNOWN => {
|
||||||
|
need_to_set = !fd.get_cloexec()?;
|
||||||
|
OPEN_CLOEXEC.store(if need_to_set {
|
||||||
|
OPEN_CLOEXEC_NOTSUPPORTED
|
||||||
|
} else {
|
||||||
|
OPEN_CLOEXEC_SUPPORTED
|
||||||
|
}, Ordering::Relaxed);
|
||||||
|
},
|
||||||
|
OPEN_CLOEXEC_SUPPORTED => need_to_set = false,
|
||||||
|
OPEN_CLOEXEC_NOTSUPPORTED => need_to_set = true,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
if need_to_set {
|
||||||
|
fd.set_cloexec()?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
fn ensure_cloexec(_: &FileDesc) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_cloexec(&fd)?;
|
||||||
Ok(File(fd))
|
Ok(File(fd))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue