@@ -9,12 +9,13 @@ namespace TALXIS.CLI.Workspace.TemplateEngine
99 /// <summary>
1010 /// Service responsible for managing template packages (installation, listing, etc.)
1111 /// </summary>
12- public class TemplatePackageService
12+ public class TemplatePackageService : IDisposable
1313 {
1414 private readonly TemplatePackageManager _templatePackageManager ;
1515 private readonly IEngineEnvironmentSettings _environmentSettings ;
1616 private readonly string _templatePackageName = "TALXIS.DevKit.Templates.Dataverse" ;
17- private bool _isTemplateInstalled = false ;
17+ private readonly SemaphoreSlim _installationSemaphore = new ( 1 , 1 ) ;
18+ private volatile bool _isTemplateInstalled = false ;
1819 private IManagedTemplatePackage ? _installedTemplatePackage ;
1920
2021 public string TemplatePackageName => _templatePackageName ;
@@ -27,13 +28,35 @@ public TemplatePackageService(TemplatePackageManager templatePackageManager, IEn
2728
2829 public async Task EnsureTemplatePackageInstalledAsync ( string ? version = null )
2930 {
31+ // Double-checked locking pattern for thread safety within the same process
3032 if ( _isTemplateInstalled && _installedTemplatePackage != null )
3133 {
3234 return ; // Already installed and we have a reference to it
3335 }
3436
37+ await _installationSemaphore . WaitAsync ( ) ;
3538 try
3639 {
40+ // Check again inside the lock (double-checked locking)
41+ if ( _isTemplateInstalled && _installedTemplatePackage != null )
42+ {
43+ return ; // Another thread completed the installation
44+ }
45+
46+ // First check if the package is already installed globally (cross-process safety)
47+ var existingPackages = await _templatePackageManager . GetManagedTemplatePackagesAsync ( false , CancellationToken . None ) ;
48+ var existingPackage = existingPackages . FirstOrDefault ( p =>
49+ string . Equals ( p . Identifier , _templatePackageName , StringComparison . OrdinalIgnoreCase ) ) ;
50+
51+ if ( existingPackage != null )
52+ {
53+ // Package is already installed globally, just store reference
54+ _installedTemplatePackage = existingPackage ;
55+ _isTemplateInstalled = true ;
56+ return ;
57+ }
58+
59+ // Package not installed, proceed with installation
3760 // Following the official dotnet CLI pattern: create install request with details
3861 var installRequest = new InstallRequest ( _templatePackageName , version , details : new Dictionary < string , string > ( ) , force : false ) ;
3962
@@ -67,6 +90,7 @@ public async Task EnsureTemplatePackageInstalledAsync(string? version = null)
6790 throw new InvalidOperationException ( $ "Template package '{ _templatePackageName } ' was installed but could not be retrieved as a managed package") ;
6891 }
6992
93+ // Set flag last to ensure atomic operation visibility
7094 _isTemplateInstalled = true ;
7195 }
7296 catch ( Exception ex ) when ( ! ( ex is InvalidOperationException ) )
@@ -81,20 +105,31 @@ public async Task EnsureTemplatePackageInstalledAsync(string? version = null)
81105
82106 throw new InvalidOperationException ( userErrorMessage , ex ) ;
83107 }
108+ finally
109+ {
110+ _installationSemaphore . Release ( ) ;
111+ }
84112 }
85113
86114 public async Task < List < ITemplateInfo > > ListTemplatesAsync ( string ? version = null )
87115 {
88116 await EnsureTemplatePackageInstalledAsync ( version ) ;
89117
90- if ( _installedTemplatePackage == null )
118+ // Read the installed package reference with memory barrier for thread safety
119+ var installedPackage = _installedTemplatePackage ;
120+ if ( installedPackage == null )
91121 {
92122 throw new InvalidOperationException ( "Template package was installed but reference is not available" ) ;
93123 }
94124
95125 // Use the official dotnet CLI pattern - get templates from the specific installed package
96- var templates = await _templatePackageManager . GetTemplatesAsync ( _installedTemplatePackage , CancellationToken . None ) ;
126+ var templates = await _templatePackageManager . GetTemplatesAsync ( installedPackage , CancellationToken . None ) ;
97127 return templates . ToList ( ) ;
98128 }
129+
130+ public void Dispose ( )
131+ {
132+ _installationSemaphore ? . Dispose ( ) ;
133+ }
99134 }
100135}
0 commit comments