@@ -19,6 +19,8 @@ namespace MSStore.CLI.Services
1919 internal class ConfigurationManager < T > ( JsonTypeInfo < T > jsonTypeInfo , string fileName , ILogger < ConfigurationManager < T > > ? logger ) : IConfigurationManager < T >
2020 where T : new ( )
2121 {
22+ private const int MaxOpenAttempts = 5 ;
23+
2224 private static readonly string SettingsDirectory = Path . Combine ( GetSystemLocalApplicationDataPath ( ) , "Microsoft" , "MSStore.CLI" ) ;
2325
2426 private static string GetSystemLocalApplicationDataPath ( )
@@ -43,6 +45,8 @@ private static string GetSystemLocalApplicationDataPath()
4345 return Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) ;
4446 }
4547
48+ private static readonly TimeSpan OpenRetryDelay = TimeSpan . FromMilliseconds ( 50 ) ;
49+
4650 private readonly string _settingsPath = Path . Combine ( SettingsDirectory , fileName ) ;
4751 private readonly JsonTypeInfo < T > _jsonTypeInfo = jsonTypeInfo ?? throw new ArgumentNullException ( nameof ( jsonTypeInfo ) ) ;
4852 private readonly ILogger ? _logger = logger ;
@@ -59,10 +63,23 @@ public async Task<T> LoadAsync(bool clearInvalidConfig, CancellationToken ct)
5963 return await ClearAsync ( ct ) ;
6064 }
6165
62- using var file = File . Open ( _settingsPath , FileMode . Open ) ;
66+ using var file = await OpenAsync ( FileMode . Open , ct ) ;
6367
6468 return await JsonSerializer . DeserializeAsync ( file , _jsonTypeInfo , ct ) ?? new T ( ) ;
6569 }
70+ catch ( IOException ex )
71+ {
72+ // Another process is using the file. Do not overwrite its contents,
73+ // just fallback to the default configuration.
74+ _logger ? . LogWarning ( ex , "Could not read the configuration file: {SettingsPath}" , _settingsPath ) ;
75+
76+ if ( ! clearInvalidConfig )
77+ {
78+ throw ;
79+ }
80+
81+ return new T ( ) ;
82+ }
6683 catch
6784 {
6885 if ( ! clearInvalidConfig )
@@ -77,7 +94,7 @@ public async Task<T> LoadAsync(bool clearInvalidConfig, CancellationToken ct)
7794 public async Task < T > ClearAsync ( CancellationToken ct )
7895 {
7996 EnsureDirectoryExists ( ) ;
80- using var file = File . Open ( _settingsPath , FileMode . OpenOrCreate ) ;
97+ using var file = await OpenAsync ( FileMode . OpenOrCreate , ct ) ;
8198 file . SetLength ( 0 ) ;
8299 await file . FlushAsync ( ct ) ;
83100 file . Position = 0 ;
@@ -88,12 +105,30 @@ public async Task<T> ClearAsync(CancellationToken ct)
88105
89106 public async Task SaveAsync ( T config , CancellationToken ct )
90107 {
91- using var file = File . Open ( _settingsPath , FileMode . OpenOrCreate ) ;
108+ using var file = await OpenAsync ( FileMode . OpenOrCreate , ct ) ;
92109 file . SetLength ( 0 ) ;
93110 file . Position = 0 ;
94111 await JsonSerializer . SerializeAsync ( file , config , _jsonTypeInfo , ct ) ;
95112 }
96113
114+ private async Task < FileStream > OpenAsync ( FileMode fileMode , CancellationToken ct )
115+ {
116+ for ( var attempt = 1 ; ; attempt ++ )
117+ {
118+ try
119+ {
120+ return File . Open ( _settingsPath , fileMode , FileAccess . ReadWrite , FileShare . None ) ;
121+ }
122+ catch ( IOException ex ) when ( attempt < MaxOpenAttempts && ex is not FileNotFoundException and not DirectoryNotFoundException )
123+ {
124+ // The file is being used by another process. Wait a bit and try again.
125+ _logger ? . LogInformation ( "Configuration file '{SettingsPath}' is in use. Retrying ({Attempt}/{MaxOpenAttempts})..." , _settingsPath , attempt , MaxOpenAttempts ) ;
126+
127+ await Task . Delay ( OpenRetryDelay * attempt , ct ) ;
128+ }
129+ }
130+ }
131+
97132 private void EnsureDirectoryExists ( )
98133 {
99134 if ( Directory . Exists ( SettingsDirectory ) )
0 commit comments