Skip to content

Commit e107ec9

Browse files
committed
fix(uucore): Replace busy loop with select(2)
Fixes issue #9091, where uu_timeout would have a static 100ms latency.
1 parent baf0a73 commit e107ec9

File tree

7 files changed

+214
-96
lines changed

7 files changed

+214
-96
lines changed

.vscode/cspell.dictionaries/jargon.wordlist.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ preload
115115
prepend
116116
prepended
117117
primality
118+
pselect
118119
pseudoprime
119120
pseudoprimes
120121
quantiles
@@ -128,10 +129,13 @@ semiprimes
128129
setcap
129130
setfacl
130131
setfattr
132+
setmask
131133
shortcode
132134
shortcodes
133135
siginfo
136+
sigmask
134137
sigusr
138+
sigprocmask
135139
strcasecmp
136140
subcommand
137141
subexpression

1

Whitespace-only changes.

src/uu/timeout/Cargo.toml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,13 @@ path = "src/timeout.rs"
2020
[dependencies]
2121
clap = { workspace = true }
2222
libc = { workspace = true }
23-
nix = { workspace = true, features = ["signal"] }
24-
uucore = { workspace = true, features = ["parser", "process", "signals"] }
23+
nix = { workspace = true, features = ["signal", "poll"] }
24+
uucore = { workspace = true, features = [
25+
"parser",
26+
"process",
27+
"signals",
28+
"pipes",
29+
] }
2530
fluent = { workspace = true }
2631

2732
[[bin]]

src/uu/timeout/src/timeout.rs

Lines changed: 37 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ mod status;
88

99
use crate::status::ExitStatus;
1010
use clap::{Arg, ArgAction, Command};
11+
use nix::sys::signal::Signal;
1112
use std::io::ErrorKind;
1213
use std::os::unix::process::ExitStatusExt;
1314
use std::process::{self, Child, Stdio};
14-
use std::sync::atomic::{self, AtomicBool};
1515
use std::time::Duration;
1616
use uucore::display::Quotable;
1717
use uucore::error::{UResult, USimpleError, UUsageError};
1818
use uucore::parser::parse_time;
19-
use uucore::process::ChildExt;
19+
use uucore::process::{ChildExt, CommandExt, SelfPipe, WaitOrTimeoutRet};
2020
use uucore::translate;
2121

2222
#[cfg(unix)]
@@ -176,34 +176,6 @@ pub fn uu_app() -> Command {
176176
.after_help(translate!("timeout-after-help"))
177177
}
178178

