Skip to content

Commit 2e8d288

Browse files
authored
[Az.Accounts] Use mutex to prevent cross process file I/O (#12281)
* use mutex to prevent cross process writing to token cache file * prevent cross-process write to profile file * change log * Use "Local" mutex; put ReleaseMutex() in finally
1 parent 827b974 commit 2e8d288

File tree

4 files changed

+49
-13
lines changed

4 files changed

+49
-13
lines changed

src/Accounts/Accounts/ChangeLog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
-->
2020
## Upcoming Release
2121

22+
* Fixed an issue that may cause authentication errors in multi-process scenarios such as running multiple Azure PowerShell cmdlets using `Start-Job` [#9448]
23+
2224
## Version 1.9.0
2325
* Supported discovering environment setting by default and adding environment via `Add-AzEnvironment`
2426
* Update preloaded assemblies [#12024], [#11976]

src/Accounts/Authentication.ResourceManager/ProtectedFileProvider.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,18 @@ public abstract class ProtectedFileProvider : IFileProvider, IDisposable
3636
public const int MaxTries = 30;
3737
static readonly TimeSpan RetryInterval = TimeSpan.FromMilliseconds(500);
3838
protected Stream _stream;
39-
object _initializationLock = new object();
39+
40+
/// <summary>
41+
/// Use a Mutex to prevent cross-process file I/O
42+
/// </summary>
43+
/// <returns></returns>
44+
private static readonly Mutex _initializationLock = new Mutex(false, @"Local\AzurePowerShellProtectedFileProviderInit");
4045
public string FilePath { get; set; }
4146

4247
protected IDataStore DataStore { get; set; }
4348

4449
/// <summary>
45-
///
50+
///
4651
/// </summary>
4752
public Stream Stream
4853
{
@@ -87,7 +92,8 @@ public StreamWriter CreateWriter()
8792

8893
protected virtual void InitializeStream()
8994
{
90-
lock (_initializationLock)
95+
_initializationLock.WaitOne();
96+
try
9197
{
9298
if (_stream == null)
9399
{
@@ -96,10 +102,13 @@ protected virtual void InitializeStream()
96102
{
97103
throw new UnauthorizedAccessException(string.Format(Resources.FileLockFailure, FilePath));
98104
}
99-
100105
_stream = stream;
101106
}
102107
}
108+
finally
109+
{
110+
_initializationLock.ReleaseMutex();
111+
}
103112
}
104113

105114
protected abstract Stream AcquireLock(string filePath);

src/Accounts/Authentication/Authentication/ProtectedFileTokenCache.cs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212
// limitations under the License.
1313
// ----------------------------------------------------------------------------------
1414

15+
using Hyak.Common;
1516
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
1617
using Microsoft.Azure.Commands.Common.Authentication.Properties;
1718
using Microsoft.IdentityModel.Clients.ActiveDirectory;
1819
using System;
1920
using System.IO;
2021
using System.Security.Cryptography;
22+
using System.Threading;
2123

2224
#if NETSTANDARD
2325
namespace Microsoft.Azure.Commands.Common.Authentication.Core
@@ -41,7 +43,10 @@ public class ProtectedFileTokenCache : TokenCache, IAzureTokenCache
4143
#endif
4244
"TokenCache.dat");
4345

44-
private static readonly object fileLock = new object();
46+
/// <summary>
47+
/// A mutex to prevent IO to token cache file across threads / processes.
48+
/// </summary>
49+
private static readonly Mutex fileLock = new Mutex(false, @"Local\AzurePowerShellAdalTokenCacheFile");
4550

4651
private static readonly Lazy<ProtectedFileTokenCache> instance = new Lazy<ProtectedFileTokenCache>(() => new ProtectedFileTokenCache());
4752

@@ -123,12 +128,13 @@ void EnsureStateSaved()
123128

124129
private void ReadFileIntoCache(string cacheFileName = null)
125130
{
126-
if(cacheFileName == null)
131+
if (cacheFileName == null)
127132
{
128133
cacheFileName = ProtectedFileTokenCache.CacheFileName;
129134
}
130135

131-
lock (fileLock)
136+
fileLock.WaitOne();
137+
try
132138
{
133139
if (_store.FileExists(cacheFileName))
134140
{
@@ -150,11 +156,15 @@ private void ReadFileIntoCache(string cacheFileName = null)
150156
}
151157
}
152158
}
159+
finally
160+
{
161+
fileLock.ReleaseMutex();
162+
}
153163
}
154164

155165
private void WriteCacheIntoFile(string cacheFileName = null)
156166
{
157-
if(cacheFileName == null)
167+
if (cacheFileName == null)
158168
{
159169
cacheFileName = ProtectedFileTokenCache.CacheFileName;
160170
}
@@ -165,19 +175,25 @@ private void WriteCacheIntoFile(string cacheFileName = null)
165175
var dataToWrite = Serialize();
166176
#endif
167177

168-
lock(fileLock)
178+
fileLock.WaitOne();
179+
try
169180
{
170181
if (HasStateChanged)
171182
{
172183
_store.WriteFile(cacheFileName, dataToWrite);
173-
HasStateChanged = false;
184+
HasStateChanged = false;
174185
}
175186
}
187+
finally
188+
{
189+
fileLock.ReleaseMutex();
190+
}
176191
}
177192

178193
private void EnsureCacheFile(string cacheFileName = null)
179194
{
180-
lock (fileLock)
195+
fileLock.WaitOne();
196+
try
181197
{
182198
if (_store.FileExists(cacheFileName))
183199
{
@@ -207,6 +223,10 @@ private void EnsureCacheFile(string cacheFileName = null)
207223
#endif
208224
_store.WriteFile(cacheFileName, dataToWrite);
209225
}
226+
finally
227+
{
228+
fileLock.ReleaseMutex();
229+
}
210230
}
211231
}
212232
}

src/Accounts/Authentication/AzureSessionInitializer.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#if NETSTANDARD
2727
using Microsoft.Azure.Commands.Common.Authentication.Core;
2828
#endif
29+
using Hyak.Common;
2930

3031
namespace Microsoft.Azure.Commands.Common.Authentication
3132
{
@@ -73,8 +74,10 @@ static IAzureTokenCache InitializeTokenCache(IDataStore store, string cacheDirec
7374
var cachePath = Path.Combine(cacheDirectory, cacheFile);
7475
result = new ProtectedFileTokenCache(cachePath, store);
7576
}
76-
catch
77+
catch (Exception ex)
7778
{
79+
TracingAdapter.Information("[AzureSessionInitializer]: Cannot initialize token cache in 'CurrentUser' mode. Falling back to 'Process' mode.");
80+
TracingAdapter.Information($"[AzureSessionInitializer]: Message: {ex.Message}; Stacktrace: {ex.StackTrace}");
7881
}
7982
}
8083

@@ -159,10 +162,12 @@ static ContextAutosaveSettings InitializeSessionSettings(IDataStore store, strin
159162
store.WriteFile(autoSavePath, JsonConvert.SerializeObject(result));
160163
}
161164
}
162-
catch
165+
catch (Exception ex)
163166
{
164167
// ignore exceptions in reading settings from disk
165168
result.Mode = ContextSaveMode.Process;
169+
TracingAdapter.Information("[AzureSessionInitializer]: Cannot read settings from disk. Falling back to 'Process' mode.");
170+
TracingAdapter.Information($"[AzureSessionInitializer]: Message: {ex.Message}; Stacktrace: {ex.StackTrace}");
166171
}
167172

168173
return result;

0 commit comments

Comments
 (0)