diff options
author | Chee-Wai Lee <cwlee@ncsa.uiuc.edu> | 2000-05-18 19:13:33 (GMT) |
---|---|---|
committer | Chee-Wai Lee <cwlee@ncsa.uiuc.edu> | 2000-05-18 19:13:33 (GMT) |
commit | e26f4e5eed2e219597d4bfa85925840c84dd64db (patch) | |
tree | 281871beb7e12d1a2dd90e3bdd49eaa2bdf19fd8 | |
parent | bc520e88b4ad3b175c0aeca2c90e021956676533 (diff) | |
download | hdf5-e26f4e5eed2e219597d4bfa85925840c84dd64db.zip hdf5-e26f4e5eed2e219597d4bfa85925840c84dd64db.tar.gz hdf5-e26f4e5eed2e219597d4bfa85925840c84dd64db.tar.bz2 |
[svn-r2264] Added Thread-safe feature. This is the phase 1 implementation
that all HDF5 API functions are protected by a mutex lock. Basically,
serialized all API calls. To use it, use
configure --enable-threadsafe --with-pthread
-rw-r--r-- | src/H5.c | 71 | ||||
-rw-r--r-- | src/H5E.c | 55 | ||||
-rw-r--r-- | src/H5Eprivate.h | 6 | ||||
-rw-r--r-- | src/H5TS.c | 243 | ||||
-rw-r--r-- | src/H5private.h | 104 | ||||
-rw-r--r-- | src/Makefile.in | 3 | ||||
-rw-r--r-- | test/Makefile.in | 20 | ||||
-rw-r--r-- | test/ttsafe.c | 334 | ||||
-rw-r--r-- | test/ttsafe.h | 141 | ||||
-rw-r--r-- | test/ttsafe_acreate.c | 166 | ||||
-rw-r--r-- | test/ttsafe_cancel.c | 203 | ||||
-rw-r--r-- | test/ttsafe_dcreate.c | 191 | ||||
-rw-r--r-- | test/ttsafe_error.c | 179 |
13 files changed, 1690 insertions, 26 deletions
@@ -40,7 +40,19 @@ FILE *fdopen(int fd, const char *mode); #define PABLO_MASK H5_mask +/* statically initialize block for pthread_once call used in initializing */ +/* the first global mutex */ +#ifdef H5_HAVE_THREADSAFE +pthread_once_t H5_first_init_g = PTHREAD_ONCE_INIT; +pthread_key_t H5_errstk_key_g; +pthread_key_t H5_cancel_key_g; +hbool_t H5_allow_concurrent_g = FALSE; /* concurrent APIs override this */ + +H5_api_t H5_g; +#else hbool_t H5_libinit_g = FALSE; +#endif + hbool_t dont_atexit_g = FALSE; H5_debug_t H5_debug_g; /*debugging info */ static void H5_debug_mask(const char*); @@ -50,17 +62,19 @@ static intn interface_initialize_g = 0; #define INTERFACE_INIT NULL /*-------------------------------------------------------------------------- -NAME - H5_init_library -- Initialize library-global information -USAGE - herr_t H5_init_library() - -RETURNS - Non-negative on success/Negative on failure -DESCRIPTION - Initializes any library-global data or routines. - ---------------------------------------------------------------------------*/ + * NAME + * H5_init_library -- Initialize library-global information + * USAGE + * herr_t H5_init_library() + * + * RETURNS + * Non-negative on success/Negative on failure + * + * DESCRIPTION + * Initializes any library-global data or routines. + * + *-------------------------------------------------------------------------- + */ herr_t H5_init_library(void) { @@ -147,7 +161,17 @@ H5_term_library(void) H5E_auto_t func; /* Don't do anything if the library is already closed */ +#ifdef H5_HAVE_THREADSAFE + + /* explicit locking of the API */ + pthread_once(&H5_first_init_g, H5_first_thread_init); + + H5_mutex_lock(&H5_g.init_lock); + + if (!H5_g.H5_libinit_g) return; +#else if (!H5_libinit_g) return; +#endif /* Check if we should display error output */ H5Eget_auto(&func,NULL); @@ -190,7 +214,13 @@ H5_term_library(void) } /* Mark library as closed */ +#ifdef H5_HAVE_THREADSAFE + H5_g.H5_libinit_g = FALSE; + + H5_mutex_unlock(&H5_g.init_lock); +#else H5_libinit_g = FALSE; +#endif } @@ -223,10 +253,20 @@ herr_t H5dont_atexit(void) { /* FUNC_ENTER_INIT() should not be called */ + + /* locking code explicitly since FUNC_ENTER is not called */ +#ifdef H5_HAVE_THREADSAFE + pthread_once(&H5_first_init_g, H5_first_thread_init); + + H5_mutex_lock(&H5_g.init_lock); +#endif H5_trace(FALSE, "H5dont_atexit", ""); if (dont_atexit_g) return FAIL; dont_atexit_g = TRUE; H5_trace(TRUE, NULL, "e", SUCCEED); +#ifdef H5_HAVE_THREADSAFE + H5_mutex_unlock(&H5_g.init_lock); +#endif return(SUCCEED); } @@ -479,7 +519,16 @@ H5close (void) * thing just to release it all right away. It is safe to call this * function for an uninitialized library. */ + /* Explicitly lock the call since FUNC_ENTER is not called */ +#ifdef H5_HAVE_THREADSAFE + pthread_once(&H5_first_init_g, H5_first_thread_init); + + H5_mutex_lock(&H5_g.init_lock); +#endif H5_term_library(); +#ifdef H5_HAVE_THREADSAFE + H5_mutex_unlock(&H5_g.init_lock); +#endif return SUCCEED; } @@ -146,13 +146,26 @@ static intn interface_initialize_g = 0; static herr_t H5E_init_interface (void); const hbool_t H5E_clearable_g = TRUE; /* DO NOT CHANGE */ +#ifdef H5_HAVE_THREADSAFE +/* + * The per-thread error stack. pthread_once() initializes a special + * key that will be used by all threads to create a stack specific to + * each thread individually. The association of stacks to threads will + * be handled by the pthread library. + * + * In order for this macro to work, H5E_get_my_stack() must be preceeded + * by "H5E_t *estack =". + */ +H5E_t *H5E_get_stack(void); +#define H5E_get_my_stack() H5E_get_stack() +#else /* * The error stack. Eventually we'll have some sort of global table so each * thread has it's own stack. The stacks will be created on demand when the - * thread first calls H5E_push(). - */ + * thread first calls H5E_push(). */ H5E_t H5E_stack_g[1]; #define H5E_get_my_stack() (H5E_stack_g+0) +#endif /* * Automatic error stack traversal occurs if the traversal callback function @@ -163,6 +176,39 @@ herr_t (*H5E_auto_g)(void*) = (herr_t(*)(void*))H5Eprint; void *H5E_auto_data_g = NULL; +#ifdef H5_HAVE_THREADSAFE +/*------------------------------------------------------------------------- + * Function: H5E_get_stack + * + * Purpose: Support function for H5E_get_my_stack() to initialize and + * acquire per-thread error stack. + * + * Return: Success: error stack (H5E_t *) + * + * Failure: NULL + * + * Programmer: Chee Wai LEE + * April 24, 2000 + * + * Modifications: + * + *------------------------------------------------------------------------- + */ +H5E_t *H5E_get_stack() { + H5E_t *estack; + + if (estack = pthread_getspecific(H5_errstk_key_g)) { + return estack; + } else { + /* no associated value with current thread - create one */ + estack = (H5E_t *)malloc(sizeof(H5E_t)); + pthread_setspecific(H5_errstk_key_g, (void *)estack); + return estack; + } +} +#endif + + /*------------------------------------------------------------------------- * Function: H5E_init_interface * @@ -311,7 +357,12 @@ H5Eprint(FILE *stream) /*NO TRACE*/ if (!stream) stream = stderr; +#ifdef H5_HAVE_THREADSAFE + fprintf (stream, "HDF5-DIAG: Error detected in thread %d." + ,pthread_self()); +#else fprintf (stream, "HDF5-DIAG: Error detected in thread 0."); +#endif if (estack && estack->nused>0) fprintf (stream, " Back trace follows."); HDfputc ('\n', stream); status = H5E_walk (H5E_WALK_DOWNWARD, H5Ewalk_cb, (void*)stream); diff --git a/src/H5Eprivate.h b/src/H5Eprivate.h index 89f9c5c..49fbc96 100644 --- a/src/H5Eprivate.h +++ b/src/H5Eprivate.h @@ -43,6 +43,9 @@ if (H5_IS_API(FUNC) && H5E_auto_g) { \ (H5E_auto_g)(H5E_auto_data_g); \ } \ + H5_API_UNLOCK_BEGIN \ + H5_API_UNLOCK_END \ + H5_API_SET_CANCEL \ return (ret_val); \ } @@ -54,6 +57,9 @@ #define HRETURN(ret_val) { \ PABLO_TRACE_OFF (PABLO_MASK, pablo_func_id); \ H5TRACE_RETURN(ret_val); \ + H5_API_UNLOCK_BEGIN \ + H5_API_UNLOCK_END \ + H5_API_SET_CANCEL \ return (ret_val); \ } diff --git a/src/H5TS.c b/src/H5TS.c new file mode 100644 index 0000000..e7317bc --- /dev/null +++ b/src/H5TS.c @@ -0,0 +1,243 @@ +/**************************************************************************** +* NCSA HDF * +* Software Development Group * +* National Center for Supercomputing Applications * +* University of Illinois at Urbana-Champaign * +* 605 E. Springfield, Champaign IL 61820 * +* * +* For conditions of distribution and use, see the accompanying * +* hdf/COPYING file. * +* * +****************************************************************************/ + +#ifdef RCSID +static char RcsId[] = "@(#)$Revision$"; +#endif + +/* $Id$ */ + +/* private headers */ +#include <H5private.h> /*library */ +#include <H5Eprivate.h> /*error handling */ + +#ifdef H5_HAVE_THREADSAFE + +/*-------------------------------------------------------------------------- + * NAME + * H5_first_thread_init + * USAGE + * H5_first_thread_init() + * + * RETURNS + * + * DESCRIPTION + * Initialization of global API lock, keys for per-thread error stacks + * and cancallability information. Called by the first thread that enters + * the library. + * + * PROGRAMMER: Chee Wai LEE + * May 2, 2000 + * + * MODIFICATIONS: + * + *-------------------------------------------------------------------------- + */ +void H5_first_thread_init() { + /* initialize global API mutex lock */ + H5_g.H5_libinit_g = FALSE; + H5_g.init_lock.owner_thread = NULL; + pthread_mutex_init(&H5_g.init_lock.atomic_lock, NULL); + pthread_cond_init(&H5_g.init_lock.cond_var, NULL); + H5_g.init_lock.lock_count = 0; + + /* initialize key for thread-specific error stacks */ + pthread_key_create(&H5_errstk_key_g, NULL); + + /* initialize key for thread cancellability mechanism */ + pthread_key_create(&H5_cancel_key_g, NULL); +} + +/*-------------------------------------------------------------------------- + * NAME + * H5_mutex_init + * USAGE + * H5_mutex_init(&mutex_var) + * + * RETURNS + * + * DESCRIPTION + * Recursive lock semantics for HDF5 (lock initialization) - + * Multiple acquisition of a lock by a thread is permitted with a + * corresponding unlock operation required. + * + * PROGRAMMER: Chee Wai LEE + * May 2, 2000 + * + * MODIFICATIONS: + * + *-------------------------------------------------------------------------- + */ +void H5_mutex_init(H5_mutex_t *H5_mutex) { + (*H5_mutex).owner_thread = NULL; + pthread_mutex_init(&(*H5_mutex).atomic_lock, NULL); + pthread_cond_init(&(*H5_mutex).cond_var, NULL); + (*H5_mutex).lock_count = 0; +} + +/*-------------------------------------------------------------------------- + * NAME + * H5_mutex_lock + * USAGE + * H5_mutex_lock(&mutex_var) + * + * RETURNS + * + * DESCRIPTION + * Recursive lock semantics for HDF5 (locking) - + * Multiple acquisition of a lock by a thread is permitted with a + * corresponding unlock operation required. + * + * PROGRAMMER: Chee Wai LEE + * May 2, 2000 + * + * MODIFICATIONS: + * + *-------------------------------------------------------------------------- + */ +void H5_mutex_lock(H5_mutex_t *H5_mutex) { + pthread_mutex_lock(&(*H5_mutex).atomic_lock); + if (pthread_equal(pthread_self(), (*H5_mutex).owner_thread)) { + /* already owned by self - increment count */ + (*H5_mutex).lock_count++; + } else { + if ((*H5_mutex).owner_thread == NULL) { + /* no one else has locked it - set owner and grab lock */ + (*H5_mutex).owner_thread = pthread_self(); + (*H5_mutex).lock_count = 1; + } else { + /* if already locked by someone else */ + while (1) { + pthread_cond_wait(&(*H5_mutex).cond_var, &(*H5_mutex).atomic_lock); + if ((*H5_mutex).owner_thread == NULL) { + (*H5_mutex).owner_thread = pthread_self(); + (*H5_mutex).lock_count = 1; + break; + } /* else do nothing and loop back to wait on condition*/ + } + } + } + pthread_mutex_unlock(&(*H5_mutex).atomic_lock); +} + +/*-------------------------------------------------------------------------- + * NAME + * H5_mutex_unlock + * USAGE + * H5_mutex_unlock(&mutex_var) + * + * RETURNS + * + * DESCRIPTION + * Recursive lock semantics for HDF5 (unlocking) - + * Multiple acquisition of a lock by a thread is permitted with a + * corresponding unlock operation required. + * + * PROGRAMMER: Chee Wai LEE + * May 2, 2000 + * + * MODIFICATIONS: + * + *-------------------------------------------------------------------------- + */ +void H5_mutex_unlock(H5_mutex_t *H5_mutex) { + pthread_mutex_lock(&(*H5_mutex).atomic_lock); + (*H5_mutex).lock_count--; + if ((*H5_mutex).lock_count == 0) { + (*H5_mutex).owner_thread = NULL; + pthread_cond_signal(&(*H5_mutex).cond_var); + } + pthread_mutex_unlock(&(*H5_mutex).atomic_lock); +} + +/*-------------------------------------------------------------------------- + * NAME + * H5_cancel_count_inc + * USAGE + * H5_cancel_count_inc() + * + * RETURNS + * + * DESCRIPTION + * Creates a cancelation counter for a thread if it is the first time + * the thread is entering the library. + * + * if counter value is zero, then set cancelability type of the thread + * to PTHREAD_CANCEL_DISABLE as thread is entering the library and store + * the previous cancelability type into cancelation counter. + * Increase the counter value by 1. + * + * PROGRAMMER: Chee Wai LEE + * May 2, 2000 + * + * MODIFICATIONS: + * + *-------------------------------------------------------------------------- + */ +void +H5_cancel_count_inc(void) +{ + H5_cancel_t *cancel_counter; + + if (cancel_counter = pthread_getspecific(H5_cancel_key_g)) { + /* do nothing here */ + } else { + /* first time thread calls library - create new counter and associate + with key + */ + cancel_counter = (H5_cancel_t *)malloc(sizeof(H5_cancel_t)); + cancel_counter->cancel_count = 0; + pthread_setspecific(H5_cancel_key_g, (void *)cancel_counter); + } + + if (cancel_counter->cancel_count == 0) { + /* thread entering library */ + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, + &(cancel_counter->previous_state)); + } + cancel_counter->cancel_count++; +} + +/*-------------------------------------------------------------------------- + * NAME + * H5_cancel_count_dec + * USAGE + * H5_cancel_count_dec() + * + * RETURNS + * + * DESCRIPTION + * if counter value is one, then set cancelability type of the thread + * to the previous cancelability type stored in the cancelation counter. + * (the thread is leaving the library). + * + * Decrement the counter value by 1. + * + * PROGRAMMER: Chee Wai LEE + * May 2, 2000 + * + * MODIFICATIONS: + * + *-------------------------------------------------------------------------- + */ +void +H5_cancel_count_dec(void) +{ + H5_cancel_t *cancel_counter = pthread_getspecific(H5_cancel_key_g); + + if (cancel_counter->cancel_count == 1) { + pthread_setcancelstate(cancel_counter->previous_state, NULL); + } + cancel_counter->cancel_count--; +} + +#endif diff --git a/src/H5private.h b/src/H5private.h index bdb59a9..60e234d 100644 --- a/src/H5private.h +++ b/src/H5private.h @@ -15,6 +15,11 @@ #include <H5public.h> /* Include Public Definitions */ #include <H5config.h> /* Include all configuration info */ +/* include the pthread library */ +#ifdef H5_HAVE_THREADSAFE +#include <pthread.h> +#endif + /* * Include ANSI-C header files. */ @@ -868,11 +873,89 @@ __DLL__ void H5_trace(hbool_t returning, const char *func, const char *type, * Added auto variable RTYPE which is initialized by the tracing macros. *------------------------------------------------------------------------- */ -extern hbool_t H5_libinit_g; /*good thing C's lazy about extern! */ /* Is `S' the name of an API function? */ #define H5_IS_API(S) ('_'!=S[2] && '_'!=S[3] && (!S[4] || '_'!=S[4])) +/* Lock headers */ +#ifdef H5_HAVE_THREADSAFE +typedef struct H5_mutex_struct { + pthread_t owner_thread; /* current lock owner */ + pthread_mutex_t atomic_lock; /* lock for atomicity of new mechanism */ + pthread_cond_t cond_var; /* condition variable */ + unsigned int lock_count; +} H5_mutex_t; + +/* cancelability structure */ +typedef struct H5_cancel_struct { + int previous_state; + unsigned int cancel_count; +} H5_cancel_t; + +/* replacement structure for original global variable */ +typedef struct H5_api_struct { + H5_mutex_t init_lock; /* API entrance mutex */ + hbool_t H5_libinit_g; +} H5_api_t; + + +/* Macro for first thread initialization */ +#define H5_FIRST_THREAD_INIT \ + pthread_once(&H5_first_init_g, H5_first_thread_init); + +/* Macros for threadsafe HDF-5 Phase I locks */ +#define H5_INIT_GLOBAL H5_g.H5_libinit_g +#define H5_API_LOCK_BEGIN \ + if (H5_IS_API(FUNC)) { \ + H5_mutex_lock(&H5_g.init_lock); +#define H5_API_LOCK_END } +#define H5_API_UNLOCK_BEGIN \ + if (H5_IS_API(FUNC)) { \ + H5_mutex_unlock(&H5_g.init_lock); +#define H5_API_UNLOCK_END } + +/* Macros for thread cancellation-safe mechanism */ +#define H5_API_UNSET_CANCEL \ + if (H5_IS_API(FUNC)) { \ + H5_cancel_count_inc(); \ + } + +#define H5_API_SET_CANCEL \ + if (H5_IS_API(FUNC)) { \ + H5_cancel_count_dec(); \ + } + +/* Extern global variables */ +extern pthread_once_t H5_first_init_g; +extern pthread_key_t H5_errstk_key_g; +extern pthread_key_t H5_cancel_key_g; +extern hbool_t H5_allow_concurrent_g; +extern H5_api_t H5_g; + +void H5_first_thread_init(void); + +#else + +/* disable any first thread init mechanism */ +#define H5_FIRST_THREAD_INIT + +#define H5_INIT_GLOBAL H5_libinit_g + +/* disable locks (sequential version) */ +#define H5_API_LOCK_BEGIN +#define H5_API_LOCK_END +#define H5_API_UNLOCK_BEGIN +#define H5_API_UNLOCK_END + +/* disable cancelability (sequential version) */ +#define H5_API_UNSET_CANCEL +#define H5_API_SET_CANCEL + +/* extern global variables */ + +extern hbool_t H5_libinit_g; /*good thing C's lazy about extern! */ +#endif + #define FUNC_ENTER(func_name,err) FUNC_ENTER_INIT(func_name,INTERFACE_INIT,err) #define FUNC_ENTER_INIT(func_name,interface_init_func,err) { \ @@ -883,13 +966,17 @@ extern hbool_t H5_libinit_g; /*good thing C's lazy about extern! */ PABLO_TRACE_ON (PABLO_MASK, pablo_func_id); \ \ /* Initialize the library */ \ - if (!H5_libinit_g) { \ - H5_libinit_g = TRUE; \ - if (H5_init_library()<0) { \ - HRETURN_ERROR (H5E_FUNC, H5E_CANTINIT, err, \ - "library initialization failed"); \ - } \ - } \ + H5_FIRST_THREAD_INIT \ + H5_API_UNSET_CANCEL \ + H5_API_LOCK_BEGIN \ + if (!(H5_INIT_GLOBAL)) { \ + H5_INIT_GLOBAL = TRUE; \ + if (H5_init_library()<0) { \ + HRETURN_ERROR (H5E_FUNC, H5E_CANTINIT, err, \ + "library initialization failed"); \ + } \ + } \ + H5_API_LOCK_END \ \ /* Initialize this interface or bust */ \ if (!interface_initialize_g) { \ @@ -925,7 +1012,6 @@ extern hbool_t H5_libinit_g; /*good thing C's lazy about extern! */ */ #define FUNC_LEAVE(return_value) HRETURN(return_value)}} - /* * The FUNC_ENTER() and FUNC_LEAVE() macros make calls to Pablo functions * through one of these two sets of macros. diff --git a/src/Makefile.in b/src/Makefile.in index 15650bf..f5e7643 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -26,7 +26,8 @@ LIB_SRC=H5.c H5A.c H5AC.c H5B.c H5D.c H5E.c H5F.c H5Farray.c H5Fistore.c \ H5Ocont.c H5Odtype.c H5Oefl.c H5Ofill.c H5Olayout.c H5Omtime.c H5Oname.c \ H5Onull.c H5Osdspace.c H5Oshared.c H5Ostab.c H5P.c H5R.c H5RA.c H5S.c \ H5Sall.c H5Shyper.c H5Smpio.c H5Snone.c H5Spoint.c H5Sselect.c H5T.c \ - H5Tbit.c H5Tconv.c H5Tinit.c H5Tvlen.c H5TB.c H5V.c H5Z.c H5Zdeflate.c + H5Tbit.c H5Tconv.c H5Tinit.c H5Tvlen.c H5TB.c H5TS.c H5V.c H5Z.c \ + H5Zdeflate.c LIB_OBJ=$(LIB_SRC:.c=.lo) diff --git a/test/Makefile.in b/test/Makefile.in index 2f0912d..eba7699 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -16,10 +16,12 @@ CPPFLAGS=-I. -I$(srcdir) -I../src -I$(top_srcdir)/src @CPPFLAGS@ ## These are our main targets. They should be listed in the order to be ## executed, generally most specific tests to least specific tests. RUNTEST=$(LT_RUN) + TEST_PROGS=testhdf5 lheap ohdr stab gheap hyperslab istore bittests dtypes \ dsets cmpd_dset extend external links unlink big mtime fillval mount \ flush1 flush2 enum gass_write gass_read gass_append dpss_write \ - dpss_read srb_write srb_append srb_read + dpss_read srb_write srb_append srb_read ttsafe + TIMINGS=iopipe chunk ragged overhead ## The libh5test.a library provides common support code for the tests. We link @@ -46,25 +48,28 @@ MOSTLYCLEAN=cmpd_dset.h5 dataset.h5 extend.h5 istore.h5 tfile1.h5 tfile2.h5 \ big.data big[0-9][0-9][0-9][0-9][0-9].h5 dtypes1.h5 dtypes2.h5 \ tattr.h5 tselect.h5 mtime.h5 ragged.h5 unlink.h5 overhead.h5 \ fillval_[0-9].h5 fillval.raw mount_[0-9].h5 trefer[12].h5 \ - tvltypes.h5 tvlstr.h5 flush.h5 enum1.h5 titerate.h5 + tvltypes.h5 tvlstr.h5 flush.h5 enum1.h5 titerate.h5 ttsafe.h5 CLEAN=$(TIMINGS) ## Source and object files for programs... The TEST_SRC list contains all the ## source files and is used for things like dependencies, archiving, etc. The ## other source lists are for the individual tests, the files of which may ## overlap with other tests. + TEST_SRC=big.c bittests.c chunk.c cmpd_dset.c dsets.c dtypes.c extend.c \ external.c fillval.c flush1.c flush2.c gheap.c h5test.c hyperslab.c \ iopipe.c istore.c lheap.c links.c mount.c mtime.c ohdr.c overhead.c \ ragged.c stab.c tattr.c testhdf5.c tfile.c th5s.c titerate.c tmeta.c \ trefer.c tselect.c ttbbt.c tvltypes.c tvlstr.c unlink.c enum.c \ + ttsafe.c ttsafe_dcreate.c ttsafe_error.c ttsafe_cancel.c \ + ttsafe_acreate.c \ gass_write.c gass_read.c gass_append.c dpss_read.c dpss_write.c \ srb_read.c srb_write.c srb_append.c TEST_OBJ=$(TEST_SRC:.c=.lo) ## Private header files (not to be installed)... -PRIVATE_HDR=testhdf5.h +PRIVATE_HDR=testhdf5.h ttsafe.h ## Additional targets .PHONY: timings _timings @@ -78,7 +83,12 @@ timings _timings: $(TIMINGS) ## How to build the tests... They all depend on the test and hdf5 libraries. $(TEST_PROGS): $(LIB) $(LIBHDF5) + TESTHDF5_OBJ=testhdf5.lo tattr.lo tfile.lo titerate.lo tmeta.lo trefer.lo tselect.lo ttbbt.lo tvltypes.lo tvlstr.lo th5s.lo + +TTS_OBJ=ttsafe.lo ttsafe_dcreate.lo ttsafe_error.lo ttsafe_cancel.lo \ + ttsafe_acreate.lo + testhdf5: $(TESTHDF5_OBJ) @$(LT_LINK_EXE) $(CFLAGS) -o $@ $(TESTHDF5_OBJ) $(LIB) $(LIBHDF5) $(LDFLAGS) $(LIBS) @@ -157,6 +167,9 @@ flush2: flush2.lo enum: enum.lo @$(LT_LINK_EXE) $(CFLAGS) -o $@ enum.lo $(LIB) $(LIBHDF5) $(LDFLAGS) $(LIBS) +ttsafe: $(TTS_OBJ) + @$(LT_LINK_EXE) $(CFLAGS) -o $@ $(TTS_OBJ) $(LIB) $(LIBHDF5) $(LDFLAGS) $(LIBS) + gass_write: gass_write.lo @$(LT_LINK_EXE) $(CFLAGS) -o $@ gass_write.lo $(LIB) $(LIBHDF5) $(LDFLAGS) $(LIBS) @@ -182,3 +195,4 @@ srb_append: srb_append.lo @$(LT_LINK_EXE) $(CFLAGS) -o $@ srb_append.lo $(LIB) $(LIBHDF5) $(LDFLAGS) $(LIBS) @CONCLUDE@ + diff --git a/test/ttsafe.c b/test/ttsafe.c new file mode 100644 index 0000000..cf87752 --- /dev/null +++ b/test/ttsafe.c @@ -0,0 +1,334 @@ +/**************************************************************************** + * NCSA HDF * + * Software Development Group * + * National Center for Supercomputing Applications * + * University of Illinois at Urbana-Champaign * + * 605 E. Springfield, Champaign IL 61820 * + * * + * For conditions of distribution and use, see the accompanying * + * hdf/COPYING file. * + * * + ****************************************************************************/ + +#ifdef RCSID +static char RcsId[] = "@(#)$Revision$"; +#endif + +/* $Id$ */ + +/* + FILE + ttsafe.c - HDF5 threadsafe testing framework main file. + + REMARKS + General test wrapper for HDF5 library thread safety test programs + + DESIGN + Each test function should be implemented as function having no + parameters and returning void (i.e. no return value). They should be put + into the list of InitTest() calls in main() below. Functions which depend + on other functionality should be placed below the InitTest() call for the + base functionality testing. + Each test module should include ttsafe.h and define a unique set of + names for test files they create. + + BUGS/LIMITATIONS + + EXPORTED ROUTINES/VARIABLES: + Two variables are exported: num_errs, and Verbosity. + + */ + +#if defined __MWERKS__ +#include <console.h> +#endif + +#include <stdarg.h> + +#define MAXNUMOFTESTS 50 +#define HDF5_TEST_MASTER + +#define MAX_NUM_NAME 1000 +#define NAME_OFFSET 6 /* offset for "name<num>" */ + +/* Internal Variables */ +static int Index = 0; + +/* Global variables */ +int num_errs = 0; +int Verbosity; + +#include <ttsafe.h> + +#ifndef H5_HAVE_THREADSAFE +int main(void) +{ + printf("Test skipped because THREADSAFE not enabled\n"); + return 0; +} +#else +/* ANY new test needs to have a prototype in tproto.h */ +struct TestStruct { + int NumErrors; + char Description[64]; + int SkipFlag; + char Name[16]; + void (*Call) (void); + void (*Cleanup) (void); +} Test[MAXNUMOFTESTS]; + +static void InitTest(const char *TheName, void (*TheCall) (void), void (*Cleanup) (void), const char *TheDescr); +static void usage(void); + +static void +InitTest(const char *TheName, void (*TheCall) (void), void (*Cleanup) (void), const char *TheDescr) +{ + if (Index >= MAXNUMOFTESTS) { + print_func("Uh-oh, too many tests added, increase MAXNUMOFTEST!\n"); + exit(-1); + } /* end if */ + HDstrcpy(Test[Index].Description, TheDescr); + HDstrcpy(Test[Index].Name, TheName); + Test[Index].Call = TheCall; + Test[Index].Cleanup = Cleanup; + Test[Index].NumErrors = -1; + Test[Index].SkipFlag = 0; + Index++; +} + +static void +usage(void) +{ + intn i; + + print_func("Usage: testhdf5 [-v[erbose] (l[ow]|m[edium]|h[igh]|0-10)] \n"); + print_func(" [-[e]x[clude] name+] \n"); + print_func(" [-o[nly] name+] \n"); + print_func(" [-b[egin] name] \n"); + print_func(" [-s[ummary]] \n"); + print_func(" [-c[leanoff]] \n"); + print_func(" [-n[ocaching]] \n"); + print_func(" [-h[elp]] \n"); + print_func("\n\n"); + print_func("verbose controls the amount of information displayed\n"); + print_func("exclude to exclude tests by name\n"); + print_func("only to name tests which should be run\n"); + print_func("begin start at the name of the test givin\n"); + print_func("summary prints a summary of test results at the end\n"); + print_func("cleanoff does not delete *.hdf files after execution of tests\n"); + print_func("nocaching do not turn on low-level DD caching\n"); + print_func("help print out this information\n"); + print_func("\n\n"); + print_func("This program currently tests the following: \n\n"); + print_func("%16s %s\n", "Name", "Description"); + print_func("%16s %s\n", "----", "-----------"); + for (i = 0; i < Index; i++) + print_func("%16s %s\n", Test[i].Name, Test[i].Description); + print_func("\n\n"); +} /* end usage() */ + +/* + * This routine is designed to provide equivalent functionality to 'printf' + * and allow easy replacement for environments which don't have stdin/stdout + * available. (i.e. Windows & the Mac) + */ +int +print_func(const char *format,...) +{ + va_list arglist; + int ret_value; + + va_start(arglist, format); + ret_value = vprintf(format, arglist); + va_end(arglist); + return (ret_value); +} + +char* gen_name(int value) { + char* temp; + int i, length; + + length = num_digits(MAX_NUM_NAME-1); + temp = (char *)malloc((NAME_OFFSET+length+1)*sizeof(char)); + temp = strcpy(temp, "attrib"); + temp[NAME_OFFSET+length] = '\0'; + + for (i=length-1;i>=0;i--) { + temp[NAME_OFFSET+i] = (char)((int)'0' + value%10); + value = value/10; + } + + return temp; +} + +/* pre-condition: num must be a non-negative number */ +int num_digits(int num) { + int i=0; + + if (num == 0) + return 1; + while (num > 0) { + num = num/10; + i++; + } + return i; +} + +int +main(int argc, char *argv[]) +{ + int CLLoop; /* Command Line Loop */ + int Loop, Loop1; + int Summary = 0; + int CleanUp = 1; + int Cache = 1; + uintn major, minor, release; + +#if defined __MWERKS__ + argc = ccommand(&argv); +#endif + +#if !(defined MAC || defined __MWERKS__ || defined SYMANTEC_C) + /* Un-buffer the stdout and stderr */ + setbuf(stderr, NULL); + setbuf(stdout, NULL); +#endif + + /* + * Turn off automatic error reporting since we do it ourselves. Besides, + * half the functions this test calls are private, so automatic error + * reporting wouldn't do much good since it's triggered at the API layer. + */ + H5Eset_auto (NULL, NULL); + + /* Tests are generally arranged from least to most complexity... */ + InitTest("dcreate", tts_dcreate, cleanup_dcreate, "multi-dataset creation"); + InitTest("error", tts_error, cleanup_error, "per-thread error stacks"); + InitTest("cancel", tts_cancel, cleanup_cancel, "Thread cancellation safety test"); + InitTest("acreate", tts_acreate, cleanup_acreate, "multi-attribute creation"); + + Verbosity = 4; /* Default Verbosity is Low */ + H5get_libversion(&major, &minor, &release); + + print_func("\nFor help use: testhdf5 -help\n"); + print_func("Linked with hdf5 version %u.%u release %u\n", + (unsigned)major, (unsigned)minor, (unsigned)release); + for (CLLoop = 1; CLLoop < argc; CLLoop++) { + if ((argc > CLLoop + 1) && ((HDstrcmp(argv[CLLoop], "-verbose") == 0) || + (HDstrcmp(argv[CLLoop], "-v") == 0))) { + if (argv[CLLoop + 1][0] == 'l') + Verbosity = 4; + else if (argv[CLLoop + 1][0] == 'm') + Verbosity = 6; + else if (argv[CLLoop + 1][0] == 'h') + Verbosity = 10; + else + Verbosity = atoi(argv[CLLoop + 1]); + } /* end if */ + if ((argc > CLLoop) && ((HDstrcmp(argv[CLLoop], "-summary") == 0) || + (HDstrcmp(argv[CLLoop], "-s") == 0))) + Summary = 1; + + if ((argc > CLLoop) && ((HDstrcmp(argv[CLLoop], "-help") == 0) || + (HDstrcmp(argv[CLLoop], "-h") == 0))) { + usage(); + exit(0); + } + if ((argc > CLLoop) && ((HDstrcmp(argv[CLLoop], "-cleanoff") == 0) || + (HDstrcmp(argv[CLLoop], "-c") == 0))) + CleanUp = 0; + + if ((argc > CLLoop) && ((HDstrcmp(argv[CLLoop], "-nocache") == 0) || + (HDstrcmp(argv[CLLoop], "-n") == 0))) { + Cache = 0; + printf ("Cache = %d\n", Cache); + } + + if ((argc > CLLoop + 1) && ((HDstrcmp(argv[CLLoop], "-exclude") == 0) || + (HDstrcmp(argv[CLLoop], "-x") == 0))) { + Loop = CLLoop + 1; + while ((Loop < argc) && (argv[Loop][0] != '-')) { + for (Loop1 = 0; Loop1 < Index; Loop1++) + if (HDstrcmp(argv[Loop], Test[Loop1].Name) == 0) + Test[Loop1].SkipFlag = 1; + Loop++; + } /* end while */ + } /* end if */ + if ((argc > CLLoop + 1) && ((HDstrcmp(argv[CLLoop], "-begin") == 0) || + (HDstrcmp(argv[CLLoop], "-b") == 0))) { + Loop = CLLoop + 1; + while ((Loop < argc) && (argv[Loop][0] != '-')) { + for (Loop1 = 0; Loop1 < Index; Loop1++) { + if (HDstrcmp(argv[Loop], Test[Loop1].Name) != 0) + Test[Loop1].SkipFlag = 1; + if (HDstrcmp(argv[Loop], Test[Loop1].Name) == 0) + Loop1 = Index; + } /* end for */ + Loop++; + } /* end while */ + } /* end if */ + if ((argc > CLLoop + 1) && ((HDstrcmp(argv[CLLoop], "-only") == 0) || + (HDstrcmp(argv[CLLoop], "-o") == 0))) { + for (Loop = 0; Loop < Index; Loop++) + Test[Loop].SkipFlag = 1; + Loop = CLLoop + 1; + while ((Loop < argc) && (argv[Loop][0] != '-')) { + for (Loop1 = 0; Loop1 < Index; Loop1++) + if (HDstrcmp(argv[Loop], Test[Loop1].Name) == 0) + Test[Loop1].SkipFlag = 0; + Loop++; + } /* end while */ + } /* end if */ + } /* end for */ + +#ifdef NOT_YET + if (Cache) /* turn on caching, unless we were instucted not to */ + Hcache(CACHE_ALL_FILES, TRUE); +#endif /* NOT_YET */ + + for (Loop = 0; Loop < Index; Loop++) { + if (Test[Loop].SkipFlag) { + MESSAGE(2, ("Skipping -- %s \n", Test[Loop].Description)); + } else { + MESSAGE(2, ("Testing -- %s (%s) \n", Test[Loop].Description, + Test[Loop].Name)); + MESSAGE(5, ("===============================================\n")); + Test[Loop].NumErrors = num_errs; + (*Test[Loop].Call) (); + Test[Loop].NumErrors = num_errs - Test[Loop].NumErrors; + MESSAGE(5, ("===============================================\n")); + MESSAGE(5, ("There were %d errors detected.\n\n", (int) Test[Loop].NumErrors)); + } /* end else */ + } /* end for */ + + MESSAGE(2, ("\n\n")) + if (num_errs) + print_func("!!! %d Error(s) were detected !!!\n\n", (int) num_errs); + else + print_func("All tests were successful. \n\n"); + + if (Summary) { + print_func("Summary of Test Results:\n"); + print_func("Name of Test Errors Description of Test\n"); + print_func("---------------- ------ --------------------------------------\n"); + + for (Loop = 0; Loop < Index; Loop++) { + if (Test[Loop].NumErrors == -1) + print_func("%16s %6s %s\n", Test[Loop].Name, "N/A", Test[Loop].Description); + else + print_func("%16s %6d %s\n", Test[Loop].Name, (int) Test[Loop].NumErrors, + Test[Loop].Description); + } /* end for */ + print_func("\n\n"); + } /* end if */ + if (CleanUp && !getenv("HDF5_NOCLEANUP")) { + MESSAGE(2, ("\nCleaning Up temp files...\n\n")); + + /* call individual cleanup routines in each source module */ + for (Loop = 0; Loop < Index; Loop++) + if (!Test[Loop].SkipFlag && Test[Loop].Cleanup!=NULL) + (*Test[Loop].Cleanup) (); + } + return (num_errs); +} /* end main() */ +#endif /*H5_HAVE_THREADSAFE*/ diff --git a/test/ttsafe.h b/test/ttsafe.h new file mode 100644 index 0000000..2359764 --- /dev/null +++ b/test/ttsafe.h @@ -0,0 +1,141 @@ +/**************************************************************************** + * NCSA HDF * + * Software Development Group * + * National Center for Supercomputing Applications * + * University of Illinois at Urbana-Champaign * + * 605 E. Springfield, Champaign IL 61820 * + * * + * For conditions of distribution and use, see the accompanying * + * hdf/COPYING file. * + * * + ****************************************************************************/ + +/* $Id$ */ + +/* + * This header file contains information required for testing the HDF5 library. + */ + +#ifndef HDF5TEST_H +#define HDF5TEST_H + +#include <hdf5.h> +#include <string.h> + +/* + * Include required headers. This file tests internal library functions, + * so we include the private headers here. + */ +#include <H5private.h> +#include <H5Eprivate.h> + +#ifdef H5_HAVE_THREADSAFE +/* Include pthread library for threadsafe tests */ +#include <pthread.h> + +extern int num_errs; +extern int Verbosity; + +/* Use %ld to print the value because long should cover most cases. */ +/* Used to make certain a return value _is_not_ a value */ +#define CHECK(ret, val, where) do { \ + if (Verbosity>9) print_func(" Call to routine: %15s at line %4d " \ + "in %s returned %ld \n", \ + where, (int)__LINE__, __FILE__, \ + (long)ret); \ + if (ret == val) { \ + print_func("*** UNEXPECTED RETURN from %s is %ld at line %4d " \ + "in %s\n", where, (long)ret, (int)__LINE__, __FILE__); \ + num_errs++; \ + H5Eprint (stdout); \ + } \ + H5Eclear(); \ +} while(0) + +#define CHECK_I(ret,where) { \ + if (Verbosity>9) { \ + print_func(" Call to routine: %15s at line %4d in %s returned %ld\n", \ + (where), (int)__LINE__, __FILE__, (long)(ret)); \ + } \ + if ((ret)<0) { \ + print_func ("*** UNEXPECTED RETURN from %s is %ld line %4d in %s\n", \ + (where), (long)(ret), (int)__LINE__, __FILE__); \ + H5Eprint (stdout); \ + num_errs++; \ + } \ + H5Eclear (); \ +} + +#define CHECK_PTR(ret,where) { \ + if (Verbosity>9) { \ + print_func(" Call to routine: %15s at line %4d in %s returned %p\n", \ + (where), (int)__LINE__, __FILE__, (ret)); \ + } \ + if (!(ret)) { \ + print_func ("*** UNEXPECTED RETURN from %s is NULL line %4d in %s\n", \ + (where), (int)__LINE__, __FILE__); \ + H5Eprint (stdout); \ + num_errs++; \ + } \ + H5Eclear (); \ +} + +/* Used to make certain a return value _is_ a value */ +#define VERIFY(x, val, where) do { \ + if (Verbosity>9) { \ + print_func(" Call to routine: %15s at line %4d in %s had value " \ + "%ld \n", where, (int)__LINE__, __FILE__, (long)x); \ + } \ + if (x != val) { \ + print_func("*** UNEXPECTED VALUE from %s is %ld at line %4d " \ + "in %s\n", where, (long)x, (int)__LINE__, __FILE__); \ + H5Eprint (stdout); \ + num_errs++; \ + } \ + H5Eclear(); \ +} while(0) + +/* Used to document process through a test and to check for errors */ +#define RESULT(ret,func) do { \ + if (Verbosity>8) { \ + print_func(" Call to routine: %15s at line %4d in %s returned " \ + "%ld\n", func, (int)__LINE__, __FILE__, (long)ret); \ + } \ + if (Verbosity>9) HEprint(stdout, 0); \ + if (ret == FAIL) { \ + print_func("*** UNEXPECTED RETURN from %s is %ld at line %4d " \ + "in %s\n", func, (long)ret, (int)__LINE__, __FILE__); \ + H5Eprint (stdout); \ + num_errs++; \ + } \ + H5Eclear(); \ +} while(0) + +/* Used to document process through a test */ +#define MESSAGE(V,A) {if (Verbosity>(V)) print_func A;} + +/* definitions for command strings */ +#define VERBOSITY_STR "Verbosity" +#define SKIP_STR "Skip" +#define TEST_STR "Test" +#define CLEAN_STR "Cleanup" + +/* Prototypes for the support routines */ +int print_func(const char *,...); +extern char* gen_name(int); +extern int num_digits(int); + +/* Prototypes for the test routines */ +void tts_dcreate(void); +void tts_error(void); +void tts_cancel(void); +void tts_acreate(void); + +/* Prototypes for the cleanup routines */ +void cleanup_dcreate(void); +void cleanup_error(void); +void cleanup_cancel(void); +void cleanup_acreate(void); + +#endif /* H5_HAVE_THREADSAFE */ +#endif /* HDF5_TESTH */ diff --git a/test/ttsafe_acreate.c b/test/ttsafe_acreate.c new file mode 100644 index 0000000..ca94470 --- /dev/null +++ b/test/ttsafe_acreate.c @@ -0,0 +1,166 @@ +/******************************************************************** + * + * Testing for thread safety in H5A (dataset attribute) library + * operations. -- Threaded program -- + * ------------------------------------------------------------------ + * + * Plan: Attempt to break H5Acreate by making many simultaneous create + * calls. + * + * Claim: N calls to H5Acreate should create N attributes for a dataset + * if threadsafe. If some unprotected shared data exists for the + * dataset (eg, a count of the number of attributes in the + * dataset), there is a small chance that consecutive reads occur + * before a write to that shared variable. + * + * Created: Oct 5 1999 + * Programmer: Chee Wai LEE + * + * Modification History + * -------------------- + * May 15 2000 - incorporated into library tests (Chee Wai LEE) + * + ********************************************************************/ + +#include "ttsafe.h" + +#ifndef H5_HAVE_THREADSAFE +static int dummy; /* just to create a non-empty object file */ +#else + +#include <stdio.h> +#include <stdlib.h> + +#define FILE "ttsafe.h5" +#define DATASETNAME "IntData" +#define NUM_THREADS 16 + +void *tts_acreate_thread(void *); + +typedef struct acreate_data_struct { + hid_t dataset; + hid_t datatype; + hid_t dataspace; + int current_index; +} ttsafe_name_data_t; + +void tts_acreate(void) { + + /* Pthread definitions + */ + pthread_t threads[NUM_THREADS]; + + /* HDF5 data definitions + */ + hid_t file, dataset; + hid_t dataspace, datatype; + hid_t attribute; + hsize_t dimsf[1]; /* dataset dimensions */ + + int data; /* data to write */ + int buffer, ret; + + int i; + ttsafe_name_data_t *attrib_data; + + /* create a hdf5 file using H5F_ACC_TRUNC access, + * default file creation plist and default file + * access plist + */ + file = H5Fcreate(FILE, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT); + + /* create a simple dataspace for the dataset + */ + dimsf[0] = 1; + dataspace = H5Screate_simple(1,dimsf,NULL); + + /* define datatype for the data using native little endian integers + */ + datatype = H5Tcopy(H5T_NATIVE_INT); + H5Tset_order(datatype, H5T_ORDER_LE); + + /* create a new dataset within the file + */ + dataset = H5Dcreate(file, DATASETNAME, datatype, dataspace, + H5P_DEFAULT); + + /* initialize data for dataset and write value to dataset + */ + data = NUM_THREADS; + H5Dwrite(dataset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, + H5P_DEFAULT, &data); + + /* simultaneously create a large number of attributes to be + associated with the dataset + */ + + for (i=0;i<NUM_THREADS;i++) { + attrib_data = malloc(sizeof(ttsafe_name_data_t)); + attrib_data->dataset = dataset; + attrib_data->datatype = datatype; + attrib_data->dataspace = dataspace; + attrib_data->current_index = i; + pthread_create(&threads[i],NULL,tts_acreate_thread,attrib_data); + } + + for (i=0;i<NUM_THREADS;i++) { + pthread_join(threads[i],NULL); + } + + /* verify the correctness of the test */ + for (i=0; i<NUM_THREADS; i++) { + attribute = H5Aopen_name(dataset,gen_name(i)); + if (attribute < 0) { + fprintf(stderr,"unable to open appropriate attribute. Test failed!\n"); + } else { + ret = H5Aread(attribute, H5T_NATIVE_INT, &buffer); + if ((ret < 0) || (buffer != i)) { + fprintf(stderr,"wrong data values. Test failed!\n"); + } + H5Aclose(attribute); + } + } + + /* close remaining resources + */ + H5Sclose(dataspace); + H5Tclose(datatype); + H5Dclose(dataset); + H5Fclose(file); +} + +void *tts_acreate_thread(void *client_data) { + + hid_t attribute; + hsize_t dimsf[1]; /* dataset dimensions */ + + char *attribute_name; + int *attribute_data; /* data for attributes */ + int i; + + ttsafe_name_data_t *attrib_data = (ttsafe_name_data_t *)client_data; + + /* create attribute + */ + attribute_name = gen_name(attrib_data->current_index); + attribute = H5Acreate(attrib_data->dataset, + attribute_name, + attrib_data->datatype, + attrib_data->dataspace, + H5P_DEFAULT); + + /* Write data to the attribute + */ + attribute_data = malloc(sizeof(int)); + *attribute_data = attrib_data->current_index; + H5Awrite(attribute,H5T_NATIVE_INT,attribute_data); + H5Aclose(attribute); + + return NULL; +} + +void cleanup_acreate(void) { +} + +#endif /*H5_HAVE_THREADSAFE*/ + diff --git a/test/ttsafe_cancel.c b/test/ttsafe_cancel.c new file mode 100644 index 0000000..4e893e9 --- /dev/null +++ b/test/ttsafe_cancel.c @@ -0,0 +1,203 @@ +/******************************************************************** + * + * Testing thread safety. Thread Cancellation safety + * ------------------------------------------------- + * + * The main thread spawns a child to perform a series of dataset writes + * to a hdf5 file. The main thread and child thread synchronizes within + * a callback function called during a H5Diterate call afterwhich the + * main thread attempts to cancel the child thread. + * + * The cancellation should only work after the child thread has safely + * left the H5Diterate call. + * + * Temporary files generated: + * ttsafe.h5 + * + * HDF5 APIs exercised in thread: + * H5Screate_simple, H5Tcopy, H5Tset_order, H5Dcreate, H5Dclose, + * H5Dwrite, H5Dread, H5Diterate, H5Tclose, H5Sclose. + * + * Created: May 15 2000 + * Programmer: Chee Wai LEE + * + * Modification History + * -------------------- + * + ********************************************************************/ +#include "ttsafe.h" + +#ifndef H5_HAVE_THREADSAFE +static int dummy; /* just to create a non-empty object file */ +#else + +#define FILE "ttsafe.h5" +#define DATASETNAME "commonname" + +void *tts_cancel_thread(void *); +void tts_cancel_barrier(void); +herr_t tts_cancel_callback(void *, hid_t, hsize_t, hssize_t *, void *); +void cancellation_cleanup(void *); + +hid_t cancel_file; +typedef struct cleanup_struct { + hid_t dataset; + hid_t datatype; + hid_t dataspace; +} cancel_cleanup_t; + +pthread_t childthread; +pthread_mutex_t mutex; +pthread_cond_t cond; + +void tts_cancel(void) { + + pthread_attr_t attribute; + hid_t dataset; + + int buffer; + + /* make thread scheduling global */ + pthread_attr_init(&attribute); + pthread_attr_setscope(&attribute, PTHREAD_SCOPE_SYSTEM); + + /* create a hdf5 file using H5F_ACC_TRUNC access, + * default file creation plist and default file + * access plist + */ + cancel_file = H5Fcreate(FILE, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT); + + pthread_create(&childthread, &attribute, tts_cancel_thread, NULL); + + tts_cancel_barrier(); + pthread_cancel(childthread); + + dataset = H5Dopen(cancel_file, DATASETNAME); + H5Dread(dataset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, + &buffer); + + if (buffer == 11) { + /* do nothing */ + } else { + fprintf(stderr, + "operation unsuccessful with value at %d instead of 11\n", + buffer); + } + + H5Dclose(dataset); + H5Fclose(cancel_file); +} + +void *tts_cancel_thread(void *arg) { + + int datavalue; + int *buffer; + hid_t dataspace, datatype, dataset; + hsize_t dimsf[1]; /* dataset dimensions */ + + cancel_cleanup_t *cleanup_structure; + + /* define dataspace for dataset + */ + dimsf[0] = 1; + dataspace = H5Screate_simple(1,dimsf,NULL); + + /* define datatype for the data using native little endian integers + */ + datatype = H5Tcopy(H5T_NATIVE_INT); + H5Tset_order(datatype, H5T_ORDER_LE); + + /* create a new dataset within the file + */ + dataset = H5Dcreate(cancel_file, DATASETNAME, datatype, dataspace, + H5P_DEFAULT); + + /* If thread is cancelled, make cleanup call */ + cleanup_structure = (cancel_cleanup_t*)malloc(sizeof(cancel_cleanup_t)); + cleanup_structure->dataset = dataset; + cleanup_structure->datatype = datatype; + cleanup_structure->dataspace = dataspace; + pthread_cleanup_push(cancellation_cleanup, cleanup_structure); + + datavalue = 1; + H5Dwrite(dataset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, + &datavalue); + + buffer = malloc(sizeof(int)); + H5Dread(dataset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, + buffer); + H5Diterate(buffer, H5T_NATIVE_INT, dataspace, tts_cancel_callback, + &dataset); + + sleep(3); + + datavalue = 100; + H5Dwrite(dataset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, + &datavalue); + H5Dclose(dataset); + H5Tclose(datatype); + H5Sclose(dataspace); + + /* required by pthreads. the argument 0 pops the stack but does not + execute the cleanup routine. + */ + pthread_cleanup_pop(0); + + return (NULL); +} + +herr_t tts_cancel_callback(void *elem, hid_t type_id, hsize_t ndim, + hssize_t *point, void *operator_data) { + int value = *(int *)elem; + hid_t dataset = *(hid_t *)operator_data; + + tts_cancel_barrier(); + sleep(3); + + if (value != 1) { + fprintf(stderr,"Error! Element value should be 1 and not %d\n", value); + return(-1); + } + + value += 10; + H5Dwrite(dataset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, + &value); + + return (0); +} + +/* need to perform the dataset, datatype and dataspace close that was + never performed because of thread cancellation +*/ +void cancellation_cleanup(void *arg) { + cancel_cleanup_t *cleanup_structure = (cancel_cleanup_t *)arg; + H5Dclose(cleanup_structure->dataset); + H5Tclose(cleanup_structure->datatype); + H5Sclose(cleanup_structure->dataspace); + /* retained for debugging */ + /* printf("cancellation noted, cleaning up ... \n"); */ +} + +/* + artificial (and specific to this test) barrier to keep track of whether + both the main and child threads have reached a point in the program. +*/ +void tts_cancel_barrier() { + + static int count = 2; + + pthread_mutex_lock(&mutex); + if (count != 1) { + count--; + pthread_cond_wait(&cond, &mutex); + } else { + pthread_cond_signal(&cond); + } + pthread_mutex_unlock(&mutex); + +} + +void cleanup_cancel() { + H5close(); +} +#endif /*H5_HAVE_THREADSAFE*/ diff --git a/test/ttsafe_dcreate.c b/test/ttsafe_dcreate.c new file mode 100644 index 0000000..cc7d6e6 --- /dev/null +++ b/test/ttsafe_dcreate.c @@ -0,0 +1,191 @@ +/******************************************************************** + * + * Testing thread safety in dataset creation in the HDF5 library + * ------------------------------------------------------------- + * + * Set of tests to run multiple threads so that each creates a different + * dataset. This is likely to cause race-conditions if run in a non + * threadsafe environment. + * + * Temporary files generated: + * ttsafe.h5 + * + * HDF5 APIs exercised in thread: + * H5Screate_simple, H5Tcopy, H5Tset_order, H5Dcreate, H5Dwrite, H5Dclose, + * H5Tclose, H5Sclose. + * + * Created: Apr 28 2000 + * Programmer: Chee Wai LEE + * + * Modification History + * -------------------- + * + ********************************************************************/ +#include "ttsafe.h" + +#ifndef H5_HAVE_THREADSAFE +static int dummy; /* just to create a non-empty object file */ +#else + +#define FILE "ttsafe.h5" +#define DATASETNAME_LENGTH 10 +#define NUM_THREAD 16 + +void *tts_dcreate_creator(void *); + +/* + ********************************************************************** + * Thread safe test - multiple dataset creation + ********************************************************************** + */ +void tts_dcreate(void) { + + /* Pthread definitions + */ + pthread_t threads[NUM_THREAD]; + + /* HDF5 data definitions + */ + hid_t file, dataset, datatype; + + int datavalue; + int i; + + typedef struct thread_info { + int id; + hid_t file; + char *dsetname; + } thread_info; + + thread_info *thread_out; + + char *dsetname[NUM_THREAD]; + pthread_attr_t attribute; + + /* set pthread attribute to perform global scheduling */ + pthread_attr_init(&attribute); + pthread_attr_setscope(&attribute, PTHREAD_SCOPE_SYSTEM); + + /* set individual dataset names (rather than generated the names + automatically) + */ + + for (i=0;i<NUM_THREAD;i++) { + dsetname[i] = (char *)malloc(sizeof(char)*DATASETNAME_LENGTH); + } + dsetname[0] = "zero"; + dsetname[1] = "one"; + dsetname[2] = "two"; + dsetname[3] = "three"; + dsetname[4] = "four"; + dsetname[5] = "five"; + dsetname[6] = "six"; + dsetname[7] = "seven"; + dsetname[8] = "eight"; + dsetname[9] = "nine"; + dsetname[10] = "ten"; + dsetname[11] = "eleven"; + dsetname[12] = "twelve"; + dsetname[13] = "thirteen"; + dsetname[14] = "fourteen"; + dsetname[15] = "fifteen"; + + /* create a hdf5 file using H5F_ACC_TRUNC access, + * default file creation plist and default file + * access plist + */ + file = H5Fcreate(FILE, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT); + + /* simultaneously create a large number of datasets within the file + */ + for (i=0;i<NUM_THREAD;i++) { + thread_out = (thread_info *)malloc(sizeof(thread_info)); + thread_out->id = i; + thread_out->file = file; + thread_out->dsetname = dsetname[i]; + pthread_create(&threads[i],NULL,tts_dcreate_creator,thread_out); + } + + for (i=0;i<NUM_THREAD;i++) { + pthread_join(threads[i],NULL); + } + + /* compare data to see if it is written correctly + */ + + /* define datatype for the data using native little endian integers + */ + datatype = H5Tcopy(H5T_NATIVE_INT); + + for (i=0;i<NUM_THREAD;i++) { + if ((dataset = H5Dopen(file,dsetname[i])) < 0) { + fprintf(stderr,"Dataset name not found - test failed\n"); + H5Fclose(file); + return; + } else { + H5Dread(dataset, datatype, H5S_ALL, H5S_ALL, H5P_DEFAULT, &datavalue); + if (datavalue != i) { + fprintf(stderr, + "Wrong value read %d for dataset name %s - test failed\n", + datavalue, dsetname[i]); + H5Dclose(dataset); + H5Fclose(file); + return; + } + H5Dclose(dataset); + } + } + /* close remaining resources + */ + H5Fclose(file); + +} + +void *tts_dcreate_creator(void *thread_data) { + + hid_t dataspace, datatype, dataset; + hsize_t dimsf[1]; /* dataset dimensions */ + + struct thread_info { + int id; + hid_t file; + char *dsetname; + } thread_in; + + thread_in.dsetname = (char *)malloc(sizeof(char)*DATASETNAME_LENGTH); + thread_in = *((struct thread_info *)thread_data); + + /* define dataspace for dataset + */ + dimsf[0] = 1; + dataspace = H5Screate_simple(1,dimsf,NULL); + + /* define datatype for the data using native little endian integers + */ + datatype = H5Tcopy(H5T_NATIVE_INT); + H5Tset_order(datatype, H5T_ORDER_LE); + + /* create a new dataset within the file + */ + dataset = H5Dcreate(thread_in.file, thread_in.dsetname, + datatype, dataspace, + H5P_DEFAULT); + + /* initialize data for dataset and write value to dataset + */ + H5Dwrite(dataset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, + H5P_DEFAULT, &thread_in.id); + + /* close dataset, datatype and dataspace resources + */ + H5Dclose(dataset); + H5Tclose(datatype); + H5Sclose(dataspace); + + return NULL; +} + +void cleanup_dcreate(void) { + H5close(); +} +#endif /*H5_HAVE_THREADSAFE*/ diff --git a/test/ttsafe_error.c b/test/ttsafe_error.c new file mode 100644 index 0000000..1dafd23 --- /dev/null +++ b/test/ttsafe_error.c @@ -0,0 +1,179 @@ +/******************************************************************** + * + * Testing thread safety. Deliberate per-thread errors to test error stack + * ----------------------------------------------------------------------- + * + * Create 16 multiple threads to create datasets with the same name. The + * library should respond with 15 equivalent error stack printouts (one for + * each bad thread). The final hdf5 file should be a valid file with one + * entry. + * + * Temporary files generated: + * ttsafe.h5 + * + * HDF5 APIs exercised in thread: + * H5Screate_simple, H5Tcopy, H5Tset_order, H5Dcreate, H5Dclose, + * H5Tclose, H5Sclose. + * + * Created: Apr 28 2000 + * Programmer: Chee Wai LEE + * + * Modification History + * -------------------- + * + ********************************************************************/ +#include "ttsafe.h" + +#ifndef H5_HAVE_THREADSAFE +static int dummy; /* just to create a non-empty object file */ +#else + +#define NUM_THREAD 16 +#define FILE "ttsafe.h5" +/* Having a common dataset name is an error */ +#define DATASETNAME "commonname" +#define EXPECTED_ERROR_DEPTH 3 +#define WRITE_NUMBER 37 + +herr_t error_callback(void *); +herr_t walk_error_callback(int, H5E_error_t *, void *); +void *tts_error_thread(void *); +hid_t error_file; + +typedef struct err_num_struct { + int maj_num; + int min_num; +} err_num_t; + +err_num_t expected[] = {{15, 23}, {15, 23}, {10, 32}}; +int error_flag = 0; +int error_count = 0; + +pthread_mutex_t error_mutex; + +void tts_error(void) { + + int i; + + pthread_t threads[NUM_THREAD]; + pthread_attr_t attribute; + + hid_t dataset; + int value; + + H5E_auto_t old_error_cb; + void *old_error_client_data; + + /* set up mutex for global count of errors */ + pthread_mutex_init(&error_mutex, NULL); + + /* preserve previous error stack handler */ + H5Eget_auto(&old_error_cb, &old_error_client_data); + + /* set our own auto error stack handler */ + H5Eset_auto(error_callback, NULL); + + /* make thread scheduling global */ + pthread_attr_init(&attribute); + pthread_attr_setscope(&attribute, PTHREAD_SCOPE_SYSTEM); + + /* create a hdf5 file using H5F_ACC_TRUNC access, + * default file creation plist and default file + * access plist + */ + error_file = H5Fcreate(FILE, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT); + + for (i=0;i<NUM_THREAD;i++) { + pthread_create(&threads[i], &attribute, tts_error_thread, NULL); + } + + for (i=0;i<NUM_THREAD;i++) { + pthread_join(threads[i],NULL); + } + + if (error_flag) { + fprintf(stderr, "Threads reporting different error values!\n"); + } + + if (error_count != NUM_THREAD - 1) { + fprintf(stderr, "Error: %d threads failed instead of %d\n", + error_count, NUM_THREAD-1); + } + + dataset = H5Dopen(error_file, DATASETNAME); + H5Dread(dataset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, &value); + + if (value != WRITE_NUMBER) { + fprintf(stderr, + "Error: Successful thread wrote value %d instead of %d\n", + value, WRITE_NUMBER); + } + + H5Dclose(dataset); + H5Fclose(error_file); + + /* turn our error stack handler off */ + H5Eset_auto(old_error_cb, old_error_client_data); +} + +void *tts_error_thread(void *arg) { + + hid_t dataspace, datatype, dataset; + hsize_t dimsf[1]; /* dataset dimensions */ + int value; + + /* define dataspace for dataset + */ + dimsf[0] = 1; + dataspace = H5Screate_simple(1,dimsf,NULL); + + /* define datatype for the data using native little endian integers + */ + datatype = H5Tcopy(H5T_NATIVE_INT); + H5Tset_order(datatype, H5T_ORDER_LE); + + /* create a new dataset within the file + */ + dataset = H5Dcreate(error_file, DATASETNAME, datatype, dataspace, + H5P_DEFAULT); + if (dataset >= 0) { /* not an error */ + value = WRITE_NUMBER; + H5Dwrite(dataset, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, &value); + + H5Dclose(dataset); + } + H5Tclose(datatype); + H5Sclose(dataspace); + return (NULL); +} + +void cleanup_error() { +} + +herr_t error_callback(void *client_data) { + pthread_mutex_lock(&error_mutex); + error_count++; + pthread_mutex_unlock(&error_mutex); + return (H5Ewalk(H5E_WALK_DOWNWARD, walk_error_callback, NULL)); +} + +herr_t walk_error_callback(int n, H5E_error_t *err_desc, + void *client_data) { + int maj_num, min_num; + + if (err_desc) { + maj_num = err_desc->maj_num; + min_num = err_desc->min_num; + + if (n < EXPECTED_ERROR_DEPTH) { + if (maj_num == expected[n].maj_num && + min_num == expected[n].min_num) { + return SUCCEED; + } + } + } + error_flag = -1; + return SUCCEED; +} + +#endif /*H5_HAVE_THREADSAFE*/ |