| /* TODO: proper output */ |
| |
| /* |
| * |
| * Copyright (c) International Business Machines Corp., 2001 |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See |
| * the GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| /* |
| * FILE : pth_str01.c |
| * DESCRIPTION : create a tree of threads |
| * HISTORY: |
| * 04/09/2001 Paul Larson (plars@us.ibm.com) |
| * -Ported |
| * |
| */ |
| |
| #include <pthread.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <stdint.h> |
| #include <sys/types.h> |
| #include "test.h" |
| #include "pth_str01.h" |
| |
| int depth = 3; |
| int breadth = 4; |
| int timeout = 30; /* minutes */ |
| int cdepth; /* current depth */ |
| int debug = 0; |
| |
| c_info *child_info; /* pointer to info array */ |
| int node_count; /* number of nodes created so far */ |
| pthread_mutex_t node_mutex; /* mutex for the node_count */ |
| pthread_cond_t node_condvar; /* condition variable for node_count */ |
| pthread_attr_t attr; /* attributes for created threads */ |
| |
| int num_nodes(int, int); |
| int synchronize_children(c_info *); |
| int doit(c_info *); |
| |
| char *TCID = "pth_str01"; |
| int TST_TOTAL = 1; |
| |
| void testexit(int value) |
| { |
| if (value == 0) |
| tst_resm(TPASS, "Test passed"); |
| else |
| tst_resm(TFAIL, "Test failed"); |
| |
| exit(value); |
| } |
| |
| /* |
| * parse_args |
| * |
| * Parse command line arguments. Any errors cause the program to exit |
| * at this point. |
| */ |
| static void parse_args( int argc, char *argv[] ) |
| { |
| int opt, errflag = 0; |
| int bflag = 0, dflag = 0, tflag = 0; |
| |
| while ((opt = getopt( argc, argv, "b:d:t:Dh?" )) != EOF) { |
| switch ( opt ) { |
| case 'b': |
| if (bflag) |
| errflag++; |
| else { |
| bflag++; |
| breadth = atoi( optarg ); |
| if (breadth <= 0) |
| errflag++; |
| } |
| break; |
| case 'd': |
| if (dflag) |
| errflag++; |
| else { |
| dflag++; |
| depth = atoi( optarg ); |
| if (depth <= 0) |
| errflag++; |
| } |
| break; |
| case 't': |
| if (tflag) |
| errflag++; |
| else { |
| tflag++; |
| timeout = atoi( optarg ); |
| if (timeout <= 0) |
| errflag++; |
| } |
| break; |
| case 'D': |
| debug = 1; |
| break; |
| case 'h': |
| default: |
| errflag++; |
| break; |
| } |
| } |
| |
| if (errflag) { |
| fprintf( stderr, "usage: %s [-b <num>] [-d <num>] [-t <num>] [-D]", argv[0] ); |
| fprintf( stderr, " where:\n" ); |
| fprintf( stderr, "\t-b <num>\tbreadth of child nodes\n" ); |
| fprintf( stderr, "\t-d <num>\tdepth of child nodes\n" ); |
| fprintf( stderr, "\t-t <num>\ttimeout for child communication (in minutes)\n" ); |
| fprintf( stderr, "\t-D\t\tdebug mode on\n" ); |
| testexit( 1 ); |
| } |
| |
| } |
| |
| /* |
| * num_nodes |
| * |
| * Caculate the number of child nodes for a given breadth and depth tree. |
| */ |
| int num_nodes( int b, int d ) |
| { |
| int n, sum = 1, partial_exp = 1; |
| |
| /* |
| * The total number of children = sum ( b ** n ) |
| * n=0->d |
| * Since b ** 0 == 1, and it's hard to compute that kind of value |
| * in this simplistic loop, we start sum at 1 (above) to compensate |
| * and do the computations from 1->d below. |
| */ |
| for (n = 1; n <= d; n++) { |
| partial_exp *= b; |
| sum += partial_exp; |
| } |
| |
| return( sum ); |
| } |
| |
| /* |
| * synchronize_children |
| * |
| * Register the child with the parent and then wait for all of the children |
| * at the same level to register also. Return when everything is synched up. |
| */ |
| int synchronize_children( c_info *parent ) { |
| int my_index, rc; |
| c_info *info_p; |
| struct timespec timer; |
| |
| if (debug) { |
| printf( "trying to lock node_mutex\n" ); |
| fflush( stdout ); |
| } |
| |
| /* |
| * Lock the node_count mutex to we can safely increment it. We |
| * will unlock it when we broadcast that all of our siblings have |
| * been created or when we block waiting for that broadcast. |
| */ |
| pthread_mutex_lock( &node_mutex ); |
| my_index = node_count++; |
| |
| tst_resm( TINFO, "thread %d started", my_index ); |
| |
| /* |
| * Get a pointer into the array of thread structures which will |
| * be "me". |
| */ |
| info_p = &child_info[my_index]; |
| info_p->index = my_index; |
| |
| if (debug) { |
| printf( "thread %d info_p=%p\n", my_index, info_p ); |
| fflush( stdout ); |
| } |
| |
| /* |
| * Register with parent bumping the parent's child_count variable. |
| * Make sure we have exclusive access to that variable before we |
| * do the increment. |
| */ |
| if (debug) { |
| printf( "thread %d locking child_mutex %p\n", my_index, |
| &parent->child_mutex ); |
| fflush( stdout ); |
| } |
| pthread_mutex_lock( &parent->child_mutex ); |
| if (debug) { |
| printf( "thread %d bumping child_count (currently %d)\n", |
| my_index, parent->child_count ); |
| fflush( stdout ); |
| } |
| parent->child_ptrs[parent->child_count++] = info_p; |
| if (debug) { |
| printf( "thread %d unlocking child_mutex %p\n", my_index, |
| &parent->child_mutex ); |
| fflush( stdout ); |
| } |
| pthread_mutex_unlock( &parent->child_mutex ); |
| |
| if (debug) { |
| printf( "thread %d node_count = %d\n", my_index, node_count ); |
| printf( "expecting %d nodes\n", num_nodes(breadth, cdepth) ); |
| fflush( stdout ); |
| } |
| |
| /* |
| * Have all the nodes at our level in the tree been created yet? |
| * If so, then send out a broadcast to wake everyone else up (to let |
| * them know they can now create their children (if they are not |
| * leaf nodes)). Otherwise, go to sleep waiting for the broadcast. |
| */ |
| if (node_count == num_nodes(breadth, cdepth)) { |
| |
| /* |
| * Increase the current depth variable, as the tree is now |
| * fully one level taller. |
| */ |
| if (debug) { |
| printf( "thread %d doing cdepth++ (%d)\n", my_index, cdepth ); |
| fflush( stdout ); |
| } |
| cdepth++; |
| |
| if (debug) { |
| printf( "thread %d sending child_mutex broadcast\n", my_index ); |
| fflush( stdout ); |
| } |
| /* |
| * Since all of our siblings have been created, this level of |
| * the tree is now allowed to continue its work, so wake up the |
| * rest of the siblings. |
| */ |
| pthread_cond_broadcast( &node_condvar ); |
| |
| } else { |
| |
| /* |
| * In this case, not all of our siblings at this level of the |
| * tree have been created, so go to sleep and wait for the |
| * broadcast on node_condvar. |
| */ |
| if (debug) { |
| printf( "thread %d waiting for siblings to register\n", |
| my_index ); |
| fflush( stdout ); |
| } |
| time( &timer.tv_sec ); |
| timer.tv_sec += (unsigned long)timeout * 60; |
| timer.tv_nsec = (unsigned long)0; |
| if ((rc = pthread_cond_timedwait(&node_condvar, &node_mutex, |
| &timer))) { |
| tst_resm( TINFO, "pthread_cond_timedwait (sync) %d: %s\n", |
| my_index, strerror(rc) ); |
| testexit( 2 ); |
| } |
| |
| if (debug) { |
| printf( "thread %d is now unblocked\n", my_index ); |
| fflush( stdout ); |
| } |
| |
| } |
| |
| /* |
| * Unlock the node_mutex lock, as this thread is finished |
| * initializing. |
| */ |
| if (debug) { |
| printf( "thread %d unlocking node_mutex\n", my_index ); |
| fflush( stdout ); |
| } |
| pthread_mutex_unlock( &node_mutex ); |
| if (debug) { |
| printf( "thread %d unlocked node_mutex\n", my_index ); |
| fflush( stdout ); |
| } |
| |
| if (debug) { |
| printf( "synchronize_children returning %d\n", my_index ); |
| fflush( stdout ); |
| } |
| |
| return( my_index ); |
| |
| } |
| |
| /* |
| * doit |
| * |
| * Do it. |
| */ |
| int doit( c_info *parent ) { |
| int rc, child, my_index; |
| void *status; |
| c_info *info_p; |
| struct timespec timer; |
| |
| if (parent != NULL) { |
| /* |
| * Synchronize with our siblings so that all the children at |
| * a given level have been created before we allow those children |
| * to spawn new ones (or do anything else for that matter). |
| */ |
| if (debug) { |
| printf( "non-root child calling synchronize_children\n" ); |
| fflush( stdout ); |
| } |
| my_index = synchronize_children( parent ); |
| if (debug) { |
| printf( "non-root child has been assigned index %d\n", |
| my_index ); |
| fflush( stdout ); |
| } |
| } else { |
| /* |
| * The first thread has no one with which to synchronize, so |
| * set some initial values for things. |
| */ |
| if (debug) { |
| printf( "root child\n" ); |
| fflush( stdout ); |
| } |
| cdepth = 1; |
| my_index = 0; |
| node_count = 1; |
| } |
| |
| /* |
| * Figure out our place in the pthread array. |
| */ |
| info_p = &child_info[my_index]; |
| |
| if (debug) { |
| printf( "thread %d getting to heart of doit.\n", my_index ); |
| printf( "info_p=%p, cdepth=%d, depth=%d\n", info_p, cdepth, depth ); |
| fflush( stdout ); |
| } |
| |
| if (cdepth <= depth) { |
| |
| /* |
| * Since the tree is not yet complete (it is not yet tall enough), |
| * we need to create another level of children. |
| */ |
| |
| tst_resm( TINFO, "thread %d creating kids, cdepth=%d", my_index, cdepth ); |
| |
| /* |
| * Create breadth children. |
| */ |
| for (child = 0; child < breadth; child++) { |
| if (debug) { |
| printf( "thread %d making child %d, ptr=%p\n", my_index, |
| child, &(info_p->threads[child]) ); |
| fflush( stdout ); |
| } |
| if ((rc = pthread_create(&(info_p->threads[child]), &attr, |
| (void *)doit, (void *)info_p))) { |
| tst_resm( TINFO, "pthread_create (doit): %s\n", |
| strerror(rc) ); |
| testexit( 3 ); |
| } else { |
| if (debug) { |
| printf( "pthread_create made thread %p\n", |
| &(info_p->threads[child]) ); |
| fflush( stdout ); |
| } |
| } |
| } |
| |
| if (debug) { |
| printf( "thread %d waits on kids, cdepth=%d\n", my_index, |
| cdepth ); |
| fflush( stdout ); |
| } |
| |
| /* |
| * Wait for our children to finish before we exit ourselves. |
| */ |
| for (child = 0; child < breadth; child++) { |
| if (debug) { |
| printf( "attempting join on thread %p\n", |
| &(info_p->threads[child]) ); |
| fflush( stdout ); |
| } |
| if ((rc = pthread_join((info_p->threads[child]), &status))) { |
| if (debug) { |
| printf( |
| "join failed on thread %d, status addr=%p: %s\n", |
| my_index, status, strerror(rc) ); |
| fflush( stdout ); |
| } |
| testexit( 4 ); |
| } else { |
| if (debug) { |
| printf( "thread %d joined child %d ok\n", my_index, |
| child ); |
| fflush( stdout ); |
| } |
| } |
| } |
| |
| } else { |
| |
| /* |
| * This is the leaf node case. These children don't create |
| * any kids; they just talk amongst themselves. |
| */ |
| tst_resm( TINFO, "thread %d is a leaf node, depth=%d", my_index, cdepth ); |
| |
| /* |
| * Talk to siblings (children of the same parent node). |
| */ |
| if (breadth > 1) { |
| |
| for (child = 0; child < breadth; child++) { |
| /* |
| * Don't talk to yourself. |
| */ |
| if (parent->child_ptrs[child] != info_p) { |
| if (debug) { |
| printf( "thread %d locking talk_mutex\n", |
| my_index ); |
| fflush( stdout ); |
| } |
| pthread_mutex_lock( |
| &(parent->child_ptrs[child]->talk_mutex) ); |
| if ( ++parent->child_ptrs[child]->talk_count |
| == (breadth - 1) ) { |
| if (debug) { |
| printf( "thread %d talk siblings\n", my_index ); |
| fflush( stdout ); |
| } |
| if ((rc = pthread_cond_broadcast( |
| &parent->child_ptrs[child]->talk_condvar))) { |
| tst_resm( TINFO, "pthread_cond_broadcast: %s\n", |
| strerror(rc) ); |
| testexit( 5 ); |
| } |
| } |
| if (debug) { |
| printf( "thread %d unlocking talk_mutex\n", |
| my_index ); |
| fflush( stdout ); |
| } |
| pthread_mutex_unlock( |
| &(parent->child_ptrs[child]->talk_mutex) ); |
| } |
| } |
| |
| /* |
| * Wait until the breadth - 1 siblings have contacted us. |
| */ |
| if (debug) { |
| printf( "thread %d waiting for talk siblings\n", |
| my_index ); |
| fflush( stdout ); |
| } |
| |
| pthread_mutex_lock( &info_p->talk_mutex ); |
| if (info_p->talk_count < (breadth - 1)) { |
| time( &timer.tv_sec ); |
| timer.tv_sec += (unsigned long)timeout * 60; |
| timer.tv_nsec = (unsigned long)0; |
| if ((rc = pthread_cond_timedwait(&info_p->talk_condvar, |
| &info_p->talk_mutex, &timer))) { |
| tst_resm( TINFO, |
| "pthread_cond_timedwait (leaf) %d: %s\n", |
| my_index, strerror(rc) ); |
| testexit( 6 ); |
| } |
| } |
| pthread_mutex_unlock( &info_p->talk_mutex ); |
| |
| } |
| |
| } |
| |
| /* |
| * Our work is done. We're outta here. |
| */ |
| tst_resm( TINFO, "thread %d exiting, depth=%d, status=%d, addr=%p", my_index, |
| cdepth, info_p->status, info_p); |
| |
| pthread_exit( 0 ); |
| |
| } |
| |
| /* |
| * main |
| */ |
| int main( int argc, char *argv[] ) { |
| int rc, ind, total; |
| pthread_t root_thread; |
| |
| parse_args( argc, argv ); |
| |
| /* |
| * Initialize node mutex. |
| */ |
| if ((rc = pthread_mutex_init(&node_mutex, NULL))) { |
| tst_resm( TINFO, "pthread_mutex_init(node_mutex): %s\n", |
| strerror(rc) ); |
| testexit( 7 ); |
| } |
| |
| /* |
| * Initialize node condition variable. |
| */ |
| if ((rc = pthread_cond_init(&node_condvar, NULL))) { |
| tst_resm( TINFO, "pthread_cond_init(node_condvar): %s\n", |
| strerror(rc) ); |
| testexit( 8 ); |
| } |
| |
| /* |
| * Allocate pthread info structure array. |
| */ |
| total = num_nodes( breadth, depth ); |
| tst_resm( TINFO, "Allocating %d nodes.", total ); |
| if ((child_info = (c_info *)malloc( total * sizeof(c_info) )) |
| == NULL ) { |
| perror( "malloc child_info" ); |
| testexit( 10 ); |
| } |
| memset( child_info, 0x00, total * sizeof(c_info) ); |
| |
| if (debug) { |
| printf( "Initializing array for %d children\n", total ); |
| fflush( stdout ); |
| } |
| |
| /* |
| * Allocate array of pthreads descriptors and initialize variables. |
| */ |
| for (ind = 0; ind < total; ind++) { |
| |
| if ( (child_info[ind].threads = |
| (pthread_t *)malloc( breadth * sizeof(pthread_t) )) |
| == NULL ) { |
| perror( "malloc threads" ); |
| testexit( 11 ); |
| } |
| memset( child_info[ind].threads, 0x00, breadth * sizeof(pthread_t) ); |
| |
| if ( (child_info[ind].child_ptrs = |
| (c_info **)malloc( breadth * sizeof(c_info *) )) == NULL ) { |
| perror( "malloc child_ptrs" ); |
| testexit( 12 ); |
| } |
| memset( child_info[ind].child_ptrs, 0x00, |
| breadth * sizeof(c_info *) ); |
| |
| if ((rc = pthread_mutex_init(&child_info[ind].child_mutex, |
| NULL))) { |
| tst_resm( TINFO, "pthread_mutex_init child_mutex: %s\n", |
| strerror(rc) ); |
| testexit( 13 ); |
| } |
| |
| if ((rc = pthread_mutex_init(&child_info[ind].talk_mutex, |
| NULL))) { |
| tst_resm( TINFO, "pthread_mutex_init talk_mutex: %s\n", |
| strerror(rc) ); |
| testexit( 14 ); |
| } |
| |
| if ((rc = pthread_cond_init(&child_info[ind].child_condvar, |
| NULL))) { |
| tst_resm( TINFO, |
| "pthread_cond_init child_condvar: %s\n", |
| strerror(rc) ); |
| testexit( 15 ); |
| } |
| |
| if ((rc = pthread_cond_init(&child_info[ind].talk_condvar, |
| NULL))) { |
| tst_resm( TINFO, "pthread_cond_init talk_condvar: %s\n", |
| strerror(rc) ); |
| testexit( 16 ); |
| } |
| |
| if (debug) { |
| printf( "Successfully initialized child %d.\n", ind ); |
| fflush( stdout ); |
| } |
| |
| } |
| |
| tst_resm( TINFO, "Creating root thread attributes via pthread_attr_init." ); |
| |
| if ((rc = pthread_attr_init(&attr))) { |
| tst_resm( TINFO, "pthread_attr_init: %s\n", strerror(rc) ); |
| testexit( 17 ); |
| } |
| |
| /* |
| * Make sure that all the thread children we create have the |
| * PTHREAD_CREATE_JOINABLE attribute. If they don't, then the |
| * pthread_join call will sometimes fail and cause mass confusion. |
| */ |
| if ((rc = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE)) |
| ) { |
| tst_resm( TINFO, "pthread_attr_setdetachstate: %s\n", |
| strerror(rc) ); |
| testexit( 18 ); |
| } |
| |
| tst_resm( TINFO, "Creating root thread via pthread_create." ); |
| |
| if ((rc = pthread_create(&root_thread, &attr, (void *)doit, NULL))) { |
| tst_resm( TINFO, "pthread_create: %s\n", strerror(rc) ); |
| testexit( 19 ); |
| } |
| |
| if (debug) { |
| printf( "Doing pthread_join.\n" ); |
| fflush( stdout ); |
| } |
| |
| /* |
| * Wait for the root child to exit. |
| */ |
| if (( rc = pthread_join(root_thread, NULL) )) { |
| tst_resm( TINFO, "pthread_join: %s\n", strerror(rc) ); |
| testexit( 20 ); |
| } |
| |
| if (debug) { |
| printf( "About to pthread_exit.\n" ); |
| fflush( stdout ); |
| } |
| |
| testexit( 0 ); |
| |
| exit(0); |
| } |