| /* |
| * Copyright (c) International Business Machines Corp., 2004. |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of version 2 of the GNU General Public License as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it would be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, write the Free Software Foundation, Inc., 59 |
| * Temple Place - Suite 330, Boston MA 02111-1307, USA. |
| * |
| */ |
| /********************************************************** |
| * |
| * TEST IDENTIFIER : nptl01 |
| * |
| * EXECUTED BY : root |
| * |
| * TEST TITLE : NPTL test for pthread_cond_timedwait() error |
| * path bug. |
| * |
| * TEST CASE TOTAL : 1 |
| * |
| * AUTHOR : Neil Richards <neil_richards@uk.ibm.com> |
| * |
| * DESCRIPTION |
| * This is a test for a bug found in the pthread_cond_timedwait() system call. |
| * of the Native POSIX Thread Library (NPTL) library code. |
| * There was an error path in the system call, where the sequence counters were |
| * getting updated w/o holding the internal condvar lock. A FAIL is indicated |
| * by the test hanging and not completing execution. |
| * |
| ****************************************************************/ |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <pthread.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <time.h> |
| #include <sys/time.h> |
| #include "test.h" |
| #include "usctest.h" |
| |
| #define MAXTIME 72000 /* Maximum # of secs to wait before failing */ |
| #define NUMLOOPS 100000 /* # of loops */ |
| |
| char *TCID="nptl01"; /* Test program identifier. */ |
| int TST_TOTAL=1; /* Total number of test cases. */ |
| extern int Tst_count; /* Test Case counter for tst_* routines */ |
| void cleanup(); |
| |
| pthread_mutex_t req; |
| pthread_mutex_t ack; |
| pthread_mutex_t wait; |
| pthread_cond_t parent; |
| pthread_cond_t child; |
| int idle_count = 0; |
| |
| /* |
| * The time to wait should be set appropriately so that the waiting thread |
| * is coming out of the wait at around the same time as the other thread is |
| * signalling it. |
| * The value of 1000 seems to work (ie. demonstrate the problem) on my |
| * 8 way (hyperthreaded) 2GHz Xeon box. |
| */ |
| #define NSECS_TO_WAIT (1) |
| |
| void call_mutex_init(pthread_mutex_t* mutex, char* buf, size_t buf_len) { |
| int ret; |
| |
| if ((ret = pthread_mutex_init(mutex, NULL)) != 0) { |
| tst_brkm(TBROK, cleanup, "pthread_mutex_init failed: %s", |
| strerror_r(ret, buf, buf_len)); |
| } |
| } |
| |
| void call_mutex_lock(pthread_mutex_t* mutex, char* buf, size_t buf_len) { |
| int ret; |
| |
| if ((ret = pthread_mutex_lock(mutex)) != 0) { |
| tst_brkm(TBROK, cleanup, "pthread_mutex_lock failed: %s", |
| strerror_r(ret, buf, buf_len)); |
| } |
| } |
| |
| void call_mutex_unlock(pthread_mutex_t* mutex, char* buf, size_t buf_len) { |
| int ret; |
| |
| if ((ret = pthread_mutex_unlock(mutex)) != 0) { |
| tst_brkm(TBROK, cleanup, "pthread_mutex_unlock failed: %s", |
| strerror_r(ret, buf, buf_len)); |
| } |
| } |
| |
| void call_cond_init(pthread_cond_t* cond, char* buf, size_t buf_len) { |
| int ret; |
| |
| if ((ret = pthread_cond_init(cond, NULL)) != 0) { |
| tst_brkm(TBROK, cleanup, "pthread_cond_init failed: %s", |
| strerror_r(ret, buf, buf_len)); |
| } |
| } |
| |
| void call_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex, |
| char* buf, size_t buf_len) { |
| int ret; |
| |
| if ((ret = pthread_cond_wait(cond, mutex)) != 0) { |
| tst_brkm(TBROK, cleanup, "pthread_cond_wait failed: %s", |
| strerror_r(ret, buf, buf_len)); |
| } |
| } |
| |
| void call_cond_signal(pthread_cond_t* cond, char* buf, size_t buf_len) { |
| int ret; |
| |
| if ((ret = pthread_cond_signal(cond)) != 0) { |
| tst_brkm(TBROK, cleanup, "pthread_cond_signal failed: %s", |
| strerror_r(ret, buf, buf_len)); |
| } |
| } |
| |
| void do_timedwait(pthread_cond_t* cond, pthread_mutex_t* mutex, |
| char* buf, size_t buf_len, int i) { |
| struct timeval tv; |
| struct timespec ts; |
| int ret; |
| |
| if (gettimeofday(&tv, NULL) != 0) { |
| tst_brkm(TBROK, cleanup, "gettimeofday failed: %s", |
| strerror_r(errno, buf, buf_len)); |
| } |
| |
| ts.tv_sec = tv.tv_sec; |
| ts.tv_nsec = (tv.tv_usec * 1000) + NSECS_TO_WAIT; |
| ts.tv_sec += ts.tv_nsec / 1000000000; |
| ts.tv_nsec = ts.tv_nsec % 1000000000; |
| |
| call_mutex_lock(mutex, buf, buf_len); |
| if ((ret = pthread_cond_timedwait(cond, mutex, &ts)) != ETIMEDOUT) { |
| #if DEBUG |
| tst_resm(TINFO, "Loop %d of 1000000: pthread_cond_timedwait() didn't timeout",i); |
| tst_resm(TINFO, "You may want to try reducing the value of NSECS_TO_WAIT (currently=%d)", |
| NSECS_TO_WAIT); |
| #endif |
| } |
| call_mutex_unlock(mutex, buf, buf_len); |
| |
| } |
| |
| void* run(void* arg) |
| { |
| char buf[1024]; |
| |
| while (1) { |
| call_mutex_lock(&ack, buf, sizeof(buf)); |
| idle_count = 1; |
| call_cond_signal(&parent, buf, sizeof(buf)); |
| call_mutex_lock(&req, buf, sizeof(buf)); |
| call_mutex_unlock(&ack, buf, sizeof(buf)); |
| |
| call_mutex_lock(&wait, buf, sizeof(buf)); |
| call_cond_signal(&parent, buf, sizeof(buf)); |
| call_mutex_unlock(&wait, buf, sizeof(buf)); |
| |
| call_cond_wait(&child, &req, buf, sizeof(buf)); |
| call_mutex_unlock(&req, buf, sizeof(buf)); |
| } |
| } |
| |
| void create_child_thread(char* buf, size_t buf_len) |
| { |
| pthread_attr_t attr; |
| pthread_t child_tid; |
| int ret; |
| |
| if ((ret = pthread_attr_init(&attr)) != 0) { |
| tst_brkm(TBROK, cleanup, "pthread_attr_init failed: %s", |
| strerror_r(ret, buf, buf_len)); |
| } |
| if ((ret = pthread_attr_setdetachstate(&attr, |
| PTHREAD_CREATE_DETACHED)) != 0) { |
| tst_brkm(TBROK, cleanup, "pthread_attr_setdetachstate failed: %s", |
| strerror_r(ret, buf, buf_len)); |
| } |
| if ((ret = pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM)) != 0) { |
| tst_brkm(TBROK, cleanup, "pthread_attr_setscope failed: %s", |
| strerror_r(ret, buf, buf_len)); |
| } |
| |
| if ((ret = pthread_create(&child_tid, &attr, run, NULL)) != 0) { |
| tst_brkm(TBROK, cleanup, "pthread_create failed: %s", |
| strerror_r(ret, buf, buf_len)); |
| } |
| } |
| |
| void trap_alarm(int sig) { |
| tst_brkm(TFAIL, cleanup, "Test hang longer than %d sec detected", MAXTIME); |
| } |
| |
| static void |
| usage(const char *progname) |
| { |
| fprintf(stderr, "usage: %s -l loops\n", progname); |
| fprintf(stderr, "\t-l specify the number of loops to carry out\n"); |
| } |
| |
| int main(int argc, char** argv) |
| { |
| #ifdef USING_NPTL |
| char buf[1024]; |
| int i; |
| extern char *optarg; |
| int numloops = NUMLOOPS; |
| |
| while ((i = getopt(argc, argv, "l:")) != -1) { |
| switch(i) { |
| case 'l': |
| if (optarg) |
| numloops = atoi(optarg); |
| else |
| fprintf(stderr, "%s: option -l requires an argument\n", argv[0]); |
| break; |
| default: |
| usage(argv[0]); |
| exit(1); |
| } |
| } |
| |
| signal(SIGALRM, trap_alarm); |
| alarm(MAXTIME); |
| |
| call_mutex_init(&req, buf, sizeof(buf)); |
| call_mutex_init(&ack, buf, sizeof(buf)); |
| call_mutex_init(&wait, buf, sizeof(buf)); |
| call_cond_init(&parent, buf, sizeof(buf)); |
| call_cond_init(&child, buf, sizeof(buf)); |
| |
| call_mutex_lock(&ack, buf, sizeof(buf)); |
| |
| create_child_thread(buf, sizeof(buf)); |
| |
| tst_resm(TINFO,"Starting test, please wait."); |
| for (i = 0; i < numloops; i++) { |
| while (idle_count == 0) { |
| call_cond_wait(&parent, &ack, buf, sizeof(buf)); |
| }; |
| idle_count = 0; |
| call_mutex_unlock(&ack, buf, sizeof(buf)); |
| |
| do_timedwait(&parent, &wait, buf, sizeof(buf),i); |
| |
| call_mutex_lock(&req, buf, sizeof(buf)); |
| call_cond_signal(&child, buf, sizeof(buf)); |
| call_mutex_unlock(&req, buf, sizeof(buf)); |
| #ifdef DEBUG |
| tst_resm(TINFO,"Success in loop %d",i); |
| #else |
| if (((i % (numloops / 10)) == 0) && (i != 0)) |
| tst_resm(TINFO,"Success thru loop %d of %i",i,numloops); |
| #endif |
| call_mutex_lock(&ack, buf, sizeof(buf)); |
| } |
| |
| alarm(0); |
| tst_resm(TPASS,"Test completed successfully!"); |
| cleanup(); |
| |
| #else |
| tst_resm(TCONF,"Skipping Execution - This system is not using NPTL"); |
| tst_exit(); |
| #endif |
| /* NOT REACHED */ |
| return 1; |
| } |
| |
| /* |
| *cleanup() - performs all ONE TIME cleanup for this test at |
| * completion or premature exit. |
| */ |
| void |
| cleanup() |
| { |
| |
| /* exit with return code appropriate for results */ |
| tst_exit(); |
| } /* End cleanup() */ |
| |
| |