Ensure uv.exe exits when uvw.exe or uvx.exe is killed#17500
Ensure uv.exe exits when uvw.exe or uvx.exe is killed#17500zanieb merged 5 commits intoastral-sh:mainfrom
uv.exe exits when uvw.exe or uvx.exe is killed#17500Conversation
c89872b to
f1ebfb9
Compare
uv.exe exits when uvw.exe is killeduv.exe exits when uvw.exe or uvx.exe is killed
|
Terrifying code. Will need to review this closely. |
934469f to
e37fe6d
Compare
aa418af to
c29d707
Compare
|
This relates to #11817 (can also close) |
|
Ah thanks I had forgotten about that discussion. |
08c1d52 to
5d4c5f6
Compare
On Windows, use Job Objects to ensure the child `uv.exe` process is terminated when wrapper processes (`uvw.exe`, `uvx.exe`) are killed. This is important for tools like Task Scheduler that terminate the wrapper process. The fix uses the `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` flag which instructs Windows to terminate all processes associated with the job when the job object handle is closed (which happens when the wrapper is terminated). This change: - Creates a shared `windows_spawn` module with the Job Object logic - Updates both `uvw.exe` and `uvx.exe` to use this shared module - Matches the approach used by the trampoline in `bounce.rs` Closes astral-sh#17492
683933f to
f920c85
Compare
f920c85 to
073f7b0
Compare
EliteTK
left a comment
There was a problem hiding this comment.
Looks good, but note the handle leak in spawn_child. That's the only real issue, the rest can wait.
| info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; | ||
| info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK; |
There was a problem hiding this comment.
If we're only writing to the basic information field why are we querying the extended information?
Looks like this is a carry-over from the previous code... Might be a problem for another PR.
| /// to receive and handle the control signal, not the wrapper. Returning `TRUE` | ||
| /// from the handler tells Windows we've handled the signal (by ignoring it). | ||
| /// | ||
| /// See: `distlib/PC/launcher.c::control_key_handler` |
There was a problem hiding this comment.
The actual logic in the referenced code is somewhat more sophisticated.
Specifically it handles CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, and CTRL_SHUTDOWN_EVENT specially by disabling JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. The reason here is, as I understand it, ignoring these events doesn't stop windows from killing your process, and these events are also going to go to the child anyway. Disabling KILL_ON_JOB_CLOSE gives the child a chance to more gracefully handle these.
There was a problem hiding this comment.
Although, again, this looks like it was taken from the previous code, so maybe not relevant here.
crates/uv-windows/src/spawn.rs
Outdated
| // Explicitly drop child to close the process handle before exiting. | ||
| drop(child); |
There was a problem hiding this comment.
If we do this we might want to drop the job first too.
There was a problem hiding this comment.
It's unclear to me if we need to do either drops manually? 🤔
There was a problem hiding this comment.
I don't believe we need to drop child manually at all.
But I did think about this a bit further and I think there's another subtle problem. Job might get dropped immediately after we assign the process as we never use it again.
I think dropping job here explicitly might guarantee it lives long enough... There are also some other options.
But, I think really what we should do here is something like:
struct SupervisedChild<'a> {
job: HANDLE,
child: HANDLE,
_child_lifetime: PhantomData<&'a ()>,
}
impl<'a> SupervisedChild<'a> {
fn new<T>(child: HANDLE, _child_reference: &'a T) -> Self {
let job = unsafe { CreateJobObjectW(None, None) }.map_err(|e| JobError::Create(e.code().0))?;
// Configure limits
// Assign the child
// Maybe even set up the control handler given both code paths do it?
Ok(Self { job, child, PhantomData::default() })
}
fn wait(self) -> Result<whatever> {
WaitForSingleObject ...
}
}
The PhantomData would ensure that the child outlives the JobSupervisedChild. And moving the wait here would ensure that we don't drop the SupervisedChild too early.
For the trampoline case we might just want to have a tiny wrapper around HANDLE that drops it at the right time and lets you access the HANDLE, then we can use a reference to the wrapper for the lifetime handling.
There was a problem hiding this comment.
We could even go a step further and have the ChildHandle wrapper be in the same place as SupervisedChild, give it a From implementation for the std case, and then you can just give SupervisedChild the ChildHandle reference directly, let it keep it, and avoid the PhantomData.
One last alternative (probably too much boilerplate for two code paths): we could make SupervisedChild generic over the type, have our ChildHandle wrapper for the trampoline case, have it implement our own AsRawHandle trait, and then implement it for Child in std contexts...
There was a problem hiding this comment.
There was a problem hiding this comment.
spawn.rs is std only right now.
There was a problem hiding this comment.
We may want to fix this in the trampolines too but maybe I'll just do that in a separate change? It seems like it'd be a pre-existing problem?
I guess in the worst-case we just vendor the std impl?
There was a problem hiding this comment.
The existing code (in the trampolines) didn't have this problem because it didn't have a Drop impl on the Job handle so it couldn't automatically get cleaned up too early.
There was a problem hiding this comment.
Ah, I see. Will look at that briefly then.
There was a problem hiding this comment.
Looks like I was mistaken, rust doesn't allow drop to be called earlier than the end of the scope.
628b493 to
15be283
Compare
This MR contains the following updates: | Package | Update | Change | |---|---|---| | [astral-sh/uv](https://github.com/astral-sh/uv) | patch | `0.9.27` → `0.9.28` | MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot). **Proposed changes to behavior should be submitted there as MRs.** --- ### Release Notes <details> <summary>astral-sh/uv (astral-sh/uv)</summary> ### [`v0.9.28`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0928) [Compare Source](astral-sh/uv@0.9.27...0.9.28) Released on 2026-01-29. ##### Python - Update CPython to use [OpenSSL 3.5.5](https://github.com/openssl/openssl/releases/tag/openssl-3.5.5) which includes fixes for high severity CVEs ([python-build-standalone#960](astral-sh/python-build-standalone#960)) ##### Enhancements - Add support for Pyodide interpreter on Windows ([#​17658](astral-sh/uv#17658)) - Warn if multiple indexes include `default = true` ([#​17713](astral-sh/uv#17713)) - Skip uploads when validation reports 'Already uploaded' ([#​17412](astral-sh/uv#17412)) ##### Configuration - Add a reflink alias for the "clone" link mode ([#​17724](astral-sh/uv#17724)) ##### Bug fixes - Ensure `uv.exe` exits when `uvw.exe` or `uvx.exe` is killed ([#​17500](astral-sh/uv#17500)) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this MR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box --- This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi45NC42IiwidXBkYXRlZEluVmVyIjoiNDIuOTQuNiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiUmVub3ZhdGUgQm90IiwiYXV0b21hdGlvbjpib3QtYXV0aG9yZWQiLCJkZXBlbmRlbmN5LXR5cGU6OnBhdGNoIl19-->
This matches the approach used by the trampoline in
bounce.rsso we combine the implementations in a newuv-windowscrate.Closes #17492
Closes #11817