summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Bmakefile2
-rw-r--r--ChangeLog57
-rw-r--r--GNUmakefile2
-rw-r--r--Makefile10
-rw-r--r--global.c10
-rw-r--r--implement.h7
-rw-r--r--pthread.h10
-rw-r--r--pthread_once.c154
-rw-r--r--ptw32_InterlockedCompareExchange.c16
-rw-r--r--ptw32_processInitialize.c1
-rw-r--r--ptw32_processTerminate.c3
-rw-r--r--tests/Bmakefile5
-rw-r--r--tests/ChangeLog4
-rw-r--r--tests/GNUmakefile6
-rw-r--r--tests/Makefile5
-rw-r--r--tests/Wmakefile5
-rw-r--r--tests/once3.c114
17 files changed, 325 insertions, 86 deletions
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 <rpj at callisto.canberra.edu.au>
+
+ * 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) {
+ <init>
+ 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 <rpj at callisto.canberra.edu.au>
* 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 <rpj@callisto.canberra.edu.au>
+
+ * once3.c: New test.
+
2005-03-08 Ross Johnson <rpj@callisto.canberra.edu.au>
* 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;
+}