@@ -59,6 +59,35 @@ public class WinGet : PackageManager
5959 internal WinGetCliToolKind SelectedCliToolKind { get ; private set ; } =
6060 WinGetCliToolKind . SystemWinGet ;
6161
62+ // winget's local index isn't safe under concurrent process access: a `source update` (writer)
63+ // running alongside list/upgrade/search (readers) yields partial or empty results. The COM
64+ // backend serialized this implicitly; the CLI backends (winget.exe / pinget.exe) must do it
65+ // explicitly. Reentrant (Monitor) so same-thread nested invocations don't self-deadlock.
66+ private static readonly object _cliInvocationLock = new ( ) ;
67+
68+ // Safety valve: never wait on the CLI lock longer than this, so a hung process can't freeze all WinGet queries.
69+ private static readonly TimeSpan CliLockTimeout = TimeSpan . FromSeconds ( 120 ) ;
70+
71+ internal static IDisposable AcquireCliLock ( )
72+ {
73+ bool taken = false ;
74+ Monitor . TryEnter ( _cliInvocationLock , CliLockTimeout , ref taken ) ;
75+ if ( ! taken )
76+ Logger . Warn ( "WinGet CLI lock not acquired within timeout; proceeding unserialized to avoid a hang." ) ;
77+ return new CliLockReleaser ( taken ) ;
78+ }
79+
80+ private sealed class CliLockReleaser ( bool taken ) : IDisposable
81+ {
82+ private bool _released ;
83+ public void Dispose ( )
84+ {
85+ if ( _released || ! taken ) return ;
86+ _released = true ;
87+ Monitor . Exit ( _cliInvocationLock ) ;
88+ }
89+ }
90+
6291 public WinGet ( )
6392 {
6493 Capabilities = new ManagerCapabilities
@@ -719,6 +748,7 @@ private static void TryRepairTempFolderPermissions()
719748
720749 public override void RefreshPackageIndexes ( )
721750 {
751+ using var _cliLock = AcquireCliLock ( ) ;
722752 using Process p = new ( )
723753 {
724754 StartInfo = new ProcessStartInfo
0 commit comments