Skip to content

Commit 1e17cef

Browse files
committed
Auto merge of #109432 - flba-eb:108594_forkspawn_exponential_backoff, r=workingjubilee
QNX Neutrino: exponential backoff when fork/spawn needs a retry Fixes #108594: When retrying, sleep with an exponential duration. When sleep duration is lower than minimum possible sleeping time, yield instead (this will not be often due to the exponential increase of duration). Minimum possible sleeping time is determined using `libc::clock_getres` but only when spawn/fork failed the first time in a request. This is cached using a LazyLock. CC `@gh-tr` r? `@workingjubilee` `@rustbot` label +O-neutrino
2 parents 8177591 + 716cc5a commit 1e17cef

File tree

2 files changed

+63
-21
lines changed

2 files changed

+63
-21
lines changed

Diff for: library/std/src/sys/unix/process/process_unix.rs

+62-14
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,26 @@ cfg_if::cfg_if! {
3535
if #[cfg(all(target_os = "nto", target_env = "nto71"))] {
3636
use crate::thread;
3737
use libc::{c_char, posix_spawn_file_actions_t, posix_spawnattr_t};
38-
// arbitrary number of tries:
39-
const MAX_FORKSPAWN_TRIES: u32 = 4;
38+
use crate::time::Duration;
39+
use crate::sync::LazyLock;
40+
// Get smallest amount of time we can sleep.
41+
// Return a common value if it cannot be determined.
42+
fn get_clock_resolution() -> Duration {
43+
static MIN_DELAY: LazyLock<Duration, fn() -> Duration> = LazyLock::new(|| {
44+
let mut mindelay = libc::timespec { tv_sec: 0, tv_nsec: 0 };
45+
if unsafe { libc::clock_getres(libc::CLOCK_MONOTONIC, &mut mindelay) } == 0
46+
{
47+
Duration::from_nanos(mindelay.tv_nsec as u64)
48+
} else {
49+
Duration::from_millis(1)
50+
}
51+
});
52+
*MIN_DELAY
53+
}
54+
// Arbitrary minimum sleep duration for retrying fork/spawn
55+
const MIN_FORKSPAWN_SLEEP: Duration = Duration::from_nanos(1);
56+
// Maximum duration of sleeping before giving up and returning an error
57+
const MAX_FORKSPAWN_SLEEP: Duration = Duration::from_millis(1000);
4058
}
4159
}
4260

@@ -163,12 +181,25 @@ impl Command {
163181
unsafe fn do_fork(&mut self) -> Result<(pid_t, pid_t), io::Error> {
164182
use crate::sys::os::errno;
165183

166-
let mut tries_left = MAX_FORKSPAWN_TRIES;
184+
let mut delay = MIN_FORKSPAWN_SLEEP;
185+
167186
loop {
168187
let r = libc::fork();
169-
if r == -1 as libc::pid_t && tries_left > 0 && errno() as libc::c_int == libc::EBADF {
170-
thread::yield_now();
171-
tries_left -= 1;
188+
if r == -1 as libc::pid_t && errno() as libc::c_int == libc::EBADF {
189+
if delay < get_clock_resolution() {
190+
// We cannot sleep this short (it would be longer).
191+
// Yield instead.
192+
thread::yield_now();
193+
} else if delay < MAX_FORKSPAWN_SLEEP {
194+
thread::sleep(delay);
195+
} else {
196+
return Err(io::const_io_error!(
197+
ErrorKind::WouldBlock,
198+
"forking returned EBADF too often",
199+
));
200+
}
201+
delay *= 2;
202+
continue;
172203
} else {
173204
return cvt(r).map(|res| (res, -1));
174205
}
@@ -480,17 +511,28 @@ impl Command {
480511
attrp: *const posix_spawnattr_t,
481512
argv: *const *mut c_char,
482513
envp: *const *mut c_char,
483-
) -> i32 {
484-
let mut tries_left = MAX_FORKSPAWN_TRIES;
514+
) -> io::Result<i32> {
515+
let mut delay = MIN_FORKSPAWN_SLEEP;
485516
loop {
486517
match libc::posix_spawnp(pid, file, file_actions, attrp, argv, envp) {
487-
libc::EBADF if tries_left > 0 => {
488-
thread::yield_now();
489-
tries_left -= 1;
518+
libc::EBADF => {
519+
if delay < get_clock_resolution() {
520+
// We cannot sleep this short (it would be longer).
521+
// Yield instead.
522+
thread::yield_now();
523+
} else if delay < MAX_FORKSPAWN_SLEEP {
524+
thread::sleep(delay);
525+
} else {
526+
return Err(io::const_io_error!(
527+
ErrorKind::WouldBlock,
528+
"posix_spawnp returned EBADF too often",
529+
));
530+
}
531+
delay *= 2;
490532
continue;
491533
}
492534
r => {
493-
return r;
535+
return Ok(r);
494536
}
495537
}
496538
}
@@ -620,14 +662,20 @@ impl Command {
620662
let spawn_fn = libc::posix_spawnp;
621663
#[cfg(target_os = "nto")]
622664
let spawn_fn = retrying_libc_posix_spawnp;
623-
cvt_nz(spawn_fn(
665+
666+
let spawn_res = spawn_fn(
624667
&mut p.pid,
625668
self.get_program_cstr().as_ptr(),
626669
file_actions.0.as_ptr(),
627670
attrs.0.as_ptr(),
628671
self.get_argv().as_ptr() as *const _,
629672
envp as *const _,
630-
))?;
673+
);
674+
675+
#[cfg(target_os = "nto")]
676+
let spawn_res = spawn_res?;
677+
678+
cvt_nz(spawn_res)?;
631679
Ok(Some(p))
632680
}
633681
}

Diff for: src/doc/rustc/src/platform-support/nto-qnx.md

+1-7
Original file line numberDiff line numberDiff line change
@@ -164,18 +164,12 @@ export exclude_tests='
164164
--exclude tests/run-make-fulldeps'
165165

166166
env $build_env \
167-
./x.py test -j 1 \
167+
./x.py test \
168168
$exclude_tests \
169169
--stage 1 \
170170
--target x86_64-pc-nto-qnx710
171171
```
172172

173-
Currently, only one thread can be used when testing due to limitations in `libc::fork` and `libc::posix_spawnp`.
174-
See [fork documentation](https://www.qnx.com/developers/docs/7.1/#com.qnx.doc.neutrino.lib_ref/topic/f/fork.html)
175-
(error section) for more information.
176-
This can be achieved by using the `-j 1` parameter in the `x.py` call.
177-
This issue is being researched and we will try to allow parallelism in the future.
178-
179173
## Building Rust programs
180174

181175
Rust does not yet ship pre-compiled artifacts for this target.

0 commit comments

Comments
 (0)