From 47d030b0ab9adf502451ef1558eeca50e67063c0 Mon Sep 17 00:00:00 2001 From: rpj Date: Sat, 12 Mar 2005 23:00:53 +0000 Subject: New design for pthread_once (with cancelability); ABI change --- Bmakefile | 2 +- ChangeLog | 57 ++++++++++++++ GNUmakefile | 2 +- Makefile | 10 +-- global.c | 10 +-- implement.h | 7 +- pthread.h | 10 ++- pthread_once.c | 154 +++++++++++++++++++++++++------------ ptw32_InterlockedCompareExchange.c | 16 ++-- ptw32_processInitialize.c | 1 + ptw32_processTerminate.c | 3 +- tests/Bmakefile | 5 +- tests/ChangeLog | 4 + tests/GNUmakefile | 6 +- tests/Makefile | 5 +- tests/Wmakefile | 5 +- tests/once3.c | 114 +++++++++++++++++++++++++++ 17 files changed, 325 insertions(+), 86 deletions(-) create mode 100644 tests/once3.c diff --git a/Bmakefile b/Bmakefile index b72d943..80b9bc6 100644 --- a/Bmakefile +++ b/Bmakefile @@ -7,7 +7,7 @@ # -DLL_VER = 1 +DLL_VER = 2 DEVROOT = . diff --git a/ChangeLog b/ChangeLog index 4cb2469..ceb14fa 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,60 @@ +2005-03-13 Ross Johnson + + * pthread_once.c (pthread_once): Completely redesigned; a change was + required to the ABI (pthread_once_t_), and resulting in a version + compatibility index increment. + + NOTES: + The design (based on pseudo code contributed by Gottlob Frege) avoids + creating a kernel object if there is no contention. See URL for details:- + http://sources.redhat.com/ml/pthreads-win32/2005/msg00029.html + This uses late initialisation similar to the technique already used for + pthreads-win32 mutexes and semaphores (from Alexander Terekhov). + + The subsequent cancelation cleanup additions (by rpj) could not be implemented + without sacrificing some of the efficiency in Gottlob's design. In particular, + although each once_control uses it's own event to block on, a global CS is + required to manage it - since the event must be either re-usable or + re-creatable under cancelation. This is not needed in the non-cancelable + design because it is able to mark the event as closed (forever). + + When uncontested, a CS operation is equivalent to an Interlocked operation + in speed. So, in the final design with cancelability, an uncontested + once_control operation involves a minimum of five interlocked operations + (including the LeaveCS operation). + + ALTERNATIVES: + An alternative design from Alexander Terekhov proposed using a named mutex, + as sketched below:- + + if (!once_control) { // May be in TLS + named_mutex::guard guard(&once_control2); + if (!once_control2) { + + once_control2 = true; + } + once_control = true; + } + + A more detailed description of this can be found here:- + http://groups.yahoo.com/group/boost/message/15442 + + [Although the definition of a suitable PTHREAD_ONCE_INIT precludes use of the + TLS located flag, this is not critical.] + + There are three primary concerns though:- + 1) The [named] mutex is 'created' even in the uncontended case. + 2) A system wide unique name must be generated. + 3) Win32 mutexes are VERY slow even in the uncontended case. An uncontested + Win32 mutex lock operation can be 50 (or more) times slower than an + uncontested EnterCS operation. + + Ultimately, the named mutex trick is making use of the global locks maintained + by the kernel. + + * pthread.h (pthread_once_t_): One flag and an event HANDLE added. + (PTHREAD_ONCE_INIT): Additional values included. + 2005-03-08 Ross Johnson * pthread_once.c (pthread_once): Redesigned to elliminate potential diff --git a/GNUmakefile b/GNUmakefile index 40f520f..9e6be70 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -29,7 +29,7 @@ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA # -DLL_VER = 1 +DLL_VER = 2 DEVROOT = C:\PTHREADS diff --git a/Makefile b/Makefile index 169f733..3827782 100644 --- a/Makefile +++ b/Makefile @@ -6,9 +6,9 @@ # dll and the lib, respectively. Probably all that needs to change is $DEVROOT. -# DLL_VER: -# See pthread.h and README - This number is computed as 'current - age' -DLL_VER = 1 +# DLL_VER: +# See pthread.h and README - This number is computed as 'current - age' +DLL_VER = 2 DEVROOT = c:\pthreads @@ -25,8 +25,8 @@ CFLAGS = /W3 /MD /nologo /Yd /I. /D_WIN32_WINNT=0x400 /DHAVE_CONFIG_H #CFLAGS = /W3 /MD /nologo /Yd /Zi /I. /D_WIN32_WINNT=0x400 /DHAVE_CONFIG_H -# Default cleanup style -CLEANUP = __CLEANUP_C +# Default cleanup style +CLEANUP = __CLEANUP_C # C++ Exceptions VCEFLAGS = /GX /TP $(CFLAGS) diff --git a/global.c b/global.c index 18e8fa3..59cb56f 100644 --- a/global.c +++ b/global.c @@ -108,15 +108,9 @@ CRITICAL_SECTION ptw32_spinlock_test_init_lock; CRITICAL_SECTION ptw32_cond_list_lock; /* - * Global condition variable and mutex for once_control management. - * The mutex must be an ERRORCHECK type because we need to - * guarantee ownership when unlocking. + * Global lock to serialise once_control event management. */ -ptw32_once_control_t ptw32_once_control = - { - PTHREAD_COND_INITIALIZER, - PTHREAD_ERRORCHECK_MUTEX_INITIALIZER - }; +CRITICAL_SECTION ptw32_once_event_lock; #ifdef _UWIN /* diff --git a/implement.h b/implement.h index 5ba9260..abba13c 100644 --- a/implement.h +++ b/implement.h @@ -318,6 +318,11 @@ struct pthread_rwlockattr_t_ int pshared; }; +enum ptw32_once_state { + PTW32_ONCE_CLEAR = 0x0, + PTW32_ONCE_DONE = 0x1, + PTW32_ONCE_CANCELLED = 0x2 +}; typedef struct { pthread_cond_t cond; @@ -478,7 +483,7 @@ extern CRITICAL_SECTION ptw32_cond_list_lock; extern CRITICAL_SECTION ptw32_cond_test_init_lock; extern CRITICAL_SECTION ptw32_rwlock_test_init_lock; extern CRITICAL_SECTION ptw32_spinlock_test_init_lock; -extern ptw32_once_control_t ptw32_once_control; +extern CRITICAL_SECTION ptw32_once_event_lock; #ifdef _UWIN extern int pthread_count; diff --git a/pthread.h b/pthread.h index e668e60..193e20f 100644 --- a/pthread.h +++ b/pthread.h @@ -37,8 +37,8 @@ * See the README file for an explanation of the pthreads-win32 version * numbering scheme and how the DLL is named etc. */ -#define PTW32_VERSION 1,3,0,0 -#define PTW32_VERSION_STRING "1, 3, 0, 0\0" +#define PTW32_VERSION 2,0,0,0 +#define PTW32_VERSION_STRING "2, 0, 0, 0\0" /* There are three implementations of cancel cleanup. * Note that pthread.h is included in both application @@ -673,12 +673,14 @@ enum { * ==================== * ==================== */ -#define PTHREAD_ONCE_INIT { PTW32_FALSE, -1 } +#define PTHREAD_ONCE_INIT { 0, PTW32_FALSE, 0, 0} struct pthread_once_t_ { - volatile int done; /* indicates if user function has been executed */ + int state; /* indicates if user function has been executed, or cancelled */ int started; + int eventUsers; + HANDLE event; }; diff --git a/pthread_once.c b/pthread_once.c index d20a04d..c15c5c1 100644 --- a/pthread_once.c +++ b/pthread_once.c @@ -38,6 +38,23 @@ #include "implement.h" +void ptw32_once_init_routine_cleanup(void * arg) +{ + pthread_once_t * once_control = (pthread_once_t *) arg; + + (void) PTW32_INTERLOCKED_EXCHANGE((LPLONG)&once_control->state, (LONG)PTW32_ONCE_CANCELLED); + + (void) PTW32_INTERLOCKED_EXCHANGE((LPLONG)&once_control->started, (LONG)PTW32_FALSE); + + // There are waiters, wake some up + // We're deliberately not using PulseEvent. It's iffy, and deprecated. + EnterCriticalSection(&ptw32_once_event_lock); + if (once_control->event) + SetEvent(once_control->event); + LeaveCriticalSection(&ptw32_once_event_lock); +} + + int pthread_once (pthread_once_t * once_control, void (*init_routine) (void)) /* @@ -54,6 +71,11 @@ pthread_once (pthread_once_t * once_control, void (*init_routine) (void)) * access is controlled by the pthread_once_t control * key. * + * pthread_once() is not a cancelation point, but the init_routine + * can be. If it's cancelled then the effect on the once_control is + * as if pthread_once had never been entered. + * + * * PARAMETERS * once_control * pointer to an instance of pthread_once_t @@ -86,66 +108,100 @@ pthread_once (pthread_once_t * once_control, void (*init_routine) (void)) result = 0; } - /* - * Use a single global cond+mutex to manage access to all once_control objects. - * Unlike a global mutex on it's own, the global cond+mutex allows faster - * once_controls to overtake slower ones. Spurious wakeups may occur, but - * can be tolerated. - * - * To maintain a separate mutex for each once_control object requires either - * cleaning up the mutex (difficult to synchronise reliably), or leaving it - * around forever. Since we can't make assumptions about how an application might - * employ pthread_once objects, the later is considered to be unacceptable. - * - * Since this is being introduced as a bug fix, the global cond+mtx also avoids - * a change in the ABI, maintaining backwards compatibility. - * - * The mutex should be an ERRORCHECK type to be sure we will never, in the event - * we're cancelled before we get the lock, unlock the mutex when it's held by - * another thread (possible with NORMAL/DEFAULT mutexes because they don't check - * ownership). - */ - - if (!once_control->done) + while (!(InterlockedExchangeAdd((LPLONG)&once_control->state, 0L) & (LONG)PTW32_ONCE_DONE)) // Atomic Read { - if (InterlockedExchange((LPLONG) &once_control->started, (LONG) 0) == -1) + if (!PTW32_INTERLOCKED_EXCHANGE((LPLONG)&once_control->started, (LONG)PTW32_TRUE)) { - (*init_routine) (); + // Clear residual state from a cancelled init_routine + // (and DONE still hasn't been set of course). + if (PTW32_INTERLOCKED_EXCHANGE((LPLONG)&once_control->state, (LONG)PTW32_ONCE_CLEAR) + & PTW32_ONCE_CANCELLED) + { + // The previous initter was cancelled. + // We now have a new initter (us) and we need to make the rest wait again. + EnterCriticalSection(&ptw32_once_event_lock); + if (once_control->event) + ResetEvent(once_control->event); + LeaveCriticalSection(&ptw32_once_event_lock); -#ifdef _MSC_VER -#pragma inline_depth(0) -#endif - /* - * Holding the mutex during the broadcast prevents threads being left - * behind waiting. - */ - pthread_cleanup_push(pthread_mutex_unlock, (void *) &ptw32_once_control.mtx); - (void) pthread_mutex_lock(&ptw32_once_control.mtx); - once_control->done = PTW32_TRUE; - (void) pthread_cond_broadcast(&ptw32_once_control.cond); - pthread_cleanup_pop(1); -#ifdef _MSC_VER -#pragma inline_depth() -#endif + /* + * Any threads entering the wait section and getting out again before + * the CANCELLED state can be cleared and the event is reset will, at worst, just go + * around again or, if they suspend and we (the initter) completes before they resume, + * they will see state == DONE and leave immediately. + */ + } + + pthread_cleanup_push(ptw32_once_init_routine_cleanup, (void *) once_control); + + (*init_routine)(); + + pthread_cleanup_pop(0); + + (void) PTW32_INTERLOCKED_EXCHANGE((LPLONG)&once_control->state, (LONG)PTW32_ONCE_DONE); + + // we didn't create the event. + // it is only there if there is someone waiting + EnterCriticalSection(&ptw32_once_event_lock); + if (once_control->event) + SetEvent(once_control->event); + LeaveCriticalSection(&ptw32_once_event_lock); } else { -#ifdef _MSC_VER -#pragma inline_depth(0) -#endif - pthread_cleanup_push(pthread_mutex_unlock, (void *) &ptw32_once_control.mtx); - (void) pthread_mutex_lock(&ptw32_once_control.mtx); - while (!once_control->done) + // wait for init. + // while waiting, create an event to wait on + + EnterCriticalSection(&ptw32_once_event_lock); + (void) InterlockedIncrement((LPLONG)&once_control->eventUsers); + + /* + * If we are the first thread after the initter thread, and the init_routine is cancelled + * while we're suspended at this point in the code:- + * - state will not get set to PTW32_ONCE_DONE; + * - cleanup will not see an event and cannot set it; + * - therefore, we will eventually resume, create an event and wait on it; + * Remedy: cleanup must set state == CANCELLED before checking for an event, so that + * we will see it and avoid waiting (as for state == DONE). We will go around again and + * we may become the initter. + * If we are still the only other thread when we get to the end of this block, we will + * have closed the event (good). If another thread beats us to be initter, then we will + * re-enter here (good). If, when we reach lights out, other threads have reached this + * point, we will not close the event. The eventUsers counter will still correctly reflect + * the real number of waiters, the old event will remain in use. It will be reset + * by the new initter after clearing the CANCELLED state, causing any threads that are + * cycling around the loop to wait again. + */ + + if (!InterlockedExchangeAdd((LPLONG)&once_control->event, 0L)) // Atomic Read { - (void) pthread_cond_wait(&ptw32_once_control.cond, &ptw32_once_control.mtx); + once_control->event = CreateEvent(NULL, PTW32_TRUE, PTW32_FALSE, NULL); } - pthread_cleanup_pop(1); -#ifdef _MSC_VER -#pragma inline_depth() -#endif + LeaveCriticalSection(&ptw32_once_event_lock); + + // check 'state' again in case the initting thread has finished or cancelled + // and left before seeing that there was an event to trigger. + // (Now that the event IS created, if init gets finished AFTER this, + // then the event handle is guaranteed to be seen and triggered). + + if (!InterlockedExchangeAdd((LPLONG)&once_control->state, 0L)) // Atomic Reads + { + // Neither DONE nor CANCELLED + (void) WaitForSingleObject(once_control->event, INFINITE); + } + + // last one out shut off the lights: + EnterCriticalSection(&ptw32_once_event_lock); + if (InterlockedDecrement((LPLONG)&once_control->eventUsers) == 0) // we were last + { + CloseHandle(once_control->event); + once_control->event = 0; + } + LeaveCriticalSection(&ptw32_once_event_lock); } } + /* * Fall through Intentionally */ diff --git a/ptw32_InterlockedCompareExchange.c b/ptw32_InterlockedCompareExchange.c index 581d19d..5ffc754 100644 --- a/ptw32_InterlockedCompareExchange.c +++ b/ptw32_InterlockedCompareExchange.c @@ -204,12 +204,13 @@ ptw32_InterlockedExchange (LPLONG location, * it doesn't lock the bus. If an interrupt or context switch * occurs between the MOV and the CMPXCHG then the value in * 'location' may have changed, in which case we will loop - * back to do the MOV again. Because both instructions - * reference the same location, they will not be re-ordered - * in the pipeline. + * back to do the MOV again. + * + * FIXME! Need memory barriers for the MOV+CMPXCHG combo? + * * Tests show that this routine has almost identical timing * to Win32's InterlockedExchange(), which is much faster than - * using the an inlined 'xchg' instruction, so it's probably + * using the inlined 'xchg' instruction above, so it's probably * doing something similar to this (on UP systems). * * Can we do without the PUSH/POP instructions? @@ -245,9 +246,10 @@ L1: MOV eax,dword ptr [ecx] * it doesn't lock the bus. If an interrupt or context switch * occurs between the movl and the cmpxchgl then the value in * 'location' may have changed, in which case we will loop - * back to do the movl again. Because both instructions - * reference the same location, they will not be re-ordered - * in the pipeline. + * back to do the movl again. + * + * FIXME! Need memory barriers for the MOV+CMPXCHG combo? + * * Tests show that this routine has almost identical timing * to Win32's InterlockedExchange(), which is much faster than * using the an inlined 'xchg' instruction, so it's probably diff --git a/ptw32_processInitialize.c b/ptw32_processInitialize.c index d13b022..d56cd66 100644 --- a/ptw32_processInitialize.c +++ b/ptw32_processInitialize.c @@ -96,6 +96,7 @@ ptw32_processInitialize (void) InitializeCriticalSection (&ptw32_cond_test_init_lock); InitializeCriticalSection (&ptw32_rwlock_test_init_lock); InitializeCriticalSection (&ptw32_spinlock_test_init_lock); + InitializeCriticalSection (&ptw32_once_event_lock); return (ptw32_processInitialized); diff --git a/ptw32_processTerminate.c b/ptw32_processTerminate.c index bd1ee7a..f80b99b 100644 --- a/ptw32_processTerminate.c +++ b/ptw32_processTerminate.c @@ -101,8 +101,7 @@ ptw32_processTerminate (void) /* * Destroy the global locks and other objects. */ - (void) pthread_cond_destroy(&ptw32_once_control.cond); - (void) pthread_mutex_destroy(&ptw32_once_control.mtx); + DeleteCriticalSection (&ptw32_once_event_lock); DeleteCriticalSection (&ptw32_spinlock_test_init_lock); DeleteCriticalSection (&ptw32_rwlock_test_init_lock); DeleteCriticalSection (&ptw32_cond_test_init_lock); diff --git a/tests/Bmakefile b/tests/Bmakefile index 294eec1..924ea06 100644 --- a/tests/Bmakefile +++ b/tests/Bmakefile @@ -31,7 +31,7 @@ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA # -DLL_VER = 1 +DLL_VER = 2 CP = copy RM = erase @@ -91,7 +91,7 @@ PASSES= loadfree.pass \ mutex6s.pass mutex6es.pass mutex6rs.pass \ mutex7.pass mutex7n.pass mutex7e.pass mutex7r.pass \ mutex8.pass mutex8n.pass mutex8e.pass mutex8r.pass \ - count1.pass once1.pass once2.pass tsd1.pass \ + count1.pass once1.pass once2.pass once3.pass tsd1.pass \ self2.pass \ cancel1.pass cancel2.pass \ semaphore4.pass semaphore4t.pass \ @@ -307,6 +307,7 @@ mutex8e.pass: mutex7e.pass mutex8r.pass: mutex7r.pass once1.pass: create1.pass once2.pass: once1.pass +once3.pass: once2.pass priority1.pass: join1.pass priority2.pass: priority1.pass barrier3.pass reuse1.pass: create2.pass diff --git a/tests/ChangeLog b/tests/ChangeLog index cea9444..321adc3 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -1,3 +1,7 @@ +2005-03-12 Ross Johnson + + * once3.c: New test. + 2005-03-08 Ross Johnson * once2.c: New test. diff --git a/tests/GNUmakefile b/tests/GNUmakefile index 6bf0546..b8b1c91 100644 --- a/tests/GNUmakefile +++ b/tests/GNUmakefile @@ -31,7 +31,7 @@ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA # -DLL_VER = 1 +DLL_VER = 2 CP = cp -f MV = mv -f @@ -74,6 +74,7 @@ COPYFILES = $(HDR) $(LIB) $(DLL) $(QAPC) # If a test case returns a non-zero exit code to the shell, make will # stop. +XTESTS = once3 TESTS = sizes loadfree \ self1 mutex5 mutex1 mutex1e mutex1n mutex1r \ semaphore1 semaphore2 semaphore3 \ @@ -86,7 +87,7 @@ TESTS = sizes loadfree \ mutex4 mutex6 mutex6n mutex6e mutex6r \ mutex6s mutex6es mutex6rs \ mutex7 mutex7n mutex7e mutex7r mutex8 mutex8n mutex8e mutex8r \ - count1 once1 once2 tsd1 self2 \ + count1 once1 once2 once3 tsd1 self2 \ cancel1 cancel2 \ semaphore4 semaphore4t \ delay1 delay2 eyal1 \ @@ -238,6 +239,7 @@ mutex8e.pass: mutex7e.pass mutex8r.pass: mutex7r.pass once1.pass: create1.pass once2.pass: once1.pass +once3.pass: once2.pass priority1.pass: join1.pass priority2.pass: priority1.pass barrier3.pass reuse1.pass: create2.pass diff --git a/tests/Makefile b/tests/Makefile index cad2a94..3b75670 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -31,7 +31,7 @@ # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA # -DLL_VER = 1 +DLL_VER = 2 CP = copy RM = erase @@ -95,7 +95,7 @@ PASSES= sizes.pass loadfree.pass \ mutex6s.pass mutex6es.pass mutex6rs.pass \ mutex7.pass mutex7n.pass mutex7e.pass mutex7r.pass \ mutex8.pass mutex8n.pass mutex8e.pass mutex8r.pass \ - count1.pass once1.pass once2.pass tsd1.pass \ + count1.pass once1.pass once2.pass once3.pass tsd1.pass \ self2.pass \ cancel1.pass cancel2.pass \ semaphore4.pass semaphore4t.pass \ @@ -319,6 +319,7 @@ mutex8e.pass: mutex7e.pass mutex8r.pass: mutex7r.pass once1.pass: create1.pass once2.pass: once1.pass +once3.pass: once2.pass priority1.pass: join1.pass priority2.pass: priority1.pass barrier3.pass reuse1.pass: create2.pass diff --git a/tests/Wmakefile b/tests/Wmakefile index 91a4854..ff54700 100644 --- a/tests/Wmakefile +++ b/tests/Wmakefile @@ -32,7 +32,7 @@ # -DLL_VER = 1 +DLL_VER = 2 .EXTENSIONS: @@ -91,7 +91,7 @@ PASSES = sizes.pass loadfree.pass & mutex6s.pass mutex6es.pass mutex6rs.pass & mutex7.pass mutex7n.pass mutex7e.pass mutex7r.pass & mutex8.pass mutex8n.pass mutex8e.pass mutex8r.pass & - count1.pass once1.pass once2.pass tsd1.pass & + count1.pass once1.pass once2.pass once3.pass tsd1.pass & self2.pass & cancel1.pass cancel2.pass & semaphore4.pass semaphore4t.pass & @@ -304,6 +304,7 @@ mutex8e.pass: mutex7e.pass mutex8r.pass: mutex7r.pass once1.pass: create1.pass once2.pass: once1.pass +once3.pass: once2.pass priority1.pass: join1.pass priority2.pass: priority1.pass barrier3.pass reuse1.pass: create2.pass diff --git a/tests/once3.c b/tests/once3.c new file mode 100644 index 0000000..fabed51 --- /dev/null +++ b/tests/once3.c @@ -0,0 +1,114 @@ +/* + * once3.c + * + * + * -------------------------------------------------------------------------- + * + * Pthreads-win32 - POSIX Threads Library for Win32 + * Copyright(C) 1998 John E. Bossom + * Copyright(C) 1999,2005 Pthreads-win32 contributors + * + * Contact Email: rpj@callisto.canberra.edu.au + * + * The current list of contributors is contained + * in the file CONTRIBUTORS included with the source + * code distribution. The list can also be seen at the + * following World Wide Web location: + * http://sources.redhat.com/pthreads-win32/contributors.html + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library in the file COPYING.LIB; + * if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + * -------------------------------------------------------------------------- + * + * Create several pthread_once objects and channel several threads + * through each. Make the init_routine cancelable and cancel them + * waiters waiting. + * + * Depends on API functions: + * pthread_once() + * pthread_create() + * pthread_testcancel() + * pthread_cancel() + * pthread_once() + */ + +#include "test.h" + +#define NUM_THREADS 100 /* Targeting each once control */ +#define NUM_ONCE 10 + +pthread_once_t o = PTHREAD_ONCE_INIT; +pthread_once_t once[NUM_ONCE]; + +static int numOnce = 0; +static int numThreads = 0; + +void +myfunc(void) +{ + numOnce++; + /* Simulate slow once routine so that following threads pile up behind it */ + Sleep(10); + /* test for cancelation late so we're sure to have waiters. */ + pthread_testcancel(); +} + +void * +mythread(void * arg) +{ + /* + * Cancel every thread. These threads are deferred cancelable only, so + * only the thread performing the init_routine will see it (there are + * no other cancelation points here). The result will be that every thread + * eventually cancels only when it becomes the new initter. + */ + pthread_cancel(pthread_self()); + assert(pthread_once(&once[(int) arg], myfunc) == 0); + numThreads++; + return 0; +} + +int +main() +{ + pthread_t t[NUM_THREADS][NUM_ONCE]; + int i, j; + + for (j = 0; j < NUM_ONCE; j++) + { + once[j] = o; + + for (i = 0; i < NUM_THREADS; i++) + { + assert(pthread_create(&t[i][j], NULL, mythread, (void *) j) == 0); + } + } + + for (j = 0; j < NUM_ONCE; j++) + for (i = 0; i < NUM_THREADS; i++) + if (pthread_join(t[i][j], NULL) != 0) + printf("Join failed for [thread,once] = [%d,%d]\n", i, j); + + /* + * All threads will cancel, none will return normally from + * pthread_once and so numThreads should never be incremented. However, + * numOnce should be incremented by every thread (NUM_THREADS*NUM_ONCE). + */ + assert(numOnce == NUM_ONCE * NUM_THREADS); + assert(numThreads == 0); + + return 0; +} -- cgit v1.2.3