• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2004, Bull S.A..  All rights reserved.
3  * Created by: Sebastien Decugis
4 
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of version 2 of the GNU General Public License as
7  * published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it would be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write the Free Software Foundation, Inc.,
15  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16 
17  * This file is a stress test for the function pthread_cond_timedwait.
18  *
19  *It aims to check the following assertion:
20  *  When inside the function, the thread releases the mutex
21  *  before waiting for the conditionnal variable.
22  *  Those two operations are atomic in the mean that
23  *  no other thread can gain access to the mutex
24  *  then signal (or broadcast) the condition
25  *  without the blocked thread behaving as if
26  *  this signal (or broadcast) had happened
27  *  after it blocked on the conditionnal variable.
28 
29  * The steps are:
30  * -> Create N mutex & N cond vars with different attributes
31  * -> Create N threads A, which
32  *    -> locks the mutex
33  *    -> create a thread B, which
34  *       -> locks the mutex
35  *       -> while the boolean is false,
36  *         -> broadcasts the condvar
37  *         -> timedwaits the condition for 10 seconds
38  *       -> broadcasts the condvar
39  *       -> unlock the mutex
40  *    -> while the boolean is false,
41  *      -> timedwaits the condvar for 10 seconds
42  *      -> signals the condvar
43  *    -> unlock the mutex
44  *    -> joins the thread B
45  * -> sets the boolean True when it receives SIGUSR1
46  * -> joins the N threads A.
47  *
48  * the test fails when a broadcast returns with a timeout.
49  *
50  * To test for pshared primitive, thread B could be in another process.
51  */
52 
53  /* We are testing conformance to IEEE Std 1003.1, 2003 Edition */
54 #define _POSIX_C_SOURCE 200112L
55 
56  /* We need the XSI extention for the mutex attributes
57     and the mkstemp() routine */
58 #ifndef WITHOUT_XOPEN
59 #define _XOPEN_SOURCE	600
60 #endif
61  /********************************************************************************************/
62 /****************************** standard includes *****************************************/
63 /********************************************************************************************/
64 #include <pthread.h>
65 #include <stdarg.h>
66 #include <stdio.h>
67 #include <stdlib.h>
68 #include <unistd.h>
69 
70 #include <errno.h>
71 #include <signal.h>
72 #include <sys/wait.h>
73 #include <sys/mman.h>
74 #include <string.h>
75 #include <time.h>
76 
77 /********************************************************************************************/
78 /******************************   Test framework   *****************************************/
79 /********************************************************************************************/
80 #include "testfrmw.h"
81 #include "testfrmw.c"
82  /* This header is responsible for defining the following macros:
83   * UNRESOLVED(ret, descr);
84   *    where descr is a description of the error and ret is an int (error code for example)
85   * FAILED(descr);
86   *    where descr is a short text saying why the test has failed.
87   * PASSED();
88   *    No parameter.
89   *
90   * Both three macros shall terminate the calling process.
91   * The testcase shall not terminate in any other maneer.
92   *
93   * The other file defines the functions
94   * void output_init()
95   * void output(char * string, ...)
96   *
97   * Those may be used to output information.
98   */
99 
100 /********************************************************************************************/
101 /********************************** Configuration ******************************************/
102 /********************************************************************************************/
103 #ifndef SCALABILITY_FACTOR
104 #define SCALABILITY_FACTOR 1
105 #endif
106 #ifndef VERBOSE
107 #define VERBOSE 1
108 #endif
109 
110 /* Number of children for each test scenario */
111 #define NCHILDREN (5)
112 
113 #define TIMEOUT 120
114 
115 #ifndef WITHOUT_ALTCLK
116 #define USE_ALTCLK		/* make tests with MONOTONIC CLOCK if supported */
117 #endif
118 
119 /********************************************************************************************/
120 /***********************************    Test case   *****************************************/
121 /********************************************************************************************/
122 
123 #ifdef WITHOUT_XOPEN
124 /* We define those to avoid compilation errors, but they won't be used */
125 #define PTHREAD_MUTEX_DEFAULT 0
126 #define PTHREAD_MUTEX_NORMAL 0
127 #define PTHREAD_MUTEX_ERRORCHECK 0
128 #define PTHREAD_MUTEX_RECURSIVE 0
129 
130 #endif
131 
132 struct _scenar {
133 	int m_type;		/* Mutex type to use */
134 	int mc_pshared;		/* 0: mutex and cond are process-private (default) ~ !0: Both are process-shared, if supported */
135 	int c_clock;		/* 0: cond uses the default clock. ~ !0: Cond uses monotonic clock, if supported. */
136 	int fork;		/* 0: Test between threads. ~ !0: Test across processes, if supported (mmap) */
137 	char *descr;		/* Case description */
138 } scenarii[] = {
139 	{
140 	PTHREAD_MUTEX_DEFAULT, 0, 0, 0, "Default mutex"}
141 	, {
142 	PTHREAD_MUTEX_NORMAL, 0, 0, 0, "Normal mutex"}
143 	, {
144 	PTHREAD_MUTEX_ERRORCHECK, 0, 0, 0, "Errorcheck mutex"}
145 	, {
146 	PTHREAD_MUTEX_RECURSIVE, 0, 0, 0, "Recursive mutex"}
147 
148 	, {
149 	PTHREAD_MUTEX_DEFAULT, 1, 0, 0, "PShared default mutex"}
150 	, {
151 	PTHREAD_MUTEX_NORMAL, 1, 0, 0, "Pshared normal mutex"}
152 	, {
153 	PTHREAD_MUTEX_ERRORCHECK, 1, 0, 0, "Pshared errorcheck mutex"}
154 	, {
155 	PTHREAD_MUTEX_RECURSIVE, 1, 0, 0, "Pshared recursive mutex"}
156 
157 	, {
158 	PTHREAD_MUTEX_DEFAULT, 1, 0, 1,
159 		    "Pshared default mutex across processes"}
160 	, {
161 	PTHREAD_MUTEX_NORMAL, 1, 0, 1,
162 		    "Pshared normal mutex across processes"}
163 	, {
164 	PTHREAD_MUTEX_ERRORCHECK, 1, 0, 1,
165 		    "Pshared errorcheck mutex across processes"}
166 	, {
167 	PTHREAD_MUTEX_RECURSIVE, 1, 0, 1,
168 		    "Pshared recursive mutex across processes"}
169 
170 #ifdef USE_ALTCLK
171 	, {
172 	PTHREAD_MUTEX_DEFAULT, 1, 1, 1,
173 		    "Pshared default mutex and alt clock condvar across processes"}
174 	, {
175 	PTHREAD_MUTEX_NORMAL, 1, 1, 1,
176 		    "Pshared normal mutex and alt clock condvar across processes"}
177 	, {
178 	PTHREAD_MUTEX_ERRORCHECK, 1, 1, 1,
179 		    "Pshared errorcheck mutex and alt clock condvar across processes"}
180 	, {
181 	PTHREAD_MUTEX_RECURSIVE, 1, 1, 1,
182 		    "Pshared recursive mutex and alt clock condvar across processes"}
183 
184 	, {
185 	PTHREAD_MUTEX_DEFAULT, 0, 1, 0,
186 		    "Default mutex and alt clock condvar"}
187 	, {
188 	PTHREAD_MUTEX_NORMAL, 0, 1, 0,
189 		    "Normal mutex and alt clock condvar"}
190 	, {
191 	PTHREAD_MUTEX_ERRORCHECK, 0, 1, 0,
192 		    "Errorcheck mutex and alt clock condvar"}
193 	, {
194 	PTHREAD_MUTEX_RECURSIVE, 0, 1, 0,
195 		    "Recursive mutex and alt clock condvar"}
196 
197 	, {
198 	PTHREAD_MUTEX_DEFAULT, 1, 1, 0,
199 		    "PShared default mutex and alt clock condvar"}
200 	, {
201 	PTHREAD_MUTEX_NORMAL, 1, 1, 0,
202 		    "Pshared normal mutex and alt clock condvar"}
203 	, {
204 	PTHREAD_MUTEX_ERRORCHECK, 1, 1, 0,
205 		    "Pshared errorcheck mutex and alt clock condvar"}
206 	, {
207 	PTHREAD_MUTEX_RECURSIVE, 1, 1, 0,
208 		    "Pshared recursive mutex and alt clock condvar"}
209 #endif
210 };
211 
212 #define NSCENAR (sizeof(scenarii)/sizeof(scenarii[0]))
213 
214 #define NTOT (NSCENAR * SCALABILITY_FACTOR * NCHILDREN)
215 
216 struct childdata {
217 	pthread_mutex_t mtx;
218 	pthread_cond_t cnd;
219 	clockid_t cid;
220 	int fork;
221 	int *pBool;
222 };
223 
224 typedef struct {
225 	struct childdata cd[NTOT];
226 	int boolean;
227 } testdata_t;
228 
229 pthread_attr_t ta;
230 
231 /***
232  * The grand child function (either sub-thread or sub-process)
233  */
threaded_B(void * arg)234 void *threaded_B(void *arg)
235 {
236 	int ret;
237 	struct timespec ts;
238 	struct childdata *cd = (struct childdata *)arg;
239 
240 	ret = pthread_mutex_lock(&(cd->mtx));
241 	if (ret != 0) {
242 		UNRESOLVED(ret, "[gchild] Unable to lock mutex");
243 	}
244 
245 	while (*(cd->pBool) == 0) {
246 		ret = pthread_cond_broadcast(&(cd->cnd));
247 		if (ret != 0) {
248 			UNRESOLVED(ret, "[gchild] Broadcast failed");
249 		}
250 
251 		ret = clock_gettime(cd->cid, &ts);
252 		if (ret != 0) {
253 			UNRESOLVED(errno, "[gchild] Unable to read clock");
254 		}
255 
256 		ts.tv_sec += TIMEOUT;
257 
258 		ret = pthread_cond_timedwait(&(cd->cnd), &(cd->mtx), &ts);
259 		if (ret == ETIMEDOUT) {
260 			FAILED
261 			    ("[gchild] Timeout occured. This means a cond signal was lost -- or parent died");
262 		}
263 		if (ret != 0) {
264 			UNRESOLVED(ret, "[gchild] Failed to wait the cond");
265 		}
266 	}
267 
268 	/* We shall broadcast again to be sure the parent is not hung */
269 	ret = pthread_cond_broadcast(&(cd->cnd));
270 	if (ret != 0) {
271 		UNRESOLVED(ret, "[gchild] Broadcast failed");
272 	}
273 
274 	ret = pthread_mutex_unlock(&(cd->mtx));
275 	if (ret != 0) {
276 		UNRESOLVED(ret, "[gchild] Failed to finally release the mutex");
277 	}
278 
279 	return NULL;
280 }
281 
282 /***
283  * The child function (always in the main thread)
284  */
threaded_A(void * arg)285 void *threaded_A(void *arg)
286 {
287 	struct childdata *cd = (struct childdata *)arg;
288 	int ret, status;
289 	pid_t child_p = 0, wrc;
290 	pthread_t child_t;
291 
292 	struct timespec ts;
293 
294 	ret = pthread_mutex_lock(&(cd->mtx));
295 	if (ret != 0) {
296 		UNRESOLVED(ret, "[child] Unable to lock mutex");
297 	}
298 
299 	/* Create the grand child */
300 	if (cd->fork == 0) {
301 		ret = pthread_create(&child_t, &ta, threaded_B, arg);
302 		if (ret != 0) {
303 			UNRESOLVED(ret,
304 				   "[child] Failed to create a grand child thread");
305 		}
306 	} else {
307 		child_p = fork();
308 		if (child_p == -1) {
309 			UNRESOLVED(ret,
310 				   "[child] Failed to create a grand child proces");
311 		}
312 
313 		if (child_p == 0) {	/* grand child */
314 			threaded_B(arg);
315 			exit(0);
316 		}
317 	}
318 
319 	while (*(cd->pBool) == 0) {
320 		ret = clock_gettime(cd->cid, &ts);
321 		if (ret != 0) {
322 			UNRESOLVED(errno, "[child] Unable to read clock");
323 		}
324 
325 		ts.tv_sec += TIMEOUT;
326 
327 		ret = pthread_cond_timedwait(&(cd->cnd), &(cd->mtx), &ts);
328 		if (ret == ETIMEDOUT) {
329 			FAILED
330 			    ("[child] Timeout occured. This means a cond broadcast was lost -- or gchild died");
331 		}
332 		if (ret != 0) {
333 			UNRESOLVED(ret, "[child] Failed to wait the cond");
334 		}
335 
336 		ret = pthread_cond_signal(&(cd->cnd));
337 		if (ret != 0) {
338 			UNRESOLVED(ret, "[child] Signal failed");
339 		}
340 	}
341 
342 	ret = pthread_mutex_unlock(&(cd->mtx));
343 	if (ret != 0) {
344 		UNRESOLVED(ret, "[gchild] Failed to finally release the mutex");
345 	}
346 
347 	/* Wait for the grand child termination */
348 	if (cd->fork == 0) {
349 		ret = pthread_join(child_t, NULL);
350 		if (ret != 0) {
351 			UNRESOLVED(ret,
352 				   "[child] Failed to join a grand child thread");
353 		}
354 	} else {
355 		wrc = waitpid(child_p, &status, 0);
356 		if (wrc != child_p) {
357 			output("Expected pid: %i. Got %i\n", (int)child_p,
358 			       (int)wrc);
359 			UNRESOLVED(errno, "Waitpid failed");
360 		}
361 
362 		if (WIFSIGNALED(status)) {
363 			output("Child process killed with signal %d\n",
364 			       WTERMSIG(status));
365 			UNRESOLVED(0, "Child process was killed");
366 		}
367 
368 		if (WIFEXITED(status)) {
369 			ret = WEXITSTATUS(status);
370 		} else {
371 			UNRESOLVED(0,
372 				   "Child process was neither killed nor exited");
373 		}
374 	}
375 
376 	/* the end */
377 	return NULL;
378 }
379 
380 int *pBoolean = NULL;
381 
382 /***
383  * Signal handler
384  */
sighdl(int sig)385 void sighdl(int sig)
386 {
387 #if VERBOSE > 1
388 	output("Received the USR1 signal; stopping everything\n");
389 #endif
390 	*pBoolean = 1;
391 }
392 
main(int argc,char * argv[])393 int main(int argc, char *argv[])
394 {
395 	int ret, i, j;
396 	struct sigaction sa;
397 
398 	pthread_mutexattr_t ma;
399 	pthread_condattr_t ca;
400 	clockid_t cid = CLOCK_REALTIME;
401 
402 	testdata_t *td;
403 	testdata_t alternativ;
404 
405 	int do_fork;
406 	long pshared, monotonic, cs, mf;
407 
408 	pthread_t th[NTOT];
409 
410 	output_init();
411 
412 	pshared = sysconf(_SC_THREAD_PROCESS_SHARED);
413 	cs = sysconf(_SC_CLOCK_SELECTION);
414 	monotonic = sysconf(_SC_MONOTONIC_CLOCK);
415 	mf = sysconf(_SC_MAPPED_FILES);
416 
417 #if VERBOSE > 0
418 	output("Test starting\n");
419 	output("System abilities:\n");
420 	output(" TPS : %li\n", pshared);
421 	output(" CS  : %li\n", cs);
422 	output(" MON : %li\n", monotonic);
423 	output(" MF  : %li\n", mf);
424 	if ((mf < 0) || (pshared < 0))
425 		output("Process-shared attributes won't be tested\n");
426 	if ((cs < 0) || (monotonic < 0))
427 		output("Alternative clock won't be tested\n");
428 #endif
429 
430 	/* We are not interested in testing the clock if we have no other clock available.. */
431 	if (monotonic < 0)
432 		cs = -1;
433 
434 #ifndef USE_ALTCLK
435 	if (cs > 0)
436 		output
437 		    ("Implementation supports the MONOTONIC CLOCK but option is disabled in test.\n");
438 #endif
439 
440 /**********
441  * Allocate space for the testdata structure
442  */
443 	if (mf < 0) {
444 		/* Cannot mmap a file, we use an alternative method */
445 		td = &alternativ;
446 		pshared = -1;	/* We won't do this testing anyway */
447 #if VERBOSE > 0
448 		output("Testdata allocated in the process memory.\n");
449 #endif
450 	} else {
451 		/* We will place the test data in a mmaped file */
452 		char filename[] = "/tmp/cond_timedwait_st1-XXXXXX";
453 		size_t sz, ps;
454 		void *mmaped;
455 		int fd;
456 		char *tmp;
457 
458 		/* We now create the temp files */
459 		fd = mkstemp(filename);
460 		if (fd == -1) {
461 			UNRESOLVED(errno,
462 				   "Temporary file could not be created");
463 		}
464 
465 		/* and make sure the file will be deleted when closed */
466 		unlink(filename);
467 
468 #if VERBOSE > 1
469 		output("Temp file created (%s).\n", filename);
470 #endif
471 
472 		ps = (size_t) sysconf(_SC_PAGESIZE);
473 		sz = ((sizeof(testdata_t) / ps) + 1) * ps;	/* # pages needed to store the testdata */
474 
475 		tmp = calloc(1, sz);
476 		if (tmp == NULL) {
477 			UNRESOLVED(errno, "Memory allocation failed");
478 		}
479 
480 		/* Write the data to the file.  */
481 		if (write(fd, tmp, sz) != (ssize_t) sz) {
482 			UNRESOLVED(sz, "Writting to the file failed");
483 		}
484 
485 		free(tmp);
486 
487 		/* Now we can map the file in memory */
488 		mmaped =
489 		    mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
490 		if (mmaped == MAP_FAILED) {
491 			UNRESOLVED(errno, "mmap failed");
492 		}
493 
494 		td = (testdata_t *) mmaped;
495 
496 		/* Our datatest structure is now in shared memory */
497 #if VERBOSE > 1
498 		output("Testdata allocated in shared memory (%ib).\n",
499 		       sizeof(testdata_t));
500 #endif
501 	}
502 
503 	/* Init the signal handler variable */
504 	pBoolean = &(td->boolean);
505 
506 	/* Init the structure */
507 	for (i = 0; i < NSCENAR; i++) {
508 #if VERBOSE > 1
509 		output("[parent] Preparing attributes for: %s\n",
510 		       scenarii[i].descr);
511 #ifdef WITHOUT_XOPEN
512 		output("[parent] Mutex attributes DISABLED -> not used\n");
513 #endif
514 #endif
515 		/* set / reset everything */
516 		do_fork = 0;
517 		ret = pthread_mutexattr_init(&ma);
518 		if (ret != 0) {
519 			UNRESOLVED(ret,
520 				   "[parent] Unable to initialize the mutex attribute object");
521 		}
522 		ret = pthread_condattr_init(&ca);
523 		if (ret != 0) {
524 			UNRESOLVED(ret,
525 				   "[parent] Unable to initialize the cond attribute object");
526 		}
527 #ifndef WITHOUT_XOPEN
528 		/* Set the mutex type */
529 		ret = pthread_mutexattr_settype(&ma, scenarii[i].m_type);
530 		if (ret != 0) {
531 			UNRESOLVED(ret, "[parent] Unable to set mutex type");
532 		}
533 #if VERBOSE > 1
534 		output("[parent] Mutex type : %i\n", scenarii[i].m_type);
535 #endif
536 #endif
537 
538 		/* Set the pshared attributes, if supported */
539 		if ((pshared > 0) && (scenarii[i].mc_pshared != 0)) {
540 			ret =
541 			    pthread_mutexattr_setpshared(&ma,
542 							 PTHREAD_PROCESS_SHARED);
543 			if (ret != 0) {
544 				UNRESOLVED(ret,
545 					   "[parent] Unable to set the mutex process-shared");
546 			}
547 			ret =
548 			    pthread_condattr_setpshared(&ca,
549 							PTHREAD_PROCESS_SHARED);
550 			if (ret != 0) {
551 				UNRESOLVED(ret,
552 					   "[parent] Unable to set the cond var process-shared");
553 			}
554 #if VERBOSE > 1
555 			output("[parent] Mutex & cond are process-shared\n");
556 #endif
557 		}
558 #if VERBOSE > 1
559 		else {
560 			output("[parent] Mutex & cond are process-private\n");
561 		}
562 #endif
563 
564 		/* Set the alternative clock, if supported */
565 #ifdef USE_ALTCLK
566 		if ((cs > 0) && (scenarii[i].c_clock != 0)) {
567 			ret = pthread_condattr_setclock(&ca, CLOCK_MONOTONIC);
568 			if (ret != 0) {
569 				UNRESOLVED(ret,
570 					   "[parent] Unable to set the monotonic clock for the cond");
571 			}
572 #if VERBOSE > 1
573 			output("[parent] Cond uses the Monotonic clock\n");
574 #endif
575 		}
576 #if VERBOSE > 1
577 		else {
578 			output("[parent] Cond uses the default clock\n");
579 		}
580 #endif
581 		ret = pthread_condattr_getclock(&ca, &cid);
582 		if (ret != 0) {
583 			UNRESOLVED(ret, "Unable to get clock from cond attr");
584 		}
585 #endif
586 
587 		/* Tell whether the test will be across processes */
588 		if ((pshared > 0) && (scenarii[i].fork != 0)) {
589 			do_fork = 1;
590 #if VERBOSE > 1
591 			output("[parent] Child will be a new process\n");
592 #endif
593 		}
594 #if VERBOSE > 1
595 		else {
596 			output("[parent] Child will be a new thread\n");
597 		}
598 #endif
599 
600 		/* Initialize all the mutex and condvars which uses those attributes */
601 		for (j = 0; j < SCALABILITY_FACTOR * NCHILDREN; j++) {
602 #define CD (td->cd[i+(j*NSCENAR)])
603 			CD.pBool = &(td->boolean);
604 			CD.fork = do_fork;
605 			CD.cid = cid;
606 
607 			/* initialize the condvar */
608 			ret = pthread_cond_init(&(CD.cnd), &ca);
609 			if (ret != 0) {
610 				UNRESOLVED(ret, "[parent] Cond init failed");
611 			}
612 
613 			/* initialize the mutex */
614 			ret = pthread_mutex_init(&(CD.mtx), &ma);
615 			if (ret != 0) {
616 				UNRESOLVED(ret, "[parent] Mutex init failed");
617 			}
618 #undef CD
619 		}
620 
621 		ret = pthread_condattr_destroy(&ca);
622 		if (ret != 0) {
623 			UNRESOLVED(ret,
624 				   "Failed to destroy the cond var attribute object");
625 		}
626 
627 		ret = pthread_mutexattr_destroy(&ma);
628 		if (ret != 0) {
629 			UNRESOLVED(ret,
630 				   "Failed to destroy the mutex attribute object");
631 		}
632 	}
633 #if VERBOSE > 1
634 	output("[parent] All condvars & mutex are ready\n");
635 #endif
636 
637 	ret = pthread_attr_init(&ta);
638 	if (ret != 0) {
639 		UNRESOLVED(ret,
640 			   "[parent] Failed to initialize a thread attribute object");
641 	}
642 	ret = pthread_attr_setstacksize(&ta, sysconf(_SC_THREAD_STACK_MIN));
643 	if (ret != 0) {
644 		UNRESOLVED(ret, "[parent] Failed to set thread stack size");
645 	}
646 
647 	sigemptyset(&sa.sa_mask);
648 	sa.sa_flags = 0;
649 	sa.sa_handler = sighdl;
650 	if ((ret = sigaction(SIGUSR1, &sa, NULL))) {
651 		UNRESOLVED(ret, "Unable to register signal handler");
652 	}
653 #if VERBOSE > 1
654 	output("[parent] Signal handler registered\n");
655 #endif
656 
657 	for (i = 0; i < NTOT; i++) {
658 		ret = pthread_create(&th[i], &ta, threaded_A, &(td->cd[i]));
659 		/* In case of failure we can exit; the child process will die after a while */
660 		if (ret != 0) {
661 			UNRESOLVED(ret, "[Parent] Failed to create a thread");
662 		}
663 #if VERBOSE > 1
664 		if ((i % 10) == 0)
665 			output("[parent] %i threads created...\n", i + 1);
666 #endif
667 	}
668 
669 #if VERBOSE > 1
670 	output("[parent] All %i threads are running...\n", NTOT);
671 #endif
672 
673 	for (i = 0; i < NTOT; i++) {
674 		ret = pthread_join(th[i], NULL);
675 		if (ret != 0) {
676 			UNRESOLVED(ret, "[Parent] Failed to join a thread");
677 		}
678 	}
679 
680 	/* Destroy everything */
681 	for (i = 0; i < NTOT; i++) {
682 		/* destroy the condvar */
683 		ret = pthread_cond_destroy(&(td->cd[i].cnd));
684 		if (ret != 0) {
685 			UNRESOLVED(ret, "[parent] Cond destroy failed");
686 		}
687 
688 		/* destroy the mutex */
689 		ret = pthread_mutex_init(&(td->cd[i].mtx), &ma);
690 		if (ret != 0) {
691 			UNRESOLVED(ret, "[parent] Mutex destroy failed");
692 		}
693 	}
694 
695 #if VERBOSE > 0
696 	output("Test passed\n");
697 #endif
698 
699 	PASSED;
700 }
701