Skip to content

Commit 4e70fb6

Browse files
committed
Fix mutex
1 parent 315dc39 commit 4e70fb6

File tree

1 file changed

+77
-58
lines changed

1 file changed

+77
-58
lines changed

src/TALXIS.CLI.Workspace/TemplateEngine/TemplatePackageService.cs

Lines changed: 77 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -63,85 +63,89 @@ private async Task EnsureTemplatePackageInstalledWithCrossProcessLockAsync(strin
6363
var mutexName = CreateCrossProcessMutexName(_templatePackageName);
6464

6565
using var mutex = new Mutex(false, mutexName, out var createdNew);
66+
var mutexAcquired = false;
67+
6668
try
6769
{
6870
// Wait for the mutex with a reasonable timeout to prevent hanging tests
69-
var acquired = mutex.WaitOne(TimeSpan.FromMinutes(5));
70-
if (!acquired)
71+
mutexAcquired = mutex.WaitOne(TimeSpan.FromMinutes(5));
72+
if (!mutexAcquired)
7173
{
7274
throw new TimeoutException($"Timeout waiting for cross-process lock to install template package '{_templatePackageName}'");
7375
}
7476

75-
try
77+
// First check if the package is already installed globally (cross-process safety)
78+
var existingPackages = await _templatePackageManager.GetManagedTemplatePackagesAsync(false, CancellationToken.None);
79+
var existingPackage = existingPackages.FirstOrDefault(p =>
80+
string.Equals(p.Identifier, _templatePackageName, StringComparison.OrdinalIgnoreCase));
81+
82+
if (existingPackage != null)
7683
{
77-
// First check if the package is already installed globally (cross-process safety)
78-
var existingPackages = await _templatePackageManager.GetManagedTemplatePackagesAsync(false, CancellationToken.None);
79-
var existingPackage = existingPackages.FirstOrDefault(p =>
80-
string.Equals(p.Identifier, _templatePackageName, StringComparison.OrdinalIgnoreCase));
81-
82-
if (existingPackage != null)
83-
{
84-
// Package is already installed globally, just store reference
85-
_installedTemplatePackage = existingPackage;
86-
_isTemplateInstalled = true;
87-
return;
88-
}
84+
// Package is already installed globally, just store reference
85+
_installedTemplatePackage = existingPackage;
86+
_isTemplateInstalled = true;
87+
return;
88+
}
8989

90-
// Package not installed, proceed with installation
91-
// Following the official dotnet CLI pattern: create install request with details
92-
var installRequest = new InstallRequest(_templatePackageName, version, details: new Dictionary<string, string>(), force: false);
93-
94-
// Get the managed provider for global scope (matches official CLI approach)
95-
var provider = _templatePackageManager.GetBuiltInManagedProvider(InstallationScope.Global);
96-
var installResults = await provider.InstallAsync(new[] { installRequest }, CancellationToken.None);
97-
98-
// Check if installation was successful
99-
var installResult = installResults.FirstOrDefault();
100-
if (installResult == null || !installResult.Success)
101-
{
102-
var packageId = _templatePackageName;
103-
var detailedErrors = installResult?.ErrorMessage ?? "Unknown installation error";
104-
105-
var userErrorMessage = $"Failed to install template package '{packageId}'.\n" +
106-
$"Details:\n{detailedErrors}\n\n" +
107-
$"💡 Corrective actions:\n" +
108-
$" • Check your internet connection\n" +
109-
$" • Verify the package name and version are correct\n" +
110-
$" • Ensure you have sufficient permissions for global package installation\n" +
111-
$" • If using a private package source, ensure it's properly configured";
112-
113-
throw new InvalidOperationException(userErrorMessage);
114-
}
90+
// Package not installed, proceed with installation
91+
// Following the official dotnet CLI pattern: create install request with details
92+
var installRequest = new InstallRequest(_templatePackageName, version, details: new Dictionary<string, string>(), force: false);
93+
94+
// Get the managed provider for global scope (matches official CLI approach)
95+
var provider = _templatePackageManager.GetBuiltInManagedProvider(InstallationScope.Global);
96+
var installResults = await provider.InstallAsync(new[] { installRequest }, CancellationToken.None);
97+
98+
// Check if installation was successful
99+
var installResult = installResults.FirstOrDefault();
100+
if (installResult == null || !installResult.Success)
101+
{
102+
var packageId = _templatePackageName;
103+
var detailedErrors = installResult?.ErrorMessage ?? "Unknown installation error";
115104

116-
// Following the official dotnet CLI pattern: store reference to the installed package
117-
// This is crucial for later template discovery
118-
_installedTemplatePackage = installResult.TemplatePackage as IManagedTemplatePackage;
119-
if (_installedTemplatePackage == null)
120-
{
121-
throw new InvalidOperationException($"Template package '{_templatePackageName}' was installed but could not be retrieved as a managed package");
122-
}
105+
var userErrorMessage = $"Failed to install template package '{packageId}'.\n" +
106+
$"Details:\n{detailedErrors}\n\n" +
107+
$"💡 Corrective actions:\n" +
108+
$" • Check your internet connection\n" +
109+
$" • Verify the package name and version are correct\n" +
110+
$" • Ensure you have sufficient permissions for global package installation\n" +
111+
$" • If using a private package source, ensure it's properly configured";
123112

124-
// Set flag last to ensure atomic operation visibility
125-
_isTemplateInstalled = true;
113+
throw new InvalidOperationException(userErrorMessage);
126114
}
127-
finally
115+
116+
// Following the official dotnet CLI pattern: store reference to the installed package
117+
// This is crucial for later template discovery
118+
_installedTemplatePackage = installResult.TemplatePackage as IManagedTemplatePackage;
119+
if (_installedTemplatePackage == null)
128120
{
129-
mutex.ReleaseMutex();
121+
throw new InvalidOperationException($"Template package '{_templatePackageName}' was installed but could not be retrieved as a managed package");
130122
}
123+
124+
// Set flag last to ensure atomic operation visibility
125+
_isTemplateInstalled = true;
131126
}
132127
catch (AbandonedMutexException)
133128
{
134129
// Previous process crashed while holding the mutex, but we can continue
135130
// The mutex is now owned by this thread
136-
try
137-
{
138-
// Retry the installation operation
139-
await EnsureTemplatePackageInstalledWithCrossProcessLockAsync(version);
140-
}
141-
finally
131+
mutexAcquired = true; // Mark as acquired since we now own it
132+
133+
// Retry the installation operation (but avoid infinite recursion)
134+
// Just perform the installation logic directly here
135+
var existingPackages = await _templatePackageManager.GetManagedTemplatePackagesAsync(false, CancellationToken.None);
136+
var existingPackage = existingPackages.FirstOrDefault(p =>
137+
string.Equals(p.Identifier, _templatePackageName, StringComparison.OrdinalIgnoreCase));
138+
139+
if (existingPackage != null)
142140
{
143-
mutex.ReleaseMutex();
141+
_installedTemplatePackage = existingPackage;
142+
_isTemplateInstalled = true;
143+
return;
144144
}
145+
146+
// If package still needs installation, let the exception propagate
147+
// to avoid complex retry logic
148+
throw new InvalidOperationException($"Template package installation was interrupted by another process crash. Please retry the operation.");
145149
}
146150
catch (Exception ex) when (!(ex is InvalidOperationException) && !(ex is TimeoutException))
147151
{
@@ -155,6 +159,21 @@ private async Task EnsureTemplatePackageInstalledWithCrossProcessLockAsync(strin
155159

156160
throw new InvalidOperationException(userErrorMessage, ex);
157161
}
162+
finally
163+
{
164+
// Only release the mutex if we successfully acquired it
165+
if (mutexAcquired)
166+
{
167+
try
168+
{
169+
mutex.ReleaseMutex();
170+
}
171+
catch (Exception)
172+
{
173+
// Ignore release errors - mutex will be released when the process exits
174+
}
175+
}
176+
}
158177
}
159178

160179
/// <summary>

0 commit comments

Comments
 (0)