From 4c474ea9a88d2d58befecc1a4780853fc2f58017 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Mon, 23 May 2016 10:42:08 -0700 Subject: Add named mutex stress tests - The existing PAL tests are run in parallel, each in a separate thread, for a specified duration --- .../threading/NamedMutex/test1/namedmutex.cpp | 447 +++++++++++++++------ 1 file changed, 313 insertions(+), 134 deletions(-) diff --git a/src/pal/tests/palsuite/threading/NamedMutex/test1/namedmutex.cpp b/src/pal/tests/palsuite/threading/NamedMutex/test1/namedmutex.cpp index 4d552cadec..3726214701 100644 --- a/src/pal/tests/palsuite/threading/NamedMutex/test1/namedmutex.cpp +++ b/src/pal/tests/palsuite/threading/NamedMutex/test1/namedmutex.cpp @@ -30,10 +30,12 @@ const char *const GlobalShmFilePathPrefix = "/tmp/.dotnet/shm/global/"; #define MaxPathSize (200) const DWORD PollLoopSleepMilliseconds = 100; const DWORD FailTimeoutMilliseconds = 30000; +DWORD g_expectedTimeoutMilliseconds = 500; -bool isParent; -char processPath[4096], processCommandLinePath[4096]; -DWORD parentPid = static_cast(-1); +bool g_isParent = true; +bool g_isStress = false; +char g_processPath[4096], g_processCommandLinePath[4096]; +DWORD g_parentPid = static_cast(-1); extern char *(*test_strcpy)(char *dest, const char *src); extern int (*test_strcmp)(const char *s1, const char *s2); @@ -56,7 +58,7 @@ extern bool WriteHeaderInfo(const char *path, char sharedMemoryType, char versio { \ if (!(expression)) \ { \ - if (!isParent) \ + if (!g_isParent) \ { \ Trace("Child process: "); \ } \ @@ -66,7 +68,7 @@ extern bool WriteHeaderInfo(const char *path, char sharedMemoryType, char versio } \ } while(false) -char *BuildName(char *buffer, const char *prefix0 = nullptr, const char *prefix1 = nullptr) +char *BuildName(const char *testName, char *buffer, const char *prefix0, const char *prefix1 = nullptr) { size_t nameLength = 0; const char *prefixes[] = {prefix0, prefix1}; @@ -80,18 +82,34 @@ char *BuildName(char *buffer, const char *prefix0 = nullptr, const char *prefix1 test_strcpy(&buffer[nameLength], prefix); nameLength += test_strlen(prefix); } - test_sprintf(&buffer[nameLength], "%u", parentPid); + + if (g_isStress) + { + // Append the test name so that tests can run in parallel + nameLength += test_sprintf(&buffer[nameLength], "%s", testName); + buffer[nameLength++] = '_'; + } + + nameLength += test_sprintf(&buffer[nameLength], "%u", g_parentPid); return buffer; } -char *BuildGlobalShmFilePath(char *buffer, const char *namePrefix) +char *BuildGlobalShmFilePath(const char *testName, char *buffer, const char *namePrefix) { size_t pathLength = 0; test_strcpy(&buffer[pathLength], GlobalShmFilePathPrefix); pathLength += test_strlen(GlobalShmFilePathPrefix); test_strcpy(&buffer[pathLength], namePrefix); pathLength += test_strlen(namePrefix); - test_sprintf(&buffer[pathLength], "%u", parentPid); + + if (g_isStress) + { + // Append the test name so that tests can run in parallel + pathLength += test_sprintf(&buffer[pathLength], "%s", testName); + buffer[pathLength++] = '_'; + } + + pathLength += test_sprintf(&buffer[pathLength], "%u", g_parentPid); return buffer; } @@ -167,41 +185,62 @@ HANDLE TestOpenMutex(const char *name) bool StartProcess(const char *funcName) { + // Command line format: [stress] + size_t processCommandLinePathLength = 0; - processCommandLinePath[processCommandLinePathLength++] = '\"'; - test_strcpy(&processCommandLinePath[processCommandLinePathLength], processPath); - processCommandLinePathLength += test_strlen(processPath); - processCommandLinePath[processCommandLinePathLength++] = '\"'; - processCommandLinePath[processCommandLinePathLength++] = ' '; - processCommandLinePathLength += test_sprintf(&processCommandLinePath[processCommandLinePathLength], "%u", parentPid); - processCommandLinePath[processCommandLinePathLength++] = ' '; - test_strcpy(&processCommandLinePath[processCommandLinePathLength], funcName); + g_processCommandLinePath[processCommandLinePathLength++] = '\"'; + test_strcpy(&g_processCommandLinePath[processCommandLinePathLength], g_processPath); + processCommandLinePathLength += test_strlen(g_processPath); + g_processCommandLinePath[processCommandLinePathLength++] = '\"'; + g_processCommandLinePath[processCommandLinePathLength++] = ' '; + processCommandLinePathLength += test_sprintf(&g_processCommandLinePath[processCommandLinePathLength], "%u", g_parentPid); + g_processCommandLinePath[processCommandLinePathLength++] = ' '; + test_strcpy(&g_processCommandLinePath[processCommandLinePathLength], funcName); processCommandLinePathLength += test_strlen(funcName); + if (g_isStress) + { + test_strcpy(&g_processCommandLinePath[processCommandLinePathLength], " stress"); + processCommandLinePathLength += _countof("stress") - 1; + } + STARTUPINFO si; memset(&si, 0, sizeof(si)); si.cb = sizeof(si); PROCESS_INFORMATION pi; memset(&pi, 0, sizeof(pi)); - return !!CreateProcessA(nullptr, processCommandLinePath, nullptr, nullptr, false, 0, nullptr, nullptr, &si, &pi); + if (!CreateProcessA(nullptr, g_processCommandLinePath, nullptr, nullptr, false, 0, nullptr, nullptr, &si, &pi)) + { + return false; + } + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return true; } -bool StartThread(LPTHREAD_START_ROUTINE func) +bool StartThread(LPTHREAD_START_ROUTINE func, void *arg = nullptr, HANDLE *threadHandleRef = nullptr) { DWORD threadId; - HANDLE handle = CreateThread(nullptr, 0, func, nullptr, 0, &threadId); + HANDLE handle = CreateThread(nullptr, 0, func, arg, 0, &threadId); if (handle != nullptr) { - CloseHandle(handle); + if (threadHandleRef == nullptr) + { + CloseHandle(handle); + } + else + { + *threadHandleRef = handle; + } return true; } return false; } -bool WaitForMutexToBeCreated(AutoCloseMutexHandle &m, const char *eventNamePrefix) +bool WaitForMutexToBeCreated(const char *testName, AutoCloseMutexHandle &m, const char *eventNamePrefix) { char eventName[MaxPathSize]; - BuildName(eventName, GlobalPrefix, eventNamePrefix); + BuildName(testName, eventName, GlobalPrefix, eventNamePrefix); DWORD startTime = GetTickCount(); while (true) { @@ -227,15 +266,15 @@ bool WaitForMutexToBeCreated(AutoCloseMutexHandle &m, const char *eventNamePrefi // YieldToParent() below control the releasing, waiting, and ping-ponging, to help create a deterministic path through the // parent and child tests while both are running concurrently. -bool AcquireChildRunningEvent(AutoCloseMutexHandle &childRunningEvent) +bool AcquireChildRunningEvent(const char *testName, AutoCloseMutexHandle &childRunningEvent) { char name[MaxPathSize]; - TestCreateMutex(childRunningEvent, BuildName(name, GlobalPrefix, ChildRunningEventNamePrefix)); + TestCreateMutex(childRunningEvent, BuildName(testName, name, GlobalPrefix, ChildRunningEventNamePrefix)); TestAssert(WaitForSingleObject(childRunningEvent, FailTimeoutMilliseconds) == WAIT_OBJECT_0); return true; } -bool InitializeParent(AutoCloseMutexHandle parentEvents[2], AutoCloseMutexHandle childEvents[2]) +bool InitializeParent(const char *testName, AutoCloseMutexHandle parentEvents[2], AutoCloseMutexHandle childEvents[2]) { // Create parent events char name[MaxPathSize]; @@ -243,19 +282,19 @@ bool InitializeParent(AutoCloseMutexHandle parentEvents[2], AutoCloseMutexHandle { TestCreateMutex( parentEvents[i], - BuildName(name, GlobalPrefix, i == 0 ? ParentEventNamePrefix0 : ParentEventNamePrefix1), + BuildName(testName, name, GlobalPrefix, i == 0 ? ParentEventNamePrefix0 : ParentEventNamePrefix1), true); TestAssert(parentEvents[i] != nullptr); TestAssert(GetLastError() != ERROR_ALREADY_EXISTS); } // Wait for the child to create and acquire locks on its events so that the parent can wait on them - TestAssert(WaitForMutexToBeCreated(childEvents[0], ChildEventNamePrefix0)); - TestAssert(WaitForMutexToBeCreated(childEvents[1], ChildEventNamePrefix1)); + TestAssert(WaitForMutexToBeCreated(testName, childEvents[0], ChildEventNamePrefix0)); + TestAssert(WaitForMutexToBeCreated(testName, childEvents[1], ChildEventNamePrefix1)); return true; } -bool UninitializeParent(AutoCloseMutexHandle parentEvents[2], bool releaseParentEvents = true) +bool UninitializeParent(const char *testName, AutoCloseMutexHandle parentEvents[2], bool releaseParentEvents = true) { if (releaseParentEvents) { @@ -267,17 +306,18 @@ bool UninitializeParent(AutoCloseMutexHandle parentEvents[2], bool releaseParent // 'childRunningEvent', so after this wait, the parent process can freely start another child that will deterministically // recreate the 'childEvents', which the next parent test will wait on, upon its initialization. AutoCloseMutexHandle childRunningEvent; - TestAssert(AcquireChildRunningEvent(childRunningEvent)); + TestAssert(AcquireChildRunningEvent(testName, childRunningEvent)); TestAssert(childRunningEvent.Release()); return true; } bool InitializeChild( + const char *testName, AutoCloseMutexHandle &childRunningEvent, AutoCloseMutexHandle parentEvents[2], AutoCloseMutexHandle childEvents[2]) { - TestAssert(AcquireChildRunningEvent(childRunningEvent)); + TestAssert(AcquireChildRunningEvent(testName, childRunningEvent)); // Create child events char name[MaxPathSize]; @@ -285,15 +325,15 @@ bool InitializeChild( { TestCreateMutex( childEvents[i], - BuildName(name, GlobalPrefix, i == 0 ? ChildEventNamePrefix0 : ChildEventNamePrefix1), + BuildName(testName, name, GlobalPrefix, i == 0 ? ChildEventNamePrefix0 : ChildEventNamePrefix1), true); TestAssert(childEvents[i] != nullptr); TestAssert(GetLastError() != ERROR_ALREADY_EXISTS); } // Wait for the parent to create and acquire locks on its events so that the child can wait on them - TestAssert(WaitForMutexToBeCreated(parentEvents[0], ParentEventNamePrefix0)); - TestAssert(WaitForMutexToBeCreated(parentEvents[1], ParentEventNamePrefix1)); + TestAssert(WaitForMutexToBeCreated(testName, parentEvents[0], ParentEventNamePrefix0)); + TestAssert(WaitForMutexToBeCreated(testName, parentEvents[1], ParentEventNamePrefix1)); // Parent/child tests start with the parent, so after initialization, wait for the parent to tell the child test to start TestAssert(WaitForSingleObject(parentEvents[0], FailTimeoutMilliseconds) == WAIT_OBJECT_0); @@ -343,6 +383,8 @@ bool YieldToParent(AutoCloseMutexHandle parentEvents[2], AutoCloseMutexHandle ch bool NameTests() { + const char *testName = "NameTests"; + AutoCloseMutexHandle m; char name[MaxPathSize]; @@ -351,15 +393,15 @@ bool NameTests() TestAssert(m != nullptr); // Normal name - TestCreateMutex(m, BuildName(name, NamePrefix)); + TestCreateMutex(m, BuildName(testName, name, NamePrefix)); TestAssert(m != nullptr); - TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(name, NamePrefix))) != nullptr); - TestCreateMutex(m, BuildName(name, SessionPrefix, NamePrefix)); + TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, NamePrefix))) != nullptr); + TestCreateMutex(m, BuildName(testName, name, SessionPrefix, NamePrefix)); TestAssert(m != nullptr); - TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(name, SessionPrefix, NamePrefix))) != nullptr); - TestCreateMutex(m, BuildName(name, GlobalPrefix, NamePrefix)); + TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, SessionPrefix, NamePrefix))) != nullptr); + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); TestAssert(m != nullptr); - TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(name, GlobalPrefix, NamePrefix))) != nullptr); + TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, GlobalPrefix, NamePrefix))) != nullptr); // Name too long. The maximum allowed length depends on the file system, so we're not checking for that. { @@ -374,33 +416,33 @@ bool NameTests() } // Invalid characters in name - TestCreateMutex(m, BuildName(name, InvalidNamePrefix0)); + TestCreateMutex(m, BuildName(testName, name, InvalidNamePrefix0)); TestAssert(m == nullptr); TestAssert(GetLastError() == ERROR_INVALID_NAME); - TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(name, InvalidNamePrefix0))) == nullptr); + TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, InvalidNamePrefix0))) == nullptr); TestAssert(GetLastError() == ERROR_INVALID_NAME); - TestCreateMutex(m, BuildName(name, InvalidNamePrefix1)); + TestCreateMutex(m, BuildName(testName, name, InvalidNamePrefix1)); TestAssert(m == nullptr); TestAssert(GetLastError() == ERROR_INVALID_NAME); - TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(name, InvalidNamePrefix1))) == nullptr); + TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, InvalidNamePrefix1))) == nullptr); TestAssert(GetLastError() == ERROR_INVALID_NAME); - TestCreateMutex(m, BuildName(name, SessionPrefix, InvalidNamePrefix0)); + TestCreateMutex(m, BuildName(testName, name, SessionPrefix, InvalidNamePrefix0)); TestAssert(m == nullptr); TestAssert(GetLastError() == ERROR_INVALID_NAME); - TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(name, SessionPrefix, InvalidNamePrefix0))) == nullptr); + TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, SessionPrefix, InvalidNamePrefix0))) == nullptr); TestAssert(GetLastError() == ERROR_INVALID_NAME); - TestCreateMutex(m, BuildName(name, GlobalPrefix, InvalidNamePrefix1)); + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, InvalidNamePrefix1)); TestAssert(m == nullptr); TestAssert(GetLastError() == ERROR_INVALID_NAME); - TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(name, GlobalPrefix, InvalidNamePrefix1))) == nullptr); + TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, GlobalPrefix, InvalidNamePrefix1))) == nullptr); TestAssert(GetLastError() == ERROR_INVALID_NAME); // Creating a second reference to the same named mutex yields an error indicating that it was opened, not created { - TestCreateMutex(m, BuildName(name, NamePrefix)); + TestCreateMutex(m, BuildName(testName, name, NamePrefix)); TestAssert(m != nullptr); AutoCloseMutexHandle m2; - TestCreateMutex(m2, BuildName(name, NamePrefix)); + TestCreateMutex(m2, BuildName(testName, name, NamePrefix)); TestAssert(m2 != nullptr); TestAssert(GetLastError() == ERROR_ALREADY_EXISTS); } @@ -410,41 +452,46 @@ bool NameTests() bool HeaderMismatchTests() { + const char *testName = "HeaderMismatchTests"; + AutoCloseMutexHandle m, m2; char name[MaxPathSize]; int fd; // Create and hold onto a mutex during this test to create the shared memory directory - TestCreateMutex(m2, BuildName(name, GlobalPrefix, TempNamePrefix)); + TestCreateMutex(m2, BuildName(testName, name, GlobalPrefix, TempNamePrefix)); + TestAssert(m2 != nullptr); // Unknown shared memory type - TestAssert(WriteHeaderInfo(BuildGlobalShmFilePath(name, NamePrefix), -1, 0, &fd)); - TestCreateMutex(m, BuildName(name, GlobalPrefix, NamePrefix)); + TestAssert(WriteHeaderInfo(BuildGlobalShmFilePath(testName, name, NamePrefix), -1, 1, &fd)); + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); TestAssert(m == nullptr); TestAssert(GetLastError() == ERROR_INVALID_HANDLE); TestAssert(test_close(fd) == 0); - TestAssert(test_unlink(BuildGlobalShmFilePath(name, NamePrefix)) == 0); + TestAssert(test_unlink(BuildGlobalShmFilePath(testName, name, NamePrefix)) == 0); // Mismatched version - TestAssert(WriteHeaderInfo(BuildGlobalShmFilePath(name, NamePrefix), 0, -1, &fd)); - TestCreateMutex(m, BuildName(name, GlobalPrefix, NamePrefix)); + TestAssert(WriteHeaderInfo(BuildGlobalShmFilePath(testName, name, NamePrefix), 0, -1, &fd)); + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); TestAssert(m == nullptr); TestAssert(GetLastError() == ERROR_INVALID_HANDLE); TestAssert(test_close(fd) == 0); - TestAssert(test_unlink(BuildGlobalShmFilePath(name, NamePrefix)) == 0); + TestAssert(test_unlink(BuildGlobalShmFilePath(testName, name, NamePrefix)) == 0); return true; } bool MutualExclusionTests_Parent() { + const char *testName = "MutualExclusionTests"; + AutoCloseMutexHandle parentEvents[2], childEvents[2]; - TestAssert(InitializeParent(parentEvents, childEvents)); + TestAssert(InitializeParent(testName, parentEvents, childEvents)); int ei = 0; char name[MaxPathSize]; AutoCloseMutexHandle m; - TestCreateMutex(m, BuildName(name, GlobalPrefix, NamePrefix)); + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); TestAssert(m != nullptr); // Recursive locking with various timeouts @@ -460,7 +507,7 @@ bool MutualExclusionTests_Parent() TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child takes the lock TestAssert(WaitForSingleObject(m, 0) == WAIT_TIMEOUT); // try to lock the mutex without waiting - TestAssert(WaitForSingleObject(m, 500) == WAIT_TIMEOUT); // try to lock the mutex with a timeout + TestAssert(WaitForSingleObject(m, g_expectedTimeoutMilliseconds) == WAIT_TIMEOUT); // try to lock the mutex with a timeout TestAssert(!m.Release()); // try to release the lock while another thread owns it TestAssert(GetLastError() == ERROR_NOT_OWNER); @@ -469,21 +516,23 @@ bool MutualExclusionTests_Parent() TestAssert(WaitForSingleObject(m, static_cast(-1)) == WAIT_OBJECT_0); // lock the mutex with no timeout and release TestAssert(m.Release()); - UninitializeParent(parentEvents); + UninitializeParent(testName, parentEvents); return true; } DWORD MutualExclusionTests_Child(void *arg = nullptr) { + const char *testName = "MutualExclusionTests"; + AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2]; - TestAssert(InitializeChild(childRunningEvent, parentEvents, childEvents)); + TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents)); int ei = 0; { char name[MaxPathSize]; AutoCloseMutexHandle m; - TestCreateMutex(m, BuildName(name, GlobalPrefix, NamePrefix)); + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); TestAssert(m != nullptr); TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0); // lock the mutex YieldToParent(parentEvents, childEvents, ei); // parent attempts to lock/release, and fails @@ -496,18 +545,20 @@ DWORD MutualExclusionTests_Child(void *arg = nullptr) bool MutualExclusionTests() { + const char *testName = "MutualExclusionTests"; + { AutoCloseMutexHandle m; char name[MaxPathSize]; // Releasing a lock that is not owned by any thread fails - TestCreateMutex(m, BuildName(name, NamePrefix)); + TestCreateMutex(m, BuildName(testName, name, NamePrefix)); TestAssert(m != nullptr); TestAssert(!m.Release()); TestAssert(GetLastError() == ERROR_NOT_OWNER); // Acquire a lock during upon creation, and release - TestCreateMutex(m, BuildName(name, NamePrefix), true); + TestCreateMutex(m, BuildName(testName, name, NamePrefix), true); TestAssert(m != nullptr); TestAssert(m.Release()); @@ -545,38 +596,42 @@ bool MutualExclusionTests() bool LifetimeTests_Parent() { + const char *testName = "LifetimeTests"; + AutoCloseMutexHandle parentEvents[2], childEvents[2]; - TestAssert(InitializeParent(parentEvents, childEvents)); + TestAssert(InitializeParent(testName, parentEvents, childEvents)); int ei = 0; char name[MaxPathSize]; AutoCloseMutexHandle m; - TestCreateMutex(m, BuildName(name, GlobalPrefix, NamePrefix)); // create first reference to mutex + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); // create first reference to mutex TestAssert(m != nullptr); - TestAssert(TestFileExists(BuildGlobalShmFilePath(name, NamePrefix))); + TestAssert(TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix))); TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child creates second reference to mutex using CreateMutex m.Close(); // close first reference - TestAssert(TestFileExists(BuildGlobalShmFilePath(name, NamePrefix))); + TestAssert(TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix))); TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child closes second reference - TestAssert(!TestFileExists(BuildGlobalShmFilePath(name, NamePrefix))); + TestAssert(!TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix))); - TestCreateMutex(m, BuildName(name, GlobalPrefix, NamePrefix)); // create first reference to mutex + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); // create first reference to mutex TestAssert(m != nullptr); - TestAssert(TestFileExists(BuildGlobalShmFilePath(name, NamePrefix))); + TestAssert(TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix))); TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child creates second reference to mutex using OpenMutex m.Close(); // close first reference - TestAssert(TestFileExists(BuildGlobalShmFilePath(name, NamePrefix))); + TestAssert(TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix))); TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child closes second reference - TestAssert(!TestFileExists(BuildGlobalShmFilePath(name, NamePrefix))); + TestAssert(!TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix))); - UninitializeParent(parentEvents); + UninitializeParent(testName, parentEvents); return true; } DWORD LifetimeTests_Child(void *arg = nullptr) { + const char *testName = "LifetimeTests"; + AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2]; - TestAssert(InitializeChild(childRunningEvent, parentEvents, childEvents)); + TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents)); int ei = 0; { @@ -584,13 +639,13 @@ DWORD LifetimeTests_Child(void *arg = nullptr) AutoCloseMutexHandle m; // ... parent creates first reference to mutex - TestCreateMutex(m, BuildName(name, GlobalPrefix, NamePrefix)); // create second reference to mutex using CreateMutex + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); // create second reference to mutex using CreateMutex TestAssert(m != nullptr); TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent closes first reference m.Close(); // close second reference TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent verifies, and creates first reference to mutex again - m = TestOpenMutex(BuildName(name, GlobalPrefix, NamePrefix)); // create second reference to mutex using OpenMutex + m = TestOpenMutex(BuildName(testName, name, GlobalPrefix, NamePrefix)); // create second reference to mutex using OpenMutex TestAssert(m != nullptr); TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent closes first reference m.Close(); // close second reference @@ -604,16 +659,18 @@ DWORD LifetimeTests_Child(void *arg = nullptr) bool LifetimeTests() { + const char *testName = "LifetimeTests"; + { AutoCloseMutexHandle m; char name[MaxPathSize]; // Shm file should be created and deleted - TestCreateMutex(m, BuildName(name, GlobalPrefix, NamePrefix)); + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); TestAssert(m != nullptr); - TestAssert(TestFileExists(BuildGlobalShmFilePath(name, NamePrefix))); + TestAssert(TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix))); m.Close(); - TestAssert(!TestFileExists(BuildGlobalShmFilePath(name, NamePrefix))); + TestAssert(!TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix))); } // Shm file should not be deleted until last reference is released @@ -629,39 +686,41 @@ DWORD AbandonTests_Child_TryLock(void *arg = nullptr); bool AbandonTests_Parent() { + const char *testName = "AbandonTests"; + + char name[MaxPathSize]; AutoCloseMutexHandle m; { AutoCloseMutexHandle parentEvents[2], childEvents[2]; - TestAssert(InitializeParent(parentEvents, childEvents)); + TestAssert(InitializeParent(testName, parentEvents, childEvents)); int ei = 0; - char name[MaxPathSize]; - TestCreateMutex(m, BuildName(name, GlobalPrefix, NamePrefix)); + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); TestAssert(m != nullptr); TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child locks mutex TestAssert(parentEvents[0].Release()); TestAssert(parentEvents[1].Release()); // child sleeps for short duration and abandons the mutex TestAssert(WaitForSingleObject(m, FailTimeoutMilliseconds) == WAIT_ABANDONED_0); // attempt to lock and see abandoned mutex - UninitializeParent(parentEvents, false /* releaseParentEvents */); // parent events are released above + UninitializeParent(testName, parentEvents, false /* releaseParentEvents */); // parent events are released above } // Verify that the mutex lock is owned by this thread, by starting a new thread and trying to lock it StartThread(AbandonTests_Child_TryLock); { AutoCloseMutexHandle parentEvents[2], childEvents[2]; - TestAssert(InitializeParent(parentEvents, childEvents)); + TestAssert(InitializeParent(testName, parentEvents, childEvents)); int ei = 0; TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child tries to lock mutex - UninitializeParent(parentEvents); + UninitializeParent(testName, parentEvents); } // Verify that the mutex lock is owned by this thread, by starting a new process and trying to lock it StartProcess("AbandonTests_Child_TryLock"); AutoCloseMutexHandle parentEvents[2], childEvents[2]; - TestAssert(InitializeParent(parentEvents, childEvents)); + TestAssert(InitializeParent(testName, parentEvents, childEvents)); int ei = 0; TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child tries to lock mutex @@ -671,14 +730,33 @@ bool AbandonTests_Parent() TestAssert(WaitForSingleObject(m, FailTimeoutMilliseconds) == WAIT_OBJECT_0); // lock again to see it's not abandoned anymore TestAssert(m.Release()); - UninitializeParent(parentEvents, false /* releaseParentEvents */); // parent events are released above + UninitializeParent(testName, parentEvents, false /* releaseParentEvents */); // parent events are released above + + // Since the child abandons the mutex, and a child process may not release the file lock on the shared memory file before + // indicating completion to the parent, make sure to delete the shared memory file by repeatedly opening/closing the mutex + // until the parent process becomes the last process to reference the mutex and closing it deletes the file. + DWORD startTime = GetTickCount(); + while (true) + { + m.Close(); + if (!TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix))) + { + break; + } + + TestAssert(GetTickCount() - startTime < FailTimeoutMilliseconds); + m = TestOpenMutex(BuildName(testName, name, GlobalPrefix, NamePrefix)); + } + return true; } -DWORD AbandonTests_Child_GracefulExit(void *arg = nullptr) +DWORD AbandonTests_Child_GracefulExit_Close(void *arg = nullptr) { + const char *testName = "AbandonTests"; + AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2]; - TestAssert(InitializeChild(childRunningEvent, parentEvents, childEvents)); + TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents)); int ei = 0; { @@ -686,22 +764,28 @@ DWORD AbandonTests_Child_GracefulExit(void *arg = nullptr) AutoCloseMutexHandle m; // ... parent waits for child to lock mutex - TestCreateMutex(m, BuildName(name, GlobalPrefix, NamePrefix)); + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); TestAssert(m != nullptr); TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0); TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent waits on mutex - Sleep(500); // wait for parent to wait on mutex - m.Abandon(); // don't close the mutex + Sleep(g_expectedTimeoutMilliseconds); // wait for parent to wait on mutex + m.Close(); // close mutex without releasing lock } UninitializeChild(childRunningEvent, parentEvents, childEvents); return 0; } -DWORD AbandonTests_Child_GracefulExit_CloseBeforeRelease(void *arg = nullptr) +DWORD AbandonTests_Child_GracefulExit_NoClose(void *arg = nullptr) { + const char *testName = "AbandonTests"; + + // This test needs to run in a separate process because it does not close the mutex handle. Running it in a separate thread + // causes the mutex object to retain a reference until the process terminates. + TestAssert(test_getpid() != g_parentPid); + AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2]; - TestAssert(InitializeChild(childRunningEvent, parentEvents, childEvents)); + TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents)); int ei = 0; { @@ -709,12 +793,12 @@ DWORD AbandonTests_Child_GracefulExit_CloseBeforeRelease(void *arg = nullptr) AutoCloseMutexHandle m; // ... parent waits for child to lock mutex - TestCreateMutex(m, BuildName(name, GlobalPrefix, NamePrefix)); + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); TestAssert(m != nullptr); TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0); TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent waits on mutex - Sleep(500); // wait for parent to wait on mutex - m.Close(); // close mutex before releasing lock + Sleep(g_expectedTimeoutMilliseconds); // wait for parent to wait on mutex + m.Abandon(); // don't close the mutex } UninitializeChild(childRunningEvent, parentEvents, childEvents); @@ -723,12 +807,14 @@ DWORD AbandonTests_Child_GracefulExit_CloseBeforeRelease(void *arg = nullptr) DWORD AbandonTests_Child_AbruptExit(void *arg = nullptr) { + const char *testName = "AbandonTests"; + DWORD currentPid = test_getpid(); - TestAssert(currentPid != parentPid); // this test needs to run in a separate process + TestAssert(currentPid != g_parentPid); // this test needs to run in a separate process { AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2]; - TestAssert(InitializeChild(childRunningEvent, parentEvents, childEvents)); + TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents)); int ei = 0; { @@ -736,11 +822,11 @@ DWORD AbandonTests_Child_AbruptExit(void *arg = nullptr) AutoCloseMutexHandle m; // ... parent waits for child to lock mutex - TestCreateMutex(m, BuildName(name, GlobalPrefix, NamePrefix)); + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); TestAssert(m != nullptr); TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0); TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent waits on mutex - Sleep(500); // wait for parent to wait on mutex + Sleep(g_expectedTimeoutMilliseconds); // wait for parent to wait on mutex m.Abandon(); // don't close the mutex } @@ -753,8 +839,10 @@ DWORD AbandonTests_Child_AbruptExit(void *arg = nullptr) DWORD AbandonTests_Child_TryLock(void *arg) { + const char *testName = "AbandonTests"; + AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2]; - TestAssert(InitializeChild(childRunningEvent, parentEvents, childEvents)); + TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents)); int ei = 0; { @@ -762,10 +850,10 @@ DWORD AbandonTests_Child_TryLock(void *arg) AutoCloseMutexHandle m; // ... parent waits for child to lock mutex - TestCreateMutex(m, BuildName(name, GlobalPrefix, NamePrefix)); + TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); TestAssert(m != nullptr); TestAssert(WaitForSingleObject(m, 0) == WAIT_TIMEOUT); // try to lock the mutex while the parent holds the lock - TestAssert(WaitForSingleObject(m, 500) == WAIT_TIMEOUT); + TestAssert(WaitForSingleObject(m, g_expectedTimeoutMilliseconds) == WAIT_TIMEOUT); } UninitializeChild(childRunningEvent, parentEvents, childEvents); @@ -774,16 +862,16 @@ DWORD AbandonTests_Child_TryLock(void *arg) bool AbandonTests() { - // Abandon by graceful exit unblocks a waiter - TestAssert(StartThread(AbandonTests_Child_GracefulExit)); - TestAssert(AbandonTests_Parent()); - TestAssert(StartProcess("AbandonTests_Child_GracefulExit")); - TestAssert(AbandonTests_Parent()); + const char *testName = "AbandonTests"; // Abandon by graceful exit where the lock owner closes the mutex before releasing it, unblocks a waiter - TestAssert(StartThread(AbandonTests_Child_GracefulExit_CloseBeforeRelease)); + TestAssert(StartThread(AbandonTests_Child_GracefulExit_Close)); TestAssert(AbandonTests_Parent()); - TestAssert(StartProcess("AbandonTests_Child_GracefulExit_CloseBeforeRelease")); + TestAssert(StartProcess("AbandonTests_Child_GracefulExit_Close")); + TestAssert(AbandonTests_Parent()); + + // Abandon by graceful exit without closing the mutex unblocks a waiter + TestAssert(StartProcess("AbandonTests_Child_GracefulExit_NoClose")); TestAssert(AbandonTests_Parent()); // Abandon by abrupt exit unblocks a waiter @@ -796,21 +884,21 @@ bool AbandonTests() //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Test harness -bool RunTests() +bool (*const (TestList[]))() = { - bool (*(testList[]))() = - { - NameTests, - HeaderMismatchTests, - MutualExclusionTests, - LifetimeTests, - AbandonTests - }; + NameTests, + HeaderMismatchTests, + MutualExclusionTests, + LifetimeTests, + AbandonTests +}; +bool RunTests() +{ bool allPassed = true; - for (int i = 0; i < _countof(testList); ++i) + for (SIZE_T i = 0; i < _countof(TestList); ++i) { - if (!testList[i]()) + if (!TestList[i]()) { allPassed = false; } @@ -818,9 +906,75 @@ bool RunTests() return allPassed; } +DWORD g_stressDurationMilliseconds = 0; +LONG g_stressTestCounts[_countof(TestList)] = {0}; +LONG g_stressResult = true; + +DWORD StressTest(void *arg) +{ + // Run the specified test continuously for the stress duration + SIZE_T testIndex = reinterpret_cast(arg); + DWORD startTime = GetTickCount(); + do + { + ++g_stressTestCounts[testIndex]; + if (!TestList[testIndex]()) + { + InterlockedExchange(&g_stressResult, false); + break; + } + } while ( + InterlockedCompareExchange(&g_stressResult, false, false) == true && + GetTickCount() - startTime < g_stressDurationMilliseconds); + return 0; +} + +bool StressTests(DWORD durationMinutes) +{ + g_isStress = true; + g_expectedTimeoutMilliseconds = 1; + g_stressDurationMilliseconds = durationMinutes * (60 * 1000); + + // Start a thread for each test + HANDLE threadHandles[_countof(TestList)]; + for (SIZE_T i = 0; i < _countof(threadHandles); ++i) + { + TestAssert(StartThread(StressTest, reinterpret_cast(i), &threadHandles[i])); + } + + while (true) + { + DWORD waitResult = + WaitForMultipleObjects(_countof(threadHandles), threadHandles, true /* bWaitAll */, 10 * 1000 /* dwMilliseconds */); + TestAssert(waitResult == WAIT_OBJECT_0 || waitResult == WAIT_TIMEOUT); + if (waitResult == WAIT_OBJECT_0) + { + break; + } + + Trace("'paltest_namedmutex_test1' stress test counts: "); + for (SIZE_T i = 0; i < _countof(g_stressTestCounts); ++i) + { + if (i != 0) + { + Trace(", "); + } + Trace("%u", g_stressTestCounts[i]); + } + Trace("\n"); + fflush(stdout); + } + + for (SIZE_T i = 0; i < _countof(threadHandles); ++i) + { + CloseHandle(threadHandles[i]); + } + return static_cast(g_stressResult); +} + int __cdecl main(int argc, char **argv) { - if (argc != 1 && argc != 3) + if (argc < 1 || argc > 4) { return FAIL; } @@ -830,26 +984,51 @@ int __cdecl main(int argc, char **argv) return FAIL; } - test_strcpy(processPath, argv[0]); + test_strcpy(g_processPath, argv[0]); + if (argc == 1) { - isParent = true; - parentPid = test_getpid(); + // Unit test arguments: + g_parentPid = test_getpid(); int result = RunTests() ? PASS : FAIL; ExitProcess(result); return result; } - isParent = false; + if (test_strcmp(argv[1], "stress") == 0) + { + // Stress test arguments: stress [durationMinutes] + + DWORD durationMinutes = 1; + if (argc >= 3 && test_sscanf(argv[2], "%u", &durationMinutes) != 1) + { + ExitProcess(FAIL); + return FAIL; + } + + g_parentPid = test_getpid(); + int result = StressTests(durationMinutes) ? PASS : FAIL; + ExitProcess(result); + return result; + } + + // Child test process arguments: [stress] + + g_isParent = false; // Get parent process' ID from argument - if (test_sscanf(argv[1], "%u", &parentPid) != 1) + if (test_sscanf(argv[1], "%u", &g_parentPid) != 1) { ExitProcess(FAIL); return FAIL; } + if (argc >= 4 && test_strcmp(argv[3], "stress") == 0) + { + g_isStress = true; + } + if (test_strcmp(argv[2], "MutualExclusionTests_Child") == 0) { MutualExclusionTests_Child(); @@ -858,13 +1037,13 @@ int __cdecl main(int argc, char **argv) { LifetimeTests_Child(); } - else if (test_strcmp(argv[2], "AbandonTests_Child_GracefulExit") == 0) + else if (test_strcmp(argv[2], "AbandonTests_Child_GracefulExit_Close") == 0) { - AbandonTests_Child_GracefulExit(); + AbandonTests_Child_GracefulExit_Close(); } - else if (test_strcmp(argv[2], "AbandonTests_Child_GracefulExit_CloseBeforeRelease") == 0) + else if (test_strcmp(argv[2], "AbandonTests_Child_GracefulExit_NoClose") == 0) { - AbandonTests_Child_GracefulExit_CloseBeforeRelease(); + AbandonTests_Child_GracefulExit_NoClose(); } else if (test_strcmp(argv[2], "AbandonTests_Child_AbruptExit") == 0) { -- cgit v1.2.3