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
36int MaxNumHelperProcess = MAX_HELPER_PROCESS;
37
38/* indicate how the test thread wake up helper thread. */
39typedef enum _TestCaseType {
40 WakeUpOneThread, /* wake up one helper thread at a time. */
41 WakeUpAllThread /* wake up all helper threads at once */
42} TestCaseType;
43
44TestCaseType 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. */
49DWORD ThreadIndexOfThreadFinishEvent = 0;
50
51struct helper_process_t
52{
53 PROCESS_INFORMATION pi;
54 HANDLE hProcessReadyEvent;
55 HANDLE hProcessFinishEvent;
56} helper_process[MAX_HELPER_PROCESS];
57
58HANDLE hProcessStartEvent;
59
60struct 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 */
71DWORD 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 */
139void
140Setup()
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 */
285DWORD
286Cleanup()
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 */
339void
340TestWakeupOneThread()
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 */
414void
415TestWakeupAllThread()
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
467int __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