-
Notifications
You must be signed in to change notification settings - Fork 872
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handle Windows AV/EDR file locks during script installations #9543
Handle Windows AV/EDR file locks during script installations #9543
Conversation
796532b
to
b865526
Compare
b865526
to
1fd66e6
Compare
(Currently developping on a Windows virtual machine, have to admit I lost a lot of productivity 😆That's why I will stick to a draft for now and abuse of the GitHub CI, not enough cpu on my side unfortunately) |
Hi @charliermarsh, I wanted to reuse an existing function, but which is already async, instead of writing a duplicate sync one. Is it a good idea? The install wheel crate is synchronous. The goal is to re-use Am I currently on the good way by trying to run it with a tokio runtime? Thanks! |
756faa1
to
f705dd6
Compare
So for now, I made a sync version of |
f705dd6
to
38ea29e
Compare
crates/uv-install-wheel/src/wheel.rs
Outdated
) | ||
})?; | ||
|
||
rename_with_retry_sync(&target, &script_absolute)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My only concern is that this is now using rename rather than persist
. And on Windows, it looks like persist is a bit different, in tempfile
?
pub fn persist(old_path: &Path, new_path: &Path, overwrite: bool) -> io::Result<()> {
unsafe {
let old_path_w = to_utf16(old_path);
let new_path_w = to_utf16(new_path);
// Don't succeed if this fails. We don't want to claim to have successfully persisted a file
// still marked as temporary because this file won't have the same consistency guarantees.
if SetFileAttributesW(old_path_w.as_ptr(), FILE_ATTRIBUTE_NORMAL) == 0 {
return Err(io::Error::last_os_error());
}
let mut flags = 0;
if overwrite {
flags |= MOVEFILE_REPLACE_EXISTING;
}
if MoveFileExW(old_path_w.as_ptr(), new_path_w.as_ptr(), flags) == 0 {
let e = io::Error::last_os_error();
// If this fails, the temporary file is now un-hidden and no longer marked temporary
// (slightly less efficient) but it will still work.
let _ = SetFileAttributesW(old_path_w.as_ptr(), FILE_ATTRIBUTE_TEMPORARY);
Err(e)
} else {
Ok(())
}
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right! I will try to see if I can still use the persist
function.
Just currently fighting with the borrow-checker, as persist
takes self
and re-returns it in case of PersistError
,
error[E0507]: cannot move out of `*from`, as `from` is a captured variable in an `FnMut` closure
--> crates\uv-fs\src\lib.rs:277:42
|
262 | from: &NamedTempFile,
| ---- captured outer variable
...
277 | backoff::retry(backoff, || match from.persist(to) {
| -- ^^^^ ----------- `*from` moved due to this method call
| | |
| | move occurs because `*from` has type `NamedTempFile`, which does not implement the `Copy` trait
| captured by this `FnMut` closure
|
note: `NamedTempFile::<F>::persist` takes ownership of the receiver `self`, which moves `*from`
--> C:\Users\Jp\.cargo\registry\src\index.crates.io-6f17d22bba15001f\tempfile-3.14.0\src\file\mod.rs:722:36
|
722 | pub fn persist<P: AsRef<Path>>(self, new_path: P) -> Result<F, PersistError<F>> {
It's a good rust exercise 😄 I will ping you when I solve it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@charliermarsh Updated! Looking forward for your opinion, the signature and the behavior of persist
was not easy at the beggining to put inside a backoff::retry
closure,
pub fn persist<P: AsRef<Path>>(self, new_path: P) -> Result<F, PersistError<F>>
Persist the temporary file at the target path.
If a file exists at the target path, persist will atomically replace it. If this method fails, it will return self in the resulting PersistError.
I got bullied a lot by the borrow checker, but I finally managed to do it 😄
So I found a way to get back the reference returned by the error for every retry.
Looking forward for your opinion on this!
38ea29e
to
0226580
Compare
0226580
to
6e9dd4d
Compare
6e9dd4d
to
f8faabb
Compare
f8faabb
to
f8c9bec
Compare
f8c9bec
to
ac02c95
Compare
b936cb1
to
fa2eec7
Compare
fa2eec7
to
ab0b6c9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great, thank you for persisting :)
Thanks for the fast review and for making this such a welcoming project for contributors! 🙌 |
This MR contains the following updates: | Package | Update | Change | |---|---|---| | [astral-sh/uv](https://github.com/astral-sh/uv) | patch | `0.5.5` -> `0.5.6` | 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.5.6`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#056) [Compare Source](astral-sh/uv@0.5.5...0.5.6) ##### Enhancements - Add `--dry-run` to `uv pip uninstall` ([#​9557](astral-sh/uv#9557)) - Allow `--constraints` and `--overrides` in `uv tool install` ([#​9547](astral-sh/uv#9547)) - Display removed Python executables on uninstall ([#​9459](astral-sh/uv#9459)) - Warn when keyring has no password for `uv publish` ([#​8827](astral-sh/uv#8827)) - Add suggested action when `.python-version` pin is incompatible with the project ([#​9590](astral-sh/uv#9590)) - Improve error messages for mismatches in `tool.uv.sources` ([#​9482](astral-sh/uv#9482)) - Use constraints in trace rather than irrelevant `requires-python` ([#​9529](astral-sh/uv#9529)) ##### Preview features - Add `uv python install --default` ([#​8650](astral-sh/uv#8650)) - Fix Python executable installation when multiple patch versions are requested ([#​9607](astral-sh/uv#9607)) - Build backend: Revamp `include` / `exclude` ([#​9525](astral-sh/uv#9525)) - Build backend: Add fast path ([#​9556](astral-sh/uv#9556)) - Build backend: Add functions to collect file list ([#​9602](astral-sh/uv#9602)) - Build backend: Default excludes ([#​9552](astral-sh/uv#9552)) - Build backend: Refactoring before list ([#​9558](astral-sh/uv#9558)) - Build backend: Warn when visiting over 10k files ([#​9523](astral-sh/uv#9523)) ##### Configuration - Make `check-url` available in configuration files ([#​9032](astral-sh/uv#9032)) ##### Performance - Avoid adding non-extra package with extra dependencies ([#​9540](astral-sh/uv#9540)) - Avoid cloning `String` in marker evaluation ([#​9598](astral-sh/uv#9598)) ##### Rust API - `uv-pep508`: Add more methods for simplifying `extra`-related expressions ([#​9469](astral-sh/uv#9469)) ##### Bug fixes - Allow `file:` URLs to include package names ([#​9493](astral-sh/uv#9493)) - Avoid using IDs across PubGrub states ([#​9538](astral-sh/uv#9538)) - Consistently enforce requested-vs.-built metadata when retrieving wheels ([#​9484](astral-sh/uv#9484)) - Do not show empty version specifier in `uv tool list` ([#​9605](astral-sh/uv#9605)) - Include Git member information when getting metadata from cache ([#​9388](astral-sh/uv#9388)) - Include base installation directory in uv run PATH ([#​9585](astral-sh/uv#9585)) - Insert backslash when appending to system drive ([#​9488](astral-sh/uv#9488)) - Normalize paths when lowering Git dependencies ([#​9595](astral-sh/uv#9595)) - Omit origin when comparing requirements ([#​9570](astral-sh/uv#9570)) - Override `manylinux_compatible` with `--python-platform` ([#​9526](astral-sh/uv#9526)) - Pass extra when evaluating lockfile markers ([#​9539](astral-sh/uv#9539)) - Propagate markers for recursive extras in resolver ([#​9509](astral-sh/uv#9509)) - Respect path dependencies within Git dependencies ([#​9594](astral-sh/uv#9594)) - Support recursive extras with marker in `pip compile -r pyproject.toml` ([#​9535](astral-sh/uv#9535)) - Don't emit unpinned warning for proxy packages ([#​9497](astral-sh/uv#9497)) - Fix `--refresh-package` flag mentioned as `--refresh-dependency` ([#​9486](astral-sh/uv#9486)) - Handle Windows AV/EDR file locks during script installations ([#​9543](astral-sh/uv#9543)) - Re-enable conflicting extra/group tests and fix regression from [#​9540](astral-sh/uv#9540) ([#​9582](astral-sh/uv#9582)) ##### Documentation - Add missing word to docs for `run.md` ([#​9527](astral-sh/uv#9527)) - Add policies reference section and license document ([#​9367](astral-sh/uv#9367)) - Fix typo in entry point docs ([#​9491](astral-sh/uv#9491)) - Fix up version in prior uninstall instructions ([#​9485](astral-sh/uv#9485)) - Mention `uv pip` behavior in build system note ([#​9586](astral-sh/uv#9586)) - Update build failures document ([#​9584](astral-sh/uv#9584)) - Correct wording for multiple sources section ([#​9504](astral-sh/uv#9504)) </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:eyJjcmVhdGVkSW5WZXIiOiIzNy40NDAuNyIsInVwZGF0ZWRJblZlciI6IjM3LjQ0MC43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiXX0=-->
Fixes #9531
Context
While working with uv, I encountered issues with a python dependency, httpx unable to be installed because of a os error 5 permission denied.
The error occur when we try to persist a .exe file from a temporary folder into a persistent one.
I only reproduce the issue in an enterprise Windows Jenkins Runner. In my virtual machines, I don't have any issues. So I think this is most probably coming from the system configuration. This windows runner contains an AV/EDR. And the fact that the file locked occured only once for an executable make me think that it's most probably the cause.
While doing some research and speaking with some colleagues (hi @vmeurisse), it seems that the issue is a very recurrent one on Windows.
In the Javascript ecosystem, there is this package, created by the @isaacs,
npm
inventor: https://www.npmjs.com/package/graceful-fs, used insidenpm
, allowing its package installations to be more resilient to filesystem errors:So I tried to implement the same algorithm on
uv
, and it fixed my issue! I can finally installhttpx
.Then, as I mentionned in this issue, I saw that you already implemented exactly the same algorithm in an asynchronous function for renames 😄
uv/crates/uv-fs/src/lib.rs
Line 221 in 22fd9f7
Summary of changes
persist
(was not easy to have the benediction of the borrow checker 😄)sync
variant ofrename_with_retry
install_script
to use the function including retries on WindowsLet me know if I should change anything 🙂
Thanks!!
Test Plan
This pull-request should be totally iso-functional, so I think it should be covered by existing tests in case of regression.
All tests are still passing on my side.
Also, of course validated that my windows machines (windows 10 & windows 11) containing AV/EDR software are now able to install
httpx.exe
script.However, if you think any additional test is needed, feel free to tell me!