179-
/// Remove pre-existing SIGCHLD handlers that would make waiting for the child's exit code fail.
180-
fn unblock_sigchld() {
181-
unsafe {
182-
nix::sys::signal::signal(
183-
nix::sys::signal::Signal::SIGCHLD,
184-
nix::sys::signal::SigHandler::SigDfl,
185-
)
186-
.unwrap();
187-
}
188-
}
189-
190-
/// We should terminate child process when receiving TERM signal.
191-
static SIGNALED: AtomicBool = AtomicBool::new(false);
192-
193-
fn catch_sigterm() {
194-
use nix::sys::signal;
195-
196-
extern "C" fn handle_sigterm(signal: libc::c_int) {
197-
let signal = signal::Signal::try_from(signal).unwrap();
198-
if signal == signal::Signal::SIGTERM {
199-
SIGNALED.store(true, atomic::Ordering::Relaxed);
200-
}
201-
}
202-
203-
let handler = signal::SigHandler::Handler(handle_sigterm);
204-
unsafe { signal::signal(signal::Signal::SIGTERM, handler) }.unwrap();
205-
}
206-
207179
/// Report that a signal is being sent if the verbose flag is set.
208180
fn report_if_verbose(signal: usize, cmd: &str, verbose: bool) {
209181
if verbose {
@@ -258,23 +230,27 @@ fn wait_or_kill_process(
258230
preserve_status: bool,
259231
foreground: bool,
260232
verbose: bool,
233+
self_pipe: &mut SelfPipe,
261234
) -> std::io::Result<i32> {
262235
// ignore `SIGTERM` here
263-
match process.wait_or_timeout(duration, None) {
264-
Ok(Some(status)) => {
236+
self_pipe.unset_other()?;
237+
238+
match process.wait_or_timeout(duration, self_pipe) {
239+
Ok(WaitOrTimeoutRet::InTime(status)) => {
265240
if preserve_status {
266241
Ok(status.code().unwrap_or_else(|| status.signal().unwrap()))
267242
} else {
268243
Ok(ExitStatus::TimeoutFailed.into())
269244
}
270245
}
271-
Ok(None) => {
246+
Ok(WaitOrTimeoutRet::TimedOut) => {
272247
let signal = signal_by_name_or_value("KILL").unwrap();
273248
report_if_verbose(signal, cmd, verbose);
274249
send_signal(process, signal, foreground);
275250
process.wait()?;
276251
Ok(ExitStatus::SignalSent(signal).into())
277252
}
253+
Ok(WaitOrTimeoutRet::CustomSignaled) => unreachable!(), // We did not set it up.
278254
Err(_) => Ok(ExitStatus::WaitingFailed.into()),
279255
}
280256
}
@@ -321,52 +297,49 @@ fn timeout(
321297
#[cfg(unix)]
322298
enable_pipe_errors()?;
323299

324-
let process = &mut process::Command::new(&cmd[0])
300+
let mut command = process::Command::new(&cmd[0]);
301+
command
325302
.args(&cmd[1..])
326303
.stdin(Stdio::inherit())
327304
.stdout(Stdio::inherit())
328-
.stderr(Stdio::inherit())
329-
.spawn()
330-
.map_err(|err| {
331-
let status_code = if err.kind() == ErrorKind::NotFound {
332-
// FIXME: not sure which to use
333-
127
334-
} else {
335-
// FIXME: this may not be 100% correct...
336-
126
337-
};
338-
USimpleError::new(
339-
status_code,
340-
translate!("timeout-error-failed-to-execute-process", "error" => err),
341-
)
342-
})?;
343-
unblock_sigchld();
344-
catch_sigterm();
305+
.stderr(Stdio::inherit());
306+
let mut self_pipe = command.set_up_timeout(Some(Signal::SIGTERM))?;
307+
let process = &mut command.spawn().map_err(|err| {
308+
let status_code = if err.kind() == ErrorKind::NotFound {
309+
// FIXME: not sure which to use
310+
127
311+
} else {
312+
// FIXME: this may not be 100% correct...
313+
126
314+
};
315+
USimpleError::new(
316+
status_code,
317+
translate!("timeout-error-failed-to-execute-process", "error" => err),
318+
)
319+
})?;
345320
// Wait for the child process for the specified time period.
346321
//
347-
// If the process exits within the specified time period (the
348-
// `Ok(Some(_))` arm), then return the appropriate status code.
349-
//
350-
// If the process does not exit within that time (the `Ok(None)`
351-
// arm) and `kill_after` is specified, then try sending `SIGKILL`.
352-
//
353322
// TODO The structure of this block is extremely similar to the
354323
// structure of `wait_or_kill_process()`. They can probably be
355324
// refactored into some common function.
356-
match process.wait_or_timeout(duration, Some(&SIGNALED)) {
357-
Ok(Some(status)) => Err(status
325+
match process.wait_or_timeout(duration, &mut self_pipe) {
326+
Ok(WaitOrTimeoutRet::InTime(status)) => Err(status
358327
.code()
359328
.unwrap_or_else(|| preserve_signal_info(status.signal().unwrap()))
360329
.into()),
361-
Ok(None) => {
330+
Ok(WaitOrTimeoutRet::CustomSignaled) => {
331+
report_if_verbose(signal, &cmd[0], verbose);
332+
send_signal(process, signal, foreground);
333+
process.wait()?;
334+
Err(ExitStatus::Terminated.into())
335+
}
336+
Ok(WaitOrTimeoutRet::TimedOut) => {
362337
report_if_verbose(signal, &cmd[0], verbose);
363338
send_signal(process, signal, foreground);
364339
match kill_after {
365340
None => {
366341
let status = process.wait()?;
367-
if SIGNALED.load(atomic::Ordering::Relaxed) {
368-
Err(ExitStatus::Terminated.into())
369-
} else if preserve_status {
342+
if preserve_status {
370343
if let Some(ec) = status.code() {
371344
Err(ec.into())
372345
} else if let Some(sc) = status.signal() {
@@ -386,6 +359,7 @@ fn timeout(
386359
preserve_status,
387360
foreground,
388361
verbose,
362+
&mut self_pipe,
389363
) {
390364
Ok(status) => Err(status.into()),
391365
Err(e) => Err(USimpleError::new(

src/uucore/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ nix = { workspace = true, features = [
9595
"signal",
9696
"dir",
9797
"user",
98+
"poll",
9899
] }
99100
xattr = { workspace = true, optional = true }
100101

0 commit comments

Comments
 (0)