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 | /*============================================================= |
6 | ** |
7 | ** Source: test5.c |
8 | ** |
9 | ** Purpose: Test the functionality of simultaneously waiting |
10 | ** on multiple processes. Create the same number of helper |
11 | ** processes and helper threads. |
12 | ** Helper threads wait on helper processes to finish. |
13 | ** Helper processes wait on the event signal from test |
14 | ** thread before exit. |
15 | ** The test thread can wake up one helper |
16 | ** thread at a time by signaling the corresponding helper |
17 | ** process to finish. |
18 | ** The test thread can also wake up all helper threads at once |
19 | ** by signaling help process 0 to exit. |
20 | ** |
21 | ** |
22 | **============================================================*/ |
23 | |
24 | #define UNICODE |
25 | |
26 | #include "commonconsts.h" |
27 | |
28 | #include <palsuite.h> |
29 | |
30 | /* The maximum number of objects a thread can wait is MAXIMUM_WAIT_OBJECTS. |
31 | The last helper thread in this test case will wait on all helper processes |
32 | plus a thread finish event so the maximum number of helper processes |
33 | can be created in this test case is (MAXIMUM_WAIT_OBJECTS-1). */ |
34 | #define MAX_HELPER_PROCESS (MAXIMUM_WAIT_OBJECTS-1) |
35 | |
36 | int MaxNumHelperProcess = MAX_HELPER_PROCESS; |
37 | |
38 | /* indicate how the test thread wake up helper thread. */ |
39 | typedef enum _TestCaseType { |
40 | WakeUpOneThread, /* wake up one helper thread at a time. */ |
41 | WakeUpAllThread /* wake up all helper threads at once */ |
42 | } TestCaseType; |
43 | |
44 | TestCaseType TestCase = WakeUpOneThread; |
45 | |
46 | /* When the test thread wakes up one thread at a time, |
47 | ThreadIndexOfThreadFinishEvent specifies the index of the thread that |
48 | should be waked up using hThreadFinishEvent instead of helper process. */ |
49 | DWORD ThreadIndexOfThreadFinishEvent = 0; |
50 | |
51 | struct helper_process_t |
52 | { |
53 | PROCESS_INFORMATION pi; |
54 | HANDLE hProcessReadyEvent; |
55 | HANDLE hProcessFinishEvent; |
56 | } helper_process[MAX_HELPER_PROCESS]; |
57 | |
58 | HANDLE hProcessStartEvent; |
59 | |
60 | struct helper_thread_t |
61 | { |
62 | HANDLE hThread; |
63 | DWORD dwThreadId; |
64 | HANDLE hThreadReadyEvent; |
65 | HANDLE hThreadFinishEvent; |
66 | } helper_thread[MAX_HELPER_PROCESS]; |
67 | |
68 | /* |
69 | * Entry Point for helper thread. |
70 | */ |
71 | DWORD PALAPI WaitForProcess(LPVOID lpParameter) |
72 | { |
73 | DWORD index, i; |
74 | DWORD dwRet; |
75 | HANDLE handles[MAX_HELPER_PROCESS+1]; |
76 | |
77 | index = (DWORD) lpParameter; |
78 | |
79 | /* The helper thread 0 will wait for helper process 0, helper thread 1 will |
80 | wait for helper process 0 and 1, helper thread 2 will wait for helper |
81 | process 0, 1, and 2, and so on ..., and the last helper thread will wait |
82 | on all helper processes. |
83 | Each helper thread also waits on hThreadFinishEvent so that |
84 | it can exit without waiting on any process to finish. */ |
85 | |
86 | for (i = 0; i <= index; i++) |
87 | { |
88 | handles[i] = helper_process[i].pi.hProcess; |
89 | } |
90 | |
91 | handles[index+1] = helper_thread[index].hThreadFinishEvent; |
92 | |
93 | if(!SetEvent(helper_thread[index].hThreadReadyEvent)) |
94 | { |
95 | Fail("test5.WaitProcess: SetEvent of hThreadReadyEvent failed for thread %d. " |
96 | "GetLastError() returned %d.\n" , index, |
97 | GetLastError()); |
98 | } |
99 | |
100 | dwRet = WaitForMultipleObjectsEx(index+2, &handles[0], FALSE, TIMEOUT, TRUE); |
101 | if (WakeUpAllThread == TestCase) |
102 | { |
103 | /* If the test thread signals helper process 0 to exit, all threads will be waked up, |
104 | and the return value must be (WAIT_OBJECT_0+0) because the handle of helper process 0 |
105 | is in handle[0]. */ |
106 | if (dwRet != (WAIT_OBJECT_0+0)) |
107 | { |
108 | Fail("test5.WaitForProcess: invalid return value %d for WakupAllThread from WaitForMultipleObjectsEx for thread %d\n" |
109 | "LastError:(%u)\n" , |
110 | dwRet, index, |
111 | GetLastError()); |
112 | } |
113 | } |
114 | else if (WakeUpOneThread == TestCase) |
115 | { |
116 | /* If the test thread wakes up one helper thread at a time, |
117 | the return value must be either (WAIT_OBJECT_0+index) if the helper thread |
118 | wakes up because the corresponding help process exits, |
119 | or (index+1) if the helper thread wakes up because of hThreadReadyEvent. */ |
120 | if ((index != ThreadIndexOfThreadFinishEvent && dwRet != (WAIT_OBJECT_0+index)) || |
121 | (index == ThreadIndexOfThreadFinishEvent && dwRet != (index+1))) |
122 | { |
123 | Fail("test5.WaitForProcess: invalid return value %d for WakupOneThread from WaitForMultipleObjectsEx for thread %d\n" |
124 | "LastError:(%u)\n" , |
125 | dwRet, index, |
126 | GetLastError()); |
127 | } |
128 | } |
129 | else |
130 | { |
131 | Fail("Unknown TestCase %d\n" , TestCase); |
132 | } |
133 | return 0; |
134 | } |
135 | |
136 | /* |
137 | * Setup the helper processes and helper threads. |
138 | */ |
139 | void |
140 | Setup() |
141 | { |
142 | |
143 | STARTUPINFO si; |
144 | DWORD dwRet; |
145 | int i; |
146 | |
147 | char szEventName[MAX_PATH]; |
148 | PWCHAR uniStringHelper; |
149 | PWCHAR uniString; |
150 | |
151 | /* Create the event to start helper process after it was created. */ |
152 | uniString = convert(szcHelperProcessStartEvName); |
153 | hProcessStartEvent = CreateEvent(NULL, TRUE, FALSE, uniString); |
154 | free(uniString); |
155 | if (!hProcessStartEvent) |
156 | { |
157 | Fail("test5.Setup: CreateEvent of '%s' failed. " |
158 | "GetLastError() returned %d.\n" , szcHelperProcessStartEvName, |
159 | GetLastError()); |
160 | } |
161 | |
162 | /* Create the helper processes. */ |
163 | ZeroMemory( &si, sizeof(si) ); |
164 | si.cb = sizeof(si); |
165 | uniStringHelper = convert("helper" ); |
166 | for (i = 0; i < MaxNumHelperProcess; i++) |
167 | { |
168 | ZeroMemory( &helper_process[i].pi, sizeof(PROCESS_INFORMATION)); |
169 | |
170 | if(!CreateProcess( NULL, uniStringHelper, NULL, NULL, |
171 | FALSE, 0, NULL, NULL, &si, &helper_process[i].pi)) |
172 | { |
173 | Fail("test5.Setup: CreateProcess failed to load executable for helper process %d. " |
174 | "GetLastError() returned %u.\n" , |
175 | i, GetLastError()); |
176 | } |
177 | |
178 | /* Create the event to let helper process tell us it is ready. */ |
179 | if (sprintf_s(szEventName, MAX_PATH-1, "%s%d" , |
180 | szcHelperProcessReadyEvName, helper_process[i].pi.dwProcessId) < 0) |
181 | { |
182 | Fail ("test5.Setup: Insufficient event name string length for %s\n" , szcHelperProcessReadyEvName); |
183 | } |
184 | |
185 | uniString = convert(szEventName); |
186 | |
187 | helper_process[i].hProcessReadyEvent = CreateEvent(NULL, FALSE, FALSE, uniString); |
188 | free(uniString); |
189 | if (!helper_process[i].hProcessReadyEvent) |
190 | { |
191 | Fail("test5.Setup: CreateEvent of '%s' failed. " |
192 | "GetLastError() returned %d.\n" , szEventName, |
193 | GetLastError()); |
194 | } |
195 | |
196 | /* Create the event to tell helper process to exit. */ |
197 | if (sprintf_s(szEventName, MAX_PATH-1, "%s%d" , |
198 | szcHelperProcessFinishEvName, helper_process[i].pi.dwProcessId) < 0) |
199 | { |
200 | Fail ("test5.Setup: Insufficient event name string length for %s\n" , szcHelperProcessFinishEvName); |
201 | } |
202 | |
203 | uniString = convert(szEventName); |
204 | |
205 | helper_process[i].hProcessFinishEvent = CreateEvent(NULL, TRUE, FALSE, uniString); |
206 | free(uniString); |
207 | if (!helper_process[i].hProcessFinishEvent) |
208 | { |
209 | Fail("test5.Setup: CreateEvent of '%s' failed. " |
210 | "GetLastError() returned %d.\n" , szEventName, |
211 | GetLastError()); |
212 | } |
213 | |
214 | } |
215 | free(uniStringHelper); |
216 | |
217 | /* Signal all helper processes to start. */ |
218 | if (!SetEvent(hProcessStartEvent)) |
219 | { |
220 | Fail("test5.Setup: SetEvent '%s' failed\n" , |
221 | "LastError:(%u)\n" , |
222 | szcHelperProcessStartEvName, GetLastError()); |
223 | } |
224 | |
225 | /* Wait for ready signals from all helper processes. */ |
226 | for (i = 0; i < MaxNumHelperProcess; i++) |
227 | { |
228 | dwRet = WaitForSingleObject(helper_process[i].hProcessReadyEvent, TIMEOUT); |
229 | if (dwRet != WAIT_OBJECT_0) |
230 | { |
231 | Fail("test5.Setup: WaitForSingleObject %s failed for helper process %d\n" |
232 | "LastError:(%u)\n" , |
233 | szcHelperProcessReadyEvName, i, GetLastError()); |
234 | } |
235 | } |
236 | |
237 | /* Create the same number of helper threads as helper processes. */ |
238 | for (i = 0; i < MaxNumHelperProcess; i++) |
239 | { |
240 | /* Create the event to let helper thread tell us it is ready. */ |
241 | helper_thread[i].hThreadReadyEvent = CreateEvent(NULL, FALSE, FALSE, NULL); |
242 | if (!helper_thread[i].hThreadReadyEvent) |
243 | { |
244 | Fail("test5.Setup: CreateEvent of hThreadReadyEvent failed for thread %d\n" |
245 | "LastError:(%u)\n" , i, GetLastError()); |
246 | } |
247 | |
248 | /* Create the event to tell helper thread to exit without waiting for helper process. */ |
249 | helper_thread[i].hThreadFinishEvent = CreateEvent(NULL, FALSE, FALSE, NULL); |
250 | if (!helper_thread[i].hThreadFinishEvent) |
251 | { |
252 | Fail("test5.Setup: CreateEvent of hThreadFinishEvent failed for thread %d\n" |
253 | "LastError:(%u)\n" , i, GetLastError()); |
254 | } |
255 | |
256 | /* Create the helper thread. */ |
257 | helper_thread[i].hThread = CreateThread( NULL, |
258 | 0, |
259 | (LPTHREAD_START_ROUTINE)WaitForProcess, |
260 | (LPVOID)i, |
261 | 0, |
262 | &helper_thread[i].dwThreadId); |
263 | if (NULL == helper_thread[i].hThread) |
264 | { |
265 | Fail("test5.Setup: Unable to create the helper thread %d\n" |
266 | "LastError:(%u)\n" , i, GetLastError()); |
267 | } |
268 | } |
269 | |
270 | /* Wait for ready signals from all helper threads. */ |
271 | for (i = 0; i < MaxNumHelperProcess; i++) |
272 | { |
273 | dwRet = WaitForSingleObject(helper_thread[i].hThreadReadyEvent, TIMEOUT); |
274 | if (dwRet != WAIT_OBJECT_0) |
275 | { |
276 | Fail("test5.Setup: WaitForSingleObject hThreadReadyEvent for thread %d\n" |
277 | "LastError:(%u)\n" , i, GetLastError()); |
278 | } |
279 | } |
280 | } |
281 | |
282 | /* |
283 | * Cleanup the helper processes and helper threads. |
284 | */ |
285 | DWORD |
286 | Cleanup() |
287 | { |
288 | DWORD dwExitCode; |
289 | DWORD dwRet; |
290 | int i; |
291 | |
292 | /* Wait for all helper process to finish and close their handles |
293 | and associated events. */ |
294 | for (i = 0; i < MaxNumHelperProcess; i++) |
295 | { |
296 | |
297 | /* wait for the child process to complete */ |
298 | dwRet = WaitForSingleObject ( helper_process[i].pi.hProcess, TIMEOUT ); |
299 | if (WAIT_OBJECT_0 != dwRet) |
300 | { |
301 | Fail("test5.Cleanup: WaitForSingleObject hThreadReadyEvent failed for thread %d\n" |
302 | "LastError:(%u)\n" , i, GetLastError()); |
303 | } |
304 | |
305 | /* check the exit code from the process */ |
306 | if (!GetExitCodeProcess(helper_process[i].pi.hProcess, &dwExitCode)) |
307 | { |
308 | Trace( "test5.Cleanup: GetExitCodeProcess %d call failed LastError:(%u)\n" , |
309 | i, GetLastError()); |
310 | dwExitCode = FAIL; |
311 | } |
312 | PEDANTIC(CloseHandle, (helper_process[i].pi.hThread)); |
313 | PEDANTIC(CloseHandle, (helper_process[i].pi.hProcess)); |
314 | PEDANTIC(CloseHandle, (helper_process[i].hProcessReadyEvent)); |
315 | PEDANTIC(CloseHandle, (helper_process[i].hProcessFinishEvent)); |
316 | } |
317 | |
318 | /* Close all helper threads' handles */ |
319 | for (i = 0; i < MaxNumHelperProcess; i++) |
320 | { |
321 | PEDANTIC(CloseHandle, (helper_thread[i].hThread)); |
322 | PEDANTIC(CloseHandle, (helper_thread[i].hThreadReadyEvent)); |
323 | PEDANTIC(CloseHandle, (helper_thread[i].hThreadFinishEvent)); |
324 | } |
325 | |
326 | /* Close all process start event. */ |
327 | PEDANTIC(CloseHandle, (hProcessStartEvent)); |
328 | |
329 | return dwExitCode; |
330 | } |
331 | |
332 | /* |
333 | * In this test case, the test thread will signal one helper |
334 | * process to exit at a time starting from the last helper |
335 | * process and then wait for the corresponding helper thread to exit. |
336 | * The ThreadIndexOfThreadFinishEvent specifies the index of the thread that |
337 | * should be waked up using hThreadFinishEvent instead of helper process. |
338 | */ |
339 | void |
340 | TestWakeupOneThread() |
341 | { |
342 | DWORD dwRet; |
343 | int i; |
344 | |
345 | TestCase = WakeUpOneThread; |
346 | |
347 | if (((LONG)ThreadIndexOfThreadFinishEvent) < 0 || |
348 | ThreadIndexOfThreadFinishEvent >= MAX_HELPER_PROCESS) |
349 | Fail("test5.TestWaitOnOneThread: Invalid ThreadIndexOfThreadFinishEvent %d\n" , ThreadIndexOfThreadFinishEvent); |
350 | |
351 | /* Since helper thread 0 waits on helper process 0, |
352 | thread 1 waits on on process 0, and 1, |
353 | thread 2 waits on process 0, 1, and 2, and so on ..., |
354 | and the last helper thread will wait on all helper processes, |
355 | the helper thread can be waked up one at a time by |
356 | waking up the help process one at a time starting from the |
357 | last helper process. */ |
358 | for (i = MaxNumHelperProcess-1; i >= 0; i--) |
359 | { |
360 | /* make sure the helper thread has not exited yet. */ |
361 | dwRet = WaitForSingleObject(helper_thread[i].hThread, 0); |
362 | if (WAIT_TIMEOUT != dwRet) |
363 | { |
364 | Fail("test5.TestWaitOnOneThread: helper thread %d already exited %d\n" , i); |
365 | } |
366 | |
367 | /* Decide how to wakeup the helper thread: |
368 | using event or using helper process. */ |
369 | if (i == ThreadIndexOfThreadFinishEvent) |
370 | { |
371 | if (!SetEvent(helper_thread[i].hThreadFinishEvent)) |
372 | { |
373 | Fail("test5.TestWaitOnOneThread: SetEvent hThreadFinishEvent failed for thread %d\n" , |
374 | "LastError:(%u)\n" , i, GetLastError()); |
375 | } |
376 | } |
377 | else |
378 | { |
379 | if (!SetEvent(helper_process[i].hProcessFinishEvent)) |
380 | { |
381 | Fail("test5.TestWaitOnOneThread: SetEvent %s%d failed for helper process %d\n" , |
382 | "LastError:(%u)\n" , |
383 | szcHelperProcessFinishEvName, helper_process[i].pi.dwProcessId, i, |
384 | GetLastError()); |
385 | } |
386 | } |
387 | |
388 | dwRet = WaitForSingleObject(helper_thread[i].hThread, TIMEOUT); |
389 | if (WAIT_OBJECT_0 != dwRet) |
390 | { |
391 | Fail("test5.TestWaitOnOneThread: WaitForSingleObject helper thread %d" |
392 | "LastError:(%u)\n" , |
393 | i, GetLastError()); |
394 | } |
395 | } |
396 | |
397 | /* Finally, need to wake up the helper process which the test thread |
398 | skips waking up in the last loop. */ |
399 | if (!SetEvent(helper_process[ThreadIndexOfThreadFinishEvent].hProcessFinishEvent)) |
400 | { |
401 | Fail("test5.TestWaitOnOneThread: SetEvent %s%d failed\n" , |
402 | "LastError:(%u)\n" , |
403 | szcHelperProcessFinishEvName, helper_process[ThreadIndexOfThreadFinishEvent].pi.dwProcessId, |
404 | GetLastError()); |
405 | } |
406 | } |
407 | |
408 | /* |
409 | * In this test case, the test thread will signal the helper |
410 | * process 0 to exit. Since all helper threads wait on process 0, |
411 | * all helper threads will wake up and exit, and the test thread |
412 | * will wait for all of them to exit. |
413 | */ |
414 | void |
415 | TestWakeupAllThread() |
416 | { |
417 | DWORD dwRet; |
418 | int i; |
419 | |
420 | TestCase = WakeUpAllThread; |
421 | |
422 | /* make sure none of the helper thread exits. */ |
423 | for (i = 0; i < MaxNumHelperProcess; i++) |
424 | { |
425 | dwRet = WaitForSingleObject(helper_thread[i].hThread, 0); |
426 | if (WAIT_TIMEOUT != dwRet) |
427 | { |
428 | Fail("test5.TestWaitOnAllThread: helper thread %d already exited %d\n" , i); |
429 | } |
430 | } |
431 | |
432 | /* Signal helper process 0 to exit. */ |
433 | if (!SetEvent(helper_process[0].hProcessFinishEvent)) |
434 | { |
435 | Fail("test5.TestWaitOnAllThread: SetEvent %s%d failed\n" , |
436 | "LastError:(%u)\n" , |
437 | szcHelperProcessFinishEvName, helper_process[0].pi.dwProcessId, |
438 | GetLastError()); |
439 | } |
440 | |
441 | /* Wait for all helper threads to exit. */ |
442 | for (i = 0; i < MaxNumHelperProcess; i++) |
443 | { |
444 | |
445 | dwRet = WaitForSingleObject(helper_thread[i].hThread, TIMEOUT); |
446 | if (WAIT_OBJECT_0 != dwRet) |
447 | { |
448 | Fail("test5.TestWaitOnAllThread: WaitForSingleObject failed for helper thread %d\n" |
449 | "LastError:(%u)\n" , |
450 | i, GetLastError()); |
451 | } |
452 | } |
453 | |
454 | /* Signal the rest of helper processes to exit. */ |
455 | for (i = 1; i < MaxNumHelperProcess; i++) |
456 | { |
457 | if (!SetEvent(helper_process[i].hProcessFinishEvent)) |
458 | { |
459 | Fail("test5.TestWaitOnAllThread: SetEvent %s%d failed\n" , |
460 | "LastError:(%u)\n" , |
461 | szcHelperProcessFinishEvName, helper_process[i].pi.dwProcessId, |
462 | GetLastError()); |
463 | } |
464 | } |
465 | } |
466 | |
467 | int __cdecl main(int argc, char *argv[]) |
468 | { |
469 | DWORD dwExitCode; |
470 | |
471 | if(0 != (PAL_Initialize(argc, argv))) |
472 | { |
473 | return FAIL; |
474 | } |
475 | |
476 | switch (argc) |
477 | { |
478 | case 1: |
479 | MaxNumHelperProcess = MAX_HELPER_PROCESS; |
480 | break; |
481 | case 2: |
482 | MaxNumHelperProcess = atol(argv[1]); |
483 | break; |
484 | default: |
485 | Fail("Invalid number of arguments\n" ); |
486 | } |
487 | |
488 | if (MaxNumHelperProcess < 1 || |
489 | MaxNumHelperProcess > MAX_HELPER_PROCESS) |
490 | Fail("test5.main: Invalid MaxNumHelperProcess %d\n" , MaxNumHelperProcess); |
491 | |
492 | Setup(); |
493 | ThreadIndexOfThreadFinishEvent = 3; |
494 | TestWakeupOneThread(); |
495 | dwExitCode = Cleanup(); |
496 | |
497 | if (PASS == dwExitCode) |
498 | { |
499 | Setup(); |
500 | TestWakeupAllThread(); |
501 | dwExitCode = Cleanup(); |
502 | } |
503 | |
504 | PAL_TerminateEx(dwExitCode); |
505 | return dwExitCode; |
506 | } |
507 | |