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 | |
15 | const char *const SessionPrefix = "Local\\" ; |
16 | const char *const GlobalPrefix = "Global\\" ; |
17 | |
18 | const char *const NamePrefix = "paltest_namedmutex_test1_" ; |
19 | const char *const TempNamePrefix = "paltest_namedmutex_test1_temp_" ; |
20 | const char *const InvalidNamePrefix0 = "paltest\\namedmutex_" ; |
21 | const char *const InvalidNamePrefix1 = "paltest/namedmutex_" ; |
22 | const char *const ParentEventNamePrefix0 = "paltest_namedmutex_test1_pe0_" ; |
23 | const char *const ParentEventNamePrefix1 = "paltest_namedmutex_test1_pe1_" ; |
24 | const char *const ChildEventNamePrefix0 = "paltest_namedmutex_test1_ce0_" ; |
25 | const char *const ChildEventNamePrefix1 = "paltest_namedmutex_test1_ce1_" ; |
26 | const char *const ChildRunningEventNamePrefix = "paltest_namedmutex_test1_cr_" ; |
27 | |
28 | const char *const GlobalShmFilePathPrefix = "/tmp/.dotnet/shm/global/" ; |
29 | |
30 | #define MaxPathSize (200) |
31 | const DWORD PollLoopSleepMilliseconds = 100; |
32 | const DWORD FailTimeoutMilliseconds = 30000; |
33 | DWORD g_expectedTimeoutMilliseconds = 500; |
34 | |
35 | bool g_isParent = true; |
36 | bool g_isStress = false; |
37 | char g_processPath[4096], g_processCommandLinePath[4096]; |
38 | DWORD g_parentPid = static_cast<DWORD>(-1); |
39 | |
40 | extern char *(*test_strcpy)(char *dest, const char *src); |
41 | extern int (*test_strcmp)(const char *s1, const char *s2); |
42 | extern size_t (*test_strlen)(const char *s); |
43 | extern int (*test_sprintf)(char *str, const char *format, ...); |
44 | extern int (*test_sscanf)(const char *str, const char *format, ...); |
45 | extern int(*test_close)(int fd); |
46 | extern int (*test_unlink)(const char *pathname); |
47 | extern unsigned int test_getpid(); |
48 | extern int test_kill(unsigned int pid); |
49 | |
50 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
51 | // Test helpers |
52 | |
53 | extern bool TestFileExists(const char *path); |
54 | extern bool (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 | |
71 | char *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 | |
97 | char *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 | |
116 | class AutoCloseMutexHandle |
117 | { |
118 | private: |
119 | HANDLE m_handle; |
120 | |
121 | public: |
122 | AutoCloseMutexHandle(HANDLE handle = nullptr) : m_handle(handle) |
123 | { |
124 | } |
125 | |
126 | ~AutoCloseMutexHandle() |
127 | { |
128 | Close(); |
129 | } |
130 | |
131 | public: |
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 | |
169 | private: |
170 | AutoCloseMutexHandle(const AutoCloseMutexHandle &other); |
171 | AutoCloseMutexHandle(AutoCloseMutexHandle &&other); |
172 | AutoCloseMutexHandle &operator =(const AutoCloseMutexHandle &other); |
173 | }; |
174 | |
175 | void TestCreateMutex(AutoCloseMutexHandle &m, const char *name, bool initiallyOwned = false) |
176 | { |
177 | m.Close(); |
178 | m = CreateMutexA(nullptr, initiallyOwned, name); |
179 | } |
180 | |
181 | HANDLE TestOpenMutex(const char *name) |
182 | { |
183 | return OpenMutexA(SYNCHRONIZE, false, name); |
184 | } |
185 | |
186 | bool 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 | |
221 | bool 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 | |
240 | bool 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 | |
269 | bool 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 | |
277 | bool 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 | |
297 | bool 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 | |
314 | bool 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 | |
344 | bool 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 | |
361 | bool 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 | |
371 | bool 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 | |
384 | bool 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 | |
453 | bool () |
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 | |
484 | bool 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 | |
523 | DWORD 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 | |
546 | bool 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 | |
597 | bool 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 | |
629 | DWORD 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 | |
660 | bool 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 | |
685 | DWORD PALAPI AbandonTests_Child_TryLock(void *arg = nullptr); |
686 | |
687 | bool 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 | |
754 | DWORD 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 | |
779 | DWORD 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 | |
808 | DWORD 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 |
843 | DWORD 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 | |
873 | DWORD 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 | |
901 | DWORD 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 | |
924 | bool 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 | |
951 | bool (*const (TestList[]))() = |
952 | { |
953 | NameTests, |
954 | HeaderMismatchTests, |
955 | MutualExclusionTests, |
956 | LifetimeTests, |
957 | AbandonTests |
958 | }; |
959 | |
960 | bool 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 | |
973 | DWORD g_stressDurationMilliseconds = 0; |
974 | LONG g_stressTestCounts[_countof(TestList)] = {0}; |
975 | LONG g_stressResult = true; |
976 | |
977 | DWORD 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 | |
996 | bool 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 | |
1039 | int __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 | |