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