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// These test cases test named mutexes, including positive
6// and negative cases, cross - thread and cross - process, mutual
7// exclusion, abandon detection, etc.
8
9#include <palsuite.h>
10
11#ifndef _countof
12#define _countof(a) (sizeof(a) / sizeof(a[0]))
13#endif // !_countof
14
15const char *const SessionPrefix = "Local\\";
16const char *const GlobalPrefix = "Global\\";
17
18const char *const NamePrefix = "paltest_namedmutex_test1_";
19const char *const TempNamePrefix = "paltest_namedmutex_test1_temp_";
20const char *const InvalidNamePrefix0 = "paltest\\namedmutex_";
21const char *const InvalidNamePrefix1 = "paltest/namedmutex_";
22const char *const ParentEventNamePrefix0 = "paltest_namedmutex_test1_pe0_";
23const char *const ParentEventNamePrefix1 = "paltest_namedmutex_test1_pe1_";
24const char *const ChildEventNamePrefix0 = "paltest_namedmutex_test1_ce0_";
25const char *const ChildEventNamePrefix1 = "paltest_namedmutex_test1_ce1_";
26const char *const ChildRunningEventNamePrefix = "paltest_namedmutex_test1_cr_";
27
28const char *const GlobalShmFilePathPrefix = "/tmp/.dotnet/shm/global/";
29
30#define MaxPathSize (200)
31const DWORD PollLoopSleepMilliseconds = 100;
32const DWORD FailTimeoutMilliseconds = 30000;
33DWORD g_expectedTimeoutMilliseconds = 500;
34
35bool g_isParent = true;
36bool g_isStress = false;
37char g_processPath[4096], g_processCommandLinePath[4096];
38DWORD g_parentPid = static_cast<DWORD>(-1);
39
40extern char *(*test_strcpy)(char *dest, const char *src);
41extern int (*test_strcmp)(const char *s1, const char *s2);
42extern size_t (*test_strlen)(const char *s);
43extern int (*test_sprintf)(char *str, const char *format, ...);
44extern int (*test_sscanf)(const char *str, const char *format, ...);
45extern int(*test_close)(int fd);
46extern int (*test_unlink)(const char *pathname);
47extern unsigned int test_getpid();
48extern int test_kill(unsigned int pid);
49
50////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
51// Test helpers
52
53extern bool TestFileExists(const char *path);
54extern bool WriteHeaderInfo(const char *path, char sharedMemoryType, char version, int *fdRef);
55
56#define TestAssert(expression) \
57 do \
58 { \
59 if (!(expression)) \
60 { \
61 if (!g_isParent) \
62 { \
63 Trace("Child process: "); \
64 } \
65 Trace("'paltest_namedmutex_test1' failed at line %u. Expression: " #expression "\n", __LINE__); \
66 fflush(stdout); \
67 return false; \
68 } \
69 } while(false)
70
71char *BuildName(const char *testName, char *buffer, const char *prefix0, const char *prefix1 = nullptr)
72{
73 size_t nameLength = 0;
74 const char *prefixes[] = {prefix0, prefix1};
75 for (int i = 0; i < 2; ++i)
76 {
77 const char *prefix = prefixes[i];
78 if (prefix == nullptr)
79 {
80 break;
81 }
82 test_strcpy(&buffer[nameLength], prefix);
83 nameLength += test_strlen(prefix);
84 }
85
86 if (g_isStress)
87 {
88 // Append the test name so that tests can run in parallel
89 nameLength += test_sprintf(&buffer[nameLength], "%s", testName);
90 buffer[nameLength++] = '_';
91 }
92
93 nameLength += test_sprintf(&buffer[nameLength], "%u", g_parentPid);
94 return buffer;
95}
96
97char *BuildGlobalShmFilePath(const char *testName, char *buffer, const char *namePrefix)
98{
99 size_t pathLength = 0;
100 test_strcpy(&buffer[pathLength], GlobalShmFilePathPrefix);
101 pathLength += test_strlen(GlobalShmFilePathPrefix);
102 test_strcpy(&buffer[pathLength], namePrefix);
103 pathLength += test_strlen(namePrefix);
104
105 if (g_isStress)
106 {
107 // Append the test name so that tests can run in parallel
108 pathLength += test_sprintf(&buffer[pathLength], "%s", testName);
109 buffer[pathLength++] = '_';
110 }
111
112 pathLength += test_sprintf(&buffer[pathLength], "%u", g_parentPid);
113 return buffer;
114}
115
116class AutoCloseMutexHandle
117{
118private:
119 HANDLE m_handle;
120
121public:
122 AutoCloseMutexHandle(HANDLE handle = nullptr) : m_handle(handle)
123 {
124 }
125
126 ~AutoCloseMutexHandle()
127 {
128 Close();
129 }
130
131public:
132 HANDLE GetHandle() const
133 {
134 return m_handle;
135 }
136
137 bool Release()
138 {
139 return !!ReleaseMutex(m_handle);
140 }
141
142 void Close()
143 {
144 if (m_handle != nullptr)
145 {
146 CloseHandle(m_handle);
147 m_handle = nullptr;
148 }
149 }
150
151 void Abandon()
152 {
153 // Don't close the handle
154 m_handle = nullptr;
155 }
156
157 AutoCloseMutexHandle &operator =(HANDLE handle)
158 {
159 Close();
160 m_handle = handle;
161 return *this;
162 }
163
164 operator HANDLE() const
165 {
166 return m_handle;
167 }
168
169private:
170 AutoCloseMutexHandle(const AutoCloseMutexHandle &other);
171 AutoCloseMutexHandle(AutoCloseMutexHandle &&other);
172 AutoCloseMutexHandle &operator =(const AutoCloseMutexHandle &other);
173};
174
175void TestCreateMutex(AutoCloseMutexHandle &m, const char *name, bool initiallyOwned = false)
176{
177 m.Close();
178 m = CreateMutexA(nullptr, initiallyOwned, name);
179}
180
181HANDLE TestOpenMutex(const char *name)
182{
183 return OpenMutexA(SYNCHRONIZE, false, name);
184}
185
186bool StartProcess(const char *funcName)
187{
188 // Command line format: <processPath> <parentPid> <testFunctionName> [stress]
189
190 size_t processCommandLinePathLength = 0;
191 g_processCommandLinePath[processCommandLinePathLength++] = '\"';
192 test_strcpy(&g_processCommandLinePath[processCommandLinePathLength], g_processPath);
193 processCommandLinePathLength += test_strlen(g_processPath);
194 g_processCommandLinePath[processCommandLinePathLength++] = '\"';
195 g_processCommandLinePath[processCommandLinePathLength++] = ' ';
196 processCommandLinePathLength += test_sprintf(&g_processCommandLinePath[processCommandLinePathLength], "%u", g_parentPid);
197 g_processCommandLinePath[processCommandLinePathLength++] = ' ';
198 test_strcpy(&g_processCommandLinePath[processCommandLinePathLength], funcName);
199 processCommandLinePathLength += test_strlen(funcName);
200
201 if (g_isStress)
202 {
203 test_strcpy(&g_processCommandLinePath[processCommandLinePathLength], " stress");
204 processCommandLinePathLength += _countof("stress") - 1;
205 }
206
207 STARTUPINFO si;
208 memset(&si, 0, sizeof(si));
209 si.cb = sizeof(si);
210 PROCESS_INFORMATION pi;
211 memset(&pi, 0, sizeof(pi));
212 if (!CreateProcessA(nullptr, g_processCommandLinePath, nullptr, nullptr, false, 0, nullptr, nullptr, &si, &pi))
213 {
214 return false;
215 }
216 CloseHandle(pi.hProcess);
217 CloseHandle(pi.hThread);
218 return true;
219}
220
221bool StartThread(LPTHREAD_START_ROUTINE func, void *arg = nullptr, HANDLE *threadHandleRef = nullptr)
222{
223 DWORD threadId;
224 HANDLE handle = CreateThread(nullptr, 0, func, arg, 0, &threadId);
225 if (handle != nullptr)
226 {
227 if (threadHandleRef == nullptr)
228 {
229 CloseHandle(handle);
230 }
231 else
232 {
233 *threadHandleRef = handle;
234 }
235 return true;
236 }
237 return false;
238}
239
240bool WaitForMutexToBeCreated(const char *testName, AutoCloseMutexHandle &m, const char *eventNamePrefix)
241{
242 char eventName[MaxPathSize];
243 BuildName(testName, eventName, GlobalPrefix, eventNamePrefix);
244 DWORD startTime = GetTickCount();
245 while (true)
246 {
247 m = TestOpenMutex(eventName);
248 if (m != nullptr)
249 {
250 return true;
251 }
252 if (GetTickCount() - startTime >= FailTimeoutMilliseconds)
253 {
254 return false;
255 }
256 Sleep(PollLoopSleepMilliseconds);
257 }
258}
259
260// The following functions are used for parent/child tests, where the child runs in a separate thread or process. The tests are
261// organized such that one the parent or child is ever running code, and they yield control and wait for the other. Since the
262// named mutex is the only type of cross-process sync object available, they are used as events to synchronize. The parent and
263// child have a pair of event mutexes each, which they own initially. To release the other waiting thread/process, the
264// thread/process releases one of its mutexes, which the other thread/process would be waiting on. To wait, the thread/process
265// waits on one of the other thread/process' mutexes. All the while, they ping-pong between the two mutexes. YieldToChild() and
266// YieldToParent() below control the releasing, waiting, and ping-ponging, to help create a deterministic path through the
267// parent and child tests while both are running concurrently.
268
269bool AcquireChildRunningEvent(const char *testName, AutoCloseMutexHandle &childRunningEvent)
270{
271 char name[MaxPathSize];
272 TestCreateMutex(childRunningEvent, BuildName(testName, name, GlobalPrefix, ChildRunningEventNamePrefix));
273 TestAssert(WaitForSingleObject(childRunningEvent, FailTimeoutMilliseconds) == WAIT_OBJECT_0);
274 return true;
275}
276
277bool InitializeParent(const char *testName, AutoCloseMutexHandle parentEvents[2], AutoCloseMutexHandle childEvents[2])
278{
279 // Create parent events
280 char name[MaxPathSize];
281 for (int i = 0; i < 2; ++i)
282 {
283 TestCreateMutex(
284 parentEvents[i],
285 BuildName(testName, name, GlobalPrefix, i == 0 ? ParentEventNamePrefix0 : ParentEventNamePrefix1),
286 true);
287 TestAssert(parentEvents[i] != nullptr);
288 TestAssert(GetLastError() != ERROR_ALREADY_EXISTS);
289 }
290
291 // Wait for the child to create and acquire locks on its events so that the parent can wait on them
292 TestAssert(WaitForMutexToBeCreated(testName, childEvents[0], ChildEventNamePrefix0));
293 TestAssert(WaitForMutexToBeCreated(testName, childEvents[1], ChildEventNamePrefix1));
294 return true;
295}
296
297bool UninitializeParent(const char *testName, AutoCloseMutexHandle parentEvents[2], bool releaseParentEvents = true)
298{
299 if (releaseParentEvents)
300 {
301 TestAssert(parentEvents[0].Release());
302 TestAssert(parentEvents[1].Release());
303 }
304
305 // Wait for the child to finish its test. Child tests will release and close 'childEvents' before releasing
306 // 'childRunningEvent', so after this wait, the parent process can freely start another child that will deterministically
307 // recreate the 'childEvents', which the next parent test will wait on, upon its initialization.
308 AutoCloseMutexHandle childRunningEvent;
309 TestAssert(AcquireChildRunningEvent(testName, childRunningEvent));
310 TestAssert(childRunningEvent.Release());
311 return true;
312}
313
314bool InitializeChild(
315 const char *testName,
316 AutoCloseMutexHandle &childRunningEvent,
317 AutoCloseMutexHandle parentEvents[2],
318 AutoCloseMutexHandle childEvents[2])
319{
320 TestAssert(AcquireChildRunningEvent(testName, childRunningEvent));
321
322 // Create child events
323 char name[MaxPathSize];
324 for (int i = 0; i < 2; ++i)
325 {
326 TestCreateMutex(
327 childEvents[i],
328 BuildName(testName, name, GlobalPrefix, i == 0 ? ChildEventNamePrefix0 : ChildEventNamePrefix1),
329 true);
330 TestAssert(childEvents[i] != nullptr);
331 TestAssert(GetLastError() != ERROR_ALREADY_EXISTS);
332 }
333
334 // Wait for the parent to create and acquire locks on its events so that the child can wait on them
335 TestAssert(WaitForMutexToBeCreated(testName, parentEvents[0], ParentEventNamePrefix0));
336 TestAssert(WaitForMutexToBeCreated(testName, parentEvents[1], ParentEventNamePrefix1));
337
338 // Parent/child tests start with the parent, so after initialization, wait for the parent to tell the child test to start
339 TestAssert(WaitForSingleObject(parentEvents[0], FailTimeoutMilliseconds) == WAIT_OBJECT_0);
340 TestAssert(parentEvents[0].Release());
341 return true;
342}
343
344bool UninitializeChild(
345 AutoCloseMutexHandle &childRunningEvent,
346 AutoCloseMutexHandle parentEvents[2],
347 AutoCloseMutexHandle childEvents[2])
348{
349 // Release and close 'parentEvents' and 'childEvents' before releasing 'childRunningEvent' to avoid races, see
350 // UnitializeParent() for more info
351 TestAssert(childEvents[0].Release());
352 TestAssert(childEvents[1].Release());
353 childEvents[0].Close();
354 childEvents[1].Close();
355 parentEvents[0].Close();
356 parentEvents[1].Close();
357 TestAssert(childRunningEvent.Release());
358 return true;
359}
360
361bool YieldToChild(AutoCloseMutexHandle parentEvents[2], AutoCloseMutexHandle childEvents[2], int &ei)
362{
363 TestAssert(parentEvents[ei].Release());
364 TestAssert(WaitForSingleObject(childEvents[ei], FailTimeoutMilliseconds) == WAIT_OBJECT_0);
365 TestAssert(childEvents[ei].Release());
366 TestAssert(WaitForSingleObject(parentEvents[ei], 0) == WAIT_OBJECT_0);
367 ei = 1 - ei;
368 return true;
369}
370
371bool YieldToParent(AutoCloseMutexHandle parentEvents[2], AutoCloseMutexHandle childEvents[2], int &ei)
372{
373 TestAssert(childEvents[ei].Release());
374 ei = 1 - ei;
375 TestAssert(WaitForSingleObject(parentEvents[ei], FailTimeoutMilliseconds) == WAIT_OBJECT_0);
376 TestAssert(parentEvents[ei].Release());
377 TestAssert(WaitForSingleObject(childEvents[1 - ei], 0) == WAIT_OBJECT_0);
378 return true;
379}
380
381////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
382// Tests
383
384bool NameTests()
385{
386 const char *testName = "NameTests";
387
388 AutoCloseMutexHandle m;
389 char name[MaxPathSize];
390
391 // Empty name
392 TestCreateMutex(m, "");
393 TestAssert(m != nullptr);
394
395 // Normal name
396 TestCreateMutex(m, BuildName(testName, name, NamePrefix));
397 TestAssert(m != nullptr);
398 TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, NamePrefix))) != nullptr);
399 TestCreateMutex(m, BuildName(testName, name, SessionPrefix, NamePrefix));
400 TestAssert(m != nullptr);
401 TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, SessionPrefix, NamePrefix))) != nullptr);
402 TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix));
403 TestAssert(m != nullptr);
404 TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, GlobalPrefix, NamePrefix))) != nullptr);
405
406 // Name too long. The maximum allowed length depends on the file system, so we're not checking for that.
407 {
408 char name[257];
409 memset(name, 'a', _countof(name) - 1);
410 name[_countof(name) - 1] = '\0';
411 TestCreateMutex(m, name);
412 TestAssert(m == nullptr);
413 TestAssert(GetLastError() == ERROR_FILENAME_EXCED_RANGE);
414 TestAssert(AutoCloseMutexHandle(TestOpenMutex(name)) == nullptr);
415 TestAssert(GetLastError() == ERROR_FILENAME_EXCED_RANGE);
416 }
417
418 // Invalid characters in name
419 TestCreateMutex(m, BuildName(testName, name, InvalidNamePrefix0));
420 TestAssert(m == nullptr);
421 TestAssert(GetLastError() == ERROR_INVALID_NAME);
422 TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, InvalidNamePrefix0))) == nullptr);
423 TestAssert(GetLastError() == ERROR_INVALID_NAME);
424 TestCreateMutex(m, BuildName(testName, name, InvalidNamePrefix1));
425 TestAssert(m == nullptr);
426 TestAssert(GetLastError() == ERROR_INVALID_NAME);
427 TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, InvalidNamePrefix1))) == nullptr);
428 TestAssert(GetLastError() == ERROR_INVALID_NAME);
429 TestCreateMutex(m, BuildName(testName, name, SessionPrefix, InvalidNamePrefix0));
430 TestAssert(m == nullptr);
431 TestAssert(GetLastError() == ERROR_INVALID_NAME);
432 TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, SessionPrefix, InvalidNamePrefix0))) == nullptr);
433 TestAssert(GetLastError() == ERROR_INVALID_NAME);
434 TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, InvalidNamePrefix1));
435 TestAssert(m == nullptr);
436 TestAssert(GetLastError() == ERROR_INVALID_NAME);
437 TestAssert(AutoCloseMutexHandle(TestOpenMutex(BuildName(testName, name, GlobalPrefix, InvalidNamePrefix1))) == nullptr);
438 TestAssert(GetLastError() == ERROR_INVALID_NAME);
439
440 // Creating a second reference to the same named mutex yields an error indicating that it was opened, not created
441 {
442 TestCreateMutex(m, BuildName(testName, name, NamePrefix));
443 TestAssert(m != nullptr);
444 AutoCloseMutexHandle m2;
445 TestCreateMutex(m2, BuildName(testName, name, NamePrefix));
446 TestAssert(m2 != nullptr);
447 TestAssert(GetLastError() == ERROR_ALREADY_EXISTS);
448 }
449
450 return true;
451}
452
453bool HeaderMismatchTests()
454{
455 const char *testName = "HeaderMismatchTests";
456
457 AutoCloseMutexHandle m, m2;
458 char name[MaxPathSize];
459 int fd;
460
461 // Create and hold onto a mutex during this test to create the shared memory directory
462 TestCreateMutex(m2, BuildName(testName, name, GlobalPrefix, TempNamePrefix));
463 TestAssert(m2 != nullptr);
464
465 // Unknown shared memory type
466 TestAssert(WriteHeaderInfo(BuildGlobalShmFilePath(testName, name, NamePrefix), -1, 1, &fd));
467 TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix));
468 TestAssert(m == nullptr);
469 TestAssert(GetLastError() == ERROR_INVALID_HANDLE);
470 TestAssert(test_close(fd) == 0);
471 TestAssert(test_unlink(BuildGlobalShmFilePath(testName, name, NamePrefix)) == 0);
472
473 // Mismatched version
474 TestAssert(WriteHeaderInfo(BuildGlobalShmFilePath(testName, name, NamePrefix), 0, -1, &fd));
475 TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix));
476 TestAssert(m == nullptr);
477 TestAssert(GetLastError() == ERROR_INVALID_HANDLE);
478 TestAssert(test_close(fd) == 0);
479 TestAssert(test_unlink(BuildGlobalShmFilePath(testName, name, NamePrefix)) == 0);
480
481 return true;
482}
483
484bool MutualExclusionTests_Parent()
485{
486 const char *testName = "MutualExclusionTests";
487
488 AutoCloseMutexHandle parentEvents[2], childEvents[2];
489 TestAssert(InitializeParent(testName, parentEvents, childEvents));
490 int ei = 0;
491 char name[MaxPathSize];
492 AutoCloseMutexHandle m;
493
494 TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix));
495 TestAssert(m != nullptr);
496
497 // Recursive locking with various timeouts
498 TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0);
499 TestAssert(WaitForSingleObject(m, FailTimeoutMilliseconds) == WAIT_OBJECT_0);
500 TestAssert(WaitForSingleObject(m, static_cast<DWORD>(-1)) == WAIT_OBJECT_0);
501 TestAssert(m.Release());
502 TestAssert(m.Release());
503 TestAssert(m.Release());
504 TestAssert(!m.Release()); // try to release the lock while nobody owns it, and verify recursive lock counting
505 TestAssert(GetLastError() == ERROR_NOT_OWNER);
506
507 TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child takes the lock
508
509 TestAssert(WaitForSingleObject(m, 0) == WAIT_TIMEOUT); // try to lock the mutex without waiting
510 TestAssert(WaitForSingleObject(m, g_expectedTimeoutMilliseconds) == WAIT_TIMEOUT); // try to lock the mutex with a timeout
511 TestAssert(!m.Release()); // try to release the lock while another thread owns it
512 TestAssert(GetLastError() == ERROR_NOT_OWNER);
513
514 TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child releases the lock
515
516 TestAssert(WaitForSingleObject(m, static_cast<DWORD>(-1)) == WAIT_OBJECT_0); // lock the mutex with no timeout and release
517 TestAssert(m.Release());
518
519 UninitializeParent(testName, parentEvents);
520 return true;
521}
522
523DWORD PALAPI MutualExclusionTests_Child(void *arg = nullptr)
524{
525 const char *testName = "MutualExclusionTests";
526
527 AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2];
528 TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents));
529 int ei = 0;
530
531 {
532 char name[MaxPathSize];
533 AutoCloseMutexHandle m;
534
535 TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix));
536 TestAssert(m != nullptr);
537 TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0); // lock the mutex
538 YieldToParent(parentEvents, childEvents, ei); // parent attempts to lock/release, and fails
539 TestAssert(m.Release()); // release the lock
540 }
541
542 UninitializeChild(childRunningEvent, parentEvents, childEvents);
543 return 0;
544}
545
546bool MutualExclusionTests()
547{
548 const char *testName = "MutualExclusionTests";
549
550 {
551 AutoCloseMutexHandle m;
552 char name[MaxPathSize];
553
554 // Releasing a lock that is not owned by any thread fails
555 TestCreateMutex(m, BuildName(testName, name, NamePrefix));
556 TestAssert(m != nullptr);
557 TestAssert(!m.Release());
558 TestAssert(GetLastError() == ERROR_NOT_OWNER);
559
560 // Acquire a lock during upon creation, and release
561 TestCreateMutex(m, BuildName(testName, name, NamePrefix), true);
562 TestAssert(m != nullptr);
563 TestAssert(m.Release());
564
565 // Multi-waits including a named mutex are not supported
566 AutoCloseMutexHandle m2;
567 TestCreateMutex(m2, nullptr);
568 TestAssert(m2 != nullptr);
569 HANDLE waitHandles[] = {m2.GetHandle(), m.GetHandle()};
570 TestAssert(
571 WaitForMultipleObjects(
572 _countof(waitHandles),
573 waitHandles,
574 false /* waitAll */,
575 FailTimeoutMilliseconds) ==
576 WAIT_FAILED);
577 TestAssert(GetLastError() == ERROR_NOT_SUPPORTED);
578 TestAssert(
579 WaitForMultipleObjects(
580 _countof(waitHandles),
581 waitHandles,
582 true /* waitAll */,
583 FailTimeoutMilliseconds) ==
584 WAIT_FAILED);
585 TestAssert(GetLastError() == ERROR_NOT_SUPPORTED);
586 }
587
588 // When another thread or process owns the lock, this process should not be able to acquire a lock, and the converse
589 TestAssert(StartThread(MutualExclusionTests_Child));
590 TestAssert(MutualExclusionTests_Parent());
591 TestAssert(StartProcess("MutualExclusionTests_Child"));
592 TestAssert(MutualExclusionTests_Parent());
593
594 return true;
595}
596
597bool LifetimeTests_Parent()
598{
599 const char *testName = "LifetimeTests";
600
601 AutoCloseMutexHandle parentEvents[2], childEvents[2];
602 TestAssert(InitializeParent(testName, parentEvents, childEvents));
603 int ei = 0;
604 char name[MaxPathSize];
605 AutoCloseMutexHandle m;
606
607 TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); // create first reference to mutex
608 TestAssert(m != nullptr);
609 TestAssert(TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix)));
610 TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child creates second reference to mutex using CreateMutex
611 m.Close(); // close first reference
612 TestAssert(TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix)));
613 TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child closes second reference
614 TestAssert(!TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix)));
615
616 TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); // create first reference to mutex
617 TestAssert(m != nullptr);
618 TestAssert(TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix)));
619 TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child creates second reference to mutex using OpenMutex
620 m.Close(); // close first reference
621 TestAssert(TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix)));
622 TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child closes second reference
623 TestAssert(!TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix)));
624
625 UninitializeParent(testName, parentEvents);
626 return true;
627}
628
629DWORD PALAPI LifetimeTests_Child(void *arg = nullptr)
630{
631 const char *testName = "LifetimeTests";
632
633 AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2];
634 TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents));
635 int ei = 0;
636
637 {
638 char name[MaxPathSize];
639 AutoCloseMutexHandle m;
640
641 // ... parent creates first reference to mutex
642 TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix)); // create second reference to mutex using CreateMutex
643 TestAssert(m != nullptr);
644 TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent closes first reference
645 m.Close(); // close second reference
646
647 TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent verifies, and creates first reference to mutex again
648 m = TestOpenMutex(BuildName(testName, name, GlobalPrefix, NamePrefix)); // create second reference to mutex using OpenMutex
649 TestAssert(m != nullptr);
650 TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent closes first reference
651 m.Close(); // close second reference
652
653 TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent verifies
654 }
655
656 UninitializeChild(childRunningEvent, parentEvents, childEvents);
657 return 0;
658}
659
660bool LifetimeTests()
661{
662 const char *testName = "LifetimeTests";
663
664 {
665 AutoCloseMutexHandle m;
666 char name[MaxPathSize];
667
668 // Shm file should be created and deleted
669 TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix));
670 TestAssert(m != nullptr);
671 TestAssert(TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix)));
672 m.Close();
673 TestAssert(!TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix)));
674 }
675
676 // Shm file should not be deleted until last reference is released
677 TestAssert(StartThread(LifetimeTests_Child));
678 TestAssert(LifetimeTests_Parent());
679 TestAssert(StartProcess("LifetimeTests_Child"));
680 TestAssert(LifetimeTests_Parent());
681
682 return true;
683}
684
685DWORD PALAPI AbandonTests_Child_TryLock(void *arg = nullptr);
686
687bool AbandonTests_Parent()
688{
689 const char *testName = "AbandonTests";
690
691 char name[MaxPathSize];
692 AutoCloseMutexHandle m;
693 {
694 AutoCloseMutexHandle parentEvents[2], childEvents[2];
695 TestAssert(InitializeParent(testName, parentEvents, childEvents));
696 int ei = 0;
697
698 TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix));
699 TestAssert(m != nullptr);
700 TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child locks mutex
701 TestAssert(parentEvents[0].Release());
702 TestAssert(parentEvents[1].Release()); // child sleeps for short duration and abandons the mutex
703 TestAssert(WaitForSingleObject(m, FailTimeoutMilliseconds) == WAIT_ABANDONED_0); // attempt to lock and see abandoned mutex
704
705 UninitializeParent(testName, parentEvents, false /* releaseParentEvents */); // parent events are released above
706 }
707
708 // Verify that the mutex lock is owned by this thread, by starting a new thread and trying to lock it
709 StartThread(AbandonTests_Child_TryLock);
710 {
711 AutoCloseMutexHandle parentEvents[2], childEvents[2];
712 TestAssert(InitializeParent(testName, parentEvents, childEvents));
713 int ei = 0;
714
715 TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child tries to lock mutex
716
717 UninitializeParent(testName, parentEvents);
718 }
719
720 // Verify that the mutex lock is owned by this thread, by starting a new process and trying to lock it
721 StartProcess("AbandonTests_Child_TryLock");
722 AutoCloseMutexHandle parentEvents[2], childEvents[2];
723 TestAssert(InitializeParent(testName, parentEvents, childEvents));
724 int ei = 0;
725
726 TestAssert(YieldToChild(parentEvents, childEvents, ei)); // child tries to lock mutex
727
728 // Continue verification
729 TestAssert(m.Release());
730 TestAssert(WaitForSingleObject(m, FailTimeoutMilliseconds) == WAIT_OBJECT_0); // lock again to see it's not abandoned anymore
731 TestAssert(m.Release());
732
733 UninitializeParent(testName, parentEvents, false /* releaseParentEvents */); // parent events are released above
734
735 // Since the child abandons the mutex, and a child process may not release the file lock on the shared memory file before
736 // indicating completion to the parent, make sure to delete the shared memory file by repeatedly opening/closing the mutex
737 // until the parent process becomes the last process to reference the mutex and closing it deletes the file.
738 DWORD startTime = GetTickCount();
739 while (true)
740 {
741 m.Close();
742 if (!TestFileExists(BuildGlobalShmFilePath(testName, name, NamePrefix)))
743 {
744 break;
745 }
746
747 TestAssert(GetTickCount() - startTime < FailTimeoutMilliseconds);
748 m = TestOpenMutex(BuildName(testName, name, GlobalPrefix, NamePrefix));
749 }
750
751 return true;
752}
753
754DWORD PALAPI AbandonTests_Child_GracefulExit_Close(void *arg = nullptr)
755{
756 const char *testName = "AbandonTests";
757
758 AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2];
759 TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents));
760 int ei = 0;
761
762 {
763 char name[MaxPathSize];
764 AutoCloseMutexHandle m;
765
766 // ... parent waits for child to lock mutex
767 TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix));
768 TestAssert(m != nullptr);
769 TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0);
770 TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent waits on mutex
771 Sleep(g_expectedTimeoutMilliseconds); // wait for parent to wait on mutex
772 m.Close(); // close mutex without releasing lock
773 }
774
775 UninitializeChild(childRunningEvent, parentEvents, childEvents);
776 return 0;
777}
778
779DWORD AbandonTests_Child_GracefulExit_NoClose(void *arg = nullptr)
780{
781 const char *testName = "AbandonTests";
782
783 // This test needs to run in a separate process because it does not close the mutex handle. Running it in a separate thread
784 // causes the mutex object to retain a reference until the process terminates.
785 TestAssert(test_getpid() != g_parentPid);
786
787 AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2];
788 TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents));
789 int ei = 0;
790
791 {
792 char name[MaxPathSize];
793 AutoCloseMutexHandle m;
794
795 // ... parent waits for child to lock mutex
796 TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix));
797 TestAssert(m != nullptr);
798 TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0);
799 TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent waits on mutex
800 Sleep(g_expectedTimeoutMilliseconds); // wait for parent to wait on mutex
801 m.Abandon(); // don't close the mutex
802 }
803
804 UninitializeChild(childRunningEvent, parentEvents, childEvents);
805 return 0;
806}
807
808DWORD AbandonTests_Child_AbruptExit(void *arg = nullptr)
809{
810 const char *testName = "AbandonTests";
811
812 DWORD currentPid = test_getpid();
813 TestAssert(currentPid != g_parentPid); // this test needs to run in a separate process
814
815 {
816 AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2];
817 TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents));
818 int ei = 0;
819
820 {
821 char name[MaxPathSize];
822 AutoCloseMutexHandle m;
823
824 // ... parent waits for child to lock mutex
825 TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix));
826 TestAssert(m != nullptr);
827 TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0);
828 TestAssert(YieldToParent(parentEvents, childEvents, ei)); // parent waits on mutex
829 Sleep(g_expectedTimeoutMilliseconds); // wait for parent to wait on mutex
830 m.Abandon(); // don't close the mutex
831 }
832
833 UninitializeChild(childRunningEvent, parentEvents, childEvents);
834 }
835
836 TestAssert(test_kill(currentPid) == 0); // abandon the mutex abruptly
837 return 0;
838}
839
840// This child process acquires the mutex lock, creates another child process (to ensure that file locks are not inherited), and
841// abandons the mutex abruptly. The second child process detects the abandonment and abandons the mutex again for the parent to
842// detect. Issue: https://github.com/dotnet/coreclr/issues/21455
843DWORD AbandonTests_Child_FileLocksNotInherited_Parent_AbruptExit(void *arg = nullptr)
844{
845 const char *testName = "AbandonTests";
846
847 DWORD currentPid = test_getpid();
848 TestAssert(currentPid != g_parentPid); // this test needs to run in a separate process
849
850 {
851 char name[MaxPathSize];
852 AutoCloseMutexHandle m;
853
854 // ... root parent waits for child to lock mutex
855 TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix));
856 TestAssert(m != nullptr);
857 TestAssert(WaitForSingleObject(m, 0) == WAIT_OBJECT_0);
858
859 // Start a child process while holding the lock on the mutex, to ensure that file locks are not inherited by the
860 // immediate child, such that the immediate child would be able to detect the mutex being abandoned below. This process
861 // does not communicate with the root parent, it only communicates to the immediate child by abandoning the mutex. The
862 // immediate child communicates with the root parent to complete the test.
863 TestAssert(StartProcess("AbandonTests_Child_FileLocksNotInherited_Child_AbruptExit")); // immediate child waits on mutex
864
865 Sleep(g_expectedTimeoutMilliseconds); // wait for immediate child to wait on mutex
866 m.Abandon(); // don't close the mutex
867 }
868
869 TestAssert(test_kill(currentPid) == 0); // abandon the mutex abruptly
870 return 0;
871}
872
873DWORD AbandonTests_Child_FileLocksNotInherited_Child_AbruptExit(void *arg = nullptr)
874{
875 const char *testName = "AbandonTests";
876
877 DWORD currentPid = test_getpid();
878 TestAssert(currentPid != g_parentPid); // this test needs to run in a separate process
879
880 AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2];
881 TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents));
882 int ei = 0;
883
884 {
885 char name[MaxPathSize];
886 AutoCloseMutexHandle m;
887
888 // ... immediate parent expects child to wait on mutex
889 TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix));
890 TestAssert(m != nullptr);
891 TestAssert(WaitForSingleObject(m, FailTimeoutMilliseconds) == WAIT_ABANDONED_0); // attempt to lock and see abandoned mutex
892 TestAssert(YieldToParent(parentEvents, childEvents, ei)); // root parent waits on mutex
893 Sleep(g_expectedTimeoutMilliseconds); // wait for root parent to wait on mutex
894 m.Close(); // close mutex without releasing lock (root parent expects the mutex to be abandoned)
895 }
896
897 UninitializeChild(childRunningEvent, parentEvents, childEvents);
898 return 0;
899}
900
901DWORD PALAPI AbandonTests_Child_TryLock(void *arg)
902{
903 const char *testName = "AbandonTests";
904
905 AutoCloseMutexHandle childRunningEvent, parentEvents[2], childEvents[2];
906 TestAssert(InitializeChild(testName, childRunningEvent, parentEvents, childEvents));
907 int ei = 0;
908
909 {
910 char name[MaxPathSize];
911 AutoCloseMutexHandle m;
912
913 // ... parent waits for child to lock mutex
914 TestCreateMutex(m, BuildName(testName, name, GlobalPrefix, NamePrefix));
915 TestAssert(m != nullptr);
916 TestAssert(WaitForSingleObject(m, 0) == WAIT_TIMEOUT); // try to lock the mutex while the parent holds the lock
917 TestAssert(WaitForSingleObject(m, g_expectedTimeoutMilliseconds) == WAIT_TIMEOUT);
918 }
919
920 UninitializeChild(childRunningEvent, parentEvents, childEvents);
921 return 0;
922}
923
924bool AbandonTests()
925{
926 const char *testName = "AbandonTests";
927
928 // Abandon by graceful exit where the lock owner closes the mutex before releasing it, unblocks a waiter
929 TestAssert(StartThread(AbandonTests_Child_GracefulExit_Close));
930 TestAssert(AbandonTests_Parent());
931 TestAssert(StartProcess("AbandonTests_Child_GracefulExit_Close"));
932 TestAssert(AbandonTests_Parent());
933
934 // Abandon by graceful exit without closing the mutex unblocks a waiter
935 TestAssert(StartProcess("AbandonTests_Child_GracefulExit_NoClose"));
936 TestAssert(AbandonTests_Parent());
937
938 // Abandon by abrupt exit unblocks a waiter
939 TestAssert(StartProcess("AbandonTests_Child_AbruptExit"));
940 TestAssert(AbandonTests_Parent());
941
942 TestAssert(StartProcess("AbandonTests_Child_FileLocksNotInherited_Parent_AbruptExit"));
943 TestAssert(AbandonTests_Parent());
944
945 return true;
946}
947
948////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
949// Test harness
950
951bool (*const (TestList[]))() =
952{
953 NameTests,
954 HeaderMismatchTests,
955 MutualExclusionTests,
956 LifetimeTests,
957 AbandonTests
958};
959
960bool RunTests()
961{
962 bool allPassed = true;
963 for (SIZE_T i = 0; i < _countof(TestList); ++i)
964 {
965 if (!TestList[i]())
966 {
967 allPassed = false;
968 }
969 }
970 return allPassed;
971}
972
973DWORD g_stressDurationMilliseconds = 0;
974LONG g_stressTestCounts[_countof(TestList)] = {0};
975LONG g_stressResult = true;
976
977DWORD PALAPI StressTest(void *arg)
978{
979 // Run the specified test continuously for the stress duration
980 SIZE_T testIndex = reinterpret_cast<SIZE_T>(arg);
981 DWORD startTime = GetTickCount();
982 do
983 {
984 ++g_stressTestCounts[testIndex];
985 if (!TestList[testIndex]())
986 {
987 InterlockedExchange(&g_stressResult, false);
988 break;
989 }
990 } while (
991 InterlockedCompareExchange(&g_stressResult, false, false) == true &&
992 GetTickCount() - startTime < g_stressDurationMilliseconds);
993 return 0;
994}
995
996bool StressTests(DWORD durationMinutes)
997{
998 g_isStress = true;
999 g_expectedTimeoutMilliseconds = 1;
1000 g_stressDurationMilliseconds = durationMinutes * (60 * 1000);
1001
1002 // Start a thread for each test
1003 HANDLE threadHandles[_countof(TestList)];
1004 for (SIZE_T i = 0; i < _countof(threadHandles); ++i)
1005 {
1006 TestAssert(StartThread(StressTest, reinterpret_cast<void *>(i), &threadHandles[i]));
1007 }
1008
1009 while (true)
1010 {
1011 DWORD waitResult =
1012 WaitForMultipleObjects(_countof(threadHandles), threadHandles, true /* bWaitAll */, 10 * 1000 /* dwMilliseconds */);
1013 TestAssert(waitResult == WAIT_OBJECT_0 || waitResult == WAIT_TIMEOUT);
1014 if (waitResult == WAIT_OBJECT_0)
1015 {
1016 break;
1017 }
1018
1019 Trace("'paltest_namedmutex_test1' stress test counts: ");
1020 for (SIZE_T i = 0; i < _countof(g_stressTestCounts); ++i)
1021 {
1022 if (i != 0)
1023 {
1024 Trace(", ");
1025 }
1026 Trace("%u", g_stressTestCounts[i]);
1027 }
1028 Trace("\n");
1029 fflush(stdout);
1030 }
1031
1032 for (SIZE_T i = 0; i < _countof(threadHandles); ++i)
1033 {
1034 CloseHandle(threadHandles[i]);
1035 }
1036 return static_cast<bool>(g_stressResult);
1037}
1038
1039int __cdecl main(int argc, char **argv)
1040{
1041 if (argc < 1 || argc > 4)
1042 {
1043 return FAIL;
1044 }
1045
1046 if (PAL_Initialize(argc, argv) != 0)
1047 {
1048 return FAIL;
1049 }
1050
1051 test_strcpy(g_processPath, argv[0]);
1052
1053 if (argc == 1)
1054 {
1055 // Unit test arguments: <processPath>
1056
1057 g_parentPid = test_getpid();
1058 int result = RunTests() ? PASS : FAIL;
1059 ExitProcess(result);
1060 return result;
1061 }
1062
1063 if (test_strcmp(argv[1], "stress") == 0)
1064 {
1065 // Stress test arguments: <processPath> stress [durationMinutes]
1066
1067 DWORD durationMinutes = 1;
1068 if (argc >= 3 && test_sscanf(argv[2], "%u", &durationMinutes) != 1)
1069 {
1070 ExitProcess(FAIL);
1071 return FAIL;
1072 }
1073
1074 g_parentPid = test_getpid();
1075 int result = StressTests(durationMinutes) ? PASS : FAIL;
1076 ExitProcess(result);
1077 return result;
1078 }
1079
1080 // Child test process arguments: <processPath> <parentPid> <testFunctionName> [stress]
1081
1082 g_isParent = false;
1083
1084 // Get parent process' ID from argument
1085 if (test_sscanf(argv[1], "%u", &g_parentPid) != 1)
1086 {
1087 ExitProcess(FAIL);
1088 return FAIL;
1089 }
1090
1091 if (argc >= 4 && test_strcmp(argv[3], "stress") == 0)
1092 {
1093 g_isStress = true;
1094 }
1095
1096 if (test_strcmp(argv[2], "MutualExclusionTests_Child") == 0)
1097 {
1098 MutualExclusionTests_Child();
1099 }
1100 else if (test_strcmp(argv[2], "LifetimeTests_Child") == 0)
1101 {
1102 LifetimeTests_Child();
1103 }
1104 else if (test_strcmp(argv[2], "AbandonTests_Child_GracefulExit_Close") == 0)
1105 {
1106 AbandonTests_Child_GracefulExit_Close();
1107 }
1108 else if (test_strcmp(argv[2], "AbandonTests_Child_GracefulExit_NoClose") == 0)
1109 {
1110 AbandonTests_Child_GracefulExit_NoClose();
1111 }
1112 else if (test_strcmp(argv[2], "AbandonTests_Child_AbruptExit") == 0)
1113 {
1114 AbandonTests_Child_AbruptExit();
1115 }
1116 else if (test_strcmp(argv[2], "AbandonTests_Child_FileLocksNotInherited_Parent_AbruptExit") == 0)
1117 {
1118 AbandonTests_Child_FileLocksNotInherited_Parent_AbruptExit();
1119 }
1120 else if (test_strcmp(argv[2], "AbandonTests_Child_FileLocksNotInherited_Child_AbruptExit") == 0)
1121 {
1122 AbandonTests_Child_FileLocksNotInherited_Child_AbruptExit();
1123 }
1124 else if (test_strcmp(argv[2], "AbandonTests_Child_TryLock") == 0)
1125 {
1126 AbandonTests_Child_TryLock();
1127 }
1128 ExitProcess(PASS);
1129 return PASS;
1130}
1131