Skip to content

Commit c4ee2f1

Browse files
authored
Stop pip updates from corrupting the Python environment (#5489)
1 parent 9bfbe4b commit c4ee2f1

8 files changed

Lines changed: 468 additions & 8 deletions

File tree

‎src/Languages/lang_en.json‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1842,5 +1842,7 @@
18421842
"GitHub returned an empty access token.": "GitHub returned an empty access token.",
18431843
"GitHub sign-in failed ({0}): {1}": "GitHub sign-in failed ({0}): {1}",
18441844
"The GitHub sign-in code has expired. Please try again.": "The GitHub sign-in code has expired. Please try again.",
1845-
"WinGet repair exited with code {0}.": "WinGet repair exited with code {0}."
1845+
"WinGet repair exited with code {0}.": "WinGet repair exited with code {0}.",
1846+
"Waiting for another {0} operation to finish...": "Waiting for another {0} operation to finish...",
1847+
"A file of {package} is in use by another program. Close any program that may be using it, then try again": "A file of {package} is in use by another program. Close any program that may be using it, then try again"
18461848
}

‎src/UniGetUI.PackageEngine.Enums/ManagerCapabilities.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public struct ManagerCapabilities
4242
public bool SupportsCustomScopesOnUpdate = true;
4343
public bool SupportsCustomScopesOnUninstall = true;
4444
public bool SupportsPreRelease = false;
45+
public bool SerializesOperations = false;
4546
public bool SupportsCustomLocations = false;
4647
public bool SupportsCustomSources = false;
4748
public bool SupportsCustomPackageIcons = false;

‎src/UniGetUI.PackageEngine.Managers.Pip/Helpers/PipPkgOperationHelper.cs‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,22 @@ int returnCode
9494
return OperationVeredict.AutoRetry;
9595
}
9696

97-
if (package.OverridenOptions.Scope != PackageScope.User)
97+
if (
98+
operation is not OperationType.Update
99+
&& package.OverridenOptions.Scope != PackageScope.User
100+
)
98101
{
99102
package.OverridenOptions.Scope = PackageScope.User;
100103
return OperationVeredict.AutoRetry;
101104
}
102105
}
103106

