summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsebres <sebres@users.sourceforge.net>2017-04-11 18:09:52 (GMT)
committersebres <sebres@users.sourceforge.net>2017-04-11 18:09:52 (GMT)
commit992af585dd13c64af68e50f9cad503afb6f0b1ed (patch)
treeee00c2a8984fdb59a627dcf91aa833b2bc168fe3
parent3ae2132f422286c745ba221dc1076475957fc2f8 (diff)
downloadtcl-992af585dd13c64af68e50f9cad503afb6f0b1ed.zip
tcl-992af585dd13c64af68e50f9cad503afb6f0b1ed.tar.gz
tcl-992af585dd13c64af68e50f9cad503afb6f0b1ed.tar.bz2
code review, robustness increase, avoid infinite wait by exit, thread exit and by pipes of closed processes);
use pipe-helpers (TI-structure handling) for all pipe-workers (tclWinConsole, tclWinSerial);
-rw-r--r--win/tclWinConsole.c281
-rw-r--r--win/tclWinInt.h8
-rw-r--r--win/tclWinPipe.c51
-rw-r--r--win/tclWinSerial.c124
4 files changed, 132 insertions, 332 deletions
diff --git a/win/tclWinConsole.c b/win/tclWinConsole.c
index da995c5..7a0965d 100644
--- a/win/tclWinConsole.c
+++ b/win/tclWinConsole.c
@@ -51,16 +51,10 @@ TCL_DECLARE_MUTEX(consoleMutex)
typedef struct ConsoleThreadInfo {
HANDLE thread; /* Handle to reader or writer thread. */
- int threadExiting; /* Boolean indicating that thread is exiting. */
HANDLE readyEvent; /* Manual-reset event to signal _to_ the main
* thread when the worker thread has finished
* waiting for its normal work to happen. */
- HANDLE startEvent; /* Auto-reset event used by the main thread to
- * signal when the thread should attempt to do
- * its normal work. Additionally this event
- * used as wait for thread event (init phase). */
- HANDLE stopEvent; /* Auto-reset event used by the main thread to
- * signal when the thread should exit. */
+ TclPipeThreadInfo *TI; /* Thread info structure of writer and reader. */
} ConsoleThreadInfo;
/*
@@ -84,16 +78,14 @@ typedef struct ConsoleInfo {
* threads. */
ConsoleThreadInfo writer; /* A specialized thread for handling
* asynchronous writes to the console; the
- * waiting starts when a start event is sent,
+ * waiting starts when a control event is sent,
* and a reset event is sent back to the main
- * thread when the write is done. A stop event
- * is used to terminate the thread. */
+ * thread when the write is done. */
ConsoleThreadInfo reader; /* A specialized thread for handling
* asynchronous reads from the console; the
- * waiting starts when a start event is sent,
+ * waiting starts when a control event is sent,
* and a reset event is sent back to the main
- * thread when input is available. A stop
- * event is used to terminate the thread. */
+ * thread when input is available. */
DWORD writeError; /* An error caused by the last background
* write. Set to 0 if no error has been
* detected. This word is shared with the
@@ -170,10 +162,6 @@ static BOOL ReadConsoleBytes(HANDLE hConsole, LPVOID lpBuffer,
static BOOL WriteConsoleBytes(HANDLE hConsole,
const void *lpBuffer, DWORD nbytes,
LPDWORD nbyteswritten);
-static void StartChannelThread(ConsoleInfo *infoPtr,
- ConsoleThreadInfo *threadInfoPtr,
- LPTHREAD_START_ROUTINE threadProc);
-static void StopChannelThread(ConsoleThreadInfo *threadInfoPtr);
/*
* This structure describes the channel type structure for command console
@@ -520,102 +508,6 @@ ConsoleBlockModeProc(
/*
*----------------------------------------------------------------------
*
- * StartChannelThread, StopChannelThread --
- *
- * Helpers that codify how to ask one of the console service threads to
- * start and stop.
- *
- *----------------------------------------------------------------------
- */
-
-static void
-StartChannelThread(
- ConsoleInfo *infoPtr,
- ConsoleThreadInfo *threadInfoPtr,
- LPTHREAD_START_ROUTINE threadProc)
-{
- DWORD id;
-
- threadInfoPtr->readyEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
- threadInfoPtr->startEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
- threadInfoPtr->threadExiting = FALSE;
- threadInfoPtr->stopEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
- threadInfoPtr->thread = CreateThread(NULL, 256, threadProc, infoPtr, 0,
- &id);
- SetThreadPriority(threadInfoPtr->thread, THREAD_PRIORITY_HIGHEST);
-}
-
-static void
-StopChannelThread(
- ConsoleThreadInfo *threadInfoPtr)
-{
- DWORD exitCode = 0;
-
- /*
- * The thread may already have closed on it's own. Check it's exit
- * code.
- */
-
- GetExitCodeThread(threadInfoPtr->thread, &exitCode);
- if (exitCode == STILL_ACTIVE) {
- /*
- * Set the stop event so that if the reader thread is blocked in
- * ConsoleReaderThread on WaitForMultipleEvents, it will exit cleanly.
- */
-
- SetEvent(threadInfoPtr->stopEvent);
-
- /*
- * Wait at most 20 milliseconds for the reader thread to close.
- */
-
- if (WaitForSingleObject(threadInfoPtr->thread, 20) == WAIT_TIMEOUT) {
- /*
- * Forcibly terminate the background thread as a last resort.
- * Note that we need to guard against terminating the thread while
- * it is in the middle of Tcl_ThreadAlert because it won't be able
- * to release the notifier lock.
- *
- * Also note that terminating threads during their initialization or teardown phase
- * may result in ntdll.dll's LoaderLock to remain locked indefinitely.
- * This causes ntdll.dll's LdrpInitializeThread() to deadlock trying to acquire LoaderLock.
- * LdrpInitializeThread() is executed within new threads to perform
- * initialization and to execute DllMain() of all loaded dlls.
- * As a result, all new threads are deadlocked in their initialization phase and never execute,
- * even though CreateThread() reports successful thread creation.
- * This results in a very weird process-wide behavior, which is extremely hard to debug.
- *
- * THREADS SHOULD NEVER BE TERMINATED. Period.
- *
- * But for now, check if thread is exiting, and if so, let it die peacefully.
- */
-
- if ( !threadInfoPtr->threadExiting
- || WaitForSingleObject(threadInfoPtr->thread, 5000) != WAIT_OBJECT_0
- ) {
- Tcl_MutexLock(&consoleMutex);
- /* BUG: this leaks memory. */
- TerminateThread(threadInfoPtr->thread, 0);
- Tcl_MutexUnlock(&consoleMutex);
- }
- }
- }
-
- /*
- * Close all the handles associated with the thread, and set the thread
- * handle field to NULL to mark that the thread has been cleaned up.
- */
-
- CloseHandle(threadInfoPtr->thread);
- CloseHandle(threadInfoPtr->readyEvent);
- CloseHandle(threadInfoPtr->startEvent);
- CloseHandle(threadInfoPtr->stopEvent);
- threadInfoPtr->thread = NULL;
-}
-
-/*
- *----------------------------------------------------------------------
- *
* ConsoleCloseProc --
*
* Closes a console based IO channel.
@@ -646,7 +538,10 @@ ConsoleCloseProc(
*/
if (consolePtr->reader.thread) {
- StopChannelThread(&consolePtr->reader);
+ TclPipeThreadStop(&consolePtr->reader.TI, consolePtr->reader.thread);
+ CloseHandle(consolePtr->reader.thread);
+ CloseHandle(consolePtr->reader.readyEvent);
+ consolePtr->reader.thread = NULL;
}
consolePtr->validMask &= ~TCL_READABLE;
@@ -663,10 +558,13 @@ ConsoleCloseProc(
* prevent infinite wait on exit. [Python Bug 216289]
*/
- WaitForSingleObject(consolePtr->writer.readyEvent, INFINITE);
+ WaitForSingleObject(consolePtr->writer.readyEvent, 5000);
}
- StopChannelThread(&consolePtr->writer);
+ TclPipeThreadStop(&consolePtr->writer.TI, consolePtr->writer.thread);
+ CloseHandle(consolePtr->writer.thread);
+ CloseHandle(consolePtr->writer.readyEvent);
+ consolePtr->writer.thread = NULL;
}
consolePtr->validMask &= ~TCL_WRITABLE;
@@ -832,8 +730,11 @@ ConsoleOutputProc(
DWORD bytesWritten, timeout;
*errorCode = 0;
- timeout = (infoPtr->flags & CONSOLE_ASYNC) ? 0 : INFINITE;
- if (WaitForSingleObject(threadInfo->readyEvent,timeout) == WAIT_TIMEOUT) {
+
+ /* avoid blocking if pipe-thread exited */
+ timeout = (infoPtr->flags & CONSOLE_ASYNC) || !TclPipeThreadIsAlive(&threadInfo->TI)
+ || TclInExit() || TclInThreadExit() ? 0 : INFINITE;
+ if (WaitForSingleObject(threadInfo->readyEvent, timeout) == WAIT_TIMEOUT) {
/*
* The writer thread is blocked waiting for a write to complete and
* the channel is in non-blocking mode.
@@ -873,7 +774,7 @@ ConsoleOutputProc(
memcpy(infoPtr->writeBuf, buf, (size_t) toWrite);
infoPtr->toWrite = toWrite;
ResetEvent(threadInfo->readyEvent);
- SetEvent(threadInfo->startEvent);
+ TclPipeThreadSignal(&threadInfo->TI);
bytesWritten = toWrite;
} else {
/*
@@ -1110,9 +1011,10 @@ WaitForRead(
* Synchronize with the reader thread.
*/
- timeout = blocking ? INFINITE : 0;
- if (WaitForSingleObject(threadInfo->readyEvent,
- timeout) == WAIT_TIMEOUT) {
+ /* avoid blocking if pipe-thread exited */
+ timeout = (!blocking || !TclPipeThreadIsAlive(&threadInfo->TI)
+ || TclInExit() || TclInThreadExit()) ? 0 : INFINITE;
+ if (WaitForSingleObject(threadInfo->readyEvent, timeout) == WAIT_TIMEOUT) {
/*
* The reader thread is blocked waiting for data and the channel
* is in non-blocking mode.
@@ -1172,7 +1074,7 @@ WaitForRead(
*/
ResetEvent(threadInfo->readyEvent);
- SetEvent(threadInfo->startEvent);
+ TclPipeThreadSignal(&threadInfo->TI);
}
}
@@ -1199,39 +1101,27 @@ static DWORD WINAPI
ConsoleReaderThread(
LPVOID arg)
{
- ConsoleInfo *infoPtr = arg;
- HANDLE *handle = infoPtr->handle;
- ConsoleThreadInfo *threadInfo = &infoPtr->reader;
- DWORD waitResult;
- HANDLE wEvents[2];
-
- /*
- * Notify caller (using startEvent) that this thread is initialized
- */
- SignalObjectAndWait(threadInfo->startEvent, threadInfo->stopEvent, INFINITE, FALSE);
-
- /*
- * The first event takes precedence.
- */
-
- wEvents[0] = threadInfo->stopEvent;
- wEvents[1] = threadInfo->startEvent;
-
- for (;;) {
+ TclPipeThreadInfo *pipeTI = (TclPipeThreadInfo *)arg;
+ ConsoleInfo *infoPtr = NULL; /* access info only after success init/wait */
+ HANDLE *handle = NULL;
+ ConsoleThreadInfo *threadInfo = NULL;
+ int done = 0;
+
+ while (!done) {
/*
- * Wait for the main thread to signal before attempting to wait.
+ * Wait for the main thread to signal before attempting to read.
*/
- waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE);
-
- if (waitResult != (WAIT_OBJECT_0 + 1)) {
- /*
- * The start event was not signaled. It must be the stop event or
- * an error, so exit this thread.
- */
-
+ if (!TclPipeThreadWaitForSignal(&pipeTI)) {
+ /* exit */
break;
}
+ if (!infoPtr) {
+ infoPtr = (ConsoleInfo *)pipeTI->clientData;
+ handle = infoPtr->handle;
+ threadInfo = &infoPtr->reader;
+ }
+
/*
* Look for data on the console, but first ignore any events that are
@@ -1251,6 +1141,7 @@ ConsoleReaderThread(
if (err == (DWORD) EOF) {
infoPtr->readFlags = CONSOLE_EOF;
}
+ done = 1;
}
/*
@@ -1278,11 +1169,8 @@ ConsoleReaderThread(
Tcl_MutexUnlock(&consoleMutex);
}
- /*
- * Inform caller that this thread should not be terminated, since it is about to exit.
- * See comment in StopChannelThread() for reasons.
- */
- threadInfo->threadExiting = TRUE;
+ /* Worker exit, so inform the main thread or free TI-structure (if owned) */
+ TclPipeThreadExit(&pipeTI);
return 0;
}
@@ -1310,40 +1198,27 @@ static DWORD WINAPI
ConsoleWriterThread(
LPVOID arg)
{
- ConsoleInfo *infoPtr = arg;
- HANDLE *handle = infoPtr->handle;
- ConsoleThreadInfo *threadInfo = &infoPtr->writer;
- DWORD count, toWrite, waitResult;
+ TclPipeThreadInfo *pipeTI = (TclPipeThreadInfo *)arg;
+ ConsoleInfo *infoPtr = NULL; /* access info only after success init/wait */
+ HANDLE *handle = NULL;
+ ConsoleThreadInfo *threadInfo = NULL;
+ DWORD count, toWrite;
char *buf;
- HANDLE wEvents[2];
-
- /*
- * Notify caller (using startEvent) that this thread is initialized
- */
- SignalObjectAndWait(threadInfo->startEvent, threadInfo->stopEvent, INFINITE, FALSE);
-
- /*
- * The first event takes precedence.
- */
-
- wEvents[0] = threadInfo->stopEvent;
- wEvents[1] = threadInfo->startEvent;
-
- for (;;) {
+ int done = 0;
+
+ while (!done) {
/*
* Wait for the main thread to signal before attempting to write.
*/
-
- waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE);
-
- if (waitResult != (WAIT_OBJECT_0 + 1)) {
- /*
- * The start event was not signaled. It must be the stop event or
- * an error, so exit this thread.
- */
-
+ if (!TclPipeThreadWaitForSignal(&pipeTI)) {
+ /* exit */
break;
}
+ if (!infoPtr) {
+ infoPtr = (ConsoleInfo *)pipeTI->clientData;
+ handle = infoPtr->handle;
+ threadInfo = &infoPtr->writer;
+ }
buf = infoPtr->writeBuf;
toWrite = infoPtr->toWrite;
@@ -1356,6 +1231,7 @@ ConsoleWriterThread(
if (WriteConsoleBytes(handle, buf, (DWORD) toWrite,
&count) == FALSE) {
infoPtr->writeError = GetLastError();
+ done = 1;
break;
}
toWrite -= count;
@@ -1387,11 +1263,8 @@ ConsoleWriterThread(
Tcl_MutexUnlock(&consoleMutex);
}
- /*
- * Inform caller that this thread should not be terminated, since it is about to exit.
- * See comment in StopChannelThread() for reasons.
- */
- threadInfo->threadExiting = TRUE;
+ /* Worker exit, so inform the main thread or free TI-structure (if owned) */
+ TclPipeThreadExit(&pipeTI);
return 0;
}
@@ -1422,8 +1295,7 @@ TclWinOpenConsoleChannel(
{
char encoding[4 + TCL_INTEGER_SPACE];
ConsoleInfo *infoPtr;
- DWORD modes, wEventsCnt = 0;
- HANDLE wEvents[2], wEventsPtr = wEvents;
+ DWORD modes;
ConsoleInit();
@@ -1464,26 +1336,23 @@ TclWinOpenConsoleChannel(
modes &= ~(ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT);
modes |= ENABLE_LINE_INPUT;
SetConsoleMode(infoPtr->handle, modes);
- StartChannelThread(infoPtr, &infoPtr->reader, ConsoleReaderThread);
- wEvents[wEventsCnt++] = infoPtr->reader.startEvent;
+
+ infoPtr->reader.readyEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
+ infoPtr->reader.thread = CreateThread(NULL, 256, ConsoleReaderThread,
+ TclPipeThreadCreateTI(&infoPtr->reader.TI, infoPtr,
+ infoPtr->reader.readyEvent), 0, NULL);
+ SetThreadPriority(infoPtr->reader.thread, THREAD_PRIORITY_HIGHEST);
}
if (permissions & TCL_WRITABLE) {
- StartChannelThread(infoPtr, &infoPtr->writer, ConsoleWriterThread);
- wEvents[wEventsCnt++] = infoPtr->writer.startEvent;
- }
- /*
- * Wait for both threads to initialize (using theirs startEvent)
- */
- if (wEventsCnt) {
- WaitForMultipleObjects(wEventsCnt, wEvents, TRUE, 5000);
- /* Resume both waiting threads, we've get the events */
- if (infoPtr->reader.thread)
- SetEvent(infoPtr->reader.stopEvent);
- if (infoPtr->writer.thread)
- SetEvent(infoPtr->writer.stopEvent);
+ infoPtr->writer.readyEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
+ infoPtr->writer.thread = CreateThread(NULL, 256, ConsoleWriterThread,
+ TclPipeThreadCreateTI(&infoPtr->writer.TI, infoPtr,
+ infoPtr->writer.readyEvent), 0, NULL);
+ SetThreadPriority(infoPtr->writer.thread, THREAD_PRIORITY_HIGHEST);
}
+
/*
* Files have default translation of AUTO and ^Z eof char, which means
* that a ^Z will be accepted as EOF when reading.
diff --git a/win/tclWinInt.h b/win/tclWinInt.h
index 67b00a8..86945a9 100644
--- a/win/tclWinInt.h
+++ b/win/tclWinInt.h
@@ -149,6 +149,14 @@ TclPipeThreadSignal(
}
};
+static inline int
+TclPipeThreadIsAlive(
+ TclPipeThreadInfo **pipeTIPtr)
+{
+ TclPipeThreadInfo *pipeTI = *pipeTIPtr;
+ return (pipeTI && pipeTI->state != PTI_STATE_DOWN);
+};
+
MODULE_SCOPE int TclPipeThreadStopSignal(TclPipeThreadInfo **pipeTIPtr, HANDLE wakeEvent);
MODULE_SCOPE void TclPipeThreadStop(TclPipeThreadInfo **pipeTIPtr, HANDLE hThread);
MODULE_SCOPE void TclPipeThreadExit(TclPipeThreadInfo **pipeTIPtr);
diff --git a/win/tclWinPipe.c b/win/tclWinPipe.c
index 528f950..87ce790 100644
--- a/win/tclWinPipe.c
+++ b/win/tclWinPipe.c
@@ -1592,7 +1592,8 @@ TclpCreateCommandChannel(
infoPtr->readable = CreateEvent(NULL, TRUE, TRUE, NULL);
infoPtr->readThread = CreateThread(NULL, 256, PipeReaderThread,
- TclPipeThreadCreateTI(&infoPtr->readTI, infoPtr, NULL), 0, NULL);
+ TclPipeThreadCreateTI(&infoPtr->readTI, infoPtr, infoPtr->readable),
+ 0, NULL);
SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST);
infoPtr->validMask |= TCL_READABLE;
} else {
@@ -1798,6 +1799,7 @@ PipeClose2Proc(
int errorCode, result;
PipeInfo *infoPtr, **nextPtrPtr;
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
+ int inExit = (TclInExit() || TclInThreadExit());
errorCode = 0;
result = 0;
@@ -1832,7 +1834,7 @@ PipeClose2Proc(
* nonblocking or may block during exit, bail out since the worker
* thread is not interruptible and we want TIP#398-fast-exit.
*/
- if ((pipePtr->flags & PIPE_ASYNC) && TclInExit()) {
+ if ((pipePtr->flags & PIPE_ASYNC) && inExit) {
/* give it a chance to leave honorably */
TclPipeThreadStopSignal(&pipePtr->writeTI, pipePtr->writable);
@@ -1843,7 +1845,7 @@ PipeClose2Proc(
} else {
- WaitForSingleObject(pipePtr->writable, INFINITE);
+ WaitForSingleObject(pipePtr->writable, inExit ? 5000 : INFINITE);
}
@@ -1885,7 +1887,7 @@ PipeClose2Proc(
}
}
- if ((pipePtr->flags & PIPE_ASYNC) || TclInExit()) {
+ if ((pipePtr->flags & PIPE_ASYNC) || inExit) {
/*
* If the channel is non-blocking or Tcl is being cleaned up, just
* detach the children PIDs, reap them (important if we are in a
@@ -2063,7 +2065,10 @@ PipeOutputProc(
DWORD bytesWritten, timeout;
*errorCode = 0;
- timeout = (infoPtr->flags & PIPE_ASYNC) ? 0 : INFINITE;
+
+ /* avoid blocking if pipe-thread exited */
+ timeout = ((infoPtr->flags & PIPE_ASYNC) || !TclPipeThreadIsAlive(&infoPtr->writeTI)
+ || TclInExit() || TclInThreadExit()) ? 0 : INFINITE;
if (WaitForSingleObject(infoPtr->writable, timeout) == WAIT_TIMEOUT) {
/*
* The writer thread is blocked waiting for a write to complete and
@@ -2614,7 +2619,9 @@ WaitForRead(
* Synchronize with the reader thread.
*/
- timeout = blocking ? INFINITE : 0;
+ /* avoid blocking if pipe-thread exited */
+ timeout = (!blocking || !TclPipeThreadIsAlive(&infoPtr->readTI)
+ || TclInExit() || TclInThreadExit()) ? 0 : INFINITE;
if (WaitForSingleObject(infoPtr->readable, timeout) == WAIT_TIMEOUT) {
/*
* The reader thread is blocked waiting for data and the channel
@@ -2756,7 +2763,7 @@ PipeReaderThread(
infoPtr->readFlags |= PIPE_EOF;
done = 1;
} else if (err == ERROR_INVALID_HANDLE) {
- break;
+ done = 1;
}
} else if (count == 0) {
if (ReadFile(handle, &(infoPtr->extraByte), 1, &count, NULL)
@@ -2778,7 +2785,7 @@ PipeReaderThread(
infoPtr->readFlags |= PIPE_EOF;
done = 1;
} else if (err == ERROR_INVALID_HANDLE) {
- break;
+ done = 1;
}
}
}
@@ -3247,7 +3254,7 @@ TclPipeThreadStop(
HANDLE hThread)
{
TclPipeThreadInfo *pipeTI = *pipeTIPtr;
- HANDLE evControl;
+ HANDLE evControl, wakeEvent;
int state;
if (!pipeTI) {
@@ -3255,6 +3262,7 @@ TclPipeThreadStop(
}
pipeTI = *pipeTIPtr;
evControl = pipeTI->evControl;
+ wakeEvent = pipeTI->evWakeUp;
pipeTI->evWakeUp = NULL;
/*
* Try to sane stop the pipe worker, corresponding its current state
@@ -3290,7 +3298,12 @@ TclPipeThreadStop(
* Thread works currently, we should try to end it, own the TI structure
* (because of possible sharing the joint structures with thread)
*/
- InterlockedExchange(&pipeTI->state, PTI_STATE_END);
+ if ((state = InterlockedCompareExchange(&pipeTI->state,
+ PTI_STATE_END, PTI_STATE_WORK)) == PTI_STATE_DOWN
+ ) {
+ /* we don't need to wait for it, but we should free pipeTI */
+ hThread = NULL;
+ };
break;
}
@@ -3305,6 +3318,8 @@ TclPipeThreadStop(
GetExitCodeThread(hThread, &exitCode);
if (exitCode == STILL_ACTIVE) {
+
+ int inExit = (TclInExit() || TclInThreadExit());
/*
* Set the stop event so that if the pipe thread is blocked
* somewhere, it may hereafter sane exit cleanly.
@@ -3325,8 +3340,7 @@ TclPipeThreadStop(
*/
/* if we want TIP#398-fast-exit. */
- if (WaitForSingleObject(hThread,
- TclInExit() ? 0 : 20) == WAIT_TIMEOUT) {
+ if (WaitForSingleObject(hThread, inExit ? 0 : 20) == WAIT_TIMEOUT) {
/*
* The thread must be blocked waiting for the pipe to
@@ -3353,22 +3367,22 @@ TclPipeThreadStop(
* THREADS SHOULD NEVER BE TERMINATED. Period.
*
* But for now, check if thread is exiting, and if so, let it die peacefully.
+ *
+ * Also don't terminate if in exit (otherwise deadlocked in ntdll.dll's).
*/
if ( pipeTI->state != PTI_STATE_DOWN
&& WaitForSingleObject(hThread,
- TclInExit() ? 0 : 5000) != WAIT_OBJECT_0
+ inExit ? 50 : 5000) != WAIT_OBJECT_0
) {
- Tcl_MutexLock(&pipeMutex);
/* BUG: this leaks memory */
- if (!TerminateThread(hThread, 0)) {
- /* terminate fails, just give thread a chance to exit */
+ if (inExit || !TerminateThread(hThread, 0)) {
+ /* in exit or terminate fails, just give thread a chance to exit */
if (InterlockedExchange(&pipeTI->state,
PTI_STATE_STOP) != PTI_STATE_DOWN) {
pipeTI = NULL;
}
};
- Tcl_MutexUnlock(&pipeMutex);
}
}
}
@@ -3376,6 +3390,9 @@ TclPipeThreadStop(
*pipeTIPtr = NULL;
if (pipeTI) {
+ if (pipeTI->evWakeUp) {
+ SetEvent(pipeTI->evWakeUp);
+ }
CloseHandle(pipeTI->evControl);
#ifndef _PTI_USE_CKALLOC
free(pipeTI);
diff --git a/win/tclWinSerial.c b/win/tclWinSerial.c
index f55f5f1..f78aa5a 100644
--- a/win/tclWinSerial.c
+++ b/win/tclWinSerial.c
@@ -93,19 +93,12 @@ typedef struct SerialInfo {
* threads. */
OVERLAPPED osRead; /* OVERLAPPED structure for read operations. */
OVERLAPPED osWrite; /* OVERLAPPED structure for write operations */
+ TclPipeThreadInfo *writeTI; /* Thread info structure of writer worker. */
HANDLE writeThread; /* Handle to writer thread. */
- int writeThreadExiting; /* Boolean indicating that thread is exiting. */
CRITICAL_SECTION csWrite; /* Writer thread synchronisation. */
HANDLE evWritable; /* Manual-reset event to signal when the
* writer thread has finished waiting for the
* current buffer to be written. */
- HANDLE evStartWriter; /* Auto-reset event used by the main thread to
- * signal when the writer thread should
- * attempt to write to the serial. Additionally
- * this event used as wait for thread event (init). */
- HANDLE evStopWriter; /* Auto-reset event used by the main thread to
- * signal when the writer thread should close.
- */
DWORD writeError; /* An error caused by the last background
* write. Set to 0 if no error has been
* detected. This word is shared with the
@@ -601,7 +594,6 @@ SerialCloseProc(
int errorCode, result = 0;
SerialInfo *infoPtr, **nextPtrPtr;
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
- DWORD exitCode;
errorCode = 0;
@@ -611,71 +603,13 @@ SerialCloseProc(
}
serialPtr->validMask &= ~TCL_READABLE;
- if (serialPtr->validMask & TCL_WRITABLE) {
- /*
- * Generally we cannot wait for a pending write operation because it
- * may hang due to handshake
- * WaitForSingleObject(serialPtr->evWritable, INFINITE);
- */
-
- /*
- * The thread may have already closed on it's own. Check it's exit
- * code.
- */
-
- GetExitCodeThread(serialPtr->writeThread, &exitCode);
-
- if (exitCode == STILL_ACTIVE) {
- /*
- * Set the stop event so that if the writer thread is blocked in
- * SerialWriterThread on WaitForMultipleEvents, it will exit
- * cleanly.
- */
+ if (serialPtr->writeThread) {
- SetEvent(serialPtr->evStopWriter);
+ TclPipeThreadStop(&serialPtr->writeTI, serialPtr->writeThread);
- /*
- * Wait at most 20 milliseconds for the writer thread to close.
- */
-
- if (WaitForSingleObject(serialPtr->writeThread,
- 20) == WAIT_TIMEOUT) {
- /*
- * Forcibly terminate the background thread as a last resort.
- * Note that we need to guard against terminating the thread
- * while it is in the middle of Tcl_ThreadAlert because it
- * won't be able to release the notifier lock.
- *
- * Also note that terminating threads during their initialization or teardown phase
- * may result in ntdll.dll's LoaderLock to remain locked indefinitely.
- * This causes ntdll.dll's LdrpInitializeThread() to deadlock trying to acquire LoaderLock.
- * LdrpInitializeThread() is executed within new threads to perform
- * initialization and to execute DllMain() of all loaded dlls.
- * As a result, all new threads are deadlocked in their initialization phase and never execute,
- * even though CreateThread() reports successful thread creation.
- * This results in a very weird process-wide behavior, which is extremely hard to debug.
- *
- * THREADS SHOULD NEVER BE TERMINATED. Period.
- *
- * But for now, check if thread is exiting, and if so, let it die peacefully.
- */
-
- if ( !serialPtr->writeThreadExiting
- || WaitForSingleObject(serialPtr->writeThread, 5000) != WAIT_OBJECT_0
- ) {
- Tcl_MutexLock(&serialMutex);
- /* BUG: this leaks memory. */
- TerminateThread(serialPtr->writeThread, 0);
- Tcl_MutexUnlock(&serialMutex);
- }
- }
- }
-
- CloseHandle(serialPtr->writeThread);
CloseHandle(serialPtr->osWrite.hEvent);
CloseHandle(serialPtr->evWritable);
- CloseHandle(serialPtr->evStartWriter);
- CloseHandle(serialPtr->evStopWriter);
+ CloseHandle(serialPtr->writeThread);
serialPtr->writeThread = NULL;
PurgeComm(serialPtr->handle, PURGE_TXABORT | PURGE_TXCLEAR);
@@ -1093,7 +1027,7 @@ SerialOutputProc(
memcpy(infoPtr->writeBuf, buf, (size_t) toWrite);
infoPtr->toWrite = toWrite;
ResetEvent(infoPtr->evWritable);
- SetEvent(infoPtr->evStartWriter);
+ TclPipeThreadSignal(&infoPtr->writeTI);
bytesWritten = (DWORD) toWrite;
} else {
@@ -1330,39 +1264,21 @@ static DWORD WINAPI
SerialWriterThread(
LPVOID arg)
{
- SerialInfo *infoPtr = (SerialInfo *)arg;
- DWORD bytesWritten, toWrite, waitResult;
+ TclPipeThreadInfo *pipeTI = (TclPipeThreadInfo *)arg;
+ SerialInfo *infoPtr = NULL; /* access info only after success init/wait */
+ DWORD bytesWritten, toWrite;
char *buf;
OVERLAPPED myWrite; /* Have an own OVERLAPPED in this thread. */
- HANDLE wEvents[2];
-
- /*
- * Notify TclWinOpenSerialChannel() that this thread is initialized
- */
- SignalObjectAndWait(infoPtr->evStartWriter, infoPtr->evStopWriter, INFINITE, FALSE);
-
- /*
- * The stop event takes precedence by being first in the list.
- */
-
- wEvents[0] = infoPtr->evStopWriter;
- wEvents[1] = infoPtr->evStartWriter;
for (;;) {
/*
* Wait for the main thread to signal before attempting to write.
*/
-
- waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE);
-
- if (waitResult != (WAIT_OBJECT_0 + 1)) {
- /*
- * The start event was not signaled. It might be the stop event or
- * an error, so exit.
- */
-
+ if (!TclPipeThreadWaitForSignal(&pipeTI)) {
+ /* exit */
break;
}
+ infoPtr = (SerialInfo *)pipeTI->clientData;
buf = infoPtr->writeBuf;
toWrite = infoPtr->toWrite;
@@ -1426,11 +1342,8 @@ SerialWriterThread(
Tcl_MutexUnlock(&serialMutex);
}
- /*
- * Inform caller that this thread should not be terminated, since it is about to exit.
- * See comment in SerialCloseProc() for reasons.
- */
- infoPtr->writeThreadExiting = TRUE;
+ /* Worker exit, so inform the main thread or free TI-structure (if owned) */
+ TclPipeThreadExit(&pipeTI);
return 0;
}
@@ -1505,7 +1418,6 @@ TclWinOpenSerialChannel(
int permissions)
{
SerialInfo *infoPtr;
- DWORD id;
SerialInit();
@@ -1557,15 +1469,9 @@ TclWinOpenSerialChannel(
infoPtr->osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
infoPtr->evWritable = CreateEvent(NULL, TRUE, TRUE, NULL);
- infoPtr->evStartWriter = CreateEvent(NULL, FALSE, FALSE, NULL);
- infoPtr->evStopWriter = CreateEvent(NULL, FALSE, FALSE, NULL);
- infoPtr->writeThreadExiting = FALSE;
infoPtr->writeThread = CreateThread(NULL, 256, SerialWriterThread,
- infoPtr, 0, &id);
- /* Wait for thread to initialize (using evStartWriter) */
- WaitForSingleObject(infoPtr->evStartWriter, 5000);
- /* Wake-up it to signal we've get an event */
- SetEvent(infoPtr->evStopWriter);
+ TclPipeThreadCreateTI(&infoPtr->writeTI, infoPtr,
+ infoPtr->evWritable), 0, NULL);
}
/*
RC#qL5T+"1Q&`AkI-1 1-5αfd|ZcjIR2Cf*1|_gGn7*4 @𢹯lܶ~K44g|===ooD3E ㊟wJ8 ] &q6>B#I9ڧs!,Vή:{uU*&f#ikya/#JW3 aw:vB3P#g؆JJgGD' Kǘd m=hSS0Qr'j?: |ny$bjQ{$JϪtncK?0a=JCԃ#g<0hk)ҭ] 5MUV胙xݤ}t&WQfszSkA \>P?N/OWY涍¯Ser=wbs2toku! S. ~n<>mGq؞XZik++ע*XR@_83Ɨg:;jjŶ`#P>ըS_<_t04±dٖbe=w%gGqտ%ZemBX6\r?C][ 8ۼU;C]"i}H 2KV`#י g8E28%\/1e[8>q~τ?cm~U逌/Q0]YԤJ,<2\b~ƩGtcEŏ`TA|!赣KZIV-XquK=Usca[O؍&&.3y"5aNNGSGmqJnA:bÿu}5,t(y dth4.k?qhg x\|<ݓw{Ekt<*~PA6gmawXxt[3/GNn̦.[1Q%j'ZyJX>K~ւa/j.‹W\:en@nAwK#u /#`GZ@_@w<䍲g*-ݤ&t1Jm%٥$KqJ&'zI6'铎?K: jue7'0?3F!J!\@o8]CфhfP|0Eed:s^O*w.rcu`]vJQ5ocydv4$?2"c E 3$F5=e.]X$JɟxSqd i!ytm˰h)aI=r`HWiNIL9Ӣ{\fz(gㆸ#nG4Cz29z2 Y_..}i:R(`ڈl"v`.y@N3M܈/Y w r泽~vm j'[%tw;>guɰVvv1We>Ԡ5Б%P VH&У|-6 LHhqqk_@+vʵ!qN,5B޸GZ#Mnp^x)DMlL .[VqND\^}AugiNvBTı_`} cO0ؠ}"~zv'b/ (`P}xY\`ynriyYr d9Y1T%IKٶ#7.kr^`˳tj 87nӆDC- utWQs%"BjEG%NfRaC^Xen,N˼6SL܁Ҭh]ÄX!Lw`as@zD TH%9fSa af]eF E'h/TA.O^~ܲ}:8zNy Rd?Һ\Cb&acL*ED|%1q N #7$+%ˌ"42&Cdcw7%3+jH*^Щ'͋ 痞fkժG \\_=:-,_3}ͦ?jǞWwgݦ}G]nwonZp4$n܍r cDפsOǤ= #t>ۊx;kB ڱSصZ=9eW``;9".8i(h" T[m+D@n7 h&~ Jup}= diecRVB` h g;ʘD]@3 LbqD*%̱Qb5Yki˞\"M))-(ٚ8p!mm()ͲGCSUEWAqC[6,~2rYLDS*31꿄AqES-H; M2op.`cCS  LfE[g;JPJ3v Y9탏Ks?s-~T=h [@{dIAJ+^("Ԙ/<n@)]DF]Gg_wp<ܟ]ODFGR=J,` & ׳Lo rC=~Q^*otFvA4-:O igv_"3IO ^x3E{p {橓@ #ucڃay\sso Yk{F;?h[ןstwvN3f?|84 ^>_d @3hi R[< t|w ! &BWu W{k*J\Ik- n4_m竛[6VXye>ONsna y HO6{=WCU| =vmgL!ْL(?Qo<)K_q ELq[0T+-/*sE8GZWf1}>' ~Q*WFno&4qt븅7t;Lɐ) ]E6-Sbc ݔ>Mkq=" zjIMuւ[vO ;<*| +gBj=n$A,=άyź`UI#>оU,لҿm>xMMn$ wnhѶ2Ex2d u#m x(P 69q fKrU~[0ׁ쐂 MOJLxWṧiv4b6t%5 [O/sf^d /w~YJ #PQ}1_á$SP 0/M.)d:BLJK؝FrI4ZA%6~I R7kђL;W6B65.}g1OqkD&ll7R@>L9$9l}?OUq-zo\N'DOc ܸ[۾gsZ㕆>E+h 1-"05 ˋY|ƝH!ROuRlrx+y`ŵW76qT=Dk.60nlD.bv^Z^ HƏfwQ* F6P# 7s;rzcmw`ϨnY]Kn[XD =zhNJrG8- 9=Db6f;`Cu҇Moڳ s)Ik&5Uя`$b':=bw$CL`TmrW^[x?aK>Gn(x>xG˃n;x£!ϣGs?_3Mmxb^ *s½VoqMbF& AYZzݢg[a" BC̈́gT$NTz6 Ux\Ϥ3Qk>z8S D7Gn 5O͇xίzѮ/p_v[ ;^<^|qM˺q04 vqi~wQ?qrQv9Y C#nl]sx^)ڇM5S;kj %hR[ ;{Agg0; ; 0"(TdbzCndV(6-LWA!6q?X W&zawX8ȷ Zn6`n??`H/_tsn 7D7F\OB" ~P4/FLq }RBR!bj}~Qw3 ;x{YD -pB@+uqv4ة-21,n&N[C%Ʉ/{XyPP3ݐ(anx,پލ !P{> ݒÄ‘f Yh" 5_YQ-28♇nw)6-?' %|+5bN:m4mI0tT'PMIjR_Q՟݁ya0Jf|c`U&FGqyھ+dKo@4X) A7ZeBmΜ9aE}x4sǠPbq0|n<CG:S縣qZ Jf&u7.3fxr )NxS+]E%tm&;kC5'(G5O¼fB5|'_̫A۽? 9|hr2[ݤp2@% zyOV^eELl.KEJy'VIEǴv<~ 0?ph,?bećC6?%!DI!F'[/jM' EHoWlmF[" J9A f?؊<|؎/vқ7@]_=i~k"F1pR2*ͭ[QoQ;J4JۍhL>O7Edk2L$j Ϫ'&pF6 V:H 0rc8X[  ]ܪ͉9Z~[7X.^u /TzWkN[F7ӑf/J/ko jC.ӟGcڜkt;s3Q;wE_buu͹6᯶bR69Iz)==j`Q90&+E'V{~hVUx<{!T %J9<߁[q5^GB?iAO("e31;ۡ[4p j|?F8R ZZy:4i`s2 I; >B%S@~FTvENQn^JZK/Pv%< Îtz`gMGa:h+T^5,fYlG=e,l$, 3g> $(חG+lq(a:tvqc<2ֻ4C_|eبHk$ V&G4>2 s<|GF\:gҸOq$IDDf>͝5!V mp]Wy4[Yk'ˬ[VYi)g(]a]uOXT0g2|;mXiO 4ƴwQWIlf *&(D!?VI7Hx岝IIY8kvfx?:!NGӏɩXEٗat(ը 2abISE;Crl zHʐZ?D]Żlw;VRd%7>ݏ=@wlAN= ]!N<E[ܠnG69x(&K@d"AA OuVTVKԙ"lx"}ZgVա=A؈N`̈́\Zl9*Cc!KY!Cֽ3Tc_t)dt9lxN9_;%O`ph|^ Q;caK$轞.z"y(Z{4`(#{V':8~\!9#NjfOЬN.3. ?ڑ6ڦQ6?ٿ@QJP F4Nd&?8WEҲq F|6}PNbW.j&i.5@<crHWFAb:ƴ5lЪjsVmǗz٤1b=LnDIRE:K_OӺ` M) R蹴@Ye5H7G, 6i@wD1Rv]'H<"M hP$@p9ԘpwN9n\9=Be?X2/QQJi׬ :stpVPSOqHŦ\tgndy0S|u`:uTL&Cגɉ҈,.P u$f$eҲT/xЯI N쁇qf]қiSU4ZwE0)UPGGA[7:K<i^&@ϿV?]ƨzYE*Ih&i4if&qĝfU¹͹ǵr ELRA `"ɧ2}hwUw,פu۠' Ibh;Ir0kzMR\?ss=~9eUq#Д{`bDt.t*3\5CV%^$jl{ 2@` lA;e[dEeosopX}by͖z#hN+#)|&#NpgM6O4'0i/G*hTŝilt|>g,Wfn6 dgC\}:c @A9X~K ڎ Zdnm !Tv_&\ZF~3̺_TФtzNjyj_Fo-HlD_蟗S Z:l?D|_ #%ar477w̮s\U*duj_ξMT1t@zY9V`De*+.[f"ݯu9raŘ!8ɀ2*%=֫´,:q"&HPղl=cQ^O<5r 5 ( Dm&@s#V'MPUFL4S6U;QVBS(^ o-V;90+i?tXKٮZOPqxr,:m BFl/g4w1.7oϏoM;sC5ͽ=R62b6r5jyvX `:+ 4~D;LYqr=e`Qw#Av_ނ?:_IwM 5Tn* muџo*)m||N;nu7 #<\k|G8k?Gc~q+|?ҳz} {ۏ{ۏ?ȃW&}|Ustf,2J,$Wc13Wy>(a|u+f:<7sfeM0%\i7}eVår uQւ[PんOn|is HJe| ϮnMrN R%'b5S^"j`7552NQN]d1RWlnmKɍwzP+7U?ۉN;" A/KJ:"׃:)8~Pű-^ ?;U|H[ACԝ!whaF&a(h1LRO)G,Ddqt2$CgNgö"mAiX-߭-[-ec<^L&~Y22=sq,@X˯?>71|74RoqCx1 }SѶէ6l1쬷XX ,~MKmW9oI@A@2 ^aO`I.i~dooqǎwӊēBivv{I/8; {Kj/>:pU\+AY"ߩ?|_W`(iHi:*Xc:%{9Bh=^gyI;HNQKT+lGgL|MOmivDT-B}fgd&ZmDB T}:ϝzh_no>?`6 g=z)eBK{vQyA}$չ%ƼZ==w?(VZT<6Si4qLM?$$ۑrcfS=y6v*a'-L[ڊlou{꫍7vb}aQOCSoi[ux]DaZD%7cΛU7 *fF0%.7˸r5bV0@/'!ņN#KhNo{6Z.jOݑLR{ 6O:|^/u쳔t9~9ǭr&!Y3GG6^QwuRpzU,ˁ}:ٟΈsQQ 쥏}Цx4 zS(\@xBNC_{8n%֕OJ. H,ps6N㒭D? ^]t䃧!).GJt{T 3뤆,nDۗIAY8V\QL @Q" 9}1X/FkvmХ$`ƭASrK`03K91l`hK{6_֌["qgRj!;WؐM¼9Ru ~ Uȭ/T\|h%1ZM®1U\ԴLc5f*%r&(1%ՊE]o?Pq\{3?Waau~!TOa$}ESe HӰ6"ʈ};6Z1nU>ZvO)(PVX, p!Qn7}W  ?#g%ie $*ֵL?3,"ƍ?聨7,ɀǃq%XPN-Ļo8 -7,ᛰy0gz'~#ˣZ=ꆪsE&c@ ѳ(_b(~NO+ź⼖/i s̒ lVY~eD\ЂlZ}C~d`:D H-Yzn3`NԸC}~ЮY)p@B wvD{<#+̗@a!#[1(蟘UN8v`9Ҏ< T5D mZZg@-89ԇ*87!weۡٮaiԠ>a%"<ց=\8:ӳDEzjlO/3ش'藗@NɎXܧz5)4?<)9ˀ~֠Hmq ({.& 0ڳHĺR(TK`g'Ws;;L8x6nQQފ-ʟF( C_3;x0ч"' dL;g>K['BxOmF^0鉅v">#G;TGlm)J;NjTCx|.;>)=qa0\]Wa\YʍVR%Ÿ'0CǚiaF9wL+ɹ7tL3QZ z8Gԯh,~ޟJ!B _%= pU/h D>pymdH]vd ʹ¤@JDVN E{FMW[Ayv2$ۻHsstTTӖRi3"%Q4SFIJ0?{xz> dqOVk'(Ն%  *L||]|pl]wqq. p8_'8|p8|< 3 ػ׊ ܒ'0ZZ~O L`G\q6, !Π%,XD߿Z/pa݆b֪6rś*9\Yho'Pt䟣5{MǽM!q̥o@CM8r|(9q׫8h8p8}ÁrFnI'r9;Hv&8I2yȲwm@9 />ɖ(Aۼ|$(k7cjf, DL502bCXsϠg>!f;V{R7 {a^X}^Uko:gB)ݥTSChvM_f+皱Wpj =. }O?l9B9տ),I$79I]4$ٙkk9VwX syn:ݦ&qtyg8_*%A>zXRcaS>r=H9L8w1g V>,) W|qHgR  Ir 0hoaXyK}@=KҕNhYhH+p4 = QEt3Xŭ(2+VD'^(Tٚ0JG!L6^$ -[}U^{TBe1w-SW8 S`ES 8?7O*=ϴ%ŃDw {@;{ ƕ?jB^Z^g=j\9^֜Xp [ы7v+r}3]q1m("Us8AS7aVVL51)w+;wjlxbR)]?xz:PS[ _MA[2Le<#i;kBX6 b3h_B:-`-(•j ~-M 6 4*UZiNn&qg93L9K>^E_f-cnTb# cr3nVM0،Os9HBUY1\6;,&EknU%Ru-<}{EtV qP73 xy&+*KvP^7^|cŔr]4hw)Q/4Oб&m`2!H膤$)!mK' +㓰ӀMVG׾G"V£J$DV_lkQ(p EXrkJ( K'I׿K8&Vh&ɯSc\JQ 4ȴA*YA0-,8؍0F-LX= ?F2@3\HSxG@lT5f>D8Z:UފpCP bQĊ{Wi^^QJϑ]] oC.%1E͈Xe!))c::qd9̊yk)j@W달Hb T+_ֺ 섴eKC@$3s0"BI=45I1&~ RtѶ'2ڇLְsk#x7Q k@:d#%#_c'l*.yx࣪?T$ZѤ#^@v3*Xf7&6\x,O\ͬ¤3I&N]3gғn~kboƝ?z*8 3@NVR"gILɓO!#wq|lc C }\#0rF+%{jA-5qDTEN^K@[bOUKWL_Sȏ3 ֝} c Lg@údo`UvT2EC5`gmH6(<7ݝI6 {$.F?Ȥ}Ú:*+4q>L_|xϯ,FvU򖥞5C0nWHYD'(QU[=L /Nlyx xna\p݃-q!QQ{Υti_8f.?>¥j& IԚȯy[nZvdս?6£/agBձ Jȇlߠr|ʩQM[9$Wl+ 3/aawKG6zAJ r/Qga~>i2eiR]٧/-v䠫B윷Fƾ8wx0o<۔DmEؗl#.q K4l]Q3/w6fF4"; ?a)svFStdrE>>K5~nQۀ'cߨ'l gʼt0 q@{xދ&ǁJⅈ@p=+O~ ~ ʁ89R6*jwEu$n~|2w~䠂s% ;Ͱ\^h<3PMKRv+Itk !N:R`Nlր`FhliE2z/u˝?^aom}soFZ$3 -x& uJN 7Gc?wj X`fPLhŒou /Ťӣs>Mq97BYIלv^o N P_ +da-6>:%Kf7}@'r_V5Y`$}Pa<۔tI94ZTgHGuS3LQOUc Ϳ`(9G rZ=:*Sњ]{mDIȪP/ AG*EC9gZc xf0_q75Zcu"Oy; {}cM?ˠ5~}JqV#n}r!P=MVR5`I׷ _C+&dl=T2M0-4zEmFV/׋Pu)n0_ R`\a)x3lRX@S2 kk}yY?zha?z8GȏIBHnםniT+>[TҞ1pD:*fr9pnT5@ػ&L֋~>&2PoyIE~A;E3HP08tFFos/F$u `3DzhWu'$]5_m_x w0Tl7:Llky )|?I0'`|IjWo2n[oi |S >oXx|IbjЭ뙋5p"/c=D^Ԓ<@(%Kw;ͷn;;߷voY: J9^Цcr!=yKB5,rDckUܒ}{/PCzԨ S6͸QwPr *Z 4@dh E]H%q-:H0/vjrM&2S重䜪u:##^E` lm>+0/FH-TK)E}Mϧ_ъI2mCh0tR ,9j1ǬE"4‘2Wۯ6o_lx[3|{AR7pX`WLSLۀ\bPVMIq(KQ2?I CX-YZEm"4l*Jʤ,[~7dYX#覺:jfC$=8 [UDa1A?6i7pJ + s̕h8e9=ٍEIE]Q}NNv{L ZEdb lBtAɲ̾Y!q!N(%|+}6RboC%mM7nI@VM$ "bw-72Ol7uf`>dvtI=&uz~&@?t hLTz"Ҝ7[ڲ z97X|w*Kت*{Qe?W+yfzD_rk*9xK27Lke6u3AQSSth\nMU=ӣv1)R每Je} 2G3o]zJShtY#[ZDO$1K(-Jё:z424u}SVj Cэ΁p oeAbʭj*u'_]kozY˦!WZSj#.%G@#GL]'@_'9PGDn`ۧjF <֝l_ѠbmnR;)*5RKW}A<`Z_-qg;sWzr}ʞ~ia9RK͊[ {1oU.KeȪý Hq4SVO50Kř}=/QЂBma1R!~$6[ (bsPɗP.-8c>&kbh)cQ{Wh,Ugtg>)+ $,P%x8ԓ'; rOo7Kd_@7'=H1=k0La]0E2ǃjYjŦk$-7T O+c(\U-()̉,8NW1y')csL!vqC .g97AOBsY{`buBf329r-)l+~p ,S@,-B90ǠȤC@?mݰcjT[cҗ1|x| `K "}ةU\o $M&b "n%6[3jq}ӘPbBEȾzKے}3?ztXdMr%3ŘF#04lG|-Mbn閛IcV>j PCWĄXn} 7xJB]tr/'0q{UXA(y{ Hqm]heean.fYTI&I*&SbMq]d%T%)^ V#͙X1ǁ;̥Ql.컜:tgC #OF,)_D憎=sGTy}m0BS {.|x1i;1Uw?\r b}A v'1b'V~tusPA@U-\dшhQ/j0SZ娪5:; 2 z `v ?@tYoYn;&jЯ5j'm"g9r+6` Y>R[v=C7S2;4 +)= e`B]$ e3*MD+"a*FPEqoirWLa$̆UNzCy*ٔp"s~eDs'&gGq;jۂ[y{i5/c>8o>`AFQyjj'`|?kOsY0nN9~Ws[9eax8pnln A^ xϜbؖEO2vv%y綸{>f Jk+ QXB/7vaÚtp2z'/USWԈg{?-=_w3#=T_bms,z5Fu:NȺ䡽´>guJOkd'@hSTLĢ;)e1WcURS~eJ,fe}'qw'GoZ3 Wc=g$oֆ':<Tg`YItڲܫ!fP H>] Ĕq1Nm MҐiؒ O3[kU&#}51mN?EI)K: ~"g9lF-9㠍! %ZXffE<!$fY`ٱ+ lhi(cwI|hPy&c' IS%:lȔ@"T_\\|d4臺4ikdpB]IJI/Tw9/CNjd*bt=5 Ѫ0Jtab:L[.EGZ S)G$d;עힵ4DQgf߀QtxMUmf zHu!g~u?P6At̓3SgvѪ #ƸI*Mjm,|4\vSSn~iP ;P>@&Fj=؎n栘QڤO9Нb1 YK[ε>)1*F&&=g  Z;#3ij.l+TU0njd-МBAu8&@t"w ?$+F QBɁJpGLRT VPQH9qZ%EiQq@SJᵕs !F :h?6"yw{`KXG>Fg8RR%AduW'ygOAʶ5;6*6j|'鰋418"'gڲ'?0J1aC1ƃs:\f# vh=%3 $rͼ&V(ݞ?w\B4fW:mKwxvR3ӧF⦫sg󻲡ol;o\^+ͨg4S#_ (s5$,"Sђ8)/S\aFQLbԳAKhAj{\uFSUUF;ׄã@h>}}M?ctr,줅@Ł_O!t1Ae}&!m*ؐȮS Yk^r)IN#r;H#q̲v؞aŸjhqio2KrfgҝŧOj{ V"K&^"$O;,8D}nӚ6=ɚfB-Ou(jpa#J*ġ<^ &FX=RןDZpޥXud7Im e.:|w# 5X6 g2Lf3@bP"W0=>]0)>-:f32a0gFi203-45 4]:2]ӧk2E8mafC>4{ 7yB(8`dG fA wAVV7S(ƜZUԙ y^+,jH^<8AڢȨzAn4 «CQX֊jOw2g$L!ڗ}LΠxg.(Q%etz!-В('+A^Bz{o=chDT1̽N%n) Dˎأ%-N4 m*u:tyt`k ](鐳NBL?{!GJ#ru" >9DfShapàGt 7WHm;?mha}(ӣsgO韌9oJC/) op L !yJz =0nNu5Ҥ*0,)oVw$KQ6ةjɖ)1{d # Z Q=3q(=/q*OV_fRz 'ct8uDm'6 ̹-6Z)f 1_ ? U}8z.)X\Vy1Z:녣Sa-χ]fmkOi|[g0IoôsN`0Osȵ{"za;rJ\ 30 BtEX/xu[\>87kpcA?kO>?m$.3o"aPr1n3( qr&c1]a 6~՟plș|A% lT65. []_Can \#їb0o/G ŝ- ^ RhD2F,3qQs>a2#%i7 9 Cqu_Mn47ؗ1̬ib.ՒŇ4x[R=g :a疏 rޙy]y|N*G;2HZyw``_ܳ.G`tdO](/6hbR-TCm\q%%KZZbE܊B#DCjӪO4*egiا*+e8nTapCb@n4gF}N2 gMHCO2y![07[~Hw%lbzIE(R7w\R}*Im%4P,3?&oli΅4W Ʌ߆zda/lBx~ɮq"'c¯*G[ 뎭BޡO#-A*5?8-,PK6h^whYk2-A~ E7Ʀzk=+!OCeD%mXЂw5ϝA|8^;p|a8"DipJ:0< P̻w燃 #kQ4fc)FUHi0I/#r-͋Po<96r6[U4[e5KJDi>ܪ[ T Ts+%;WrmTmZx#jȑ+u_.\v}iy*anCH֝+oߔ{sg~ T8_+`a'0P83RaŰByxQaU/(1+=S؇H0D77(&iLDb|_%{#o\}3ai՟FD8YiQ vq;r}e}VB5 c}>NLK*j$M~h[Vlv i】I3pPv -m8М i_T/ܐpDA2, 0IX .@5f1od x?.ZFm91W5 %0dXOȝ`ym`4ᄍ0E# UO ,9w63p|Q0!E_<.'zq@$LT??VZ}bnmqZ6*Lw$DqM0ج<0/uL7`ddjYYֶ`΃jhbRhPKʄ'cBriہ+xi3ㄫ[ ][gt}ч]jb|Vd߃yИe8 ~EN.apPBeqQ,׹W1|W!_&]Oa-*ٴlYm8Ə[-.q\b&aW@:p0q00EsБ _~_P|Ѣz?fQ6 jƪՌAZ%BZ@QA#',!U۟ QpLa^E+4 y`NxCӦy{هz-~llAxU|,@mrIaBK@r7{AߵD~WʏF!-NF-QDoGh3?$PlU*^EJ)ڂ$oh>7XqQ=f]B"TO& dJwǏ,`$'H֕65FtN]~z=DZYd-<}"< Tt~ą8/"EZ5+ky<2 Th(\‚f ײj=bhTVJO] N-oK{ b.E <]UoY[XKH6WMZ 1H-Xn|D).9C%%zbԄyqL$cd:ȁ,N r11l Vb)dH99OLJ$/o"G9@q[cVx9hY|<^4rs4Boφrh֘]6pJTpx.'{ʂn8!h7cǰ%[:NaJ =Rm2%X4 [dH= Eq ĻpbM[/R\6pAPr\bxOA""ǥVSxx3܂zEnW. ԓ h%Ҙ.#@jZ#NmĈ?OX 36Ԥ:RbT#3g;9/k 5CDVgZA<[Ncx$Sb1[{}9(`,,Es:nf |w(qnFqK4 QHp,LxqBťC6w+ia!6B \1Co4B/zqe.F땕ҋ`m'z[e c@c{6?bdfX cڳysʽP2#^:Aӵ-qwjFM )V᳗i 'cp>(#)I%Ii1w-JK#QlUXÌ 2Vi{^P 6y[mʋ1䊔\ɒʉY;H. _*j G™# ࠛ#aE]OXҸ֯ں-'V<6!%Έ&2POd & +M]s강!>pMSJMc#0:`f#D.defeFlBIgPd܋kۺƨpT._v8~ +({|h" &o_`(`]QyAv?٫A X-\q ?3.WNֶ~ZkoiٲiΪgչc0;W][Oo='=]IB NNy?ʪ*8k;OCR,T*)XAn-QIĢ*T)H@&H"xs)Fa$ oyݗ:2fdfNĉ=Cg,fTC#rA^!lc 2H#\;0[9l>EgX3$X _QF/ޞ7m?pμ$<J| ;@Q\\膘n\dIk0AjԪ9+\Zb,~w0Xʣo9+te*S!q„ƌ8B&N̎uVJl;> j:҂Fը\Sv(cG :Ot%}@Ncyl&jtFBz?Wf b+;aI~\v ;z2w=w:v ]#Z,/Ty>{aO֡<5 !D-C ƣX |%*Jt~j3[hq3;U7!2YY}k$ɇ!Mp~ׄ`N&|#qbl- ".>6޻Υkvi=8vR~-\”¯6VJ38\IPld m;TqQ0xW~ahȲp O^`Ԭ"~K #88 FazN`k/hMnl6+s"QnбfA&30p ]p *b><B댃7 4qû=["V۝s H{hceemXn* "8l*p7+sZn5aݸb-5DJ:\B;voH{Bpe7$1 +OdTUc]1 0^0͘d{=.Y_`0P &#S֢6gk=NolU|T]N*O˷ꋬDcm̞*;FD " Dع "'DÛ J˅pUKq)_%ݯvQ|M[]d  x;Ƴǀ}DZlT׳Tޛlth6zRa: Vf%t)%dC4xtIfԙUiQޮVb"(e0}~ 6E;^$C`;ĉab"+LH7=L-#}_,o4~=ĖȠ،fP2z(5ו_G dQPŃd&(X ?挳yccQ⹕tA$P)Xdx%Xԛ 1`Fi+R ?zwc-h8%5RJC֪}WB0i߈ڧ1bq:?yT9,e|ESCwDemGf[;ߏ|Kpa</ 2q"[Aq>sxл 5~D+rM)S3Y#$ 8@k/wyG먏Mn /0!1td6hg2~qYg3{,4HjVls3{DDƝ:)Wpg*M мV5#O%ПaK_*}q#}`V.Z28Wp+Y5\LŏqE G`'#ߕ ~#7kIEP@GqYTF Cp\?p;M )SVLE{83a9rDpQR?gFqޑUo_=Պ(  qSD^QO~?kBx0,ASn {52@RByeF4 gڞ3{}/.0(?:xO]J`f`u֗0C^/?s/#"O>[M_kziTBzu8<[qq,36iK)]voDtAftJ&;nL '5~€B P̤T"s'Qf?)K+ :]K[CsU۳|R3qZA<W@~8F$d&sZ'} UקFaX& 9u[*ea2:m.ϩI ϤMؘJujUI?N}S0n^`&jNg6af4sa65wF3>k$S,g uLedc.[ovt̔Z鳮h3250O2׷Z`\e0y9O*-3ej 7I?sLb&U2:3Ȅr{vվ Yg|6&xAypBiyc`͹h<˪;=g`1yVn]&<=|yN-X5N~O/<<^ucA/!3,&7 ?gT"Q7HPgx/tmrmi\ [G~QL囩` {g=^܂W{Q,KEb6OgcggxRQҦ7 Dbr/Bq( '##pf" Lfc\wL5o AmeZ ^)`>6@3=O%f,ЯW6_S<0J6C ⮜`+ X.Η껳'{2=㽝ӽw^}x3*eH~﷏F#NMMc_ώջd#fS/v6\#h$q? / Vb?s~4.g_ϖZe.׆ѳ}y%l_7cȜSD|*ޏs?'nً$}ix2 C57띿^3'{'ef:lDR$D O`Dr/pdTGLjۃ& \ٯUc.,+sޢ:fUN=O]Gnq0{zTؒ*$NN\ി"jb^2ު;EãҒ] o:~4gAQDf%hzQ=7ouƩdvP8nә*xM)y$zZe}U8V.5_ +j1y BuJ'XA[Ɠ'*cJ0ߋvwSJՀV|WIjȨ$$;*(俘 zM(B<~YRàxGTqhL>8l)d( L:^MYU7'jt0.ϱou[\]DqFp Q\'YNst& O?BbmM i*#PMb#2',u%J~Xb\aX,N "S9(*^ê+X $o/FPr[xmI'JD3 y2Nprkτч㝽AV`G04!'@C \VE"rqK.ܒCP a fjFpl10sn1=1 ΄~jo!VT3<=sgcoôVоwy.Jdd_Vbb{ ͬ-{T(gj;,p[=} , 1d {$22aϒ U 06>] ?ۑOr`)P~e=S"- P4⍸.0#3k=óGo|&[ Ų)\}&{,"ك퓠3??o>#=LU Nml9h< Q1 {Ͻ}ƏK+a_4/[|t_2pOdvRw|A떮ng^{a_^(1RA7Umߦ g v(ʂӳ[N =ޞÚ(!>S;iw|V&ˑ4XNV28 D;Crs\ G@v'0> w;0 0tno=ۇ;" k0H/adVv {hCH_j;y]XO MLW# 1@j5*9!6=f6a}|Z@o\T77O6"7B=̺>txCeq ypikHS>xϔ4ȉ I< _8Axq0%DW4"1&HM[M5?