Skip to content

Conversation

@ChrisDryden
Copy link
Collaborator

@ChrisDryden ChrisDryden commented Jan 15, 2026

There was quite a bunch of different features missing in timeout, to start off, now that we have a mechanism to read the SIGPIPE handlers before they are overwritten by the rust runtime, it means that we can not propagate this signal down to the child processes if the signal is set to ignore.

This also includes all of the latest changes since 9.9 where the specific signal sent to timeout will be propagated instead of just defaulting to a TERM signal. For the timeout GNU integration tests to pass I also had to enable to pipe signal handlers to the yes utility since the test relied on that utility outputting the correct error code when ignoring the the pipe signal.

@ChrisDryden ChrisDryden force-pushed the timeout-fixes-squashed branch from 93ea128 to 228824e Compare January 15, 2026 02:10
@github-actions
Copy link

GNU testsuite comparison:

Skip an intermittent issue tests/misc/tee (fails in this run but passes in the 'main' branch)
Skip an intermittent issue tests/shuf/shuf-reservoir (fails in this run but passes in the 'main' branch)
Skip an intermittent issue tests/sort/sort-stale-thread-mem (fails in this run but passes in the 'main' branch)
Skipping an intermittent issue tests/timeout/timeout (passes in this run but fails in the 'main' branch)
Congrats! The gnu test tests/timeout/timeout-group is now passing!

@github-actions
Copy link

GNU testsuite comparison:

Skip an intermittent issue tests/misc/tee (fails in this run but passes in the 'main' branch)
Skipping an intermittent issue tests/timeout/timeout (passes in this run but fails in the 'main' branch)
Congrats! The gnu test tests/timeout/timeout-group is now passing!

@ChrisDryden
Copy link
Collaborator Author

ChrisDryden commented Jan 15, 2026

This might seem like a fairly large PR, but its actually just added ontop of this PR: #10166 and this PR: #10194, when those are in this would be much smaller

@ChrisDryden
Copy link
Collaborator Author

Another note is that we have a bunch of over-rides in the build-gnu script that override the yes and timeout command that we can remove once this is removed.

@Ecordonnier Ecordonnier self-assigned this Jan 15, 2026
@codspeed-hq
Copy link

codspeed-hq bot commented Jan 15, 2026

CodSpeed Performance Report

Merging this PR will improve performance by 4.01%

Comparing ChrisDryden:timeout-fixes-squashed (90318a5) with main (5fd3459)

Summary

⚡ 1 improved benchmark
✅ 281 untouched benchmarks
⏩ 38 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Memory numfmt_large_numbers_si[10000] 4.9 MB 4.7 MB +4.01%

Footnotes

  1. 38 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
#[cfg(unix)]
if !uucore::signals::sigpipe_was_ignored() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like I wrote in #10265 I think we're going to need this almost for all utilities. We can merge this for now, but the call to enable_pipe_errors() will probably need to be removed from yes.rs and moved to a central place.

return;
}
let _ = process.send_signal_group(signal);
let kill_sig = signal_by_name_or_value("KILL").unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: The variable renames (kill_signal -> kill_sig, continued_signal -> cont_sig) add diff noise without
functional benefit. Consider keeping original names to make the actual changes clearer. Not blocking though.

let kill_sig = signal_by_name_or_value("KILL").unwrap();
let cont_sig = signal_by_name_or_value("CONT").unwrap();
if signal != kill_sig && signal != cont_sig {
let _ = process.send_signal(cont_sig);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for reference, because it took me a while to understand: this extra call to send_signal(cont_sig) makes sense to handle the corner-case where the child process has created its own process-group using setpgid().
When that's not the case, the child process gets two SIGCONT, and the second one is harmless.

static RECEIVED_SIGNAL: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);

/// Install signal handlers for termination signals.
fn install_signal_handlers(term_signal: usize) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: this replaces catch_sigterm() which was too limited and wasn't handling e.g. SIGINT

Signal::SIGUSR1,
Signal::SIGUSR2,
] {
if sig != Signal::SIGPIPE || !sigpipe_ignored {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion to avoid the double negative and improve readability:

if sig == Signal::SIGPIPE && sigpipe_ignored {
    continue; // Skip SIGPIPE if it was ignored by parent
}
let _ = unsafe { nix::sys::signal::signal(sig, handler) };

Signal::SIGTERM,
Signal::SIGPIPE,
Signal::SIGUSR1,
Signal::SIGUSR2,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why only those signals? why not SIGILL, SIGTRAP, SIGABRT, SIGBUS,SIGSEGV, etc.? and why not check if those signals are set to ignored by the parent process?
Is it because this logic will be extended later?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants