Compare commits

...
Author SHA1 Message Date
jan.nijtmans a13bb5c7e5 Merge 9.0. Further Makefile tweaks 2023-05-03 16:12:46 +00:00
mchn1350 77a0b2b716 Windows Threads are joinable 2023-04-03 07:41:10 +00:00
Konrad Schwarz 83d4470ed9 Clarified that it is suboptimal to hold the associated mutex when signaling a condition variable.
If the mutex is held, there is a danger of the signaled, hence newly awakened threads
of immediately blocking again when trying to acquire the mutex.  This is avoided
by signaling after releasing the mutex. Naturally, this somewhat increases the
danger that an unrelated thread will enter the mutex and invalidate the
underlying condition.  However, this case cannot be ruled out even when
the condition variable is signaled while the mutex is being held.
The "spurious wakeup" clause forces applications to re-check the condition
after reacquiring the mutex in any case.
2023-04-02 15:41:39 +00:00
Konrad Schwarz fe5d5b9dec Replaced non-thread-safe re-initialization checks with the Win32 INIT_ONCE one-time initialization API 2023-04-02 08:45:41 +00:00
mchn1350 31855faf56 Use native Win32 ConditionVariables
Replaces the home-grown condition variable implementation
with Win32 ConditionVariables, as introduced in Windows Vista.
2023-04-01 23:01:34 +00:00
Konrad Schwarz 45b907287d added copyright note 2023-03-29 09:02:07 +00:00
mchn1350 71d42c7655 On Windows, join threads using Win32 HANDLEs.
This works by extending the existing WinThread structure
with the thread's HANDLE. The lifetime of the WinThread structure
is until the thread terminates, for non-joinable threads,
or until the thread is joined. For joinable
threadsd, the thread's HANDLE remains open;
Windows signals the HANDLE when the thread terminates.
The thread's return value is transported using
the standard Windows mechanism.

A new thread-specific variable is introduced so a thread can
find its WinThread structure. Initial threads (those
not created via Tcl_CreateThread) receive a
WinThread lazily, when Tcl_GetCurrentThread is called
for the first time.

Instead of using the Windows ThreadId as the Tcl threadId,
the address of the WinThread is used. The least significant
bit of the pointer value is used to distinguish
joinable threads from non-joinable threads.
(For future use or debugging, the Windows ThreadId is stored
in the WinThread structure as well.)

generic/tclThreadJoin.c is made redudant by this patch.
2023-03-28 18:58:30 +00:00
9 changed files with 247 additions and 586 deletions
+2 -4
View File
@@ -107,8 +107,6 @@ and join it.
Restrictions: On some UNIX systems the pthread-library does not
contain the functionality to specify the stack size of a thread. The
specified value for the stack size is ignored on these systems.
Windows currently does not support joinable threads. This
flag value is therefore ignored on this platform.
.PP
Tcl provides the \fBTcl_ExitThread\fR and \fBTcl_FinalizeThread\fR functions
for terminating threads and invoking optional per-thread exit
@@ -158,9 +156,9 @@ A condition variable is used as a signaling mechanism:
a thread can lock a mutex and then wait on a condition variable
with \fBTcl_ConditionWait\fR. This atomically releases the mutex lock
and blocks the waiting thread until another thread calls
\fBTcl_ConditionNotify\fR. The caller of \fBTcl_ConditionNotify\fR should
\fBTcl_ConditionNotify\fR. The caller of \fBTcl_ConditionNotify\fR may
have the associated mutex held by previously calling \fBTcl_MutexLock\fR,
but this is not enforced. Notifying the
but this tends to produce more thread context switches. Notifying the
condition variable unblocks all threads waiting on the condition variable,
but they do not proceed until the mutex is released with \fBTcl_MutexUnlock\fR.
The implementation of \fBTcl_ConditionWait\fR automatically locks
-3
View File
@@ -3235,7 +3235,6 @@ MODULE_SCOPE Tcl_Obj * TclGetHomeDirObj(Tcl_Interp *interp, const char *user);
MODULE_SCOPE Tcl_Obj * TclResolveTildePath(Tcl_Interp *interp,
Tcl_Obj *pathObj);
MODULE_SCOPE Tcl_Obj * TclResolveTildePathList(Tcl_Obj *pathsObj);
MODULE_SCOPE int TclJoinThread(Tcl_ThreadId id, int *result);
MODULE_SCOPE void TclLimitRemoveAllHandlers(Tcl_Interp *interp);
MODULE_SCOPE Tcl_Obj * TclLindexList(Tcl_Interp *interp,
Tcl_Obj *listPtr, Tcl_Obj *argPtr);
@@ -3363,7 +3362,6 @@ MODULE_SCOPE void TclThreadStorageKeySet(Tcl_ThreadDataKey *keyPtr,
void *data);
MODULE_SCOPE TCL_NORETURN void TclpThreadExit(int status);
MODULE_SCOPE void TclRememberCondition(Tcl_Condition *mutex);
MODULE_SCOPE void TclRememberJoinableThread(Tcl_ThreadId id);
MODULE_SCOPE void TclRememberMutex(Tcl_Mutex *mutex);
MODULE_SCOPE void TclRemoveScriptLimitCallbacks(Tcl_Interp *interp);
MODULE_SCOPE int TclReToGlob(Tcl_Interp *interp, const char *reStr,
@@ -3382,7 +3380,6 @@ MODULE_SCOPE void TclSetCmdNameObj(Tcl_Interp *interp, Tcl_Obj *objPtr,
MODULE_SCOPE void TclSetDuplicateObj(Tcl_Obj *dupPtr, Tcl_Obj *objPtr);
MODULE_SCOPE void TclSetProcessGlobalValue(ProcessGlobalValue *pgvPtr,
Tcl_Obj *newValue, Tcl_Encoding encoding);
MODULE_SCOPE void TclSignalExitThread(Tcl_ThreadId id, int result);
MODULE_SCOPE void TclSpellFix(Tcl_Interp *interp,
Tcl_Obj *const *objv, Tcl_Size objc, Tcl_Size subIdx,
Tcl_Obj *bad, Tcl_Obj *fix);
-318
View File
@@ -1,318 +0,0 @@
/*
* tclThreadJoin.c --
*
* This file implements a platform independent emulation layer for the
* handling of joinable threads. The Windows platform uses this code to
* provide the functionality of joining threads. This code is currently
* not necessary on Unix.
*
* Copyright © 2000 Scriptics Corporation
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#include "tclInt.h"
#ifdef _WIN32
/*
* The information about each joinable thread is remembered in a structure as
* defined below.
*/
typedef struct JoinableThread {
Tcl_ThreadId id; /* The id of the joinable thread. */
int result; /* A place for the result after the demise of
* the thread. */
int done; /* Boolean flag. Initialized to 0 and set to 1
* after the exit of the thread. This allows a
* thread requesting a join to detect when
* waiting is not necessary. */
int waitedUpon; /* Boolean flag. Initialized to 0 and set to 1
* by the thread waiting for this one via
* Tcl_JoinThread. Used to lock any other
* thread trying to wait on this one. */
Tcl_Mutex threadMutex; /* The mutex used to serialize access to this
* structure. */
Tcl_Condition cond; /* This is the condition a thread has to wait
* upon to get notified of the end of the
* described thread. It is signaled indirectly
* by Tcl_ExitThread. */
struct JoinableThread *nextThreadPtr;
/* Reference to the next thread in the list of
* joinable threads. */
} JoinableThread;
/*
* The following variable is used to maintain the global list of all joinable
* threads. Usage by a thread is allowed only if the thread acquired the
* 'joinMutex'.
*/
TCL_DECLARE_MUTEX(joinMutex)
static JoinableThread *firstThreadPtr;
/*
*----------------------------------------------------------------------
*
* TclJoinThread --
*
* This procedure waits for the exit of the thread with the specified id
* and returns its result.
*
* Results:
* A standard tcl result signaling the overall success/failure of the
* operation and an integer result delivered by the thread which was
* waited upon.
*
* Side effects:
* Deallocates the memory allocated by TclRememberJoinableThread.
* Removes the data associated to the thread waited upon from the list of
* joinable threads.
*
*----------------------------------------------------------------------
*/
int
TclJoinThread(
Tcl_ThreadId id, /* The id of the thread to wait upon. */
int *result) /* Reference to a location for the result of
* the thread we are waiting upon. */
{
JoinableThread *threadPtr;
/*
* Steps done here:
* i. Acquire the joinMutex and search for the thread.
* ii. Error out if it could not be found.
* iii. If found, switch from exclusive access to the list to exclusive
* access to the thread structure.
* iv. Error out if some other is already waiting.
* v. Skip the waiting part of the thread is already done.
* vi. Wait for the thread to exit, mark it as waited upon too.
* vii. Get the result form the structure,
* viii. switch to exclusive access of the list,
* ix. remove the structure from the list,
* x. then switch back to exclusive access to the structure
* xi. and delete it.
*/
Tcl_MutexLock(&joinMutex);
threadPtr = firstThreadPtr;
while (threadPtr!=NULL && threadPtr->id!=id) {
threadPtr = threadPtr->nextThreadPtr;
}
if (threadPtr == NULL) {
/*
* Thread not found. Either not joinable, or already waited upon and
* exited. Whatever, an error is in order.
*/
Tcl_MutexUnlock(&joinMutex);
return TCL_ERROR;
}
/*
* [1] If we don't lock the structure before giving up exclusive access to
* the list some other thread just completing its wait on the same thread
* can delete the structure from under us, leaving us with a dangling
* pointer.
*/
Tcl_MutexLock(&threadPtr->threadMutex);
Tcl_MutexUnlock(&joinMutex);
/*
* [2] Now that we have the structure mutex any other thread that just
* tries to delete structure will wait at location [3] until we are done
* with the structure. And in that case we are done with it rather quickly
* as 'waitedUpon' will be set and we will have to error out.
*/
if (threadPtr->waitedUpon) {
Tcl_MutexUnlock(&threadPtr->threadMutex);
return TCL_ERROR;
}
/*
* We are waiting now, let other threads recognize this.
*/
threadPtr->waitedUpon = 1;
while (!threadPtr->done) {
Tcl_ConditionWait(&threadPtr->cond, &threadPtr->threadMutex, NULL);
}
/*
* We have to release the structure before trying to access the list again
* or we can run into deadlock with a thread at [1] (see above) because of
* us holding the structure and the other holding the list. There is no
* problem with dangling pointers here as 'waitedUpon == 1' is still valid
* and any other thread will error out and not come to this place. IOW,
* the fact that we are here also means that no other thread came here
* before us and is able to delete the structure.
*/
Tcl_MutexUnlock(&threadPtr->threadMutex);
Tcl_MutexLock(&joinMutex);
/*
* We have to search the list again as its structure may (may, almost
* certainly) have changed while we were waiting. Especially now is the
* time to compute the predecessor in the list. Any earlier result can be
* dangling by now.
*/
if (firstThreadPtr == threadPtr) {
firstThreadPtr = threadPtr->nextThreadPtr;
} else {
JoinableThread *prevThreadPtr = firstThreadPtr;
while (prevThreadPtr->nextThreadPtr != threadPtr) {
prevThreadPtr = prevThreadPtr->nextThreadPtr;
}
prevThreadPtr->nextThreadPtr = threadPtr->nextThreadPtr;
}
Tcl_MutexUnlock(&joinMutex);
/*
* [3] Now that the structure is not part of the list anymore no other
* thread can acquire its mutex from now on. But it is possible that
* another thread is still holding the mutex though, see location [2]. So
* we have to acquire the mutex one more time to wait for that thread to
* finish. We can (and have to) release the mutex immediately.
*/
Tcl_MutexLock(&threadPtr->threadMutex);
Tcl_MutexUnlock(&threadPtr->threadMutex);
/*
* Copy the result to us, finalize the synchronisation objects, then free
* the structure and return.
*/
*result = threadPtr->result;
Tcl_ConditionFinalize(&threadPtr->cond);
Tcl_MutexFinalize(&threadPtr->threadMutex);
Tcl_Free(threadPtr);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TclRememberJoinableThread --
*
* This procedure remembers a thread as joinable. Only a call to
* TclJoinThread will remove the structure created (and initialized) here.
* IOW, not waiting upon a joinable thread will cause memory leaks.
*
* Results:
* None.
*
* Side effects:
* Allocates memory, adds it to the global list of all joinable threads.
*
*----------------------------------------------------------------------
*/
void
TclRememberJoinableThread(
Tcl_ThreadId id) /* The thread to remember as joinable */
{
JoinableThread *threadPtr;
threadPtr = (JoinableThread *)Tcl_Alloc(sizeof(JoinableThread));
threadPtr->id = id;
threadPtr->done = 0;
threadPtr->waitedUpon = 0;
threadPtr->threadMutex = (Tcl_Mutex) NULL;
threadPtr->cond = (Tcl_Condition) NULL;
Tcl_MutexLock(&joinMutex);
threadPtr->nextThreadPtr = firstThreadPtr;
firstThreadPtr = threadPtr;
Tcl_MutexUnlock(&joinMutex);
}
/*
*----------------------------------------------------------------------
*
* TclSignalExitThread --
*
* This procedure signals that the specified thread is done with its
* work. If the thread is joinable this signal is propagated to the
* thread waiting upon it.
*
* Results:
* None.
*
* Side effects:
* Modifies the associated structure to hold the result.
*
*----------------------------------------------------------------------
*/
void
TclSignalExitThread(
Tcl_ThreadId id, /* Id of the thread signaling its exit. */
int result) /* The result from the thread. */
{
JoinableThread *threadPtr;
Tcl_MutexLock(&joinMutex);
threadPtr = firstThreadPtr;
while ((threadPtr != NULL) && (threadPtr->id != id)) {
threadPtr = threadPtr->nextThreadPtr;
}
if (threadPtr == NULL) {
/*
* Thread not found. Not joinable. No problem, nothing to do.
*/
Tcl_MutexUnlock(&joinMutex);
return;
}
/*
* Switch over the exclusive access from the list to the structure, then
* store the result, set the flag and notify the waiting thread, provided
* that it exists. The order of lock/unlock ensures that a thread entering
* 'TclJoinThread' will not interfere with us.
*/
Tcl_MutexLock(&threadPtr->threadMutex);
Tcl_MutexUnlock(&joinMutex);
threadPtr->done = 1;
threadPtr->result = result;
if (threadPtr->waitedUpon) {
Tcl_ConditionNotify(&threadPtr->cond);
}
Tcl_MutexUnlock(&threadPtr->threadMutex);
}
#else
TCL_MAC_EMPTY_FILE(generic_tclThreadJoin_c)
#endif /* _WIN32 */
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* End:
*/
-4
View File
@@ -86,7 +86,6 @@
F96D45C808F272BC004A47F5 /* tclTestProcBodyObj.c in Sources */ = {isa = PBXBuildFile; fileRef = F96D3F2908F272A7004A47F5 /* tclTestProcBodyObj.c */; };
F96D45C908F272BC004A47F5 /* tclThread.c in Sources */ = {isa = PBXBuildFile; fileRef = F96D3F2A08F272A7004A47F5 /* tclThread.c */; };
F96D45CA08F272BC004A47F5 /* tclThreadAlloc.c in Sources */ = {isa = PBXBuildFile; fileRef = F96D3F2B08F272A7004A47F5 /* tclThreadAlloc.c */; };
F96D45CB08F272BC004A47F5 /* tclThreadJoin.c in Sources */ = {isa = PBXBuildFile; fileRef = F96D3F2C08F272A7004A47F5 /* tclThreadJoin.c */; };
F96D45CC08F272BC004A47F5 /* tclThreadStorage.c in Sources */ = {isa = PBXBuildFile; fileRef = F96D3F2D08F272A7004A47F5 /* tclThreadStorage.c */; };
F96D45CD08F272BC004A47F5 /* tclThreadTest.c in Sources */ = {isa = PBXBuildFile; fileRef = F96D3F2E08F272A7004A47F5 /* tclThreadTest.c */; };
F96D45CE08F272BC004A47F5 /* tclTimer.c in Sources */ = {isa = PBXBuildFile; fileRef = F96D3F2F08F272A7004A47F5 /* tclTimer.c */; };
@@ -522,7 +521,6 @@
F96D3F2908F272A7004A47F5 /* tclTestProcBodyObj.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = tclTestProcBodyObj.c; sourceTree = "<group>"; };
F96D3F2A08F272A7004A47F5 /* tclThread.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = tclThread.c; sourceTree = "<group>"; };
F96D3F2B08F272A7004A47F5 /* tclThreadAlloc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = tclThreadAlloc.c; sourceTree = "<group>"; };
F96D3F2C08F272A7004A47F5 /* tclThreadJoin.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = tclThreadJoin.c; sourceTree = "<group>"; };
F96D3F2D08F272A7004A47F5 /* tclThreadStorage.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = tclThreadStorage.c; sourceTree = "<group>"; };
F96D3F2E08F272A7004A47F5 /* tclThreadTest.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = tclThreadTest.c; sourceTree = "<group>"; };
F96D3F2F08F272A7004A47F5 /* tclTimer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = tclTimer.c; sourceTree = "<group>"; };
@@ -1297,7 +1295,6 @@
F96D3F2908F272A7004A47F5 /* tclTestProcBodyObj.c */,
F96D3F2A08F272A7004A47F5 /* tclThread.c */,
F96D3F2B08F272A7004A47F5 /* tclThreadAlloc.c */,
F96D3F2C08F272A7004A47F5 /* tclThreadJoin.c */,
F96D3F2D08F272A7004A47F5 /* tclThreadStorage.c */,
F96D3F2E08F272A7004A47F5 /* tclThreadTest.c */,
F96D3F2F08F272A7004A47F5 /* tclTimer.c */,
@@ -2000,7 +1997,6 @@
F96D45C808F272BC004A47F5 /* tclTestProcBodyObj.c in Sources */,
F96D45C908F272BC004A47F5 /* tclThread.c in Sources */,
F96D45CA08F272BC004A47F5 /* tclThreadAlloc.c in Sources */,
F96D45CB08F272BC004A47F5 /* tclThreadJoin.c in Sources */,
F96D45CC08F272BC004A47F5 /* tclThreadStorage.c in Sources */,
F96D45CD08F272BC004A47F5 /* tclThreadTest.c in Sources */,
F96D45CE08F272BC004A47F5 /* tclTimer.c in Sources */,
+1 -5
View File
@@ -314,7 +314,7 @@ GENERIC_OBJS = regcomp.o regexec.o regfree.o regerror.o tclAlloc.o \
tclPreserve.o tclProc.o tclProcess.o tclRegexp.o \
tclResolve.o tclResult.o tclScan.o tclStringObj.o \
tclStrToD.o tclThread.o \
tclThreadAlloc.o tclThreadJoin.o tclThreadStorage.o tclStubInit.o \
tclThreadAlloc.o tclThreadStorage.o tclStubInit.o \
tclTimer.o tclTrace.o tclUtf.o tclUtil.o tclVar.o tclZlib.o \
tclTomMathInterface.o tclZipfs.o
@@ -472,7 +472,6 @@ GENERIC_SRCS = \
$(GENERIC_DIR)/tclTestProcBodyObj.c \
$(GENERIC_DIR)/tclThread.c \
$(GENERIC_DIR)/tclThreadAlloc.c \
$(GENERIC_DIR)/tclThreadJoin.c \
$(GENERIC_DIR)/tclThreadStorage.c \
$(GENERIC_DIR)/tclTimer.c \
$(GENERIC_DIR)/tclTrace.c \
@@ -1574,9 +1573,6 @@ tclThread.o: $(GENERIC_DIR)/tclThread.c
tclThreadAlloc.o: $(GENERIC_DIR)/tclThreadAlloc.c
$(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclThreadAlloc.c
tclThreadJoin.o: $(GENERIC_DIR)/tclThreadJoin.c
$(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclThreadJoin.c
tclThreadStorage.o: $(GENERIC_DIR)/tclThreadStorage.c
$(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclThreadStorage.c
-1
View File
@@ -354,7 +354,6 @@ GENERIC_OBJS = \
tclStubInit.$(OBJEXT) \
tclThread.$(OBJEXT) \
tclThreadAlloc.$(OBJEXT) \
tclThreadJoin.$(OBJEXT) \
tclThreadStorage.$(OBJEXT) \
tclTimer.$(OBJEXT) \
tclTomMathInterface.$(OBJEXT) \
-1
View File
@@ -310,7 +310,6 @@ COREOBJS = \
$(TMP_DIR)\tclStubInit.obj \
$(TMP_DIR)\tclThread.obj \
$(TMP_DIR)\tclThreadAlloc.obj \
$(TMP_DIR)\tclThreadJoin.obj \
$(TMP_DIR)\tclThreadStorage.obj \
$(TMP_DIR)\tclTimer.obj \
$(TMP_DIR)\tclTomMathInterface.obj \
-4
View File
@@ -1320,10 +1320,6 @@ SOURCE=..\generic\tclThread.c
# End Source File
# Begin Source File
SOURCE=..\generic\tclThreadJoin.c
# End Source File
# Begin Source File
SOURCE=..\generic\tclThreadTest.c
# End Source File
# Begin Source File
+244 -246
View File
@@ -21,109 +21,51 @@
_CRTIMP unsigned int __cdecl _controlfp (unsigned int unNew, unsigned int unMask);
#endif
/*
* This is the global lock used to serialize access to other serialization
* data structures.
*/
static struct tclWinGlobals {
INIT_ONCE init_once; /* must be first member,
because of static initialziation */
static CRITICAL_SECTION globalLock;
static int initialized = 0;
/*
* This is the global lock used to serialize access to other serialization
* data structures.
*/
CRITICAL_SECTION globalLock;
/*
* This is the global lock used to serialize initialization and finalization
* of Tcl as a whole.
*/
/*
* This is the global lock used to serialize initialization and finalization
* of Tcl as a whole.
*/
static CRITICAL_SECTION initLock;
CRITICAL_SECTION initLock;
/*
* allocLock is used by Tcl's version of malloc for synchronization. For
* obvious reasons, cannot use any dyamically allocated storage.
*/
/* holds the TLS key for the Tcl Thread_ID */
DWORD tls_thread_id;
/*
* allocLock is used by Tcl's version of malloc for synchronization. For
* obvious reasons, cannot use any dyamically allocated storage.
*/
#if TCL_THREADS
static struct Tcl_Mutex_ {
CRITICAL_SECTION crit;
} allocLock;
static Tcl_Mutex allocLockPtr = &allocLock;
static int allocOnce = 0;
struct Tcl_Mutex_ {
CRITICAL_SECTION crit;
} allocLock;
Tcl_Mutex allocLockPtr;
#endif /* TCL_THREADS */
/*
* The joinLock serializes Create- and ExitThread. This is necessary to
* prevent a race where a new joinable thread exits before the creating thread
* had the time to create the necessary data structures in the emulation
* layer.
*/
static CRITICAL_SECTION joinLock;
/*
* Condition variables are implemented with a combination of a per-thread
* Windows Event and a per-condition waiting queue. The idea is that each
* thread has its own Event that it waits on when it is doing a ConditionWait;
* it uses the same event for all condition variables because it only waits on
* one at a time. Each condition variable has a queue of waiting threads, and
* a mutex used to serialize access to this queue.
*
* Special thanks to David Nichols and Jim Davidson for advice on the
* Condition Variable implementation.
*/
/*
* The per-thread event and queue pointers.
*/
} TclWinGlobals = {
INIT_ONCE_STATIC_INIT
};
#if TCL_THREADS
typedef struct ThreadSpecificData {
HANDLE condEvent; /* Per-thread condition event */
struct ThreadSpecificData *nextPtr; /* Queue pointers */
struct ThreadSpecificData *prevPtr;
int flags; /* See flags below */
} ThreadSpecificData;
static Tcl_ThreadDataKey dataKey;
#endif /* TCL_THREADS */
/*
* State bits for the thread.
* WIN_THREAD_UNINIT Uninitialized. Must be zero because of the way
* ThreadSpecificData is created.
* WIN_THREAD_RUNNING Running, not waiting.
* WIN_THREAD_BLOCKED Waiting, or trying to wait.
*/
#define WIN_THREAD_UNINIT 0x0
#define WIN_THREAD_RUNNING 0x1
#define WIN_THREAD_BLOCKED 0x2
/*
* The per condition queue pointers and the Mutex used to serialize access to
* the queue.
*/
typedef struct {
CRITICAL_SECTION condLock; /* Lock to serialize queuing on the
* condition. */
struct ThreadSpecificData *firstPtr; /* Queue pointers */
struct ThreadSpecificData *lastPtr;
typedef struct Tcl_Condition_ {
CONDITION_VARIABLE cv;
} WinCondition;
/*
* Additions by AOL for specialized thread memory allocator.
*/
#endif /* TCL_THREADS */
#ifdef USE_THREAD_ALLOC
static DWORD tlsKey;
typedef struct {
Tcl_Mutex tlock;
CRITICAL_SECTION wlock;
} allocMutex;
#endif /* USE_THREAD_ALLOC */
/*
* The per thread data passed from TclpThreadCreate
@@ -135,8 +77,50 @@ typedef struct {
LPVOID lpParameter; /* Original startup data */
unsigned int fpControl; /* Floating point control word from the
* main thread */
HANDLE tHandle; /* used to wait on thread completion */
# if defined(_MSC_VER) || defined(__MSVCRT__)
unsigned
# else
DWORD
# endif
thread_id; /* initialized but unused,
possibly useful for debugging */
} WinThread;
/* assuming alignment constraints result in lsb being constant 0 */
# define TCL_WIN_THREAD_JOIN_FLAG ((size_t) 1 << 0)
# define TCL_WIN_THREAD_ID(PTR, JOINABLE)\
((Tcl_ThreadId) ((sizeof ((PTR) - (WinThread *) 0),\
(size_t) (PTR)) | ((JOINABLE)? TCL_WIN_THREAD_JOIN_FLAG: 0)))
# define TCL_WIN_THREAD_PTR(ID)\
((WinThread *) ((sizeof ((ID) == (Tcl_ThreadId) 0),\
(size_t) (ID) & ~TCL_WIN_THREAD_JOIN_FLAG)))
# define TCL_WIN_THREAD_JOINABLE(ID)\
(((sizeof ((ID) == (Tcl_ThreadId) 0),\
(size_t) (ID) & TCL_WIN_THREAD_JOIN_FLAG)))
/*
*----------------------------------------------------------------------
*
* TclWinFailure --
*
* Call this when an unexpected WIN32 error occurs
*
* Does not return
*
*----------------------------------------------------------------------
*/
void TCL_NORETURN WINAPI
TclWinFailure (
char const *const routine)
{
Tcl_Panic ("%s failed, last error = %#x\n", routine, GetLastError ());
}
/*
*----------------------------------------------------------------------
@@ -161,9 +145,8 @@ TclWinThreadStart(
LPVOID lpParameter) /* The WinThread structure pointer passed
* from TclpThreadCreate */
{
WinThread *winThreadPtr = (WinThread *) lpParameter;
LPTHREAD_START_ROUTINE lpOrigStartAddress;
LPVOID lpOrigParameter;
WinThread *const winThreadPtr = (WinThread *) lpParameter;
static DWORD result;
if (!winThreadPtr) {
return TCL_ERROR;
@@ -175,11 +158,12 @@ TclWinThreadStart(
#endif
);
lpOrigStartAddress = winThreadPtr->lpStartAddress;
lpOrigParameter = winThreadPtr->lpParameter;
Tcl_Free(winThreadPtr);
return lpOrigStartAddress(lpOrigParameter);
if (!TlsSetValue (TclWinGlobals.tls_thread_id, winThreadPtr))
TclWinFailure ("TlsSetValue");
result = winThreadPtr->lpStartAddress (winThreadPtr->lpParameter);
if (!winThreadPtr->tHandle)
TclpSysFree (winThreadPtr);
return result;
}
/*
@@ -211,41 +195,50 @@ TclpThreadCreate(
WinThread *winThreadPtr; /* Per-thread startup info */
HANDLE tHandle;
winThreadPtr = (WinThread *)Tcl_Alloc(sizeof(WinThread));
winThreadPtr = (WinThread *) TclpSysAlloc (sizeof *winThreadPtr);
if (!winThreadPtr)
TclWinFailure ("memory allocation");
winThreadPtr->lpStartAddress = (LPTHREAD_START_ROUTINE) proc;
winThreadPtr->lpParameter = clientData;
winThreadPtr->fpControl = _controlfp(0, 0);
EnterCriticalSection(&joinLock);
*idPtr = 0; /* must initialize as Tcl_Thread is a pointer and
* on WIN64 sizeof void* != sizeof unsigned
*/
#if defined(_MSC_VER) || defined(__MSVCRT__)
tHandle = (HANDLE) _beginthreadex(NULL, (unsigned) stackSize,
(Tcl_ThreadCreateProc*) TclWinThreadStart, winThreadPtr,
0, (unsigned *)idPtr);
0 /* flags */, &winThreadPtr->thread_id);
#else
tHandle = CreateThread(NULL, (DWORD) stackSize,
TclWinThreadStart, winThreadPtr, 0, (LPDWORD)idPtr);
TclWinThreadStart, winThreadPtr, 0 /* flags */,
&winThreadPtr->thread_id);
#endif
if (tHandle == NULL) {
LeaveCriticalSection(&joinLock);
if (!tHandle) {
TclpSysFree (winThreadPtr);
# if 1
TclWinFailure ("CreateThread");
# else
return TCL_ERROR;
# endif
} else {
if (flags & TCL_THREAD_JOINABLE) {
TclRememberJoinableThread(*idPtr);
if (!(flags & TCL_THREAD_JOINABLE)) {
/*
* The only purpose of this is to decrement the reference count
* so the OS resources will be reacquired when the thread closes.
*/
if (!CloseHandle (tHandle))
TclWinFailure ("ClosseHandle");
/* note: the WIN32 OpenHandle() can be used to open a HANDLE
from the given threadID. However, if the thread
terminates with a HANDLE count of zero, the kernel may clean up
the thread, in particular its return status, immediately.
Hence, joining the thread requires keeping a HANDLE open
until the thread has been successfully joined. */
tHandle = 0;
}
winThreadPtr->tHandle = tHandle;
/*
* The only purpose of this is to decrement the reference count so the
* OS resources will be reacquired when the thread closes.
*/
CloseHandle(tHandle);
LeaveCriticalSection(&joinLock);
*idPtr = TCL_WIN_THREAD_ID (winThreadPtr, 0 != tHandle);
return TCL_OK;
}
}
@@ -273,7 +266,26 @@ Tcl_JoinThread(
int *result) /* Reference to the storage the result of the
* thread we wait upon will be written into. */
{
return TclJoinThread(threadId, result);
if (!TCL_WIN_THREAD_JOINABLE (threadId))
return TCL_ERROR;
WinThread *winThreadPtr = TCL_WIN_THREAD_PTR (threadId);
HANDLE const tHandle = winThreadPtr->tHandle;
switch (WaitForSingleObject (tHandle, INFINITE)) {
DWORD exit_code;
default:
return TCL_ERROR;
case WAIT_FAILED:
TclWinFailure ("WaitForSingleObject");
case WAIT_OBJECT_0:
if (!GetExitCodeThread (tHandle, &exit_code))
TclWinFailure ("GetExitCodeThread");
*result = exit_code;
if (!CloseHandle (tHandle))
TclWinFailure ("CloseHandle");
TclpSysFree (winThreadPtr);
return TCL_OK;
}
}
/*
@@ -296,10 +308,12 @@ void
TclpThreadExit(
int status)
{
EnterCriticalSection(&joinLock);
TclSignalExitThread(Tcl_GetCurrentThread(), status);
LeaveCriticalSection(&joinLock);
WinThread *winThreadPtr;
if (winThreadPtr = (WinThread *) TlsGetValue (
TclWinGlobals.tls_thread_id)) {
if (!winThreadPtr->tHandle)
TclpSysFree (winThreadPtr);
}
#if defined(_MSC_VER) || defined(__MSVCRT__)
_endthreadex((unsigned) status);
#else
@@ -326,8 +340,64 @@ TclpThreadExit(
Tcl_ThreadId
Tcl_GetCurrentThread(void)
{
return (Tcl_ThreadId)INT2PTR(GetCurrentThreadId());
WinThread *winThreadPtr;
if (!(winThreadPtr = (WinThread *) TlsGetValue (
TclWinGlobals.tls_thread_id))) {
if (ERROR_SUCCESS != GetLastError ())
TclWinFailure ("TlsGetValue in Tcl_GetCurrentThread");
/* this is an initial thread */
/* can't use TclAlloc, as the USE_THREAD_ALLOC per-thread allocator
calls Tcl_GetCurrentThread() */
if (!(winThreadPtr = (WinThread *) TclpSysAlloc (sizeof *winThreadPtr)))
TclWinFailure ("memory allocation");
winThreadPtr->tHandle = 0;
winThreadPtr->thread_id = GetCurrentThreadId ();
if (!TlsSetValue (TclWinGlobals.tls_thread_id, winThreadPtr))
TclWinFailure ("TlsSetValue");
}
return TCL_WIN_THREAD_ID (winThreadPtr, 0 != winThreadPtr->tHandle);
}
/*
*----------------------------------------------------------------------
*
* TclWinInitOnce
*
* Perform module initialization exactly once
* storage keys.
*
* Results:
* None.
*
*
*----------------------------------------------------------------------
*/
static BOOL
TclWinInitOnce (
INIT_ONCE *init_once,
void *parameter,
void *context)
{
struct tclWinGlobals *const twg = parameter;
InitializeCriticalSection(&twg->initLock);
InitializeCriticalSection(&twg->globalLock);
twg->tls_thread_id = TlsAlloc ();
#if TCL_THREADS
InitializeCriticalSection(&twg->allocLock.crit);
twg->allocLockPtr = &twg->allocLock;
#endif /* TCL_THREADS */
return TRUE;
}
# define TCL_WIN_INITIALIZE_ONCE()\
InitOnceExecuteOnce (&TclWinGlobals.init_once,\
TclWinInitOnce,\
&TclWinGlobals,\
0)
/*
*----------------------------------------------------------------------
@@ -351,20 +421,9 @@ Tcl_GetCurrentThread(void)
void
TclpInitLock(void)
{
if (!initialized) {
/*
* There is a fundamental race here that is solved by creating the
* first Tcl interpreter in a single threaded environment. Once the
* interpreter has been created, it is safe to create more threads
* that create interpreters in parallel.
*/
initialized = 1;
InitializeCriticalSection(&joinLock);
InitializeCriticalSection(&initLock);
InitializeCriticalSection(&globalLock);
}
EnterCriticalSection(&initLock);
if (!TCL_WIN_INITIALIZE_ONCE ())
TclWinFailure ("InitOnceExecuteOnce");
EnterCriticalSection(&TclWinGlobals.initLock);
}
/*
@@ -387,7 +446,7 @@ TclpInitLock(void)
void
TclpInitUnlock(void)
{
LeaveCriticalSection(&initLock);
LeaveCriticalSection(&TclWinGlobals.initLock);
}
/*
@@ -413,20 +472,9 @@ TclpInitUnlock(void)
void
TclpGlobalLock(void)
{
if (!initialized) {
/*
* There is a fundamental race here that is solved by creating the
* first Tcl interpreter in a single threaded environment. Once the
* interpreter has been created, it is safe to create more threads
* that create interpreters in parallel.
*/
initialized = 1;
InitializeCriticalSection(&joinLock);
InitializeCriticalSection(&initLock);
InitializeCriticalSection(&globalLock);
}
EnterCriticalSection(&globalLock);
if (!TCL_WIN_INITIALIZE_ONCE ())
TclWinFailure ("InitOnceExecuteOnce");
EnterCriticalSection(&TclWinGlobals.globalLock);
}
/*
@@ -449,7 +497,7 @@ TclpGlobalLock(void)
void
TclpGlobalUnlock(void)
{
LeaveCriticalSection(&globalLock);
LeaveCriticalSection(&TclWinGlobals.globalLock);
}
/*
@@ -475,11 +523,9 @@ Tcl_Mutex *
Tcl_GetAllocMutex(void)
{
#if TCL_THREADS
if (!allocOnce) {
InitializeCriticalSection(&allocLock.crit);
allocOnce = 1;
}
return &allocLockPtr;
if (!TCL_WIN_INITIALIZE_ONCE ())
TclWinFailure ("InitOnceExecuteOnce");
return &TclWinGlobals.allocLockPtr;
#else
return NULL;
#endif
@@ -506,36 +552,35 @@ Tcl_GetAllocMutex(void)
void
TclFinalizeLock(void)
{
struct tclWinGlobals *const twg = &TclWinGlobals;
TclpGlobalLock();
DeleteCriticalSection(&joinLock);
if (!TlsFree (twg->tls_thread_id))
TclWinFailure ("TlsFree");
/*
* Destroy the critical section that we are holding!
*/
DeleteCriticalSection(&globalLock);
initialized = 0;
DeleteCriticalSection(&twg->globalLock);
#if TCL_THREADS
if (allocOnce) {
DeleteCriticalSection(&allocLock.crit);
allocOnce = 0;
}
DeleteCriticalSection(&twg->allocLock.crit);
#endif
LeaveCriticalSection(&initLock);
LeaveCriticalSection(&twg->initLock);
/*
* Destroy the critical section that we were holding.
*/
DeleteCriticalSection(&initLock);
DeleteCriticalSection(&twg->initLock);
InitOnceInitialize (&twg->init_once); /* assuming we want to
re-initialize at some point -- dubious */
}
#if TCL_THREADS
/* locally used prototype */
static void FinalizeConditionEvent(void *data);
/*
*----------------------------------------------------------------------
@@ -558,25 +603,25 @@ void
Tcl_MutexLock(
Tcl_Mutex *mutexPtr) /* The lock */
{
CRITICAL_SECTION *csPtr;
struct Tcl_Mutex_ *mp;
if (*mutexPtr == NULL) {
TclpGlobalLock();
/*
* Double inside global lock check to avoid a race.
* BROKEN: does work in relaxed memory models
*/
if (*mutexPtr == NULL) {
csPtr = (CRITICAL_SECTION *)Tcl_Alloc(sizeof(CRITICAL_SECTION));
InitializeCriticalSection(csPtr);
*mutexPtr = (Tcl_Mutex)csPtr;
mp = Tcl_Alloc (sizeof *mp);
InitializeCriticalSection (&mp->crit);
*mutexPtr = mp;
TclRememberMutex(mutexPtr);
}
TclpGlobalUnlock();
}
csPtr = *((CRITICAL_SECTION **)mutexPtr);
EnterCriticalSection(csPtr);
EnterCriticalSection (&(*mutexPtr)->crit);
}
/*
@@ -599,9 +644,7 @@ void
Tcl_MutexUnlock(
Tcl_Mutex *mutexPtr) /* The lock */
{
CRITICAL_SECTION *csPtr = *((CRITICAL_SECTION **)mutexPtr);
LeaveCriticalSection(csPtr);
LeaveCriticalSection (&(*mutexPtr)->crit);
}
/*
@@ -625,11 +668,9 @@ void
TclpFinalizeMutex(
Tcl_Mutex *mutexPtr)
{
CRITICAL_SECTION *csPtr = *(CRITICAL_SECTION **)mutexPtr;
if (csPtr != NULL) {
DeleteCriticalSection(csPtr);
Tcl_Free(csPtr);
if (*mutexPtr) {
DeleteCriticalSection(&(*mutexPtr)->crit);
Tcl_Free(*mutexPtr);
*mutexPtr = NULL;
}
}
@@ -808,8 +849,8 @@ Tcl_ConditionWait(
*
* This procedure is invoked to signal a condition variable.
*
* The mutex must be held during this call to avoid races, but this
* interface does not enforce that.
* The mutex does not need to be held during this call to avoid races
* and should not be, to minimize the number of context switches
*
* Results:
* None.
@@ -824,68 +865,19 @@ void
Tcl_ConditionNotify(
Tcl_Condition *condPtr)
{
WinCondition *winCondPtr;
ThreadSpecificData *tsdPtr;
/* double checked locking, i.e., BROKEN */
if (*condPtr == NULL) {
TclpGlobalLock();
if (*condPtr != NULL) {
winCondPtr = *((WinCondition **)condPtr);
if (winCondPtr == NULL) {
return;
if (*condPtr == NULL) {
WinCondition *winCondPtr;
winCondPtr = (WinCondition *)Tcl_Alloc(sizeof *winCondPtr);
InitializeConditionVariable (&winCondPtr->cv);
*condPtr = winCondPtr;
}
/*
* Loop through all the threads waiting on the condition and notify
* them (i.e., broadcast semantics). The queue manipulation is guarded
* by the per-condition coordinating mutex.
*/
EnterCriticalSection(&winCondPtr->condLock);
while (winCondPtr->firstPtr != NULL) {
tsdPtr = winCondPtr->firstPtr;
winCondPtr->firstPtr = tsdPtr->nextPtr;
if (winCondPtr->lastPtr == tsdPtr) {
winCondPtr->lastPtr = NULL;
}
tsdPtr->flags = WIN_THREAD_RUNNING;
tsdPtr->nextPtr = NULL;
tsdPtr->prevPtr = NULL; /* Not strictly necessary, see A: */
SetEvent(tsdPtr->condEvent);
}
LeaveCriticalSection(&winCondPtr->condLock);
} else {
/*
* No-one has used the condition variable, so there are no waiters.
*/
TclpGlobalUnlock();
}
}
/*
*----------------------------------------------------------------------
*
* FinalizeConditionEvent --
*
* This procedure is invoked to clean up the per-thread event used to
* implement condition waiting. This is only safe to call at the end of
* time.
*
* Results:
* None.
*
* Side effects:
* The per-thread event is closed.
*
*----------------------------------------------------------------------
*/
static void
FinalizeConditionEvent(
void *data)
{
ThreadSpecificData *tsdPtr = (ThreadSpecificData *) data;
tsdPtr->flags = WIN_THREAD_UNINIT;
CloseHandle(tsdPtr->condEvent);
WakeAllConditionVariable (&(*condPtr)->cv);
}
/*
@@ -911,7 +903,7 @@ void
TclpFinalizeCondition(
Tcl_Condition *condPtr)
{
WinCondition *winCondPtr = *(WinCondition **)condPtr;
WinCondition *winCondPtr = *condPtr;
/*
* Note - this is called long after the thread-local storage is reclaimed.
@@ -921,7 +913,6 @@ TclpFinalizeCondition(
*/
if (winCondPtr != NULL) {
DeleteCriticalSection(&winCondPtr->condLock);
Tcl_Free(winCondPtr);
*condPtr = NULL;
}
@@ -935,6 +926,13 @@ TclpFinalizeCondition(
*/
#ifdef USE_THREAD_ALLOC
static DWORD tlsKey;
typedef struct {
Tcl_Mutex tlock;
CRITICAL_SECTION wlock;
} allocMutex;
Tcl_Mutex *
TclpNewAllocMutex(void)
{