You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Stop pip updates from corrupting the Python environment - #5489
Fixes the two UniGetUI behaviours behind the broken dist-info and duplicated packages in #5488. Both were reproduced against real pip 26.2.1, the reporter's version, using a pair of local test packages (a client that depends on a core).
1. Concurrent pip processes
UniGetUI could run several pip operations at once against the same site-packages. Updating a package and one of its dependencies at the same time races on the shared dependency.
Without the fix: 1 run in 3 broke the environment with [WinError 2] and left the client on its old version.
With the fix: 9 runs in 9 finished clean (both packages updated, a single copy, pip check passes).
Managers can now declare SerializesOperations. Pip does, so its local operations run one at a time and show "Waiting for another Pip operation to finish..." while they wait. An operation that is waiting does not count toward the parallel-operation limit, so other managers' operations are not held up behind a backlog of Pip ones. Cancelling while waiting ends the operation as cancelled, with no stack trace.
2. The automatic --user retry after a locked file
When a package file is in use, pip fails partway through the uninstall with [WinError 32] and does not roll back. It leaves a ~-prefixed stash folder plus a half-removed copy, then suggests --user. UniGetUI followed that suggestion and retried in the user folder, installing a second copy next to the half-removed one. That is the duplicate state in the issue.
The --user retry is no longer taken for updates, or for any operation that fails with a file-in-use error. A first install refused with "access denied" ([WinError 5]) still retries in the user folder, because there is no existing copy to duplicate.
A file-in-use failure now tells the user: "A file of {package} is in use by another program. Close any program that may be using it, then try again". Retrying the normal update after the lock is gone repaired the package in place in testing.
On the exact pip output captured from the locked-file run, the old code chose the retry and the new code reports a failure.
Not covered
Already-damaged environments are not repaired. The reporter's install needs manual cleanup.
No per-file integrity check. The reporter asked for one; this PR does not add it.
Broker operations are not gated. Operations routed through the Devolutions Agent broker don't use the new lock.
I don't know what locked the reporter's files. This fix doesn't depend on which program it was.
Notes for reviewers
The file-in-use explanation lives in the shared PackageOperation and matches on Python's [WinError 32] text, since the Operations project doesn't reference the Pip project.
The gate tests start a real ping process to keep an operation busy. They ignore its exit code: on ARM64, ping occasionally crashes with 0xC000001D when test processes run side by side, and these tests only check ordering.
Both dotnet format checks (whitespace and style) pass.
UniGetUI.PackageEngine.Tests pass in full on the Windows target. On net10.0, the only failure is an unrelated NuGetV3ManagerTests HTTP-listener flake.
This new wait state is only emitted as a progress log line. OperationViewModel copies it to LiveLine, but the operation card exposes that value only as AutomationProperties.HelpText (MainWindow.axaml:403-405), not a live region, so screen-reader users are not notified that execution has changed from running to waiting. Announce this transition once via AccessibilityAnnouncementService with Polite, consistent with operation progress/success announcements in AvaloniaOperationRegistry.cs:219-237.
Avoid logging stack traces for raced operation cancellation
Cancellation can race with acquiring the gate: WaitAsync may complete successfully just as the token is canceled, and base.PerformOperation() then throws at its pre-start cancellation check. _runOperation converts that exception to Canceled but also logs its stack trace, so the promised stack-trace-free cancellation is not guaranteed. Handle cancellation around the process call while still releasing the acquired gate.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the two UniGetUI behaviours behind the broken
dist-infoand duplicated packages in #5488. Both were reproduced against real pip 26.2.1, the reporter's version, using a pair of local test packages (a client that depends on a core).1. Concurrent pip processes
UniGetUI could run several pip operations at once against the same
site-packages. Updating a package and one of its dependencies at the same time races on the shared dependency.[WinError 2]and left the client on its old version.pip checkpasses).Managers can now declare
SerializesOperations. Pip does, so its local operations run one at a time and show "Waiting for another Pip operation to finish..." while they wait. An operation that is waiting does not count toward the parallel-operation limit, so other managers' operations are not held up behind a backlog of Pip ones. Cancelling while waiting ends the operation as cancelled, with no stack trace.2. The automatic
--userretry after a locked fileWhen a package file is in use, pip fails partway through the uninstall with
[WinError 32]and does not roll back. It leaves a~-prefixed stash folder plus a half-removed copy, then suggests--user. UniGetUI followed that suggestion and retried in the user folder, installing a second copy next to the half-removed one. That is the duplicate state in the issue.--userretry is no longer taken for updates, or for any operation that fails with a file-in-use error. A first install refused with "access denied" ([WinError 5]) still retries in the user folder, because there is no existing copy to duplicate.Not covered
Notes for reviewers
PackageOperationand matches on Python's[WinError 32]text, since the Operations project doesn't reference the Pip project.pingprocess to keep an operation busy. They ignore its exit code: on ARM64,pingoccasionally crashes with0xC000001Dwhen test processes run side by side, and these tests only check ordering.dotnet formatchecks (whitespace and style) pass.UniGetUI.PackageEngine.Testspass in full on the Windows target. Onnet10.0, the only failure is an unrelatedNuGetV3ManagerTestsHTTP-listener flake.fix #5488