Skip to content

Commit 2a95831

Browse files
authored
Add some tests for shared mutex corner cases (#118710)
1 parent c277b32 commit 2a95831

File tree

1 file changed

+116
-1
lines changed

1 file changed

+116
-1
lines changed

src/libraries/System.Threading/tests/MutexTests.cs

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Diagnostics;
66
using System.IO;
77
using System.Runtime.InteropServices;
8+
using System.Runtime.Versioning;
89
using System.Threading.Tasks;
910
using Microsoft.DotNet.RemoteExecutor;
1011
using Xunit;
@@ -250,6 +251,11 @@ public void MutualExclusionTest()
250251
public void Ctor_InvalidNames_Unix()
251252
{
252253
AssertExtensions.Throws<ArgumentException>("name", null, () => new Mutex(new string('a', 1000), options: default));
254+
Assert.Throws<IOException>(() => new Mutex("Foo/Bar", options: default));
255+
AssertExtensions.Throws<ArgumentException>("name", null, () => new Mutex("Foo\\Bar", options: default));
256+
AssertExtensions.Throws<ArgumentException>("name", null, () => new Mutex("Foo\\Bar", options: new NamedWaitHandleOptions { CurrentSessionOnly = false }));
257+
Assert.Throws<IOException>(() => new Mutex("Global\\Foo/Bar", options: new NamedWaitHandleOptions { CurrentSessionOnly = false }));
258+
Assert.Throws<IOException>(() => new Mutex("Global\\Foo\\Bar", options: new NamedWaitHandleOptions { CurrentSessionOnly = false }));
253259
}
254260

255261
[Theory]
@@ -971,9 +977,118 @@ public void NamedMutex_DisposeWhenLockedRaceTest()
971977
}
972978
}
973979

