1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Collections.Generic; 6 using System.Diagnostics; 7 using System.IO; 8 using System.Runtime.InteropServices; 9 using System.Threading.Tasks; 10 using Xunit; 11 12 namespace System.Threading.Tests 13 { 14 public class MutexTests : RemoteExecutorTestBase 15 { 16 private const int FailedWaitTimeout = 30000; 17 18 [Fact] Ctor_ConstructWaitRelease()19 public void Ctor_ConstructWaitRelease() 20 { 21 using (Mutex m = new Mutex()) 22 { 23 Assert.True(m.WaitOne(FailedWaitTimeout)); 24 m.ReleaseMutex(); 25 } 26 27 using (Mutex m = new Mutex(false)) 28 { 29 Assert.True(m.WaitOne(FailedWaitTimeout)); 30 m.ReleaseMutex(); 31 } 32 33 using (Mutex m = new Mutex(true)) 34 { 35 Assert.True(m.WaitOne(FailedWaitTimeout)); 36 m.ReleaseMutex(); 37 m.ReleaseMutex(); 38 } 39 } 40 41 [Fact] Ctor_InvalidName()42 public void Ctor_InvalidName() 43 { 44 AssertExtensions.Throws<ArgumentException>("name", null, () => new Mutex(false, new string('a', 1000))); 45 } 46 47 [Fact] Ctor_ValidName()48 public void Ctor_ValidName() 49 { 50 string name = Guid.NewGuid().ToString("N"); 51 bool createdNew; 52 using (Mutex m1 = new Mutex(false, name, out createdNew)) 53 { 54 Assert.True(createdNew); 55 using (Mutex m2 = new Mutex(false, name, out createdNew)) 56 { 57 Assert.False(createdNew); 58 } 59 } 60 } 61 62 [PlatformSpecific(TestPlatforms.Windows)] // named semaphores aren't supported on Unix 63 [Fact] Ctor_NameUsedByOtherSynchronizationPrimitive_Windows()64 public void Ctor_NameUsedByOtherSynchronizationPrimitive_Windows() 65 { 66 string name = Guid.NewGuid().ToString("N"); 67 using (Semaphore s = new Semaphore(1, 1, name)) 68 { 69 Assert.Throws<WaitHandleCannotBeOpenedException>(() => new Mutex(false, name)); 70 } 71 } 72 73 [PlatformSpecific(TestPlatforms.Windows)] 74 [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInAppContainer))] // Can't create global objects in appcontainer 75 [SkipOnTargetFramework( 76 TargetFrameworkMonikers.NetFramework, 77 "The fix necessary for this test (PR https://github.com/dotnet/coreclr/pull/12381) is not in the .NET Framework.")] Ctor_ImpersonateAnonymousAndTryCreateGlobalMutexTest()78 public void Ctor_ImpersonateAnonymousAndTryCreateGlobalMutexTest() 79 { 80 ThreadTestHelpers.RunTestInBackgroundThread(() => 81 { 82 if (!ImpersonateAnonymousToken(GetCurrentThread())) 83 { 84 // Impersonation is not allowed in the current context, this test is inappropriate in such a case 85 return; 86 } 87 88 Assert.Throws<UnauthorizedAccessException>(() => new Mutex(false, "Global\\" + Guid.NewGuid().ToString("N"))); 89 Assert.True(RevertToSelf()); 90 }); 91 } 92 93 [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsInAppContainer))] // Can't create global objects in appcontainer 94 [PlatformSpecific(TestPlatforms.Windows)] Ctor_TryCreateGlobalMutexTest_Uwp()95 public void Ctor_TryCreateGlobalMutexTest_Uwp() 96 { 97 ThreadTestHelpers.RunTestInBackgroundThread(() => 98 Assert.Throws<UnauthorizedAccessException>(() => new Mutex(false, "Global\\" + Guid.NewGuid().ToString("N")))); 99 } 100 101 [Fact] OpenExisting()102 public void OpenExisting() 103 { 104 string name = Guid.NewGuid().ToString("N"); 105 106 Mutex resultHandle; 107 Assert.False(Mutex.TryOpenExisting(name, out resultHandle)); 108 109 using (Mutex m1 = new Mutex(false, name)) 110 { 111 using (Mutex m2 = Mutex.OpenExisting(name)) 112 { 113 Assert.True(m1.WaitOne(FailedWaitTimeout)); 114 Assert.False(Task.Factory.StartNew(() => m2.WaitOne(0), CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).Result); 115 m1.ReleaseMutex(); 116 117 Assert.True(m2.WaitOne(FailedWaitTimeout)); 118 Assert.False(Task.Factory.StartNew(() => m1.WaitOne(0), CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).Result); 119 m2.ReleaseMutex(); 120 } 121 122 Assert.True(Mutex.TryOpenExisting(name, out resultHandle)); 123 Assert.NotNull(resultHandle); 124 resultHandle.Dispose(); 125 } 126 } 127 128 [Fact] OpenExisting_InvalidNames()129 public void OpenExisting_InvalidNames() 130 { 131 AssertExtensions.Throws<ArgumentNullException>("name", () => Mutex.OpenExisting(null)); 132 AssertExtensions.Throws<ArgumentException>("name", null, () => Mutex.OpenExisting(string.Empty)); 133 AssertExtensions.Throws<ArgumentException>("name", null, () => Mutex.OpenExisting(new string('a', 10000))); 134 } 135 136 [Fact] OpenExisting_UnavailableName()137 public void OpenExisting_UnavailableName() 138 { 139 string name = Guid.NewGuid().ToString("N"); 140 Assert.Throws<WaitHandleCannotBeOpenedException>(() => Mutex.OpenExisting(name)); 141 Mutex ignored; 142 Assert.False(Mutex.TryOpenExisting(name, out ignored)); 143 } 144 145 [PlatformSpecific(TestPlatforms.Windows)] // named semaphores aren't supported on Unix 146 [Fact] OpenExisting_NameUsedByOtherSynchronizationPrimitive_Windows()147 public void OpenExisting_NameUsedByOtherSynchronizationPrimitive_Windows() 148 { 149 string name = Guid.NewGuid().ToString("N"); 150 using (Semaphore sema = new Semaphore(1, 1, name)) 151 { 152 Assert.Throws<WaitHandleCannotBeOpenedException>(() => Mutex.OpenExisting(name)); 153 Mutex ignored; 154 Assert.False(Mutex.TryOpenExisting(name, out ignored)); 155 } 156 } 157 GetNamePrefixes()158 private static IEnumerable<string> GetNamePrefixes() 159 { 160 yield return string.Empty; 161 yield return "Local\\"; 162 163 // Creating global sync objects is not allowed in UWP apps 164 if (!PlatformDetection.IsUap) 165 { 166 yield return "Global\\"; 167 } 168 } 169 AbandonExisting_MemberData()170 public static IEnumerable<object[]> AbandonExisting_MemberData() 171 { 172 var nameGuidStr = Guid.NewGuid().ToString("N"); 173 for (int waitType = 0; waitType < 2; ++waitType) // 0 == WaitOne, 1 == WaitAny 174 { 175 yield return new object[] { null, waitType }; 176 foreach (var namePrefix in GetNamePrefixes()) 177 { 178 yield return new object[] { namePrefix + nameGuidStr, waitType }; 179 } 180 } 181 } 182 183 [Theory] 184 [MemberData(nameof(AbandonExisting_MemberData))] AbandonExisting(string name, int waitType)185 public void AbandonExisting(string name, int waitType) 186 { 187 ThreadTestHelpers.RunTestInBackgroundThread(() => 188 { 189 using (var m = new Mutex(false, name)) 190 { 191 Task t = Task.Factory.StartNew(() => 192 { 193 Assert.True(m.WaitOne(FailedWaitTimeout)); 194 // don't release the mutex; abandon it on this thread 195 }, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default); 196 Assert.True(t.Wait(FailedWaitTimeout)); 197 198 switch (waitType) 199 { 200 case 0: // WaitOne 201 Assert.Throws<AbandonedMutexException>(() => m.WaitOne(FailedWaitTimeout)); 202 break; 203 204 case 1: // WaitAny 205 AbandonedMutexException ame = Assert.Throws<AbandonedMutexException>(() => WaitHandle.WaitAny(new[] { m }, FailedWaitTimeout)); 206 Assert.Equal(0, ame.MutexIndex); 207 Assert.Equal(m, ame.Mutex); 208 break; 209 } 210 } 211 }); 212 } 213 CrossProcess_NamedMutex_ProtectedFileAccessAtomic_MemberData()214 public static IEnumerable<object[]> CrossProcess_NamedMutex_ProtectedFileAccessAtomic_MemberData() 215 { 216 var nameGuidStr = Guid.NewGuid().ToString("N"); 217 foreach (var namePrefix in GetNamePrefixes()) 218 { 219 yield return new object[] { namePrefix + nameGuidStr }; 220 } 221 } 222 223 [Theory] 224 [MemberData(nameof(CrossProcess_NamedMutex_ProtectedFileAccessAtomic_MemberData))] CrossProcess_NamedMutex_ProtectedFileAccessAtomic(string prefix)225 public void CrossProcess_NamedMutex_ProtectedFileAccessAtomic(string prefix) 226 { 227 ThreadTestHelpers.RunTestInBackgroundThread(() => 228 { 229 string mutexName = prefix + Guid.NewGuid().ToString("N"); 230 string fileName = GetTestFilePath(); 231 232 Func<string, string, int> otherProcess = (m, f) => 233 { 234 using (var mutex = Mutex.OpenExisting(m)) 235 { 236 mutex.WaitOne(); 237 try 238 { File.WriteAllText(f, "0"); } 239 finally { mutex.ReleaseMutex(); } 240 241 IncrementValueInFileNTimes(mutex, f, 10); 242 } 243 return SuccessExitCode; 244 }; 245 246 using (var mutex = new Mutex(false, mutexName)) 247 using (var remote = RemoteInvoke(otherProcess, mutexName, fileName)) 248 { 249 SpinWait.SpinUntil(() => File.Exists(fileName)); 250 251 IncrementValueInFileNTimes(mutex, fileName, 10); 252 } 253 254 Assert.Equal(20, int.Parse(File.ReadAllText(fileName))); 255 }); 256 } 257 IncrementValueInFileNTimes(Mutex mutex, string fileName, int n)258 private static void IncrementValueInFileNTimes(Mutex mutex, string fileName, int n) 259 { 260 for (int i = 0; i < n; i++) 261 { 262 mutex.WaitOne(); 263 try 264 { 265 int current = int.Parse(File.ReadAllText(fileName)); 266 Thread.Sleep(10); 267 File.WriteAllText(fileName, (current + 1).ToString()); 268 } 269 finally { mutex.ReleaseMutex(); } 270 } 271 } 272 273 [DllImport("kernel32.dll")] GetCurrentThread()274 private static extern IntPtr GetCurrentThread(); 275 276 [DllImport("advapi32.dll")] 277 [return: MarshalAs(UnmanagedType.Bool)] ImpersonateAnonymousToken(IntPtr threadHandle)278 private static extern bool ImpersonateAnonymousToken(IntPtr threadHandle); 279 280 [DllImport("advapi32.dll")] 281 [return: MarshalAs(UnmanagedType.Bool)] RevertToSelf()282 private static extern bool RevertToSelf(); 283 } 284 } 285