Skip to content

Commit 5910ab6

Browse files
committed
Implement condition variables using native CONDITION_VARIABLE
1 parent 9d51245 commit 5910ab6

File tree

1 file changed

+27
-242
lines changed

1 file changed

+27
-242
lines changed

win/tclWinThrd.c

Lines changed: 27 additions & 242 deletions
Original file line numberDiff line numberDiff line change
@@ -69,56 +69,6 @@ static int allocOnce = 0;
6969

7070
static CRITICAL_SECTION joinLock;
7171

72-
/*
73-
* Condition variables are implemented with a combination of a per-thread
74-
* Windows Event and a per-condition waiting queue. The idea is that each
75-
* thread has its own Event that it waits on when it is doing a ConditionWait;
76-
* it uses the same event for all condition variables because it only waits on
77-
* one at a time. Each condition variable has a queue of waiting threads, and
78-
* a mutex used to serialize access to this queue.
79-
*
80-
* Special thanks to David Nichols and Jim Davidson for advice on the
81-
* Condition Variable implementation.
82-
*/
83-
84-
/*
85-
* The per-thread event and queue pointers.
86-
*/
87-
88-
#if TCL_THREADS
89-
90-
typedef struct ThreadSpecificData {
91-
HANDLE condEvent; /* Per-thread condition event */
92-
struct ThreadSpecificData *nextPtr; /* Queue pointers */
93-
struct ThreadSpecificData *prevPtr;
94-
int flags; /* See ThreadStateFlags below */
95-
} ThreadSpecificData;
96-
static Tcl_ThreadDataKey dataKey;
97-
98-
#endif /* TCL_THREADS */
99-
100-
/*
101-
* State bits for the thread.
102-
*/
103-
enum ThreadStateFlags {
104-
WIN_THREAD_UNINIT = 0x0, /* Uninitialized. Must be zero because of the
105-
* way ThreadSpecificData is created. */
106-
WIN_THREAD_RUNNING = 0x1, /* Running, not waiting. */
107-
WIN_THREAD_BLOCKED = 0x2 /* Waiting, or trying to wait. */
108-
};
109-
110-
/*
111-
* The per condition queue pointers and the Mutex used to serialize access to
112-
* the queue.
113-
*/
114-
115-
typedef struct {
116-
CRITICAL_SECTION condLock; /* Lock to serialize queuing on the
117-
* condition. */
118-
ThreadSpecificData *firstPtr; /* Queue pointers */
119-
ThreadSpecificData *lastPtr;
120-
} WinCondition;
121-
12272
/*
12373
* Additions by AOL for specialized thread memory allocator.
12474
*/
@@ -705,8 +655,8 @@ TclpFinalizeMutex(
705655
*
706656
* Side effects:
707657
* May block the current thread. The mutex is acquired when this returns.
708-
* Will allocate memory for a HANDLE and initialize this the first time
709-
* this Tcl_Condition is used.
658+
* Will allocate memory for a CONDITION_VARIABLE and initialize the first
659+
* time this Tcl_Condition is used.
710660
*
711661
*----------------------------------------------------------------------
712662
*/
@@ -717,149 +667,44 @@ Tcl_ConditionWait(
717667
Tcl_Mutex *mutexPtr, /* Really (CRITICAL_SECTION **) */
718668
const Tcl_Time *timePtr) /* Timeout on waiting period */
719669
{
720-
WinCondition *winCondPtr; /* Per-condition queue head */
670+
CONDITION_VARIABLE *cvPtr; /* Per-condition queue head */
721671
WMutex *wmPtr; /* Caller's Mutex, after casting */
722672
DWORD wtime; /* Windows time value */
723-
int timeout; /* True if we got a timeout */
724-
int counter; /* Caller's Mutex counter */
725-
int doExit = 0; /* True if we need to do exit setup */
726-
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
727673

728-
/*
729-
* Self initialize the two parts of the condition. The per-condition and
730-
* per-thread parts need to be handled independently.
731-
*/
732-
733-
if (tsdPtr->flags == WIN_THREAD_UNINIT) {
734-
TclpGlobalLock();
735-
736-
/*
737-
* Create the per-thread event and queue pointers.
738-
*/
739-
740-
if (tsdPtr->flags == WIN_THREAD_UNINIT) {
741-
tsdPtr->condEvent = CreateEventW(NULL, TRUE /* manual reset */,
742-
FALSE /* non signaled */, NULL);
743-
tsdPtr->nextPtr = NULL;
744-
tsdPtr->prevPtr = NULL;
745-
tsdPtr->flags = WIN_THREAD_RUNNING;
746-
doExit = 1;
747-
}
748-
TclpGlobalUnlock();
749-
750-
if (doExit) {
751-
/*
752-
* Create a per-thread exit handler to clean up the condEvent. We
753-
* must be careful to do this outside the Global Lock because
754-
* Tcl_CreateThreadExitHandler uses its own ThreadSpecificData,
755-
* and initializing that may drop back into the Global Lock.
756-
*/
757-
758-
Tcl_CreateThreadExitHandler(FinalizeConditionEvent, tsdPtr);
759-
}
674+
if (timePtr == NULL) {
675+
wtime = INFINITE;
676+
} else {
677+
wtime = (DWORD)timePtr->sec * 1000 + (DWORD)timePtr->usec / 1000;
760678
}
761679

762680
if (*condPtr == NULL) {
763681
TclpGlobalLock();
764-
765-
/*
766-
* Initialize the per-condition queue pointers and Mutex.
767-
*/
768-
769682
if (*condPtr == NULL) {
770-
winCondPtr = (WinCondition *)Tcl_Alloc(sizeof(WinCondition));
771-
InitializeCriticalSection(&winCondPtr->condLock);
772-
winCondPtr->firstPtr = NULL;
773-
winCondPtr->lastPtr = NULL;
774-
*condPtr = (Tcl_Condition) winCondPtr;
683+
cvPtr = (CONDITION_VARIABLE *)Tcl_Alloc(sizeof(*cvPtr));
684+
InitializeConditionVariable(cvPtr);
685+
*condPtr = (Tcl_Condition) cvPtr;
775686
TclRememberCondition(condPtr);
776687
}
777688
TclpGlobalUnlock();
778689
}
779690
wmPtr = *((WMutex **)mutexPtr);
780-
winCondPtr = *((WinCondition **)condPtr);
781-
if (timePtr == NULL) {
782-
wtime = INFINITE;
783-
} else {
784-
wtime = (DWORD)timePtr->sec * 1000 + (DWORD)timePtr->usec / 1000;
785-
}
691+
cvPtr = *((CONDITION_VARIABLE **)condPtr);
786692

787-
/*
788-
* Queue the thread on the condition, using the per-condition lock for
789-
* serialization.
790-
*/
791-
792-
tsdPtr->flags = WIN_THREAD_BLOCKED;
793-
tsdPtr->nextPtr = NULL;
794-
EnterCriticalSection(&winCondPtr->condLock);
795-
tsdPtr->prevPtr = winCondPtr->lastPtr; /* A: */
796-
winCondPtr->lastPtr = tsdPtr;
797-
if (tsdPtr->prevPtr != NULL) {
798-
tsdPtr->prevPtr->nextPtr = tsdPtr;
799-
}
800-
if (winCondPtr->firstPtr == NULL) {
801-
winCondPtr->firstPtr = tsdPtr;
802-
}
803-
804-
/*
805-
* Unlock the caller's mutex and wait for the condition, or a timeout.
806-
* There is a minor issue here in that we don't count down the timeout if
807-
* we get notified, but another thread grabs the condition before we do.
808-
* In that race condition we'll wait again for the full timeout. Timed
809-
* waits are dubious anyway. Either you have the locking protocol wrong
810-
* and are masking a deadlock, or you are using conditions to pause your
811-
* thread.
812-
*/
813-
814-
counter = wmPtr->counter;
693+
int counter = wmPtr->counter;
815694
wmPtr->counter = 0;
816695
LONG mythread = GetCurrentThreadId();
817696
assert(wmPtr->thread == mythread);
818697
wmPtr->thread = 0;
819-
LeaveCriticalSection(&wmPtr->crit);
820-
timeout = 0;
821-
while (!timeout && (tsdPtr->flags & WIN_THREAD_BLOCKED)) {
822-
ResetEvent(tsdPtr->condEvent);
823-
LeaveCriticalSection(&winCondPtr->condLock);
824-
if (WaitForSingleObjectEx(tsdPtr->condEvent, wtime,
825-
TRUE) == WAIT_TIMEOUT) {
826-
timeout = 1;
827-
}
828-
EnterCriticalSection(&winCondPtr->condLock);
829-
}
830-
831-
/*
832-
* Be careful on timeouts because the signal might arrive right around the
833-
* time limit and someone else could have taken us off the queue.
834-
*/
835-
836-
if (timeout) {
837-
if (tsdPtr->flags & WIN_THREAD_RUNNING) {
838-
timeout = 0;
839-
} else {
840-
/*
841-
* When dequeueing, we can leave the tsdPtr->nextPtr and
842-
* tsdPtr->prevPtr with dangling pointers because they are
843-
* reinitialized w/out reading them when the thread is enqueued
844-
* later.
845-
*/
846-
847-
if (winCondPtr->firstPtr == tsdPtr) {
848-
winCondPtr->firstPtr = tsdPtr->nextPtr;
849-
} else {
850-
tsdPtr->prevPtr->nextPtr = tsdPtr->nextPtr;
851-
}
852-
if (winCondPtr->lastPtr == tsdPtr) {
853-
winCondPtr->lastPtr = tsdPtr->prevPtr;
854-
} else {
855-
tsdPtr->nextPtr->prevPtr = tsdPtr->prevPtr;
856-
}
857-
tsdPtr->flags = WIN_THREAD_RUNNING;
698+
if (SleepConditionVariableCS(cvPtr,
699+
&wmPtr->crit, wtime) == 0) {
700+
DWORD err = GetLastError();
701+
if (err != ERROR_TIMEOUT) {
702+
Tcl_Panic(
703+
"Tcl_ConditionWait: SleepConditionVariableCS error %d",
704+
err);
858705
}
859706
}
860707

861-
LeaveCriticalSection(&winCondPtr->condLock);
862-
EnterCriticalSection(&wmPtr->crit);
863708
wmPtr->counter = counter;
864709
wmPtr->thread = mythread;
865710
}
@@ -887,70 +732,18 @@ void
887732
Tcl_ConditionNotify(
888733
Tcl_Condition *condPtr)
889734
{
890-
WinCondition *winCondPtr;
891-
ThreadSpecificData *tsdPtr;
735+
CONDITION_VARIABLE *cvPtr;
892736

737+
/* If uninitialized, no could be waiting on the condition variable */
893738
if (*condPtr != NULL) {
894-
winCondPtr = *((WinCondition **)condPtr);
739+
cvPtr = *((CONDITION_VARIABLE **)condPtr);
895740

896-
if (winCondPtr == NULL) {
897-
return;
741+
if (cvPtr) {
742+
WakeAllConditionVariable(cvPtr);
898743
}
899-
900-
/*
901-
* Loop through all the threads waiting on the condition and notify
902-
* them (i.e., broadcast semantics). The queue manipulation is guarded
903-
* by the per-condition coordinating mutex.
904-
*/
905-
906-
EnterCriticalSection(&winCondPtr->condLock);
907-
while (winCondPtr->firstPtr != NULL) {
908-
tsdPtr = winCondPtr->firstPtr;
909-
winCondPtr->firstPtr = tsdPtr->nextPtr;
910-
if (winCondPtr->lastPtr == tsdPtr) {
911-
winCondPtr->lastPtr = NULL;
912-
}
913-
tsdPtr->flags = WIN_THREAD_RUNNING;
914-
tsdPtr->nextPtr = NULL;
915-
tsdPtr->prevPtr = NULL; /* Not strictly necessary, see A: */
916-
SetEvent(tsdPtr->condEvent);
917-
}
918-
LeaveCriticalSection(&winCondPtr->condLock);
919-
} else {
920-
/*
921-
* No-one has used the condition variable, so there are no waiters.
922-
*/
923744
}
924745
}
925746

926-
/*
927-
*----------------------------------------------------------------------
928-
*
929-
* FinalizeConditionEvent --
930-
*
931-
* This procedure is invoked to clean up the per-thread event used to
932-
* implement condition waiting. This is only safe to call at the end of
933-
* time.
934-
*
935-
* Results:
936-
* None.
937-
*
938-
* Side effects:
939-
* The per-thread event is closed.
940-
*
941-
*----------------------------------------------------------------------
942-
*/
943-
944-
static void
945-
FinalizeConditionEvent(
946-
void *data)
947-
{
948-
ThreadSpecificData *tsdPtr = (ThreadSpecificData *) data;
949-
950-
tsdPtr->flags = WIN_THREAD_UNINIT;
951-
CloseHandle(tsdPtr->condEvent);
952-
}
953-
954747
/*
955748
*----------------------------------------------------------------------
956749
*
@@ -974,18 +767,10 @@ void
974767
TclpFinalizeCondition(
975768
Tcl_Condition *condPtr)
976769
{
977-
WinCondition *winCondPtr = *(WinCondition **)condPtr;
978-
979-
/*
980-
* Note - this is called long after the thread-local storage is reclaimed.
981-
* The per-thread condition waiting event is reclaimed earlier in a
982-
* per-thread exit handler, which is called before thread local storage is
983-
* reclaimed.
984-
*/
770+
CONDITION_VARIABLE *cvPtr = *(CONDITION_VARIABLE **)condPtr;
985771

986-
if (winCondPtr != NULL) {
987-
DeleteCriticalSection(&winCondPtr->condLock);
988-
Tcl_Free(winCondPtr);
772+
if (cvPtr) {
773+
Tcl_Free(cvPtr);
989774
*condPtr = NULL;
990775
}
991776
}

0 commit comments

Comments
 (0)