Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

[3.1] Test-only followup for "Fix Unix named mutex crash during some race conditions" #42917

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions src/System.Threading/tests/MutexTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,103 @@ private static void IncrementValueInFileNTimes(Mutex mutex, string fileName, int
}
}

[Fact]
public void NamedMutex_ThreadExitDisposeRaceTest()
{
var mutexName = Guid.NewGuid().ToString("N");

for (int i = 0; i < 1000; ++i)
{
var m = new Mutex(false, mutexName);
var startParallelTest = new ManualResetEvent(false);

var t0Ready = new AutoResetEvent(false);
Thread t0 = ThreadTestHelpers.CreateGuardedThread(out Action waitForT0, () =>
{
m.CheckedWait();
t0Ready.Set();
startParallelTest.CheckedWait(); // after this, exit T0
});
t0.IsBackground = true;

var t1Ready = new AutoResetEvent(false);
Thread t1 = ThreadTestHelpers.CreateGuardedThread(out Action waitForT1, () =>
{
using (var m2 = Mutex.OpenExisting(mutexName))
{
m.Dispose();
t1Ready.Set();
startParallelTest.CheckedWait(); // after this, close last handle to named mutex, exit T1
}
});
t1.IsBackground = true;

t0.Start();
t0Ready.CheckedWait(); // wait for T0 to acquire the mutex
t1.Start();
t1Ready.CheckedWait(); // wait for T1 to open the existing mutex in a new mutex object and dispose one of the two

// Release both threads at the same time. T0 will be exiting the thread, perhaps trying to abandon the mutex
// that is still locked by it. In parallel, T1 will be disposing the last mutex instance, which would try to
// destroy the mutex.
startParallelTest.Set();
waitForT0();
waitForT1();

// Create a new mutex object with the same name and acquire it. There can be a delay between Thread.Join() above
// returning and for T0 to abandon its mutex, keep trying to also verify that the mutex object is actually
// destroyed and created new again.
SpinWait.SpinUntil(() =>
{
using (m = new Mutex(true, mutexName, out bool createdNew))
{
if (createdNew)
{
m.ReleaseMutex();
}
return createdNew;
}
});
}
}

[Fact]
public void NamedMutex_DisposeWhenLockedRaceTest()
{
var mutexName = Guid.NewGuid().ToString("N");
var mutex2Name = mutexName + "_2";

var waitsForThread = new Action[Environment.ProcessorCount];
for (int i = 0; i < waitsForThread.Length; ++i)
{
var t = ThreadTestHelpers.CreateGuardedThread(out waitsForThread[i], () =>
{
for (int i = 0; i < 1000; ++i)
{
// Create or open two mutexes with different names, acquire the lock if created, and dispose without
// releasing the lock. What may occasionally happen is, one thread T0 will acquire the lock, another
// thread T1 will open the same mutex, T0 will dispose its mutex while the lock is held, and T1 will
// then release the last reference to the mutex. On some implementations T1 may not be able to destroy
// the mutex when it is still locked by T0, or there may be potential for races in the sequence. This
// test only looks for errors from race conditions.
using (var mutex = new Mutex(true, mutexName))
{
}
using (var mutex = new Mutex(true, mutex2Name))
{
}
}
});
t.IsBackground = true;
t.Start();
}

foreach (var waitForThread in waitsForThread)
{
waitForThread();
}
}

public static TheoryData<string> GetValidNames()
{
var names = new TheoryData<string>() { Guid.NewGuid().ToString("N") };
Expand Down