Skip to content

Commit d623f45

Browse files
authored
Rollup merge of rust-lang#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).
2 parents 2a3f536 + 6d1da82 commit d623f45

File tree

2 files changed

+44
-4
lines changed

2 files changed

+44
-4
lines changed

src/libstd/sys/unix/fd.rs

+7
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,13 @@ impl FileDesc {
154154
}
155155
}
156156

157+
#[cfg(target_os = "linux")]
158+
pub fn get_cloexec(&self) -> io::Result<bool> {
159+
unsafe {
160+
Ok((cvt(libc::fcntl(self.fd, libc::F_GETFD))? & libc::FD_CLOEXEC) != 0)
161+
}
162+
}
163+
157164
#[cfg(not(any(target_env = "newlib",
158165
target_os = "solaris",
159166
target_os = "emscripten",

src/libstd/sys/unix/fs.rs

+37-4
Original file line numberDiff line numberDiff line change
@@ -441,15 +441,48 @@ impl File {
441441

442442
// Currently the standard library supports Linux 2.6.18 which did not
443443
// have the O_CLOEXEC flag (passed above). If we're running on an older
444-
// Linux kernel then the flag is just ignored by the OS, so we continue
445-
// to explicitly ask for a CLOEXEC fd here.
444+
// Linux kernel then the flag is just ignored by the OS. After we open
445+
// the first file, we check whether it has CLOEXEC set. If it doesn't,
446+
// we will explicitly ask for a CLOEXEC fd for every further file we
447+
// open, if it does, we will skip that step.
446448
//
447449
// The CLOEXEC flag, however, is supported on versions of macOS/BSD/etc
448450
// that we support, so we only do this on Linux currently.
449-
if cfg!(target_os = "linux") {
450-
fd.set_cloexec()?;
451+
#[cfg(target_os = "linux")]
452+
fn ensure_cloexec(fd: &FileDesc) -> io::Result<()> {
453+
use sync::atomic::{AtomicUsize, Ordering};
454+
455+
const OPEN_CLOEXEC_UNKNOWN: usize = 0;
456+
const OPEN_CLOEXEC_SUPPORTED: usize = 1;
457+
const OPEN_CLOEXEC_NOTSUPPORTED: usize = 2;
458+
static OPEN_CLOEXEC: AtomicUsize = AtomicUsize::new(OPEN_CLOEXEC_UNKNOWN);
459+
460+
let need_to_set;
461+
match OPEN_CLOEXEC.load(Ordering::Relaxed) {
462+
OPEN_CLOEXEC_UNKNOWN => {
463+
need_to_set = !fd.get_cloexec()?;
464+
OPEN_CLOEXEC.store(if need_to_set {
465+
OPEN_CLOEXEC_NOTSUPPORTED
466+
} else {
467+
OPEN_CLOEXEC_SUPPORTED
468+
}, Ordering::Relaxed);
469+
},
470+
OPEN_CLOEXEC_SUPPORTED => need_to_set = false,
471+
OPEN_CLOEXEC_NOTSUPPORTED => need_to_set = true,
472+
_ => unreachable!(),
473+
}
474+
if need_to_set {
475+
fd.set_cloexec()?;
476+
}
477+
Ok(())
478+
}
479+
480+
#[cfg(not(target_os = "linux"))]
481+
fn ensure_cloexec(_: &FileDesc) -> io::Result<()> {
482+
Ok(())
451483
}
452484

485+
ensure_cloexec(&fd)?;
453486
Ok(File(fd))
454487
}
455488

0 commit comments

Comments
 (0)