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