980+
[ConditionalFact(nameof(IsCrossProcessNamedMutexSupported))]
981+
[PlatformSpecific(TestPlatforms.AnyUnix)]
982+
public void NamedMutex_OtherEvent_NotCompatible()
983+
{
984+
using Mutex m = new Mutex(Guid.NewGuid().ToString("N"), options: default);
985+
using ManualResetEvent mre = new(false);
986+
987+
Assert.Throws<PlatformNotSupportedException>(() => WaitHandle.WaitAny(new WaitHandle[] { m, mre }, 0));
988+
}
989+
990+
private const string GlobalSharedMemoryDirectory = $"/tmp/.dotnet/shm/global";
991+
private const UnixFileMode AllUsersRwx =
992+
UnixFileMode.UserRead
993+
| UnixFileMode.UserWrite
994+
| UnixFileMode.UserExecute
995+
| UnixFileMode.GroupRead
996+
| UnixFileMode.GroupWrite
997+
| UnixFileMode.GroupExecute
998+
| UnixFileMode.OtherRead
999+
| UnixFileMode.OtherWrite
1000+
| UnixFileMode.OtherExecute;
1001+
1002+
[ConditionalFact(nameof(IsCrossProcessNamedMutexSupported))]
1003+
[PlatformSpecific(TestPlatforms.AnyUnix)]
1004+
[UnsupportedOSPlatform("windows")]
1005+
public void NamedMutex_InvalidSharedMemoryHeaderVersion()
1006+
{
1007+
string name = Guid.NewGuid().ToString("N");
1008+
string path = $"{GlobalSharedMemoryDirectory}/{name}";
1009+
1010+
Directory.CreateDirectory(GlobalSharedMemoryDirectory, AllUsersRwx);
1011+
using (FileStream fs = new(path, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite, 4096))
1012+
using (BinaryWriter bw = new(fs))
1013+
{
1014+
bw.Write((byte)1); // Write the shared memory type (mutex)
1015+
bw.Write((byte)2); // Write an invalid version number
1016+
// Make the file large enough for a valid named mutex file and divisible by page size (it should always be under one page).
1017+
fs.SetLength(Environment.SystemPageSize);
1018+
1019+
// Try opening a mutex when we still have the file locked.
1020+
Assert.Throws<WaitHandleCannotBeOpenedException>(() => new Mutex($"Global\\{name}", new NamedWaitHandleOptions { CurrentSessionOnly = false, CurrentUserOnly = false }));
1021+
}
1022+
}
1023+
1024+
[ConditionalFact(nameof(IsCrossProcessNamedMutexSupported))]
1025+
[PlatformSpecific(TestPlatforms.AnyUnix)]
1026+
[UnsupportedOSPlatform("windows")]
1027+
public void NamedMutex_SharedMemoryFileAlreadyOpen()
1028+
{
1029+
string name = Guid.NewGuid().ToString("N");
1030+
string path = $"{GlobalSharedMemoryDirectory}/{name}";
1031+
1032+
Directory.CreateDirectory(GlobalSharedMemoryDirectory, AllUsersRwx);
1033+
// Take an exclusive file lock of the global shared memory file.
1034+
using (FileStream fs = new(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096))
1035+
using (BinaryWriter bw = new(fs))
1036+
{
1037+
bw.Write((byte)1); // Write the shared memory type (mutex)
1038+
bw.Write((byte)1); // Write valid version number
1039+
// Make the file large enough for a valid named mutex file and divisible by page size (it should always be under one page).
1040+
fs.SetLength(Environment.SystemPageSize);
1041+
1042+
// Try opening a mutex when we still have the file locked.
1043+
Assert.Throws<IOException>(() => new Mutex($"Global\\{name}", new NamedWaitHandleOptions { CurrentSessionOnly = false, CurrentUserOnly = false }));
1044+
}
1045+
}
1046+
1047+
[ConditionalFact(nameof(IsCrossProcessNamedMutexSupported))]
1048+
[PlatformSpecific(TestPlatforms.AnyUnix)]
1049+
[UnsupportedOSPlatform("windows")]
1050+
public void NamedMutex_InvalidSharedMemoryHeaderKind()
1051+
{
1052+
string name = Guid.NewGuid().ToString("N");
1053+
string path = $"{GlobalSharedMemoryDirectory}/{name}";
1054+
1055+
Directory.CreateDirectory(GlobalSharedMemoryDirectory, AllUsersRwx);
1056+
using (FileStream fs = new(path, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite, 4096))
1057+
using (BinaryWriter bw = new(fs))
1058+
{
1059+
bw.Write((byte)2); // Write the shared memory type (invalid)
1060+
bw.Write((byte)1); // Write a version number
1061+
// Make the file large enough for a valid named mutex file and divisible by page size (it should always be under one page).
1062+
fs.SetLength(Environment.SystemPageSize);
1063+
// Try opening a mutex when we still have the file locked.
1064+
Assert.Throws<WaitHandleCannotBeOpenedException>(() => new Mutex($"Global\\{name}", new NamedWaitHandleOptions { CurrentSessionOnly = false, CurrentUserOnly = false }));
1065+
}
1066+
}
1067+
1068+
[ConditionalFact(nameof(IsCrossProcessNamedMutexSupported))]
1069+
[PlatformSpecific(TestPlatforms.AnyUnix)]
1070+
[UnsupportedOSPlatform("windows")]
1071+
public void NamedMutex_TooSmallSharedMemoryFile()
1072+
{
1073+
string name = Guid.NewGuid().ToString("N");
1074+
string path = $"{GlobalSharedMemoryDirectory}/{name}";
1075+
1076+
Directory.CreateDirectory(GlobalSharedMemoryDirectory, AllUsersRwx);
1077+
using (FileStream fs = new(path, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite, 4096))
1078+
using (BinaryWriter bw = new(fs))
1079+
{
1080+
bw.Write((byte)1); // Write the shared memory type (mutex)
1081+
bw.Write((byte)1); // Write a valid version number
1082+
// Make the file large enough for a valid named mutex file but not divisible by page size.
1083+
fs.SetLength(Environment.SystemPageSize - 1);
1084+
// Try opening a mutex when we still have the file locked.
1085+
Assert.Throws<WaitHandleCannotBeOpenedException>(() => new Mutex($"Global\\{name}", new NamedWaitHandleOptions { CurrentSessionOnly = false, CurrentUserOnly = false }));
1086+
}
1087+
}
1088+
9741089
public static TheoryData<string> GetValidNames()
9751090
{
976-
var names = new TheoryData<string>() { Guid.NewGuid().ToString("N") };
1091+
var names = new TheoryData<string>() { Guid.NewGuid().ToString("N") };
9771092

9781093
// Windows native named mutexes and in-proc named mutexes support very long (1000+ char) names.
9791094
// Non-Windows cross-process named mutexes are emulated using file system. It imposes limit

0 commit comments

Comments
 (0)