104-
if (output_string.Contains("--user") && package.OverridenOptions.Scope != PackageScope.User)
107+
if (
108+
operation is not OperationType.Update
109+
&& output_string.Contains("--user")
110+
&& !output_string.Contains("[WinError 32]")
111+
&& package.OverridenOptions.Scope != PackageScope.User
112+
)
105113
{
106114
package.OverridenOptions.Scope = PackageScope.User;
107115
return OperationVeredict.AutoRetry;

‎src/UniGetUI.PackageEngine.Managers.Pip/Pip.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ public Pip()
6464
SupportsCustomScopes = true,
6565
CanDownloadInstaller = true,
6666
SupportsPreRelease = true,
67+
SerializesOperations = true,
6768
CanListDependencies = true,
6869
SupportsProxy = ProxySupport.Yes,
6970
SupportsProxyAuth = true,

‎src/UniGetUI.PackageEngine.Operations/AbstractOperation.cs‎

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -319,15 +319,14 @@ private async Task MainThreadCore(CancellationTokenSource runCancellation)
319319

320320
while (
321321
FORCE_HOLD_QUEUE
322-
|| (IndexInQueue(this) >= MAX_OPERATIONS && !SKIP_QUEUE)
322+
|| (SlotsTakenAhead(this) >= MAX_OPERATIONS && !SKIP_QUEUE)
323323
)
324324
{
325-
int pos = IndexInQueue(this) - MAX_OPERATIONS + 1;
326-
327-
if (pos == -1)
325+
if (IndexInQueue(this) - MAX_OPERATIONS + 1 == -1)
328326
return;
329327
// In this case, operation was canceled;
330328

329+
int pos = SlotsTakenAhead(this) - MAX_OPERATIONS + 1;
331330
if (pos != lastPos)
332331
{
333332
lastPos = pos;
@@ -646,6 +645,31 @@ public static int IndexInQueue(AbstractOperation operation)
646645
return OperationQueue.IndexOf(operation);
647646
}
648647

648+
internal bool IsWaitingForManager
649+
{
650+
get => _isWaitingForManager;
651+
set => _isWaitingForManager = value;
652+
}
653+
private volatile bool _isWaitingForManager;
654+
655+
private static int SlotsTakenAhead(AbstractOperation operation)
656+
{
657+
lock (QueueLock)
658+
{
659+
int index = OperationQueue.IndexOf(operation);
660+
if (index < 0)
661+
return index;
662+
663+
int taken = 0;
664+
for (int i = 0; i < index; i++)
665+
{
666+
if (!OperationQueue[i].IsWaitingForManager)
667+
taken++;
668+
}
669+
return taken;
670+
}
671+
}
672+
649673
public static int QueueLength()
650674
{
651675
lock (QueueLock)

‎src/UniGetUI.PackageEngine.Operations/PackageOperations.cs‎

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections.Concurrent;
12
using System.Diagnostics;
23
using System.Text;
34
using UniGetUI.Core.Classes;
@@ -344,12 +345,53 @@ protected override async Task<OperationVeredict> PerformOperation()
344345

345346
if (!ShouldUseAgentBroker())
346347
{
347-
return await base.PerformOperation();
348+
if (!Package.Manager.Capabilities.SerializesOperations)
349+
return await base.PerformOperation();
350+
351+
SemaphoreSlim gate = ManagerOperationGates.GetOrAdd(
352+
Package.Manager.Name,
353+
_ => new SemaphoreSlim(1, 1)
354+
);
355+
if (!await gate.WaitAsync(0))
356+
{
357+
Line(
358+
CoreTools.Translate(
359+
"Waiting for another {0} operation to finish...",
360+
Package.Manager.DisplayName
361+
),
362+
LineType.ProgressIndicator
363+
);
364+
IsWaitingForManager = true;
365+
try
366+
{
367+
await gate.WaitAsync(CancellationToken);
368+
}
369+
catch (OperationCanceledException)
370+
{
371+
return OperationVeredict.Canceled;
372+
}
373+
finally
374+
{
375+
IsWaitingForManager = false;
376+
}
377+
}
378+
379+
try
380+
{
381+
return await base.PerformOperation();
382+
}
383+
finally
384+
{
385+
gate.Release();
386+
}
348387
}
349388

350389
return await PerformBrokerOperation();
351390
}
352391

392+
private static readonly ConcurrentDictionary<string, SemaphoreSlim> ManagerOperationGates =
393+
new();
394+
353395
/// <summary>
354396
/// Determines whether this operation should be routed through the agent broker.
355397
/// </summary>
@@ -1058,6 +1100,7 @@ List<string> Output
10581100
ExplainNotApplicableUpdate(Output, ReturnCode);
10591101
ExplainInstallerHashMismatch(ReturnCode);
10601102
ExplainApplicationCurrentlyRunning(Output, ReturnCode);
1103+
ExplainFileInUse(Output);
10611104
}
10621105

10631106
return Task.FromResult(veredict);
@@ -1197,6 +1240,17 @@ private void ExplainApplicationCurrentlyRunning(List<string> output, int returnC
11971240
#endif
11981241
}
11991242

1243+
private void ExplainFileInUse(List<string> output)
1244+
{
1245+
if (!output.Any(line => line.Contains("[WinError 32]", StringComparison.Ordinal)))
1246+
return;
1247+
1248+
Metadata.FailureMessage = CoreTools.Translate(
1249+
"A file of {package} is in use by another program. Close any program that may be using it, then try again",
1250+
new Dictionary<string, object?> { { "package", Package.Name } }
1251+
);
1252+
}
1253+
12001254
private void ExplainNotApplicableUpdate(List<string> output, int returnCode)
12011255
{
12021256
#if WINDOWS

0 commit comments

Comments
 (0)