Use "terminal driver" instead of "shell" in SIGINT docs (#13787)
Some checks are pending
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / build binary | linux libc (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / smoke test | linux (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | pyodide on ubuntu (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | free-threaded python on github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / integration test | uv_build (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | graalpy on ubuntu (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.8 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks | walltime aarch64 linux (push) Blocked by required conditions
CI / benchmarks | instrumented (push) Blocked by required conditions

Addressing the comment at
https://github.com/astral-sh/uv/issues/12108#issuecomment-2925703719
This commit is contained in:
Zanie Blue 2025-06-03 17:07:03 -05:00 committed by GitHub
parent f5c3601445
commit 3ca8d074a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 29 additions and 29 deletions

View file

@ -9,33 +9,33 @@ use crate::commands::ExitStatus;
/// long as the command is the last thing that runs in this process; otherwise, we'd need to restore
/// the default signal handlers after the command completes.
pub(crate) async fn run_to_completion(mut handle: Child) -> anyhow::Result<ExitStatus> {
// On Unix, shells will send SIGINT to the active process group when a user presses `Ctrl-C`. In
// general, this means that uv should ignore SIGINT, allowing the child process to cleanly exit
// instead. If uv forwarded the SIGINT immediately, the child process would receive _two_ SIGINT
// signals which has semantic meaning for some programs, i.e., slow exit on the first signal and
// fast exit on the second. The exception to this is if a child process changes its process
// group, in which case the shell will _not_ send SIGINT to the child process and uv must take
// ownership of forwarding the signal.
// On Unix, the terminal driver will send SIGINT to the active process group when a user presses
// `Ctrl-C`. In general, this means that uv should ignore SIGINT, allowing the child process to
// cleanly exit instead. If uv forwarded the SIGINT immediately, the child process would receive
// _two_ SIGINT signals which has semantic meaning for some programs, i.e., slow exit on the
// first signal and fast exit on the second. The exception to this is if a child process changes
// its process group, in which case the terminal driver will _not_ send SIGINT to the child
// process and uv must take ownership of forwarding the signal.
//
// Note this assumes an interactive shell. If a signal is sent directly to the uv parent process
// (e.g., `kill -2 <pid>`), the process group is not involved and a signal is not sent to the
// child by default. In this context, uv must forward the signal to the child. We work around
// this by forwarding SIGINT if it is received more than once. We could attempt to infer if the
// parent is a shell using TTY detection(?), but there hasn't been sufficient motivation to
// explore alternatives yet.
// Note this assumes an interactive terminal. If a signal is sent directly to the uv parent
// process (e.g., `kill -2 <pid>`), the process group is not involved and a signal is not sent
// to the child by default. In this context, uv must forward the signal to the child. We work
// around this by forwarding SIGINT if it is received more than once. We could attempt to infer
// if the parent is a terminal using TTY detection(?), but there hasn't been sufficient
// motivation to explore alternatives yet.
//
// Use of SIGTERM is also a bit complicated. If a shell receives a SIGTERM, it just waits for
// Use of SIGTERM is also a bit complicated. If a terminal receives a SIGTERM, it just waits for
// its children to exit — multiple SIGTERMs do not have any effect and the signals are not
// forwarded to the children. Consequently, the description for SIGINT above does not apply to
// SIGTERM in shells. It is _possible_ to have a parent process that sends a SIGTERM to the
// process group; for example, `tini` supports this via a `-g` option. In this case, it's
// possible that uv will improperly send a second SIGTERM to the child process. However,
// this seems preferable to not forwarding it in the first place. In the Docker case, if `uv`
// is invoked directly (instead of via an init system), it's PID 1 which has a special-cased
// default signal handler for SIGTERM by default. Generally, if a process receives a SIGTERM and
// does not have a SIGTERM handler, it is terminated. However, if PID 1 receives a SIGTERM, it
// is not terminated. In this context, it is essential for uv to forward the SIGTERM to the
// child process or the process will not be killable.
// SIGTERM in the terminal driver. It is _possible_ to have a parent process that sends a
// SIGTERM to the process group; for example, `tini` supports this via a `-g` option. In this
// case, it's possible that uv will improperly send a second SIGTERM to the child process.
// However, this seems preferable to not forwarding it in the first place. In the Docker case,
// if `uv` is invoked directly (instead of via an init system), it's PID 1 which has a
// special-cased default signal handler for SIGTERM by default. Generally, if a process receives
// a SIGTERM and does not have a SIGTERM handler, it is terminated. However, if PID 1 receives a
// SIGTERM, it is not terminated. In this context, it is essential for uv to forward the SIGTERM
// to the child process or the process will not be killable.
#[cfg(unix)]
let status = {
use std::ops::Deref;
@ -162,8 +162,8 @@ pub(crate) async fn run_to_completion(mut handle: Child) -> anyhow::Result<ExitS
continue;
}
// The shell may still be forwarding these signals, give the child a moment to
// handle that signal before hitting it with another one
// The terminal may still be forwarding these signals, give the child a moment
// to handle that signal before hitting it with another one
debug!("Received SIGINT, forwarding to child at {child_pid} in 200ms");
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
let _ = signal::kill(child_pid, signal::Signal::SIGINT);
@ -176,7 +176,7 @@ pub(crate) async fn run_to_completion(mut handle: Child) -> anyhow::Result<ExitS
};
// We unconditionally forward SIGTERM to the child process; unlike SIGINT, this
// isn't usually handled by the shell and in cases like
// isn't usually handled by the terminal.
debug!("Received SIGTERM, forwarding to child at {child_pid}");
let _ = signal::kill(child_pid, signal::Signal::SIGTERM);
}

View file

@ -91,9 +91,9 @@ uv does not cede control of the process to the spawned command in order to provi
messages on failure. Consequently, uv is responsible for forwarding some signals to the child
process the requested command runs in.
On Unix systems, uv will forward SIGINT and SIGTERM to the child process. Since shells send SIGINT
to the foreground process group on Ctrl-C, uv will only forward a SIGINT to the child process if it
is seen more than once or the child process group differs from uv's.
On Unix systems, uv will forward SIGINT and SIGTERM to the child process. Since terminals send
SIGINT to the foreground process group on Ctrl-C, uv will only forward a SIGINT to the child process
if it is sent more than once or the child process group differs from uv's.
On Windows, these concepts do not apply and uv ignores Ctrl-C events, deferring handling to the
child process so it can exit cleanly.