From 5cedce81d3ffdc7413339ab0e96fc79b3a67b8c0 Mon Sep 17 00:00:00 2001 From: sebres Date: Tue, 23 May 2017 21:57:13 +0000 Subject: [win32] optimized calibration cycle (makes Tcl for windows "RTS" resp. NRT-capable): - the clock ticks never backwards (avoid it by negative drifts using comparison of times before and after calibration); - more precise, smooth/soft drifting (avoids too large drifts, already after 10 iterations the drift gets fewer as 0.1 microseconds); - because of more accurate drifting (aspire to the smallest difference), we can prolong calibration interval (up to 10 seconds by small tdiff-value); Closes ticket [b7b707a310ea42e9f1b29954ee8ca13ae91ccabe] "[win32] NRT-only - NativeGetTime backwards time-drifts bug" --- win/tclWinTime.c | 175 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 122 insertions(+), 53 deletions(-) diff --git a/win/tclWinTime.c b/win/tclWinTime.c index 374c41c..93f62b8 100644 --- a/win/tclWinTime.c +++ b/win/tclWinTime.c @@ -51,6 +51,7 @@ typedef struct TimeInfo { * initialized. */ int perfCounterAvailable; /* Flag == 1 if the hardware has a performance * counter. */ + DWORD calibrationInterv; /* Calibration interval in seconds (start 1 sec) */ HANDLE calibrationThread; /* Handle to the thread that keeps the virtual * clock calibrated. */ HANDLE readyEvent; /* System event used to trigger the requesting @@ -61,7 +62,6 @@ typedef struct TimeInfo { LARGE_INTEGER nominalFreq; /* Nominal frequency of the system performance * counter, that is, the value returned from * QueryPerformanceFrequency. */ - /* * The following values are used for calculating virtual time. Virtual * time is always equal to: @@ -74,6 +74,8 @@ typedef struct TimeInfo { ULARGE_INTEGER fileTimeLastCall; LARGE_INTEGER perfCounterLastCall; LARGE_INTEGER curCounterFreq; + LARGE_INTEGER posixEpoch; /* Posix epoch expressed as 100-ns ticks since + * the windows epoch. */ /* * Data used in developing the estimate of performance counter frequency @@ -87,9 +89,10 @@ typedef struct TimeInfo { } TimeInfo; static TimeInfo timeInfo = { - { NULL }, + { NULL, 0, 0, NULL, NULL, 0 }, 0, 0, + 1, (HANDLE) NULL, (HANDLE) NULL, (HANDLE) NULL, @@ -98,11 +101,13 @@ static TimeInfo timeInfo = { (ULARGE_INTEGER) (DWORDLONG) 0, (LARGE_INTEGER) (Tcl_WideInt) 0, (LARGE_INTEGER) (Tcl_WideInt) 0, + (LARGE_INTEGER) (Tcl_WideInt) 0, #else - 0, - 0, - 0, - 0, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, #endif { 0 }, { 0 }, @@ -464,12 +469,20 @@ NativeScaleTime( *---------------------------------------------------------------------- */ +static inline Tcl_WideInt +NativeCalc100NsTicks( + ULONGLONG fileTimeLastCall, + LONGLONG perfCounterLastCall, + LONGLONG curCounterFreq, + LONGLONG curCounter +) { + return fileTimeLastCall + + ((curCounter - perfCounterLastCall) * 10000000 / curCounterFreq); +} + static Tcl_WideInt NativeGetMicroseconds(void) { - static LARGE_INTEGER posixEpoch; - /* Posix epoch expressed as 100-ns ticks since - * the windows epoch. */ /* * Initialize static storage on the first trip through. * @@ -481,8 +494,8 @@ NativeGetMicroseconds(void) TclpInitLock(); if (!timeInfo.initialized) { - posixEpoch.LowPart = 0xD53E8000; - posixEpoch.HighPart = 0x019DB1DE; + timeInfo.posixEpoch.LowPart = 0xD53E8000; + timeInfo.posixEpoch.HighPart = 0x019DB1DE; timeInfo.perfCounterAvailable = QueryPerformanceFrequency(&timeInfo.nominalFreq); @@ -588,16 +601,12 @@ NativeGetMicroseconds(void) * time. */ - ULARGE_INTEGER fileTimeLastCall; - LARGE_INTEGER perfCounterLastCall, curCounterFreq; + ULONGLONG fileTimeLastCall; + LONGLONG perfCounterLastCall, curCounterFreq; /* Copy with current data of calibration cycle */ LARGE_INTEGER curCounter; /* Current performance counter. */ - Tcl_WideInt curFileTime;/* Current estimated time, expressed as 100-ns - * ticks since the Windows epoch. */ - Tcl_WideInt usecSincePosixEpoch; - /* Current microseconds since Posix epoch. */ QueryPerformanceCounter(&curCounter); @@ -606,19 +615,18 @@ NativeGetMicroseconds(void) */ EnterCriticalSection(&timeInfo.cs); - fileTimeLastCall.QuadPart = timeInfo.fileTimeLastCall.QuadPart; - perfCounterLastCall.QuadPart = timeInfo.perfCounterLastCall.QuadPart; - curCounterFreq.QuadPart = timeInfo.curCounterFreq.QuadPart; + fileTimeLastCall = timeInfo.fileTimeLastCall.QuadPart; + perfCounterLastCall = timeInfo.perfCounterLastCall.QuadPart; + curCounterFreq = timeInfo.curCounterFreq.QuadPart; LeaveCriticalSection(&timeInfo.cs); /* * If calibration cycle occurred after we get curCounter */ - if (curCounter.QuadPart <= perfCounterLastCall.QuadPart) { - usecSincePosixEpoch = - (fileTimeLastCall.QuadPart - posixEpoch.QuadPart) / 10; - return usecSincePosixEpoch; + if (curCounter.QuadPart <= perfCounterLastCall) { + /* Calibrated file-time is saved from posix in 100-ns ticks */ + return fileTimeLastCall / 10; } /* @@ -631,15 +639,12 @@ NativeGetMicroseconds(void) * loop should recover. */ - if (curCounter.QuadPart - perfCounterLastCall.QuadPart < - 11 * curCounterFreq.QuadPart / 10 + if (curCounter.QuadPart - perfCounterLastCall < + 11 * curCounterFreq * timeInfo.calibrationInterv / 10 ) { - curFileTime = fileTimeLastCall.QuadPart + - ((curCounter.QuadPart - perfCounterLastCall.QuadPart) - * 10000000 / curCounterFreq.QuadPart); - - usecSincePosixEpoch = (curFileTime - posixEpoch.QuadPart) / 10; - return usecSincePosixEpoch; + /* Calibrated file-time is saved from posix in 100-ns ticks */ + return NativeCalc100NsTicks(fileTimeLastCall, + perfCounterLastCall, curCounterFreq, curCounter.QuadPart) / 10; } } @@ -710,6 +715,8 @@ NativeGetTime( *---------------------------------------------------------------------- */ +void TclWinResetTimerResolution(void); + static void StopCalibration( ClientData unused) /* Client data is unused */ @@ -1076,6 +1083,8 @@ CalibrationThread( QueryPerformanceFrequency(&timeInfo.curCounterFreq); timeInfo.fileTimeLastCall.LowPart = curFileTime.dwLowDateTime; timeInfo.fileTimeLastCall.HighPart = curFileTime.dwHighDateTime; + /* Calibrated file-time will be saved from posix in 100-ns ticks */ + timeInfo.fileTimeLastCall.QuadPart -= timeInfo.posixEpoch.QuadPart; ResetCounterSamples(timeInfo.fileTimeLastCall.QuadPart, timeInfo.perfCounterLastCall.QuadPart, @@ -1135,6 +1144,7 @@ UpdateTimeEachSecond(void) /* Current value returned from * QueryPerformanceCounter. */ FILETIME curSysTime; /* Current system time. */ + static LARGE_INTEGER lastFileTime; /* File time of the previous calibration */ LARGE_INTEGER curFileTime; /* File time at the time this callback was * scheduled. */ Tcl_WideInt estFreq; /* Estimated perf counter frequency. */ @@ -1146,15 +1156,24 @@ UpdateTimeEachSecond(void) * step over 1 second. */ /* - * Sample performance counter and system time. + * Sample performance counter and system time (from posix epoch). */ - QueryPerformanceCounter(&curPerfCounter); GetSystemTimeAsFileTime(&curSysTime); curFileTime.LowPart = curSysTime.dwLowDateTime; curFileTime.HighPart = curSysTime.dwHighDateTime; - - EnterCriticalSection(&timeInfo.cs); + curFileTime.QuadPart -= timeInfo.posixEpoch.QuadPart; + /* If calibration still not needed (check for possible time switch) */ + if ( curFileTime.QuadPart > lastFileTime.QuadPart + && curFileTime.QuadPart < lastFileTime.QuadPart + + (timeInfo.calibrationInterv * 10000000) + ) { + /* again in next one second */ + return; + } + QueryPerformanceCounter(&curPerfCounter); + + lastFileTime.QuadPart = curFileTime.QuadPart; /* * We devide by timeInfo.curCounterFreq.QuadPart in several places. That @@ -1166,7 +1185,6 @@ UpdateTimeEachSecond(void) */ if (timeInfo.curCounterFreq.QuadPart == 0){ - LeaveCriticalSection(&timeInfo.cs); timeInfo.perfCounterAvailable = 0; return; } @@ -1185,7 +1203,7 @@ UpdateTimeEachSecond(void) * estimate the performance counter frequency. */ - estFreq = AccumulateSample(curPerfCounter.QuadPart, + estFreq = AccumulateSample(curPerfCounter.QuadPart, (Tcl_WideUInt) curFileTime.QuadPart); /* @@ -1205,12 +1223,9 @@ UpdateTimeEachSecond(void) * is estFreq * 20000000 / (vt1 - vt0) */ - vt0 = 10000000 * (curPerfCounter.QuadPart - - timeInfo.perfCounterLastCall.QuadPart) - / timeInfo.curCounterFreq.QuadPart - + timeInfo.fileTimeLastCall.QuadPart; - vt1 = 20000000 + curFileTime.QuadPart; - + vt0 = NativeCalc100NsTicks(timeInfo.fileTimeLastCall.QuadPart, + timeInfo.perfCounterLastCall.QuadPart, timeInfo.curCounterFreq.QuadPart, + curPerfCounter.QuadPart); /* * If we've gotten more than a second away from system time, then drifting * the clock is going to be pretty hopeless. Just let it jump. Otherwise, @@ -1219,21 +1234,75 @@ UpdateTimeEachSecond(void) tdiff = vt0 - curFileTime.QuadPart; if (tdiff > 10000000 || tdiff < -10000000) { - timeInfo.fileTimeLastCall.QuadPart = curFileTime.QuadPart; - timeInfo.curCounterFreq.QuadPart = estFreq; + /* jump to current system time, use curent estimated frequency */ + vt0 = curFileTime.QuadPart; } else { - driftFreq = estFreq * 20000000 / (vt1 - vt0); + /* calculate new frequency and estimate drift to the next second */ + vt1 = 20000000 + curFileTime.QuadPart; + driftFreq = (estFreq * 20000000 / (vt1 - vt0)); + /* + * Avoid too large drifts (only half of the current difference), + * that allows also be more accurate (aspire to the smallest tdiff), + * so then we can prolong calibration interval by tdiff < 100000 + */ + driftFreq = timeInfo.curCounterFreq.QuadPart + + (driftFreq - timeInfo.curCounterFreq.QuadPart) / 2; - if (driftFreq > 1003*estFreq/1000) { - driftFreq = 1003*estFreq/1000; - } else if (driftFreq < 997*estFreq/1000) { - driftFreq = 997*estFreq/1000; + /* + * Average between estimated, 2 current and 5 drifted frequencies, + * (do the soft drifting as possible) + */ + estFreq = (estFreq + 2 * timeInfo.curCounterFreq.QuadPart + 5 * driftFreq) / 8; + } + + /* Avoid too large discrepancy from nominal frequency */ + if (estFreq > 1003*timeInfo.nominalFreq.QuadPart/1000) { + estFreq = 1003*timeInfo.nominalFreq.QuadPart/1000; + vt0 = curFileTime.QuadPart; + } else if (estFreq < 997*timeInfo.nominalFreq.QuadPart/1000) { + estFreq = 997*timeInfo.nominalFreq.QuadPart/1000; + vt0 = curFileTime.QuadPart; + } else if (vt0 != curFileTime.QuadPart) { + /* + * Be sure the clock ticks never backwards (avoid it by negative drifting) + * just compare native time (in 100-ns) before and hereafter using + * new calibrated values) and do a small adjustment (short time freeze) + */ + LARGE_INTEGER newPerfCounter; + Tcl_WideInt nt0, nt1; + + QueryPerformanceCounter(&newPerfCounter); + nt0 = NativeCalc100NsTicks(timeInfo.fileTimeLastCall.QuadPart, + timeInfo.perfCounterLastCall.QuadPart, timeInfo.curCounterFreq.QuadPart, + newPerfCounter.QuadPart); + nt1 = NativeCalc100NsTicks(vt0, + curPerfCounter.QuadPart, estFreq, + newPerfCounter.QuadPart); + if (nt0 > nt1) { /* drifted backwards, try to compensate with new base */ + /* first adjust with a micro jump (short frozen time is acceptable) */ + vt0 += nt0 - nt1; + /* if drift unavoidable (e. g. we had a time switch), then reset it */ + vt1 = vt0 - curFileTime.QuadPart; + if (vt1 > 10000000 || vt1 < -10000000) { + /* larger jump resp. shift relative new file-time */ + vt0 = curFileTime.QuadPart; + } } + } + + /* In lock commit new values to timeInfo (hold lock as short as possible) */ + EnterCriticalSection(&timeInfo.cs); - timeInfo.fileTimeLastCall.QuadPart = vt0; - timeInfo.curCounterFreq.QuadPart = driftFreq; + /* grow calibration interval up to 10 seconds (if still precise enough) */ + if (tdiff < -100000 || tdiff > 100000) { + /* too long drift - reset calibration interval to 1000 second */ + timeInfo.calibrationInterv = 1; + } else if (timeInfo.calibrationInterv < 10) { + timeInfo.calibrationInterv++; } + timeInfo.fileTimeLastCall.QuadPart = vt0; + timeInfo.curCounterFreq.QuadPart = estFreq; timeInfo.perfCounterLastCall.QuadPart = curPerfCounter.QuadPart; LeaveCriticalSection(&timeInfo.cs); -- cgit v0.12 From 5c5c34d0f377a01a93633a879290b3ecda260b9a Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:21:24 +0000 Subject: [performance] after-event list optimized (interp-assoc switched to doubly linked list, because requires handling from both ends of the list) --- generic/tclInt.h | 30 ++++++++++++++++++++++++--- generic/tclTimer.c | 59 ++++++++++++++++++++++++------------------------------ 2 files changed, 53 insertions(+), 36 deletions(-) diff --git a/generic/tclInt.h b/generic/tclInt.h index a184950..7e70627 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -1946,18 +1946,37 @@ typedef struct Interp { * existence of struct items 'prevPtr' and 'nextPtr'. * * a = element to add or remove. - * b = list head. + * b = list head (points to the first element). + * e = list tail (points to the last element). * * TclSpliceIn adds to the head of the list. + * TclSpliceTail adds to the tail of the list. */ #define TclSpliceIn(a,b) \ - (a)->nextPtr = (b); \ - if ((b) != NULL) { \ + if (((a)->nextPtr = (b)) != NULL) { \ (b)->prevPtr = (a); \ } \ (a)->prevPtr = NULL, (b) = (a); +#define TclSpliceInEx(a,b,e) \ + TclSpliceIn(a,b); \ + if ((e) == NULL) { \ + (e) = (a); \ + } + +#define TclSpliceTail(a,e) \ + if (((a)->prevPtr = (e)) != NULL) { \ + (e)->nextPtr = (a); \ + } \ + (a)->nextPtr = NULL, (e) = (a); + +#define TclSpliceTailEx(a,b,e) \ + TclSpliceTail(a,e); \ + if ((b) == NULL) { \ + (b) = (a); \ + } + #define TclSpliceOut(a,b) \ if ((a)->prevPtr != NULL) { \ (a)->prevPtr->nextPtr = (a)->nextPtr; \ @@ -1968,6 +1987,11 @@ typedef struct Interp { (a)->nextPtr->prevPtr = (a)->prevPtr; \ } +#define TclSpliceOutEx(a,b,e) \ + TclSpliceOut(a,b) else { \ + (e) = (e)->prevPtr; \ + } + /* * EvalFlag bits for Interp structures: * diff --git a/generic/tclTimer.c b/generic/tclTimer.c index 33838ec..d05320d 100644 --- a/generic/tclTimer.c +++ b/generic/tclTimer.c @@ -49,6 +49,8 @@ typedef struct AfterInfo { * rather than a timer handler. */ struct AfterInfo *nextPtr; /* Next in list of all "after" commands for * this interpreter. */ + struct AfterInfo *prevPtr; /* Prev in list of all "after" commands for + * this interpreter. */ } AfterInfo; /* @@ -63,6 +65,7 @@ typedef struct AfterAssocData { AfterInfo *firstAfterPtr; /* First in list of all "after" commands still * pending for this interpreter, or NULL if * none. */ + AfterInfo *lastAfterPtr; /* Last in list of all "after" commands. */ } AfterAssocData; /* @@ -797,6 +800,7 @@ Tcl_AfterObjCmd( assocPtr = (AfterAssocData *) ckalloc(sizeof(AfterAssocData)); assocPtr->interp = interp; assocPtr->firstAfterPtr = NULL; + assocPtr->lastAfterPtr = NULL; Tcl_SetAssocData(interp, "tclAfter", AfterCleanupProc, (ClientData) assocPtr); } @@ -865,8 +869,9 @@ Tcl_AfterObjCmd( } afterPtr->token = TclCreateAbsoluteTimerHandler(&wakeup, AfterProc, (ClientData) afterPtr); - afterPtr->nextPtr = assocPtr->firstAfterPtr; - assocPtr->firstAfterPtr = afterPtr; + /* attach to the list */ + TclSpliceTailEx(afterPtr, assocPtr->firstAfterPtr, assocPtr->lastAfterPtr); + Tcl_SetObjResult(interp, Tcl_ObjPrintf("after#%d", afterPtr->id)); return TCL_OK; } @@ -885,8 +890,8 @@ Tcl_AfterObjCmd( commandPtr = Tcl_ConcatObj(objc-2, objv+2);; } command = Tcl_GetStringFromObj(commandPtr, &length); - for (afterPtr = assocPtr->firstAfterPtr; afterPtr != NULL; - afterPtr = afterPtr->nextPtr) { + for (afterPtr = assocPtr->lastAfterPtr; afterPtr != NULL; + afterPtr = afterPtr->prevPtr) { tempCommand = Tcl_GetStringFromObj(afterPtr->commandPtr, &tempLength); if ((length == tempLength) @@ -927,8 +932,9 @@ Tcl_AfterObjCmd( afterPtr->id = tsdPtr->afterId; tsdPtr->afterId += 1; afterPtr->token = NULL; - afterPtr->nextPtr = assocPtr->firstAfterPtr; - assocPtr->firstAfterPtr = afterPtr; + /* attach to the list */ + TclSpliceTailEx(afterPtr, assocPtr->firstAfterPtr, assocPtr->lastAfterPtr); + Tcl_DoWhenIdle(AfterProc, (ClientData) afterPtr); Tcl_SetObjResult(interp, Tcl_ObjPrintf("after#%d", afterPtr->id)); break; @@ -936,8 +942,8 @@ Tcl_AfterObjCmd( Tcl_Obj *resultListPtr; if (objc == 2) { - for (afterPtr = assocPtr->firstAfterPtr; afterPtr != NULL; - afterPtr = afterPtr->nextPtr) { + for (afterPtr = assocPtr->lastAfterPtr; afterPtr != NULL; + afterPtr = afterPtr->prevPtr) { if (assocPtr->interp == interp) { sprintf(buf, "after#%d", afterPtr->id); Tcl_AppendElement(interp, buf); @@ -1082,8 +1088,8 @@ GetAfterEvent( if ((end == cmdString) || (*end != 0)) { return NULL; } - for (afterPtr = assocPtr->firstAfterPtr; afterPtr != NULL; - afterPtr = afterPtr->nextPtr) { + for (afterPtr = assocPtr->lastAfterPtr; afterPtr != NULL; + afterPtr = afterPtr->prevPtr) { if (afterPtr->id == id) { return afterPtr; } @@ -1116,7 +1122,6 @@ AfterProc( { AfterInfo *afterPtr = (AfterInfo *) clientData; AfterAssocData *assocPtr = afterPtr->assocPtr; - AfterInfo *prevPtr; int result; Tcl_Interp *interp; @@ -1126,15 +1131,8 @@ AfterProc( * a core dump. */ - if (assocPtr->firstAfterPtr == afterPtr) { - assocPtr->firstAfterPtr = afterPtr->nextPtr; - } else { - for (prevPtr = assocPtr->firstAfterPtr; prevPtr->nextPtr != afterPtr; - prevPtr = prevPtr->nextPtr) { - /* Empty loop body. */ - } - prevPtr->nextPtr = afterPtr->nextPtr; - } + /* detach entry from the owner's list */ + TclSpliceOutEx(afterPtr, assocPtr->firstAfterPtr, assocPtr->lastAfterPtr); /* * Execute the callback. @@ -1179,18 +1177,12 @@ static void FreeAfterPtr( AfterInfo *afterPtr) /* Command to be deleted. */ { - AfterInfo *prevPtr; AfterAssocData *assocPtr = afterPtr->assocPtr; - if (assocPtr->firstAfterPtr == afterPtr) { - assocPtr->firstAfterPtr = afterPtr->nextPtr; - } else { - for (prevPtr = assocPtr->firstAfterPtr; prevPtr->nextPtr != afterPtr; - prevPtr = prevPtr->nextPtr) { - /* Empty loop body. */ - } - prevPtr->nextPtr = afterPtr->nextPtr; - } + /* detach entry from the owner's list */ + TclSpliceOutEx(afterPtr, assocPtr->firstAfterPtr, assocPtr->lastAfterPtr); + + /* free command and entry */ Tcl_DecrRefCount(afterPtr->commandPtr); ckfree((char *) afterPtr); } @@ -1222,9 +1214,10 @@ AfterCleanupProc( AfterAssocData *assocPtr = (AfterAssocData *) clientData; AfterInfo *afterPtr; - while (assocPtr->firstAfterPtr != NULL) { - afterPtr = assocPtr->firstAfterPtr; - assocPtr->firstAfterPtr = afterPtr->nextPtr; + while ( (afterPtr = assocPtr->lastAfterPtr) ) { + /* detach entry from the owner's list */ + TclSpliceOutEx(afterPtr, assocPtr->firstAfterPtr, assocPtr->lastAfterPtr); + if (afterPtr->token != NULL) { Tcl_DeleteTimerHandler(afterPtr->token); } else { -- cgit v0.12 From 04a1210ce8351b888d853d55e0eb0cade2dc0deb Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:21:34 +0000 Subject: timer resp. idle events optimized: better handling using doubly linked lists, prevents allocating memory twice for the "after" events (use memory inside timer/idle event for the "after" structure), etc. --- generic/tcl.h | 2 + generic/tclInt.h | 67 +++++++ generic/tclTimer.c | 539 ++++++++++++++++++++++++++++++++++------------------- 3 files changed, 418 insertions(+), 190 deletions(-) diff --git a/generic/tcl.h b/generic/tcl.h index 64c4683..e36f4d8 100644 --- a/generic/tcl.h +++ b/generic/tcl.h @@ -696,6 +696,7 @@ typedef void (Tcl_FileFreeProc) _ANSI_ARGS_((ClientData clientData)); typedef void (Tcl_FreeInternalRepProc) _ANSI_ARGS_((struct Tcl_Obj *objPtr)); typedef void (Tcl_FreeProc) _ANSI_ARGS_((char *blockPtr)); typedef void (Tcl_IdleProc) _ANSI_ARGS_((ClientData clientData)); +typedef void (Tcl_IdleDeleteProc) _ANSI_ARGS_((ClientData clientData)); typedef void (Tcl_InterpDeleteProc) _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp)); typedef int (Tcl_MathProc) _ANSI_ARGS_((ClientData clientData, @@ -710,6 +711,7 @@ typedef void (Tcl_PanicProc) _ANSI_ARGS_((CONST char *format, ...)); typedef void (Tcl_TcpAcceptProc) _ANSI_ARGS_((ClientData callbackData, Tcl_Channel chan, char *address, int port)); typedef void (Tcl_TimerProc) _ANSI_ARGS_((ClientData clientData)); +typedef void (Tcl_TimerDeleteProc) _ANSI_ARGS_((ClientData clientData)); typedef int (Tcl_SetFromAnyProc) _ANSI_ARGS_((Tcl_Interp *interp, struct Tcl_Obj *objPtr)); typedef void (Tcl_UpdateStringProc) _ANSI_ARGS_((struct Tcl_Obj *objPtr)); diff --git a/generic/tclInt.h b/generic/tclInt.h index 7e70627..99f9346 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -2499,6 +2499,65 @@ MODULE_SCOPE char tclEmptyString; #define TCL_DD_SHORTEST0 0x0 /* 'Shortest possible' after masking */ + + +/* + *---------------------------------------------------------------- + * Data structures related to timer / idle events. + *---------------------------------------------------------------- + */ + +/* + * For each timer callback that's pending there is one record of the following + * type. The normal handlers (created by Tcl_CreateTimerHandler) are chained + * together in a list sorted by time (earliest event first). + */ + + +typedef struct TimerHandler { + Tcl_TimerProc *proc; /* Function to call timer event */ + Tcl_TimerDeleteProc *deleteProc; /* Function to cleanup timer event */ + ClientData clientData; /* Argument to pass to proc and deleteProc */ + size_t generation; /* Used to distinguish older handlers from + * recently-created ones. */ + Tcl_TimerToken token; /* Identifies handler so it can be deleted. */ + struct TimerHandler *nextPtr; /* Next and prev event in timer/idle queue, */ + struct TimerHandler *prevPtr; /* or NULL for end/start of the queue. */ + Tcl_Time time; /* When timer is to fire (if timer event). */ +/* ExtraData */ +} TimerHandler; + +/* + * There is one of the following structures for each of the handlers declared + * in a call to Tcl_DoWhenIdle. All of the currently-active handlers are + * linked together into a list. + */ + +typedef struct IdleHandler { + Tcl_IdleProc *proc; /* Function to call idle event */ + Tcl_IdleDeleteProc *deleteProc; /* Function to cleanup idle event */ + ClientData clientData; /* Argument to pass to proc and deleteProc */ + size_t generation; /* Used to distinguish older handlers from + * recently-created ones. */ + struct IdleHandler *nextPtr;/* Next and prev event in idle queue, */ + struct IdleHandler *prevPtr;/* or NULL for end/start of the queue. */ +/* ExtraData */ +} IdleHandler; + + +/* + * Macros to wrap ExtraData and TimerHandler resp. IdleHandler (and vice versa) + */ +#define TimerHandler2ClientData(ptr) \ + ( (ClientData)(((TimerHandler *)(ptr))+1) ) +#define ClientData2TimerHandler(ptr) \ + ( ((TimerHandler *)(ptr))-1 ) + +#define IdleHandler2ClientData(ptr) \ + ( (ClientData)(((IdleHandler *)(ptr))+1) ) +#define ClientData2IdleHandler(ptr) \ + ( ((IdleHandler *)(ptr))-1 ) + /* *---------------------------------------------------------------- * Procedures shared among Tcl modules but not used by the outside world: @@ -2867,6 +2926,14 @@ MODULE_SCOPE int Tcl_ContinueObjCmd(ClientData clientData, MODULE_SCOPE Tcl_TimerToken TclCreateAbsoluteTimerHandler( Tcl_Time *timePtr, Tcl_TimerProc *proc, ClientData clientData); +MODULE_SCOPE TimerHandler* TclCreateAbsoluteTimerHandlerEx( + Tcl_Time *timePtr, Tcl_TimerProc *proc, + Tcl_TimerDeleteProc *deleteProc, size_t extraSize); +MODULE_SCOPE void TclDeleteTimerHandler(TimerHandler *timerHandlerPtr); +MODULE_SCOPE IdleHandler* TclCreateIdleHandlerEx( + Tcl_IdleProc *proc, Tcl_IdleDeleteProc *deleteProc, + size_t extraDataSize); +MODULE_SCOPE void TclDeleteIdleHandler(IdleHandler *idlePtr); MODULE_SCOPE int TclDefaultBgErrorHandlerObjCmd( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); diff --git a/generic/tclTimer.c b/generic/tclTimer.c index d05320d..40316ca 100644 --- a/generic/tclTimer.c +++ b/generic/tclTimer.c @@ -13,40 +13,21 @@ #include "tclInt.h" /* - * For each timer callback that's pending there is one record of the following - * type. The normal handlers (created by Tcl_CreateTimerHandler) are chained - * together in a list sorted by time (earliest event first). - */ - -typedef struct TimerHandler { - Tcl_Time time; /* When timer is to fire. */ - Tcl_TimerProc *proc; /* Function to call. */ - ClientData clientData; /* Argument to pass to proc. */ - Tcl_TimerToken token; /* Identifies handler so it can be deleted. */ - struct TimerHandler *nextPtr; - /* Next event in queue, or NULL for end of - * queue. */ -} TimerHandler; - -/* * The data structure below is used by the "after" command to remember the * command to be executed later. All of the pending "after" commands for an * interpreter are linked together in a list. */ +#define IDLE_EVENT (1 << 1) /* Mark idle event */ + typedef struct AfterInfo { struct AfterAssocData *assocPtr; /* Pointer to the "tclAfter" assocData for the * interp in which command will be * executed. */ Tcl_Obj *commandPtr; /* Command to execute. */ - int id; /* Integer identifier for command; used to - * cancel it. */ - Tcl_TimerToken token; /* Used to cancel the "after" command. NULL - * means that the command is run as an idle - * handler rather than as a timer handler. - * NULL means this is an "after idle" handler - * rather than a timer handler. */ + size_t id; /* Integer identifier for command */ + int flags; /* Flags (IDLE_EVENT) */ struct AfterInfo *nextPtr; /* Next in list of all "after" commands for * this interpreter. */ struct AfterInfo *prevPtr; /* Prev in list of all "after" commands for @@ -69,20 +50,6 @@ typedef struct AfterAssocData { } AfterAssocData; /* - * There is one of the following structures for each of the handlers declared - * in a call to Tcl_DoWhenIdle. All of the currently-active handlers are - * linked together into a list. - */ - -typedef struct IdleHandler { - Tcl_IdleProc (*proc); /* Function to call. */ - ClientData clientData; /* Value to pass to proc. */ - int generation; /* Used to distinguish older handlers from - * recently-created ones. */ - struct IdleHandler *nextPtr;/* Next in list of active handlers. */ -} IdleHandler; - -/* * The timer and idle queues are per-thread because they are associated with * the notifier, which is also per-thread. * @@ -94,25 +61,42 @@ typedef struct IdleHandler { * The structure defined below is used in this file only. */ -typedef struct ThreadSpecificData { - TimerHandler *firstTimerHandlerPtr; /* First event in queue. */ +typedef struct { + TimerHandler *timerList; /* First event in queue. */ + TimerHandler *lastTimerPtr; /* Last event in queue. */ + size_t timerListEpoch; /* Used for safe process of event queue (stop + * the cycle after modifying of event queue) */ int lastTimerId; /* Timer identifier of most recently created * timer. */ int timerPending; /* 1 if a timer event is in the queue. */ IdleHandler *idleList; /* First in list of all idle handlers. */ IdleHandler *lastIdlePtr; /* Last in list (or NULL for empty list). */ - int idleGeneration; /* Used to fill in the "generation" fields of - * IdleHandler structures. Increments each - * time Tcl_DoOneEvent starts calling idle - * handlers, so that all old handlers can be + size_t timerGeneration; /* Used to fill in the "generation" fields of */ + size_t idleGeneration; /* timer or idle structures. Increments each + * time we place a new handler to queue inside, + * a new loop, so that all old handlers can be * called without calling any of the new ones * created by old ones. */ - int afterId; /* For unique identifiers of after events. */ + size_t afterId; /* For unique identifiers of after events. */ } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* + * Helper macros to wrap AfterInfo and handlers (and vice versa) + */ + +#define TimerHandler2AfterInfo(ptr) \ + ( (AfterInfo*)TimerHandler2ClientData(ptr) ) +#define AfterInfo2TimerHandler(ptr) \ + ClientData2TimerHandler(ptr) + +#define IdleHandler2AfterInfo(ptr) \ + ( (AfterInfo*)IdleHandler2ClientData(ptr) ) +#define AfterInfo2IdleHandler(ptr) \ + ClientData2IdleHandler(ptr) + +/* * Helper macros for working with times. TCL_TIME_BEFORE encodes how to write * the ordering relation on (normalized) times, and TCL_TIME_DIFF_MS computes * the number of milliseconds difference between two times. Both macros use @@ -138,7 +122,7 @@ static void AfterCleanupProc(ClientData clientData, Tcl_Interp *interp); static int AfterDelay(Tcl_Interp *interp, Tcl_WideInt ms); static void AfterProc(ClientData clientData); -static void FreeAfterPtr(AfterInfo *afterPtr); +static void FreeAfterPtr(ClientData clientData); static AfterInfo * GetAfterEvent(AfterAssocData *assocPtr, Tcl_Obj *commandPtr); static ThreadSpecificData *InitTimer(void); @@ -146,8 +130,8 @@ static void TimerExitProc(ClientData clientData); static int TimerHandlerEventProc(Tcl_Event *evPtr, int flags); static void TimerCheckProc(ClientData clientData, int flags); static void TimerSetupProc(ClientData clientData, int flags); - -/* + + /* *---------------------------------------------------------------------- * * InitTimer -- @@ -205,12 +189,11 @@ TimerExitProc( if (tsdPtr != NULL) { register TimerHandler *timerHandlerPtr; - timerHandlerPtr = tsdPtr->firstTimerHandlerPtr; - while (timerHandlerPtr != NULL) { - tsdPtr->firstTimerHandlerPtr = timerHandlerPtr->nextPtr; + while ((timerHandlerPtr = tsdPtr->timerList) != NULL) { + tsdPtr->timerList = timerHandlerPtr->nextPtr; ckfree((char *) timerHandlerPtr); - timerHandlerPtr = tsdPtr->firstTimerHandlerPtr; } + tsdPtr->lastTimerPtr = NULL; } } @@ -239,6 +222,7 @@ Tcl_CreateTimerHandler( Tcl_TimerProc *proc, /* Function to invoke. */ ClientData clientData) /* Arbitrary data to pass to proc. */ { + register TimerHandler *timerHandlerPtr; Tcl_Time time; /* @@ -252,20 +236,27 @@ Tcl_CreateTimerHandler( time.usec -= 1000000; time.sec += 1; } - return TclCreateAbsoluteTimerHandler(&time, proc, clientData); + + timerHandlerPtr = TclCreateAbsoluteTimerHandlerEx(&time, proc, NULL, 0); + if (timerHandlerPtr == NULL) { + return NULL; + } + timerHandlerPtr->clientData = clientData; + + return timerHandlerPtr->token; } /* *-------------------------------------------------------------- * - * TclCreateAbsoluteTimerHandler -- + * TclCreateAbsoluteTimerHandlerEx -- , TclCreateAbsoluteTimerHandler -- * * Arrange for a given function to be invoked at a particular time in the * future. * * Results: - * The return value is a token for the timer event, which may be used to - * delete the event before it fires. + * The return value is a handler or token of the timer event, which may + * be used to delete the event before it fires. * * Side effects: * When the time in timePtr has been reached, proc will be invoked @@ -274,17 +265,21 @@ Tcl_CreateTimerHandler( *-------------------------------------------------------------- */ -Tcl_TimerToken -TclCreateAbsoluteTimerHandler( - Tcl_Time *timePtr, - Tcl_TimerProc *proc, - ClientData clientData) +TimerHandler* +TclCreateAbsoluteTimerHandlerEx( + Tcl_Time *timePtr, /* Time to be invoked */ + Tcl_TimerProc *proc, /* Function to invoke */ + Tcl_TimerDeleteProc *deleteProc, /* Function to cleanup */ + size_t extraDataSize) { - register TimerHandler *timerHandlerPtr, *tPtr2, *prevPtr; + register TimerHandler *timerHandlerPtr, *thPtrPos; ThreadSpecificData *tsdPtr; tsdPtr = InitTimer(); - timerHandlerPtr = (TimerHandler *) ckalloc(sizeof(TimerHandler)); + timerHandlerPtr = (TimerHandler *) ckalloc(sizeof(TimerHandler) + extraDataSize); + if (timerHandlerPtr == NULL) { + return NULL; + } /* * Fill in fields for the event. @@ -292,7 +287,10 @@ TclCreateAbsoluteTimerHandler( memcpy((void *)&timerHandlerPtr->time, (void *)timePtr, sizeof(Tcl_Time)); timerHandlerPtr->proc = proc; - timerHandlerPtr->clientData = clientData; + timerHandlerPtr->deleteProc = deleteProc; + timerHandlerPtr->clientData = TimerHandler2ClientData(timerHandlerPtr); + timerHandlerPtr->generation = tsdPtr->timerGeneration; + tsdPtr->timerListEpoch++; /* signal-timer list was changed */ tsdPtr->lastTimerId++; timerHandlerPtr->token = (Tcl_TimerToken) INT2PTR(tsdPtr->lastTimerId); @@ -301,21 +299,57 @@ TclCreateAbsoluteTimerHandler( * (ordered by event firing time). */ - for (tPtr2 = tsdPtr->firstTimerHandlerPtr, prevPtr = NULL; tPtr2 != NULL; - prevPtr = tPtr2, tPtr2 = tPtr2->nextPtr) { - if (TCL_TIME_BEFORE(timerHandlerPtr->time, tPtr2->time)) { - break; - } - } - timerHandlerPtr->nextPtr = tPtr2; - if (prevPtr == NULL) { - tsdPtr->firstTimerHandlerPtr = timerHandlerPtr; + /* if before current first (e. g. "after 0" before first "after 1000") */ + if ( !(thPtrPos = tsdPtr->timerList) + || TCL_TIME_BEFORE(timerHandlerPtr->time, thPtrPos->time) + ) { + /* splice to the head */ + TclSpliceInEx(timerHandlerPtr, + tsdPtr->timerList, tsdPtr->lastTimerPtr); } else { - prevPtr->nextPtr = timerHandlerPtr; + /* search from end as long as one with time before not found */ + for (thPtrPos = tsdPtr->lastTimerPtr; thPtrPos != NULL; + thPtrPos = thPtrPos->prevPtr) { + if (TCL_TIME_BEFORE(thPtrPos->time, timerHandlerPtr->time)) { + break; + } + } + /* normally it should be always true, because checked above, but ... */ + if (thPtrPos != NULL) { + /* insert after found element (with time before new) */ + timerHandlerPtr->prevPtr = thPtrPos; + if ((timerHandlerPtr->nextPtr = thPtrPos->nextPtr)) { + thPtrPos->nextPtr->prevPtr = timerHandlerPtr; + } else { + tsdPtr->lastTimerPtr = timerHandlerPtr; + } + thPtrPos->nextPtr = timerHandlerPtr; + } else { + /* unexpected case, but ... splice to the head */ + TclSpliceInEx(timerHandlerPtr, + tsdPtr->timerList, tsdPtr->lastTimerPtr); + } } TimerSetupProc(NULL, TCL_ALL_EVENTS); + return timerHandlerPtr; +} + +Tcl_TimerToken +TclCreateAbsoluteTimerHandler( + Tcl_Time *timePtr, + Tcl_TimerProc *proc, + ClientData clientData) +{ + register TimerHandler *timerHandlerPtr; + + timerHandlerPtr = TclCreateAbsoluteTimerHandlerEx(timePtr, proc, NULL, 0); + if (timerHandlerPtr == NULL) { + return NULL; + } + timerHandlerPtr->clientData = clientData; + return timerHandlerPtr->token; } @@ -340,30 +374,51 @@ TclCreateAbsoluteTimerHandler( void Tcl_DeleteTimerHandler( Tcl_TimerToken token) /* Result previously returned by - * Tcl_DeleteTimerHandler. */ + * Tcl_CreateTimerHandler. */ { - register TimerHandler *timerHandlerPtr, *prevPtr; + register TimerHandler *timerHandlerPtr; ThreadSpecificData *tsdPtr = InitTimer(); if (token == NULL) { return; } - for (timerHandlerPtr = tsdPtr->firstTimerHandlerPtr, prevPtr = NULL; - timerHandlerPtr != NULL; prevPtr = timerHandlerPtr, - timerHandlerPtr = timerHandlerPtr->nextPtr) { + for (timerHandlerPtr = tsdPtr->lastTimerPtr; + timerHandlerPtr != NULL; + timerHandlerPtr = timerHandlerPtr->prevPtr + ) { if (timerHandlerPtr->token != token) { continue; } - if (prevPtr == NULL) { - tsdPtr->firstTimerHandlerPtr = timerHandlerPtr->nextPtr; - } else { - prevPtr->nextPtr = timerHandlerPtr->nextPtr; - } - ckfree((char *) timerHandlerPtr); + + TclDeleteTimerHandler(timerHandlerPtr); return; } } + +void +TclDeleteTimerHandler( + TimerHandler *timerHandlerPtr) /* Result previously returned by */ + /* TclCreateAbsoluteTimerHandlerEx. */ +{ + ThreadSpecificData *tsdPtr; + + if (timerHandlerPtr == NULL) { + return; + } + + tsdPtr = InitTimer(); + + tsdPtr->timerListEpoch++; /* signal-timer list was changed */ + TclSpliceOutEx(timerHandlerPtr, tsdPtr->timerList, tsdPtr->lastTimerPtr); + + /* free it via deleteProc or ckfree */ + if (timerHandlerPtr->deleteProc) { + (*timerHandlerPtr->deleteProc)(timerHandlerPtr->clientData); + } + + ckfree((char *)timerHandlerPtr); +} /* *---------------------------------------------------------------------- @@ -400,14 +455,14 @@ TimerSetupProc( blockTime.sec = 0; blockTime.usec = 0; - } else if ((flags & TCL_TIMER_EVENTS) && tsdPtr->firstTimerHandlerPtr) { + } else if ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerList) { /* * Compute the timeout for the next timer on the list. */ Tcl_GetTime(&blockTime); - blockTime.sec = tsdPtr->firstTimerHandlerPtr->time.sec - blockTime.sec; - blockTime.usec = tsdPtr->firstTimerHandlerPtr->time.usec - + blockTime.sec = tsdPtr->timerList->time.sec - blockTime.sec; + blockTime.usec = tsdPtr->timerList->time.usec - blockTime.usec; if (blockTime.usec < 0) { blockTime.sec -= 1; @@ -451,14 +506,14 @@ TimerCheckProc( Tcl_Time blockTime; ThreadSpecificData *tsdPtr = InitTimer(); - if ((flags & TCL_TIMER_EVENTS) && tsdPtr->firstTimerHandlerPtr) { + if ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerList) { /* * Compute the timeout for the next timer on the list. */ Tcl_GetTime(&blockTime); - blockTime.sec = tsdPtr->firstTimerHandlerPtr->time.sec - blockTime.sec; - blockTime.usec = tsdPtr->firstTimerHandlerPtr->time.usec - + blockTime.sec = tsdPtr->timerList->time.sec - blockTime.sec; + blockTime.usec = tsdPtr->timerList->time.usec - blockTime.usec; if (blockTime.usec < 0) { blockTime.sec -= 1; @@ -510,9 +565,9 @@ TimerHandlerEventProc( int flags) /* Flags that indicate what events to handle, * such as TCL_FILE_EVENTS. */ { - TimerHandler *timerHandlerPtr, **nextPtrPtr; + TimerHandler *timerHandlerPtr, *nextPtr; Tcl_Time time; - int currentTimerId; + size_t currentGeneration, currentEpoch; ThreadSpecificData *tsdPtr = InitTimer(); /* @@ -531,9 +586,7 @@ TimerHandlerEventProc( * 1. New handlers can get added to the list while the current one is * being processed. If new ones get added, we don't want to process * them during this pass through the list to avoid starving other event - * sources. This is implemented using the token number in the handler: - * new handlers will have a newer token than any of the ones currently - * on the list. + * sources. This is implemented using check of the generation epoch. * 2. The handler can call Tcl_DoOneEvent, so we have to remove the * handler from the list before calling it. Otherwise an infinite loop * could result. @@ -551,36 +604,60 @@ TimerHandlerEventProc( */ tsdPtr->timerPending = 0; - currentTimerId = tsdPtr->lastTimerId; + currentGeneration = tsdPtr->timerGeneration++; Tcl_GetTime(&time); - while (1) { - nextPtrPtr = &tsdPtr->firstTimerHandlerPtr; - timerHandlerPtr = tsdPtr->firstTimerHandlerPtr; - if (timerHandlerPtr == NULL) { - break; - } + for (timerHandlerPtr = tsdPtr->timerList; + timerHandlerPtr != NULL; + timerHandlerPtr = nextPtr + ) { + nextPtr = timerHandlerPtr->nextPtr; if (TCL_TIME_BEFORE(time, timerHandlerPtr->time)) { break; } /* - * Bail out if the next timer is of a newer generation. + * Bypass timers of newer generation. */ - if ((currentTimerId - PTR2INT(timerHandlerPtr->token)) < 0) { - break; + if (timerHandlerPtr->generation > currentGeneration) { + continue; } + tsdPtr->timerListEpoch++; /* signal-timer list was changed */ + /* * Remove the handler from the queue before invoking it, to avoid * potential reentrancy problems. */ - (*nextPtrPtr) = timerHandlerPtr->nextPtr; + TclSpliceOutEx(timerHandlerPtr, + tsdPtr->timerList, tsdPtr->lastTimerPtr); + + currentEpoch = tsdPtr->timerListEpoch; + + /* invoke timer proc */ (*timerHandlerPtr->proc)(timerHandlerPtr->clientData); + /* free it via deleteProc or ckfree */ + if (timerHandlerPtr->deleteProc) { + (*timerHandlerPtr->deleteProc)(timerHandlerPtr->clientData); + } + ckfree((char *) timerHandlerPtr); + + /* be sure that timer-list was not changed inside the proc call */ + if (currentEpoch != tsdPtr->timerListEpoch) { + /* timer-list was changed - stop processing */ + break; + } } + + /* Reset generation */ + if (!tsdPtr->timerList) { + tsdPtr->timerGeneration = 0; + } + + /* Compute the next timeout (first timer on the list). */ TimerSetupProc(NULL, TCL_TIMER_EVENTS); return 1; } @@ -588,7 +665,7 @@ TimerHandlerEventProc( /* *-------------------------------------------------------------- * - * Tcl_DoWhenIdle -- + * TclCreateIdleHandlerEx --, Tcl_DoWhenIdle -- * * Arrange for proc to be invoked the next time the system is idle (i.e., * just before the next time that Tcl_DoOneEvent would have to wait for @@ -604,30 +681,85 @@ TimerHandlerEventProc( *-------------------------------------------------------------- */ -void -Tcl_DoWhenIdle( - Tcl_IdleProc *proc, /* Function to invoke. */ - ClientData clientData) /* Arbitrary value to pass to proc. */ +IdleHandler * +TclCreateIdleHandlerEx( + Tcl_IdleProc *proc, /* Function to invoke. */ + Tcl_IdleDeleteProc *deleteProc, /* Function to cleanup */ + size_t extraDataSize) { register IdleHandler *idlePtr; Tcl_Time blockTime; ThreadSpecificData *tsdPtr = InitTimer(); - idlePtr = (IdleHandler *) ckalloc(sizeof(IdleHandler)); + idlePtr = (IdleHandler *) ckalloc(sizeof(IdleHandler) + extraDataSize); + if (idlePtr == NULL) { + return NULL; + } idlePtr->proc = proc; - idlePtr->clientData = clientData; + idlePtr->deleteProc = deleteProc; + idlePtr->clientData = IdleHandler2ClientData(idlePtr); idlePtr->generation = tsdPtr->idleGeneration; - idlePtr->nextPtr = NULL; - if (tsdPtr->lastIdlePtr == NULL) { - tsdPtr->idleList = idlePtr; - } else { - tsdPtr->lastIdlePtr->nextPtr = idlePtr; - } - tsdPtr->lastIdlePtr = idlePtr; + /* attach to the idle queue */ + TclSpliceTailEx(idlePtr, tsdPtr->idleList, tsdPtr->lastIdlePtr); + + /* reset next block time */ blockTime.sec = 0; blockTime.usec = 0; Tcl_SetMaxBlockTime(&blockTime); + + return idlePtr; +} + +void +Tcl_DoWhenIdle( + Tcl_IdleProc *proc, /* Function to invoke. */ + ClientData clientData) /* Arbitrary value to pass to proc. */ +{ + register IdleHandler *idlePtr = TclCreateIdleHandlerEx(proc, NULL, 0); + + if (idlePtr) { + idlePtr->clientData = clientData; + } +} + +/* + *-------------------------------------------------------------- + * + * TclDeleteIdleHandler -- + * + * Delete a previously-registered idle handler. + * + * Results: + * None. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +void +TclDeleteIdleHandler( + IdleHandler *idlePtr) /* Result previously returned by */ + /* TclCreateIdleHandlerEx */ +{ + ThreadSpecificData *tsdPtr; + + if (idlePtr == NULL) { + return; + } + + tsdPtr = InitTimer(); + + /* detach entry from the owner list */ + TclSpliceOutEx(idlePtr, tsdPtr->idleList, tsdPtr->lastIdlePtr); + + /* free it via deleteProc and ckfree */ + if (idlePtr->deleteProc) { + (*idlePtr->deleteProc)(idlePtr->clientData); + } + ckfree((char *)idlePtr); } /* @@ -653,26 +785,24 @@ Tcl_CancelIdleCall( Tcl_IdleProc *proc, /* Function that was previously registered. */ ClientData clientData) /* Arbitrary value to pass to proc. */ { - register IdleHandler *idlePtr, *prevPtr; - IdleHandler *nextPtr; + register IdleHandler *idlePtr, *nextPtr; ThreadSpecificData *tsdPtr = InitTimer(); - for (prevPtr = NULL, idlePtr = tsdPtr->idleList; idlePtr != NULL; - prevPtr = idlePtr, idlePtr = idlePtr->nextPtr) { - while ((idlePtr->proc == proc) + for (idlePtr = tsdPtr->idleList; + idlePtr != NULL; + idlePtr = nextPtr + ) { + nextPtr = idlePtr->nextPtr; + if ((idlePtr->proc == proc) && (idlePtr->clientData == clientData)) { - nextPtr = idlePtr->nextPtr; - ckfree((char *) idlePtr); - idlePtr = nextPtr; - if (prevPtr == NULL) { - tsdPtr->idleList = idlePtr; - } else { - prevPtr->nextPtr = idlePtr; - } - if (idlePtr == NULL) { - tsdPtr->lastIdlePtr = prevPtr; - return; + /* detach entry from the owner list */ + TclSpliceOutEx(idlePtr, tsdPtr->idleList, tsdPtr->lastIdlePtr); + + /* free it via deleteProc and ckfree */ + if (idlePtr->deleteProc) { + (*idlePtr->deleteProc)(idlePtr->clientData); } + ckfree((char *) idlePtr); } } } @@ -700,7 +830,7 @@ int TclServiceIdle(void) { IdleHandler *idlePtr; - int oldGeneration; + size_t currentGeneration; Tcl_Time blockTime; ThreadSpecificData *tsdPtr = InitTimer(); @@ -708,8 +838,7 @@ TclServiceIdle(void) return 0; } - oldGeneration = tsdPtr->idleGeneration; - tsdPtr->idleGeneration++; + currentGeneration = tsdPtr->idleGeneration++; /* * The code below is trickier than it may look, for the following reasons: @@ -728,15 +857,19 @@ TclServiceIdle(void) * during the call. */ - for (idlePtr = tsdPtr->idleList; - ((idlePtr != NULL) - && ((oldGeneration - idlePtr->generation) >= 0)); - idlePtr = tsdPtr->idleList) { - tsdPtr->idleList = idlePtr->nextPtr; - if (tsdPtr->idleList == NULL) { - tsdPtr->lastIdlePtr = NULL; - } + while ((idlePtr = tsdPtr->idleList) != NULL + && idlePtr->generation <= currentGeneration + ) { + /* detach entry from the owner's list */ + TclSpliceOutEx(idlePtr, tsdPtr->idleList, tsdPtr->lastIdlePtr); + + /* execute event */ (*idlePtr->proc)(idlePtr->clientData); + + /* free it via deleteProc and ckfree */ + if (idlePtr->deleteProc) { + (*idlePtr->deleteProc)(idlePtr->clientData); + } ckfree((char *) idlePtr); } if (tsdPtr->idleList) { @@ -744,6 +877,10 @@ TclServiceIdle(void) blockTime.usec = 0; Tcl_SetMaxBlockTime(&blockTime); } + /* Reset generation */ + if (!tsdPtr->idleList) { + tsdPtr->idleGeneration = 0; + } return 1; } @@ -833,14 +970,35 @@ Tcl_AfterObjCmd( switch (index) { case -1: { + TimerHandler *timerPtr; if (ms < 0) { ms = 0; } if (objc == 2) { return AfterDelay(interp, ms); } - afterPtr = (AfterInfo *) ckalloc((unsigned) (sizeof(AfterInfo))); + + Tcl_GetTime(&wakeup); + wakeup.sec += (long)(ms / 1000); + wakeup.usec += ((long)(ms % 1000)) * 1000; + if (wakeup.usec > 1000000) { + wakeup.sec++; + wakeup.usec -= 1000000; + } + + timerPtr = TclCreateAbsoluteTimerHandlerEx(&wakeup, AfterProc, + FreeAfterPtr, sizeof(AfterInfo)); + if (timerPtr == NULL) { /* error handled in panic */ + return TCL_ERROR; + } + afterPtr = TimerHandler2AfterInfo(timerPtr); + + /* attach to the list */ afterPtr->assocPtr = assocPtr; + TclSpliceTailEx(afterPtr, + assocPtr->firstAfterPtr, assocPtr->lastAfterPtr); + afterPtr->flags = 0; + if (objc == 3) { afterPtr->commandPtr = objv[2]; } else { @@ -858,19 +1016,7 @@ Tcl_AfterObjCmd( * around when wrap-around occurs. */ - afterPtr->id = tsdPtr->afterId; - tsdPtr->afterId += 1; - Tcl_GetTime(&wakeup); - wakeup.sec += (long)(ms / 1000); - wakeup.usec += ((long)(ms % 1000)) * 1000; - if (wakeup.usec > 1000000) { - wakeup.sec++; - wakeup.usec -= 1000000; - } - afterPtr->token = TclCreateAbsoluteTimerHandler(&wakeup, AfterProc, - (ClientData) afterPtr); - /* attach to the list */ - TclSpliceTailEx(afterPtr, assocPtr->firstAfterPtr, assocPtr->lastAfterPtr); + afterPtr->id = tsdPtr->afterId++; Tcl_SetObjResult(interp, Tcl_ObjPrintf("after#%d", afterPtr->id)); return TCL_OK; @@ -907,37 +1053,48 @@ Tcl_AfterObjCmd( Tcl_DecrRefCount(commandPtr); } if (afterPtr != NULL) { - if (afterPtr->token != NULL) { - Tcl_DeleteTimerHandler(afterPtr->token); + if (!(afterPtr->flags & IDLE_EVENT)) { + TclDeleteTimerHandler(AfterInfo2TimerHandler(afterPtr)); } else { - Tcl_CancelIdleCall(AfterProc, (ClientData) afterPtr); + TclDeleteIdleHandler(AfterInfo2IdleHandler(afterPtr)); } - FreeAfterPtr(afterPtr); } break; } - case AFTER_IDLE: + case AFTER_IDLE: { + IdleHandler *idlePtr; + if (objc < 3) { Tcl_WrongNumArgs(interp, 2, objv, "script script ..."); return TCL_ERROR; } - afterPtr = (AfterInfo *) ckalloc((unsigned) (sizeof(AfterInfo))); + + idlePtr = TclCreateIdleHandlerEx(AfterProc, + FreeAfterPtr, sizeof(AfterInfo)); + if (idlePtr == NULL) { /* error handled in panic */ + return TCL_ERROR; + } + afterPtr = IdleHandler2AfterInfo(idlePtr); + + /* attach to the list */ afterPtr->assocPtr = assocPtr; + TclSpliceTailEx(afterPtr, + assocPtr->firstAfterPtr, assocPtr->lastAfterPtr); + afterPtr->flags = IDLE_EVENT; + if (objc == 3) { afterPtr->commandPtr = objv[2]; } else { afterPtr->commandPtr = Tcl_ConcatObj(objc-2, objv+2); } Tcl_IncrRefCount(afterPtr->commandPtr); - afterPtr->id = tsdPtr->afterId; - tsdPtr->afterId += 1; - afterPtr->token = NULL; - /* attach to the list */ - TclSpliceTailEx(afterPtr, assocPtr->firstAfterPtr, assocPtr->lastAfterPtr); - - Tcl_DoWhenIdle(AfterProc, (ClientData) afterPtr); + + afterPtr->id = tsdPtr->afterId++; + Tcl_SetObjResult(interp, Tcl_ObjPrintf("after#%d", afterPtr->id)); - break; + + return TCL_OK; + }; case AFTER_INFO: { Tcl_Obj *resultListPtr; @@ -964,7 +1121,7 @@ Tcl_AfterObjCmd( resultListPtr = Tcl_NewObj(); Tcl_ListObjAppendElement(interp, resultListPtr, afterPtr->commandPtr); Tcl_ListObjAppendElement(interp, resultListPtr, Tcl_NewStringObj( - (afterPtr->token == NULL) ? "idle" : "timer", -1)); + (afterPtr->flags & IDLE_EVENT) ? "idle" : "timer", -1)); Tcl_SetObjResult(interp, resultListPtr); break; } @@ -1131,6 +1288,14 @@ AfterProc( * a core dump. */ + + /* remove delete proc from handler (we'll do cleanup here) */ + if (!(afterPtr->flags & IDLE_EVENT)) { + AfterInfo2TimerHandler(afterPtr)->deleteProc = NULL; + } else { + AfterInfo2IdleHandler(afterPtr)->deleteProc = NULL; + } + /* detach entry from the owner's list */ TclSpliceOutEx(afterPtr, assocPtr->firstAfterPtr, assocPtr->lastAfterPtr); @@ -1152,7 +1317,7 @@ AfterProc( */ Tcl_DecrRefCount(afterPtr->commandPtr); - ckfree((char *) afterPtr); + } /* @@ -1168,23 +1333,23 @@ AfterProc( * None. * * Side effects: - * The memory associated with afterPtr is released. + * The memory associated with afterPtr is not released (owned by handler). * *---------------------------------------------------------------------- */ static void FreeAfterPtr( - AfterInfo *afterPtr) /* Command to be deleted. */ + ClientData clientData) /* Command to be deleted. */ { + AfterInfo *afterPtr = (AfterInfo *) clientData; AfterAssocData *assocPtr = afterPtr->assocPtr; /* detach entry from the owner's list */ TclSpliceOutEx(afterPtr, assocPtr->firstAfterPtr, assocPtr->lastAfterPtr); - /* free command and entry */ + /* free command of entry */ Tcl_DecrRefCount(afterPtr->commandPtr); - ckfree((char *) afterPtr); } /* @@ -1215,18 +1380,12 @@ AfterCleanupProc( AfterInfo *afterPtr; while ( (afterPtr = assocPtr->lastAfterPtr) ) { - /* detach entry from the owner's list */ - TclSpliceOutEx(afterPtr, assocPtr->firstAfterPtr, assocPtr->lastAfterPtr); - - if (afterPtr->token != NULL) { - Tcl_DeleteTimerHandler(afterPtr->token); + if (!(afterPtr->flags & IDLE_EVENT)) { + TclDeleteTimerHandler(AfterInfo2TimerHandler(afterPtr)); } else { - Tcl_CancelIdleCall(AfterProc, (ClientData) afterPtr); + TclDeleteIdleHandler(AfterInfo2IdleHandler(afterPtr)); } - Tcl_DecrRefCount(afterPtr->commandPtr); - ckfree((char *) afterPtr); } - ckfree((char *) assocPtr); } /* -- cgit v0.12 From a66afb09682a193da54b17a65482242d1e7a23ff Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:21:57 +0000 Subject: rewrite interpreter limit handling using new timer event handling (with delete callback) --- generic/tclInt.h | 91 ++++++++++++++++++++++++++--------------------------- generic/tclInterp.c | 18 ++++++++--- 2 files changed, 59 insertions(+), 50 deletions(-) diff --git a/generic/tclInt.h b/generic/tclInt.h index 99f9346..021ba00 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -128,6 +128,50 @@ typedef int ptrdiff_t; #endif /* + *---------------------------------------------------------------- + * Data structures related to timer / idle events. + *---------------------------------------------------------------- + */ + +/* + * For each timer callback that's pending there is one record of the following + * type. The normal handlers (created by Tcl_CreateTimerHandler) are chained + * together in a list sorted by time (earliest event first). + */ + + +typedef struct TimerHandler { + Tcl_TimerProc *proc; /* Function to call timer event */ + Tcl_TimerDeleteProc *deleteProc; /* Function to cleanup timer event */ + ClientData clientData; /* Argument to pass to proc and deleteProc */ + size_t generation; /* Used to distinguish older handlers from + * recently-created ones. */ + Tcl_TimerToken token; /* Identifies handler so it can be deleted. */ + struct TimerHandler *nextPtr; /* Next and prev event in timer/idle queue, */ + struct TimerHandler *prevPtr; /* or NULL for end/start of the queue. */ + Tcl_Time time; /* When timer is to fire (if timer event). */ +/* ExtraData */ +} TimerHandler; + +/* + * There is one of the following structures for each of the handlers declared + * in a call to Tcl_DoWhenIdle. All of the currently-active handlers are + * linked together into a list. + */ + +typedef struct IdleHandler { + Tcl_IdleProc *proc; /* Function to call idle event */ + Tcl_IdleDeleteProc *deleteProc; /* Function to cleanup idle event */ + ClientData clientData; /* Argument to pass to proc and deleteProc */ + size_t generation; /* Used to distinguish older handlers from + * recently-created ones. */ + struct IdleHandler *nextPtr;/* Next and prev event in idle queue, */ + struct IdleHandler *prevPtr;/* or NULL for end/start of the queue. */ +/* ExtraData */ +} IdleHandler; + + +/* * The following procedures allow namespaces to be customized to support * special name resolution rules for commands/variables. */ @@ -1797,7 +1841,7 @@ typedef struct Interp { * reached. */ int timeGranularity; /* Mod factor used to determine how often to * evaluate the limit check. */ - Tcl_TimerToken timeEvent; + TimerHandler *timeEvent; /* Handle for a timer callback that will occur * when the time-limit is exceeded. */ @@ -2500,51 +2544,6 @@ MODULE_SCOPE char tclEmptyString; /* 'Shortest possible' after masking */ - -/* - *---------------------------------------------------------------- - * Data structures related to timer / idle events. - *---------------------------------------------------------------- - */ - -/* - * For each timer callback that's pending there is one record of the following - * type. The normal handlers (created by Tcl_CreateTimerHandler) are chained - * together in a list sorted by time (earliest event first). - */ - - -typedef struct TimerHandler { - Tcl_TimerProc *proc; /* Function to call timer event */ - Tcl_TimerDeleteProc *deleteProc; /* Function to cleanup timer event */ - ClientData clientData; /* Argument to pass to proc and deleteProc */ - size_t generation; /* Used to distinguish older handlers from - * recently-created ones. */ - Tcl_TimerToken token; /* Identifies handler so it can be deleted. */ - struct TimerHandler *nextPtr; /* Next and prev event in timer/idle queue, */ - struct TimerHandler *prevPtr; /* or NULL for end/start of the queue. */ - Tcl_Time time; /* When timer is to fire (if timer event). */ -/* ExtraData */ -} TimerHandler; - -/* - * There is one of the following structures for each of the handlers declared - * in a call to Tcl_DoWhenIdle. All of the currently-active handlers are - * linked together into a list. - */ - -typedef struct IdleHandler { - Tcl_IdleProc *proc; /* Function to call idle event */ - Tcl_IdleDeleteProc *deleteProc; /* Function to cleanup idle event */ - ClientData clientData; /* Argument to pass to proc and deleteProc */ - size_t generation; /* Used to distinguish older handlers from - * recently-created ones. */ - struct IdleHandler *nextPtr;/* Next and prev event in idle queue, */ - struct IdleHandler *prevPtr;/* or NULL for end/start of the queue. */ -/* ExtraData */ -} IdleHandler; - - /* * Macros to wrap ExtraData and TimerHandler resp. IdleHandler (and vice versa) */ diff --git a/generic/tclInterp.c b/generic/tclInterp.c index dbbf10a..cab58bd 100644 --- a/generic/tclInterp.c +++ b/generic/tclInterp.c @@ -3511,7 +3511,7 @@ TclLimitRemoveAllHandlers( */ if (iPtr->limit.timeEvent != NULL) { - Tcl_DeleteTimerHandler(iPtr->limit.timeEvent); + TclDeleteTimerHandler(iPtr->limit.timeEvent); iPtr->limit.timeEvent = NULL; } } @@ -3681,6 +3681,15 @@ Tcl_LimitGetCommands( return iPtr->limit.cmdCount; } + +static void +TimeLimitDeleteCallback( + ClientData clientData) +{ + Interp *iPtr = clientData; + + iPtr->limit.timeEvent = NULL; +} /* *---------------------------------------------------------------------- @@ -3711,7 +3720,7 @@ Tcl_LimitSetTime( memcpy(&iPtr->limit.time, timeLimitPtr, sizeof(Tcl_Time)); if (iPtr->limit.timeEvent != NULL) { - Tcl_DeleteTimerHandler(iPtr->limit.timeEvent); + TclDeleteTimerHandler(iPtr->limit.timeEvent); } nextMoment.sec = timeLimitPtr->sec; nextMoment.usec = timeLimitPtr->usec+10; @@ -3719,8 +3728,9 @@ Tcl_LimitSetTime( nextMoment.sec++; nextMoment.usec -= 1000000; } - iPtr->limit.timeEvent = TclCreateAbsoluteTimerHandler(&nextMoment, - TimeLimitCallback, interp); + iPtr->limit.timeEvent = TclCreateAbsoluteTimerHandlerEx(&nextMoment, + TimeLimitCallback, TimeLimitDeleteCallback, 0); + iPtr->limit.timeEvent->clientData = interp; iPtr->limit.exceeded &= ~TCL_LIMIT_TIME; } -- cgit v0.12 From c2e3847bb8d6327328f95266f169d2d3d49b1861 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:22:04 +0000 Subject: after-id: introduced object of type "afterObjType" as self-referenced weak pointer to timer/idle event, used for fast access to the "after" event (cancel, info etc.); test cases extended to cover it additionally --- generic/tclTimer.c | 203 +++++++++++++++++++++++++++++++++++++++++++++-------- tests/event.test | 53 ++++++++++++++ 2 files changed, 227 insertions(+), 29 deletions(-) diff --git a/generic/tclTimer.c b/generic/tclTimer.c index 40316ca..68c1865 100644 --- a/generic/tclTimer.c +++ b/generic/tclTimer.c @@ -26,6 +26,7 @@ typedef struct AfterInfo { * interp in which command will be * executed. */ Tcl_Obj *commandPtr; /* Command to execute. */ + Tcl_Obj *selfPtr; /* Points to the handle object (self) */ size_t id; /* Integer identifier for command */ int flags; /* Flags (IDLE_EVENT) */ struct AfterInfo *nextPtr; /* Next in list of all "after" commands for @@ -123,15 +124,127 @@ static void AfterCleanupProc(ClientData clientData, static int AfterDelay(Tcl_Interp *interp, Tcl_WideInt ms); static void AfterProc(ClientData clientData); static void FreeAfterPtr(ClientData clientData); -static AfterInfo * GetAfterEvent(AfterAssocData *assocPtr, - Tcl_Obj *commandPtr); +static AfterInfo * GetAfterEvent(AfterAssocData *assocPtr, Tcl_Obj *objPtr); static ThreadSpecificData *InitTimer(void); static void TimerExitProc(ClientData clientData); static int TimerHandlerEventProc(Tcl_Event *evPtr, int flags); static void TimerCheckProc(ClientData clientData, int flags); static void TimerSetupProc(ClientData clientData, int flags); - /* +static void AfterObj_DupInternalRep(Tcl_Obj *, Tcl_Obj *); +static void AfterObj_FreeInternalRep(Tcl_Obj *); +static void AfterObj_UpdateString(Tcl_Obj *); + + + +/* + * Type definition. + */ + +Tcl_ObjType afterObjType = { + "after", /* name */ + AfterObj_FreeInternalRep, /* freeIntRepProc */ + AfterObj_DupInternalRep, /* dupIntRepProc */ + AfterObj_UpdateString, /* updateStringProc */ + NULL /* setFromAnyProc */ +}; + +/* + *---------------------------------------------------------------------- + */ +static void +AfterObj_DupInternalRep(srcPtr, dupPtr) + Tcl_Obj *srcPtr; + Tcl_Obj *dupPtr; +{ + /* + * Because we should have only a single reference to the after event, + * we'll copy string representation only. + */ + if (dupPtr->bytes == NULL) { + if (srcPtr->bytes == NULL) { + AfterObj_UpdateString(srcPtr); + } + if (srcPtr->bytes != tclEmptyStringRep) { + TclInitStringRep(dupPtr, srcPtr->bytes, srcPtr->length); + } else { + dupPtr->bytes = tclEmptyStringRep; + } + } +} +/* + *---------------------------------------------------------------------- + */ +static void +AfterObj_FreeInternalRep(objPtr) + Tcl_Obj *objPtr; +{ + /* + * Because we should always have a reference by active after event, + * so it is a triggered / canceled event - just reset type and pointers + */ + objPtr->internalRep.twoPtrValue.ptr1 = NULL; + objPtr->internalRep.twoPtrValue.ptr2 = NULL; + objPtr->typePtr = NULL; + + /* prevent no string representation bug */ + if (objPtr->bytes == NULL) { + objPtr->length = 0; + objPtr->bytes = tclEmptyStringRep; + } +} +/* + *---------------------------------------------------------------------- + */ +static void +AfterObj_UpdateString(objPtr) + Tcl_Obj *objPtr; +{ + char buf[16 + TCL_INTEGER_SPACE]; + int len; + + AfterInfo *afterPtr = (AfterInfo*)objPtr->internalRep.twoPtrValue.ptr1; + + /* if already triggered / canceled - equivalent not found, we can use empty */ + if (!afterPtr) { + objPtr->length = 0; + objPtr->bytes = tclEmptyStringRep; + return; + } + + len = sprintf(buf, "after#%d", afterPtr->id); + + objPtr->length = len; + objPtr->bytes = ckalloc((size_t)++len); + if (objPtr->bytes) + memcpy(objPtr->bytes, buf, len); + +} +/* + *---------------------------------------------------------------------- + */ +Tcl_Obj* +GetAfterObj( + AfterInfo *afterPtr) +{ + Tcl_Obj * objPtr = afterPtr->selfPtr; + + if (objPtr != NULL) { + return objPtr; + } + + TclNewObj(objPtr); + objPtr->typePtr = &afterObjType; + objPtr->bytes = NULL; + objPtr->internalRep.twoPtrValue.ptr1 = afterPtr; + objPtr->internalRep.twoPtrValue.ptr2 = NULL; + Tcl_IncrRefCount(objPtr); + afterPtr->selfPtr = objPtr; + + return objPtr; +}; + +/* *---------------------------------------------------------------------- * * InitTimer -- @@ -915,7 +1028,6 @@ Tcl_AfterObjCmd( AfterAssocData *assocPtr; int length; int index; - char buf[16 + TCL_INTEGER_SPACE]; static CONST char *afterSubCmds[] = { "cancel", "idle", "info", NULL }; @@ -998,6 +1110,7 @@ Tcl_AfterObjCmd( TclSpliceTailEx(afterPtr, assocPtr->firstAfterPtr, assocPtr->lastAfterPtr); afterPtr->flags = 0; + afterPtr->selfPtr = NULL; if (objc == 3) { afterPtr->commandPtr = objv[2]; @@ -1018,7 +1131,7 @@ Tcl_AfterObjCmd( afterPtr->id = tsdPtr->afterId++; - Tcl_SetObjResult(interp, Tcl_ObjPrintf("after#%d", afterPtr->id)); + Tcl_SetObjResult(interp, GetAfterObj(afterPtr)); return TCL_OK; } case AFTER_CANCEL: { @@ -1030,27 +1143,35 @@ Tcl_AfterObjCmd( Tcl_WrongNumArgs(interp, 2, objv, "id|command"); return TCL_ERROR; } + + afterPtr = NULL; if (objc == 3) { commandPtr = objv[2]; } else { commandPtr = Tcl_ConcatObj(objc-2, objv+2);; } - command = Tcl_GetStringFromObj(commandPtr, &length); - for (afterPtr = assocPtr->lastAfterPtr; afterPtr != NULL; - afterPtr = afterPtr->prevPtr) { - tempCommand = Tcl_GetStringFromObj(afterPtr->commandPtr, - &tempLength); - if ((length == tempLength) - && (memcmp((void*) command, (void*) tempCommand, - (unsigned) length) == 0)) { - break; + if (commandPtr->typePtr == &afterObjType) { + afterPtr = (AfterInfo*)commandPtr->internalRep.twoPtrValue.ptr1; + } else { + command = Tcl_GetStringFromObj(commandPtr, &length); + for (afterPtr = assocPtr->lastAfterPtr; + afterPtr != NULL; + afterPtr = afterPtr->prevPtr + ) { + tempCommand = Tcl_GetStringFromObj(afterPtr->commandPtr, + &tempLength); + if ((length == tempLength) + && (memcmp((void*) command, (void*) tempCommand, + (unsigned) length) == 0)) { + break; + } + } + if (afterPtr == NULL) { + afterPtr = GetAfterEvent(assocPtr, commandPtr); + } + if (objc != 3) { + Tcl_DecrRefCount(commandPtr); } - } - if (afterPtr == NULL) { - afterPtr = GetAfterEvent(assocPtr, commandPtr); - } - if (objc != 3) { - Tcl_DecrRefCount(commandPtr); } if (afterPtr != NULL) { if (!(afterPtr->flags & IDLE_EVENT)) { @@ -1081,6 +1202,7 @@ Tcl_AfterObjCmd( TclSpliceTailEx(afterPtr, assocPtr->firstAfterPtr, assocPtr->lastAfterPtr); afterPtr->flags = IDLE_EVENT; + afterPtr->selfPtr = NULL; if (objc == 3) { afterPtr->commandPtr = objv[2]; @@ -1091,7 +1213,7 @@ Tcl_AfterObjCmd( afterPtr->id = tsdPtr->afterId++; - Tcl_SetObjResult(interp, Tcl_ObjPrintf("after#%d", afterPtr->id)); + Tcl_SetObjResult(interp, GetAfterObj(afterPtr)); return TCL_OK; }; @@ -1099,19 +1221,27 @@ Tcl_AfterObjCmd( Tcl_Obj *resultListPtr; if (objc == 2) { - for (afterPtr = assocPtr->lastAfterPtr; afterPtr != NULL; - afterPtr = afterPtr->prevPtr) { - if (assocPtr->interp == interp) { - sprintf(buf, "after#%d", afterPtr->id); - Tcl_AppendElement(interp, buf); + /* return list of all after-events */ + Tcl_Obj *listPtr = Tcl_NewListObj(0, NULL); + for (afterPtr = assocPtr->lastAfterPtr; + afterPtr != NULL; + afterPtr = afterPtr->prevPtr + ) { + if (assocPtr->interp != interp) { + continue; } + + Tcl_ListObjAppendElement(NULL, listPtr, GetAfterObj(afterPtr)); } + + Tcl_SetObjResult(interp, listPtr); return TCL_OK; } if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "?id?"); return TCL_ERROR; } + afterPtr = GetAfterEvent(assocPtr, objv[2]); if (afterPtr == NULL) { Tcl_AppendResult(interp, "event \"", TclGetString(objv[2]), @@ -1228,7 +1358,7 @@ static AfterInfo * GetAfterEvent( AfterAssocData *assocPtr, /* Points to "after"-related information for * this interpreter. */ - Tcl_Obj *commandPtr) + Tcl_Obj *objPtr) { char *cmdString; /* Textual identifier for after event, such as * "after#6". */ @@ -1236,7 +1366,11 @@ GetAfterEvent( int id; char *end; - cmdString = TclGetString(commandPtr); + if (objPtr->typePtr == &afterObjType) { + return (AfterInfo*)objPtr->internalRep.twoPtrValue.ptr1; + } + + cmdString = TclGetString(objPtr); if (strncmp(cmdString, "after#", 6) != 0) { return NULL; } @@ -1288,7 +1422,6 @@ AfterProc( * a core dump. */ - /* remove delete proc from handler (we'll do cleanup here) */ if (!(afterPtr->flags & IDLE_EVENT)) { AfterInfo2TimerHandler(afterPtr)->deleteProc = NULL; @@ -1296,6 +1429,12 @@ AfterProc( AfterInfo2IdleHandler(afterPtr)->deleteProc = NULL; } + /* release object (mark it was triggered) */ + if (afterPtr->selfPtr && afterPtr->selfPtr->typePtr == &afterObjType) { + afterPtr->selfPtr->internalRep.twoPtrValue.ptr1 = NULL; + Tcl_DecrRefCount(afterPtr->selfPtr); + } + /* detach entry from the owner's list */ TclSpliceOutEx(afterPtr, assocPtr->firstAfterPtr, assocPtr->lastAfterPtr); @@ -1345,6 +1484,12 @@ FreeAfterPtr( AfterInfo *afterPtr = (AfterInfo *) clientData; AfterAssocData *assocPtr = afterPtr->assocPtr; + /* release object (mark it was triggered) */ + if (afterPtr->selfPtr && afterPtr->selfPtr->typePtr == &afterObjType) { + afterPtr->selfPtr->internalRep.twoPtrValue.ptr1 = NULL; + Tcl_DecrRefCount(afterPtr->selfPtr); + } + /* detach entry from the owner's list */ TclSpliceOutEx(afterPtr, assocPtr->firstAfterPtr, assocPtr->lastAfterPtr); diff --git a/tests/event.test b/tests/event.test index 4996b97..b6d4144 100644 --- a/tests/event.test +++ b/tests/event.test @@ -513,6 +513,59 @@ test event-11.4 {Tcl_VwaitCmd procedure} {} { list [vwait y] $x $y $z $q } {{} x-done y-done before q-done} +test event-11.4.2 {cancel} {} { + foreach i [after info] { + after cancel $i + } + set x {} + # success cases: + after 10 {lappend x 1} + after 10 {lappend x 2} + after 10 {lappend x 3} + # cancel via object representation (4-6) and searching by id (7-9): + foreach i [list \ + [after 0 {lappend x 4-unexpected}] \ + [after 5 {lappend x 5-unexpected}] \ + [after 10 {lappend x 6-unexpected}] \ + [string trim " [after 0 {lappend x 7-unexpected}] "] \ + [string trim " [after 5 {lappend x 8-unexpected}] "] \ + [string trim " [after 10 {lappend x 9-unexpected}] "] \ + ] { + after cancel $i + } + after 20 {set y done} + list [vwait y] $x $y +} {{} {1 2 3} done} + +test event-11.4.3 {cancel twice and info} {} { + foreach i [after info] { + after cancel $i + } + set x {} + # success cases: + after 10 {lappend x 1} + after 10 {lappend x 2} + after 10 {lappend x 3} + # cancel via object representation (4-6) and searching by id (7-9): + foreach i [list \ + [after 0 {lappend x 4-unexpected}] \ + [after 5 {lappend x 5-unexpected}] \ + [after 10 {lappend x 6-unexpected}] \ + [string trim " [after 0 {lappend x 7-unexpected}] "] \ + [string trim " [after 5 {lappend x 8-unexpected}] "] \ + [string trim " [after 10 {lappend x 9-unexpected}] "] \ + ] { + after cancel $i + # just to test possible segfault: + after cancel $i + if {![catch {after info $i} i]} {; # unexpected (event doesn't exist) + error "\"after info\" returns \"$i\" - should be an error" + } + } + after 20 {set y done} + list [vwait y] $x $y +} {{} {1 2 3} done} + foreach i [after info] { after cancel $i } -- cgit v0.12 From 420147bdad4cfcba7f38fb2497d6a1848c22d162 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:23:20 +0000 Subject: [bug/stable fix] don't execute TimerSetupProc directly (may be unwanted, because changes the blocking time, also if TCL_TIMER_EVENTS|TCL_IDLE_EVENTS not set), so let do that within Tcl_DoOneEvent cycle only (we have registered an event source). [performance] optimization for "after 0" as immediately execution without time (invoke as soon as possible) - generation and invocation of such timers twice faster now. [performance] leave handler-event in the queue as long as pending timers still available (with expired time or immediate timers) by generation lock, resp. changed/not invalidated timer-queue) - so fewer event/allocations and guarantee to be executed within the next event cycle; --- generic/tclTimer.c | 76 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 21 deletions(-) diff --git a/generic/tclTimer.c b/generic/tclTimer.c index 68c1865..5a34260 100644 --- a/generic/tclTimer.c +++ b/generic/tclTimer.c @@ -135,7 +135,15 @@ static void AfterObj_DupInternalRep(Tcl_Obj *, Tcl_Obj *); static void AfterObj_FreeInternalRep(Tcl_Obj *); static void AfterObj_UpdateString(Tcl_Obj *); +static inline void +QueueTimerHandlerEvent() +{ + Tcl_Event *timerEvPtr; + timerEvPtr = (Tcl_Event *) ckalloc(sizeof(Tcl_Event)); + timerEvPtr->proc = TimerHandlerEventProc; + Tcl_QueueEvent(timerEvPtr, TCL_QUEUE_TAIL); +} /* * Type definition. @@ -367,6 +375,10 @@ Tcl_CreateTimerHandler( * Arrange for a given function to be invoked at a particular time in the * future. * + * Specifying the timePtr as NULL ensures that timer event-handler will + * be queued immediately to guarantee the execution of timer-event as + * soon as possible + * * Results: * The return value is a handler or token of the timer event, which may * be used to delete the event before it fires. @@ -398,7 +410,13 @@ TclCreateAbsoluteTimerHandlerEx( * Fill in fields for the event. */ - memcpy((void *)&timerHandlerPtr->time, (void *)timePtr, sizeof(Tcl_Time)); + if (timePtr) { + memcpy((void *)&timerHandlerPtr->time, (void *)timePtr, + sizeof(timerHandlerPtr->time)); + } else { + memset((void *)&timerHandlerPtr->time, 0, + sizeof(timerHandlerPtr->time)); + } timerHandlerPtr->proc = proc; timerHandlerPtr->deleteProc = deleteProc; timerHandlerPtr->clientData = TimerHandler2ClientData(timerHandlerPtr); @@ -423,7 +441,7 @@ TclCreateAbsoluteTimerHandlerEx( /* search from end as long as one with time before not found */ for (thPtrPos = tsdPtr->lastTimerPtr; thPtrPos != NULL; thPtrPos = thPtrPos->prevPtr) { - if (TCL_TIME_BEFORE(thPtrPos->time, timerHandlerPtr->time)) { + if (!TCL_TIME_BEFORE(timerHandlerPtr->time, thPtrPos->time)) { break; } } @@ -444,7 +462,13 @@ TclCreateAbsoluteTimerHandlerEx( } } - TimerSetupProc(NULL, TCL_ALL_EVENTS); + if (!timePtr) { + /* execute immediately: queue handler event right now */ + if (!tsdPtr->timerPending) { + QueueTimerHandlerEvent(); + } + tsdPtr->timerPending++; + } return timerHandlerPtr; } @@ -615,11 +639,12 @@ TimerCheckProc( ClientData data, /* Not used. */ int flags) /* Event flags as passed to Tcl_DoOneEvent. */ { - Tcl_Event *timerEvPtr; Tcl_Time blockTime; ThreadSpecificData *tsdPtr = InitTimer(); - if ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerList) { + if ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerList + && !tsdPtr->timerPending + ) { /* * Compute the timeout for the next timer on the list. */ @@ -641,12 +666,9 @@ TimerCheckProc( * If the first timer has expired, stick an event on the queue. */ - if (blockTime.sec == 0 && blockTime.usec == 0 && - !tsdPtr->timerPending) { + if (blockTime.sec == 0 && blockTime.usec == 0) { tsdPtr->timerPending = 1; - timerEvPtr = (Tcl_Event *) ckalloc(sizeof(Tcl_Event)); - timerEvPtr->proc = TimerHandlerEventProc; - Tcl_QueueEvent(timerEvPtr, TCL_QUEUE_TAIL); + QueueTimerHandlerEvent(); } } } @@ -716,7 +738,6 @@ TimerHandlerEventProc( * timers appearing before later ones. */ - tsdPtr->timerPending = 0; currentGeneration = tsdPtr->timerGeneration++; Tcl_GetTime(&time); for (timerHandlerPtr = tsdPtr->timerList; @@ -734,6 +755,7 @@ TimerHandlerEventProc( */ if (timerHandlerPtr->generation > currentGeneration) { + tsdPtr->timerPending++; continue; } @@ -761,18 +783,26 @@ TimerHandlerEventProc( /* be sure that timer-list was not changed inside the proc call */ if (currentEpoch != tsdPtr->timerListEpoch) { /* timer-list was changed - stop processing */ + tsdPtr->timerPending++; break; } } + /* don't need to queue event again by pending timer events */ + if (tsdPtr->timerPending > 1) { + tsdPtr->timerPending = 1; + return 0; /* leave handler event in the queue */ + } + /* Reset generation */ if (!tsdPtr->timerList) { tsdPtr->timerGeneration = 0; } - /* Compute the next timeout (first timer on the list). */ - TimerSetupProc(NULL, TCL_TIMER_EVENTS); - return 1; + /* Compute the next timeout (later via TimerSetupProc using the first timer). */ + tsdPtr->timerPending = 0; + + return 1; /* processing done, again later via TimerCheckProc */ } /* @@ -1082,6 +1112,7 @@ Tcl_AfterObjCmd( switch (index) { case -1: { + Tcl_Time *timePtr = NULL; TimerHandler *timerPtr; if (ms < 0) { ms = 0; @@ -1090,15 +1121,18 @@ Tcl_AfterObjCmd( return AfterDelay(interp, ms); } - Tcl_GetTime(&wakeup); - wakeup.sec += (long)(ms / 1000); - wakeup.usec += ((long)(ms % 1000)) * 1000; - if (wakeup.usec > 1000000) { - wakeup.sec++; - wakeup.usec -= 1000000; + if (ms) { + timePtr = &wakeup; + Tcl_GetTime(&wakeup); + wakeup.sec += (long)(ms / 1000); + wakeup.usec += ((long)(ms % 1000)) * 1000; + if (wakeup.usec > 1000000) { + wakeup.sec++; + wakeup.usec -= 1000000; + } } - timerPtr = TclCreateAbsoluteTimerHandlerEx(&wakeup, AfterProc, + timerPtr = TclCreateAbsoluteTimerHandlerEx(timePtr, AfterProc, FreeAfterPtr, sizeof(AfterInfo)); if (timerPtr == NULL) { /* error handled in panic */ return TCL_ERROR; -- cgit v0.12 From 13a39efcafa31a2d71bd8b642e02f9235a34bfda Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:23:31 +0000 Subject: bug fix: wrong release of after-id tcl-object if it switch type (object leak) --- generic/tclTimer.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/generic/tclTimer.c b/generic/tclTimer.c index 5a34260..041163b 100644 --- a/generic/tclTimer.c +++ b/generic/tclTimer.c @@ -1464,12 +1464,15 @@ AfterProc( } /* release object (mark it was triggered) */ - if (afterPtr->selfPtr && afterPtr->selfPtr->typePtr == &afterObjType) { - afterPtr->selfPtr->internalRep.twoPtrValue.ptr1 = NULL; + if (afterPtr->selfPtr) { + if (afterPtr->selfPtr->typePtr == &afterObjType) { + afterPtr->selfPtr->internalRep.twoPtrValue.ptr1 = NULL; + } Tcl_DecrRefCount(afterPtr->selfPtr); + afterPtr->selfPtr = NULL; } - /* detach entry from the owner's list */ + /* detach after-entry from the owner's list */ TclSpliceOutEx(afterPtr, assocPtr->firstAfterPtr, assocPtr->lastAfterPtr); /* @@ -1518,13 +1521,16 @@ FreeAfterPtr( AfterInfo *afterPtr = (AfterInfo *) clientData; AfterAssocData *assocPtr = afterPtr->assocPtr; - /* release object (mark it was triggered) */ - if (afterPtr->selfPtr && afterPtr->selfPtr->typePtr == &afterObjType) { - afterPtr->selfPtr->internalRep.twoPtrValue.ptr1 = NULL; + /* release object (mark it was removed) */ + if (afterPtr->selfPtr) { + if (afterPtr->selfPtr->typePtr == &afterObjType) { + afterPtr->selfPtr->internalRep.twoPtrValue.ptr1 = NULL; + } Tcl_DecrRefCount(afterPtr->selfPtr); + afterPtr->selfPtr = NULL; } - /* detach entry from the owner's list */ + /* detach after-entry from the owner's list */ TclSpliceOutEx(afterPtr, assocPtr->firstAfterPtr, assocPtr->lastAfterPtr); /* free command of entry */ -- cgit v0.12 From f629f9d07d2b465bba72bc71df2d2c9d3856bca8 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:23:40 +0000 Subject: [performance] introduced additional queue for prompt timer events (after 0) that should be executed immediately (no time); normalizes timer, prompt and idle events structures using common TimerEntry structure for all types; --- generic/tcl.h | 1 - generic/tclInt.h | 89 +++++----- generic/tclInterp.c | 4 +- generic/tclTimer.c | 478 +++++++++++++++++++++++++++++----------------------- 4 files changed, 310 insertions(+), 262 deletions(-) diff --git a/generic/tcl.h b/generic/tcl.h index e36f4d8..6a3c66a 100644 --- a/generic/tcl.h +++ b/generic/tcl.h @@ -696,7 +696,6 @@ typedef void (Tcl_FileFreeProc) _ANSI_ARGS_((ClientData clientData)); typedef void (Tcl_FreeInternalRepProc) _ANSI_ARGS_((struct Tcl_Obj *objPtr)); typedef void (Tcl_FreeProc) _ANSI_ARGS_((char *blockPtr)); typedef void (Tcl_IdleProc) _ANSI_ARGS_((ClientData clientData)); -typedef void (Tcl_IdleDeleteProc) _ANSI_ARGS_((ClientData clientData)); typedef void (Tcl_InterpDeleteProc) _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp)); typedef int (Tcl_MathProc) _ANSI_ARGS_((ClientData clientData, diff --git a/generic/tclInt.h b/generic/tclInt.h index 021ba00..a0bab62 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -133,43 +133,54 @@ typedef int ptrdiff_t; *---------------------------------------------------------------- */ +#define TCL_PROMPT_EVENT (1 << 0) /* Mark immediate event */ +#define TCL_IDLE_EVENT (1 << 5) /* Mark idle event */ + /* - * For each timer callback that's pending there is one record of the following - * type. The normal handlers (created by Tcl_CreateTimerHandler) are chained - * together in a list sorted by time (earliest event first). + * This structure used for handling of prompt timer events (without time to + * invoke, e. g. created with "after 0") or declared in a call to Tcl_DoWhenIdle + * (created with "after idle"). All of the currently-active handlers are linked + * together into corresponding list. */ - -typedef struct TimerHandler { - Tcl_TimerProc *proc; /* Function to call timer event */ - Tcl_TimerDeleteProc *deleteProc; /* Function to cleanup timer event */ +typedef struct TimerEntry { + Tcl_TimerProc *proc; /* Function to call timer/idle event */ + Tcl_TimerDeleteProc *deleteProc; /* Function to cleanup idle event */ ClientData clientData; /* Argument to pass to proc and deleteProc */ + int flags; /* Flags (TCL_IDLE_EVENT, TCL_PROMPT_EVENT) */ size_t generation; /* Used to distinguish older handlers from * recently-created ones. */ - Tcl_TimerToken token; /* Identifies handler so it can be deleted. */ - struct TimerHandler *nextPtr; /* Next and prev event in timer/idle queue, */ - struct TimerHandler *prevPtr; /* or NULL for end/start of the queue. */ - Tcl_Time time; /* When timer is to fire (if timer event). */ + struct TimerEntry *nextPtr;/* Next and prev event in idle queue, */ + struct TimerEntry *prevPtr;/* or NULL for end/start of the queue. */ /* ExtraData */ -} TimerHandler; +} TimerEntry; /* - * There is one of the following structures for each of the handlers declared - * in a call to Tcl_DoWhenIdle. All of the currently-active handlers are - * linked together into a list. + * For each timer callback that's pending there is one record of the following + * type. The normal handlers (created by Tcl_CreateTimerHandler) are chained + * together in a list via TimerEntry sorted by time (earliest event first). */ -typedef struct IdleHandler { - Tcl_IdleProc *proc; /* Function to call idle event */ - Tcl_IdleDeleteProc *deleteProc; /* Function to cleanup idle event */ - ClientData clientData; /* Argument to pass to proc and deleteProc */ - size_t generation; /* Used to distinguish older handlers from - * recently-created ones. */ - struct IdleHandler *nextPtr;/* Next and prev event in idle queue, */ - struct IdleHandler *prevPtr;/* or NULL for end/start of the queue. */ + +typedef struct TimerHandler { + Tcl_Time time; /* When timer is to fire (if timer event). */ + Tcl_TimerToken token; /* Identifies handler so it can be deleted. */ + struct TimerEntry entry; /* ExtraData */ -} IdleHandler; +} TimerHandler; + +/* + * Macros to wrap ExtraData and TimerHandler resp. TimerEntry (and vice versa) + */ +#define TimerEntry2ClientData(ptr) \ + ( (ClientData)(((TimerEntry *)(ptr))+1) ) +#define ClientData2TimerEntry(ptr) \ + ( ((TimerEntry *)(ptr))-1 ) +#define TimerEntry2TimerHandler(ptr) \ + ( (TimerHandler *)(((char *)(ptr)) - TclOffset(TimerHandler,entry)) ) +#define TimerHandler2TimerEntry(ptr) \ + ( &(ptr)->entry ) /* * The following procedures allow namespaces to be customized to support @@ -1841,8 +1852,7 @@ typedef struct Interp { * reached. */ int timeGranularity; /* Mod factor used to determine how often to * evaluate the limit check. */ - TimerHandler *timeEvent; - /* Handle for a timer callback that will occur + TimerEntry *timeEvent; /* Handle for a timer callback that will occur * when the time-limit is exceeded. */ Tcl_HashTable callbacks;/* Mapping from (interp,type) pair to data @@ -2545,19 +2555,6 @@ MODULE_SCOPE char tclEmptyString; /* - * Macros to wrap ExtraData and TimerHandler resp. IdleHandler (and vice versa) - */ -#define TimerHandler2ClientData(ptr) \ - ( (ClientData)(((TimerHandler *)(ptr))+1) ) -#define ClientData2TimerHandler(ptr) \ - ( ((TimerHandler *)(ptr))-1 ) - -#define IdleHandler2ClientData(ptr) \ - ( (ClientData)(((IdleHandler *)(ptr))+1) ) -#define ClientData2IdleHandler(ptr) \ - ( ((IdleHandler *)(ptr))-1 ) - -/* *---------------------------------------------------------------- * Procedures shared among Tcl modules but not used by the outside world: *---------------------------------------------------------------- @@ -2925,14 +2922,14 @@ MODULE_SCOPE int Tcl_ContinueObjCmd(ClientData clientData, MODULE_SCOPE Tcl_TimerToken TclCreateAbsoluteTimerHandler( Tcl_Time *timePtr, Tcl_TimerProc *proc, ClientData clientData); -MODULE_SCOPE TimerHandler* TclCreateAbsoluteTimerHandlerEx( - Tcl_Time *timePtr, Tcl_TimerProc *proc, - Tcl_TimerDeleteProc *deleteProc, size_t extraSize); -MODULE_SCOPE void TclDeleteTimerHandler(TimerHandler *timerHandlerPtr); -MODULE_SCOPE IdleHandler* TclCreateIdleHandlerEx( - Tcl_IdleProc *proc, Tcl_IdleDeleteProc *deleteProc, +MODULE_SCOPE TimerEntry* TclCreateAbsoluteTimerHandlerEx( + Tcl_Time *timePtr, + Tcl_TimerProc *proc, Tcl_TimerDeleteProc *deleteProc, size_t extraDataSize); -MODULE_SCOPE void TclDeleteIdleHandler(IdleHandler *idlePtr); +MODULE_SCOPE TimerEntry* TclCreateTimerEntryEx( + Tcl_TimerProc *proc, Tcl_TimerDeleteProc *deleteProc, + size_t extraDataSize, int flags); +MODULE_SCOPE void TclDeleteTimerEntry(TimerEntry *entryPtr); MODULE_SCOPE int TclDefaultBgErrorHandlerObjCmd( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); diff --git a/generic/tclInterp.c b/generic/tclInterp.c index cab58bd..50537de 100644 --- a/generic/tclInterp.c +++ b/generic/tclInterp.c @@ -3511,7 +3511,7 @@ TclLimitRemoveAllHandlers( */ if (iPtr->limit.timeEvent != NULL) { - TclDeleteTimerHandler(iPtr->limit.timeEvent); + TclDeleteTimerEntry(iPtr->limit.timeEvent); iPtr->limit.timeEvent = NULL; } } @@ -3720,7 +3720,7 @@ Tcl_LimitSetTime( memcpy(&iPtr->limit.time, timeLimitPtr, sizeof(Tcl_Time)); if (iPtr->limit.timeEvent != NULL) { - TclDeleteTimerHandler(iPtr->limit.timeEvent); + TclDeleteTimerEntry(iPtr->limit.timeEvent); } nextMoment.sec = timeLimitPtr->sec; nextMoment.usec = timeLimitPtr->usec+10; diff --git a/generic/tclTimer.c b/generic/tclTimer.c index 041163b..7eee89e 100644 --- a/generic/tclTimer.c +++ b/generic/tclTimer.c @@ -18,8 +18,6 @@ * interpreter are linked together in a list. */ -#define IDLE_EVENT (1 << 1) /* Mark idle event */ - typedef struct AfterInfo { struct AfterAssocData *assocPtr; /* Pointer to the "tclAfter" assocData for the @@ -28,7 +26,6 @@ typedef struct AfterInfo { Tcl_Obj *commandPtr; /* Command to execute. */ Tcl_Obj *selfPtr; /* Points to the handle object (self) */ size_t id; /* Integer identifier for command */ - int flags; /* Flags (IDLE_EVENT) */ struct AfterInfo *nextPtr; /* Next in list of all "after" commands for * this interpreter. */ struct AfterInfo *prevPtr; /* Prev in list of all "after" commands for @@ -63,15 +60,17 @@ typedef struct AfterAssocData { */ typedef struct { - TimerHandler *timerList; /* First event in queue. */ - TimerHandler *lastTimerPtr; /* Last event in queue. */ + TimerEntry *timerList; /* First event in queue. */ + TimerEntry *lastTimerPtr; /* Last event in queue. */ + TimerEntry *promptList; /* First immediate event in queue. */ + TimerEntry *lastPromptPtr; /* Last immediate event in queue. */ size_t timerListEpoch; /* Used for safe process of event queue (stop * the cycle after modifying of event queue) */ int lastTimerId; /* Timer identifier of most recently created * timer. */ int timerPending; /* 1 if a timer event is in the queue. */ - IdleHandler *idleList; /* First in list of all idle handlers. */ - IdleHandler *lastIdlePtr; /* Last in list (or NULL for empty list). */ + TimerEntry *idleList; /* First in list of all idle handlers. */ + TimerEntry *lastIdlePtr; /* Last in list (or NULL for empty list). */ size_t timerGeneration; /* Used to fill in the "generation" fields of */ size_t idleGeneration; /* timer or idle structures. Increments each * time we place a new handler to queue inside, @@ -87,15 +86,10 @@ static Tcl_ThreadDataKey dataKey; * Helper macros to wrap AfterInfo and handlers (and vice versa) */ -#define TimerHandler2AfterInfo(ptr) \ - ( (AfterInfo*)TimerHandler2ClientData(ptr) ) -#define AfterInfo2TimerHandler(ptr) \ - ClientData2TimerHandler(ptr) - -#define IdleHandler2AfterInfo(ptr) \ - ( (AfterInfo*)IdleHandler2ClientData(ptr) ) -#define AfterInfo2IdleHandler(ptr) \ - ClientData2IdleHandler(ptr) +#define TimerEntry2AfterInfo(ptr) \ + ( (AfterInfo*)TimerEntry2ClientData(ptr) ) +#define AfterInfo2TimerEntry(ptr) \ + ClientData2TimerEntry(ptr) /* * Helper macros for working with times. TCL_TIME_BEFORE encodes how to write @@ -128,7 +122,9 @@ static AfterInfo * GetAfterEvent(AfterAssocData *assocPtr, Tcl_Obj *objPtr); static ThreadSpecificData *InitTimer(void); static void TimerExitProc(ClientData clientData); static int TimerHandlerEventProc(Tcl_Event *evPtr, int flags); +#if 0 static void TimerCheckProc(ClientData clientData, int flags); +#endif static void TimerSetupProc(ClientData clientData, int flags); static void AfterObj_DupInternalRep(Tcl_Obj *, Tcl_Obj *); @@ -276,7 +272,7 @@ InitTimer(void) if (tsdPtr == NULL) { tsdPtr = TCL_TSD_INIT(&dataKey); - Tcl_CreateEventSource(TimerSetupProc, TimerCheckProc, NULL); + Tcl_CreateEventSource(TimerSetupProc, NULL, tsdPtr); Tcl_CreateThreadExitHandler(TimerExitProc, NULL); } return tsdPtr; @@ -306,15 +302,18 @@ TimerExitProc( ThreadSpecificData *tsdPtr = (ThreadSpecificData *) TclThreadDataKeyGet(&dataKey); - Tcl_DeleteEventSource(TimerSetupProc, TimerCheckProc, NULL); if (tsdPtr != NULL) { - register TimerHandler *timerHandlerPtr; + Tcl_DeleteEventSource(TimerSetupProc, NULL, tsdPtr); - while ((timerHandlerPtr = tsdPtr->timerList) != NULL) { - tsdPtr->timerList = timerHandlerPtr->nextPtr; - ckfree((char *) timerHandlerPtr); + while ((tsdPtr->lastPromptPtr) != NULL) { + TclDeleteTimerEntry(tsdPtr->lastPromptPtr); + } + while ((tsdPtr->lastTimerPtr) != NULL) { + TclDeleteTimerEntry(tsdPtr->lastTimerPtr); + } + while ((tsdPtr->lastIdlePtr) != NULL) { + TclDeleteTimerEntry(tsdPtr->lastIdlePtr); } - tsdPtr->lastTimerPtr = NULL; } } @@ -343,7 +342,7 @@ Tcl_CreateTimerHandler( Tcl_TimerProc *proc, /* Function to invoke. */ ClientData clientData) /* Arbitrary data to pass to proc. */ { - register TimerHandler *timerHandlerPtr; + register TimerEntry *entryPtr; Tcl_Time time; /* @@ -358,13 +357,13 @@ Tcl_CreateTimerHandler( time.sec += 1; } - timerHandlerPtr = TclCreateAbsoluteTimerHandlerEx(&time, proc, NULL, 0); - if (timerHandlerPtr == NULL) { + entryPtr = TclCreateAbsoluteTimerHandlerEx(&time, proc, NULL, 0); + if (entryPtr == NULL) { return NULL; } - timerHandlerPtr->clientData = clientData; + entryPtr->clientData = clientData; - return timerHandlerPtr->token; + return TimerEntry2TimerHandler(entryPtr)->token; } /* @@ -375,13 +374,9 @@ Tcl_CreateTimerHandler( * Arrange for a given function to be invoked at a particular time in the * future. * - * Specifying the timePtr as NULL ensures that timer event-handler will - * be queued immediately to guarantee the execution of timer-event as - * soon as possible - * * Results: - * The return value is a handler or token of the timer event, which may - * be used to delete the event before it fires. + * The return value is a handler entry or token of the timer event, which + * may be used to delete the event before it fires. * * Side effects: * When the time in timePtr has been reached, proc will be invoked @@ -390,40 +385,37 @@ Tcl_CreateTimerHandler( *-------------------------------------------------------------- */ -TimerHandler* +TimerEntry* TclCreateAbsoluteTimerHandlerEx( Tcl_Time *timePtr, /* Time to be invoked */ Tcl_TimerProc *proc, /* Function to invoke */ Tcl_TimerDeleteProc *deleteProc, /* Function to cleanup */ size_t extraDataSize) { - register TimerHandler *timerHandlerPtr, *thPtrPos; + register TimerEntry *entryPtr, *entryPtrPos; + register TimerHandler *timerPtr; ThreadSpecificData *tsdPtr; tsdPtr = InitTimer(); - timerHandlerPtr = (TimerHandler *) ckalloc(sizeof(TimerHandler) + extraDataSize); - if (timerHandlerPtr == NULL) { + timerPtr = (TimerHandler *) ckalloc(sizeof(TimerHandler) + extraDataSize); + if (timerPtr == NULL) { return NULL; } + entryPtr = TimerHandler2TimerEntry(timerPtr); /* * Fill in fields for the event. */ - if (timePtr) { - memcpy((void *)&timerHandlerPtr->time, (void *)timePtr, - sizeof(timerHandlerPtr->time)); - } else { - memset((void *)&timerHandlerPtr->time, 0, - sizeof(timerHandlerPtr->time)); - } - timerHandlerPtr->proc = proc; - timerHandlerPtr->deleteProc = deleteProc; - timerHandlerPtr->clientData = TimerHandler2ClientData(timerHandlerPtr); - timerHandlerPtr->generation = tsdPtr->timerGeneration; + memcpy((void *)&(timerPtr->time), (void *)timePtr, sizeof(*timePtr)); + entryPtr->proc = proc; + entryPtr->deleteProc = deleteProc; + entryPtr->clientData = TimerEntry2ClientData(entryPtr); + entryPtr->flags = 0; + entryPtr->generation = tsdPtr->timerGeneration; tsdPtr->timerListEpoch++; /* signal-timer list was changed */ tsdPtr->lastTimerId++; - timerHandlerPtr->token = (Tcl_TimerToken) INT2PTR(tsdPtr->lastTimerId); + timerPtr->token = (Tcl_TimerToken) INT2PTR(tsdPtr->lastTimerId); /* * Add the event to the queue in the correct position @@ -431,46 +423,40 @@ TclCreateAbsoluteTimerHandlerEx( */ /* if before current first (e. g. "after 0" before first "after 1000") */ - if ( !(thPtrPos = tsdPtr->timerList) - || TCL_TIME_BEFORE(timerHandlerPtr->time, thPtrPos->time) + if ( !(entryPtrPos = tsdPtr->timerList) + || TCL_TIME_BEFORE(timerPtr->time, + TimerEntry2TimerHandler(entryPtrPos)->time) ) { /* splice to the head */ - TclSpliceInEx(timerHandlerPtr, + TclSpliceInEx(entryPtr, tsdPtr->timerList, tsdPtr->lastTimerPtr); } else { /* search from end as long as one with time before not found */ - for (thPtrPos = tsdPtr->lastTimerPtr; thPtrPos != NULL; - thPtrPos = thPtrPos->prevPtr) { - if (!TCL_TIME_BEFORE(timerHandlerPtr->time, thPtrPos->time)) { + for (entryPtrPos = tsdPtr->lastTimerPtr; entryPtrPos != NULL; + entryPtrPos = entryPtrPos->prevPtr) { + if (!TCL_TIME_BEFORE(timerPtr->time, + TimerEntry2TimerHandler(entryPtrPos)->time)) { break; } } /* normally it should be always true, because checked above, but ... */ - if (thPtrPos != NULL) { + if (entryPtrPos != NULL) { /* insert after found element (with time before new) */ - timerHandlerPtr->prevPtr = thPtrPos; - if ((timerHandlerPtr->nextPtr = thPtrPos->nextPtr)) { - thPtrPos->nextPtr->prevPtr = timerHandlerPtr; + entryPtr->prevPtr = entryPtrPos; + if ((entryPtr->nextPtr = entryPtrPos->nextPtr)) { + entryPtrPos->nextPtr->prevPtr = entryPtr; } else { - tsdPtr->lastTimerPtr = timerHandlerPtr; + tsdPtr->lastTimerPtr = entryPtr; } - thPtrPos->nextPtr = timerHandlerPtr; + entryPtrPos->nextPtr = entryPtr; } else { /* unexpected case, but ... splice to the head */ - TclSpliceInEx(timerHandlerPtr, + TclSpliceInEx(entryPtr, tsdPtr->timerList, tsdPtr->lastTimerPtr); } } - if (!timePtr) { - /* execute immediately: queue handler event right now */ - if (!tsdPtr->timerPending) { - QueueTimerHandlerEvent(); - } - tsdPtr->timerPending++; - } - - return timerHandlerPtr; + return entryPtr; } Tcl_TimerToken @@ -479,15 +465,15 @@ TclCreateAbsoluteTimerHandler( Tcl_TimerProc *proc, ClientData clientData) { - register TimerHandler *timerHandlerPtr; + register TimerEntry *entryPtr; - timerHandlerPtr = TclCreateAbsoluteTimerHandlerEx(timePtr, proc, NULL, 0); - if (timerHandlerPtr == NULL) { + entryPtr = TclCreateAbsoluteTimerHandlerEx(timePtr, proc, NULL, 0); + if (entryPtr == NULL) { return NULL; } - timerHandlerPtr->clientData = clientData; + entryPtr->clientData = clientData; - return timerHandlerPtr->token; + return TimerEntry2TimerHandler(entryPtr)->token; } /* @@ -513,48 +499,81 @@ Tcl_DeleteTimerHandler( Tcl_TimerToken token) /* Result previously returned by * Tcl_CreateTimerHandler. */ { - register TimerHandler *timerHandlerPtr; + register TimerEntry *entryPtr; ThreadSpecificData *tsdPtr = InitTimer(); if (token == NULL) { return; } - for (timerHandlerPtr = tsdPtr->lastTimerPtr; - timerHandlerPtr != NULL; - timerHandlerPtr = timerHandlerPtr->prevPtr + for (entryPtr = tsdPtr->lastTimerPtr; + entryPtr != NULL; + entryPtr = entryPtr->prevPtr ) { - if (timerHandlerPtr->token != token) { + if (TimerEntry2TimerHandler(entryPtr)->token != token) { continue; } - TclDeleteTimerHandler(timerHandlerPtr); + TclDeleteTimerEntry(entryPtr); return; } } + +/* + *-------------------------------------------------------------- + * + * TclDeleteTimerEntry -- + * + * Delete a previously-registered prompt, timer or idle handler. + * + * Results: + * None. + * + * Side effects: + * Destroy the timer callback, so that its associated function will + * not be called. If the callback has already fired this will be executed + * internally. + * + *-------------------------------------------------------------- + */ + void -TclDeleteTimerHandler( - TimerHandler *timerHandlerPtr) /* Result previously returned by */ - /* TclCreateAbsoluteTimerHandlerEx. */ +TclDeleteTimerEntry( + TimerEntry *entryPtr) /* Result previously returned by */ + /* TclCreateAbsoluteTimerHandlerEx or TclCreateTimerEntryEx. */ { ThreadSpecificData *tsdPtr; - if (timerHandlerPtr == NULL) { + if (entryPtr == NULL) { return; } tsdPtr = InitTimer(); - tsdPtr->timerListEpoch++; /* signal-timer list was changed */ - TclSpliceOutEx(timerHandlerPtr, tsdPtr->timerList, tsdPtr->lastTimerPtr); + if (entryPtr->flags & TCL_PROMPT_EVENT) { + /* prompt handler */ + TclSpliceOutEx(entryPtr, tsdPtr->promptList, tsdPtr->lastPromptPtr); + } else if (entryPtr->flags & TCL_IDLE_EVENT) { + /* idle handler */ + TclSpliceOutEx(entryPtr, tsdPtr->idleList, tsdPtr->lastIdlePtr); + } else { + /* timer event-handler */ + tsdPtr->timerListEpoch++; /* signal-timer list was changed */ + TclSpliceOutEx(entryPtr, tsdPtr->timerList, tsdPtr->lastTimerPtr); + } /* free it via deleteProc or ckfree */ - if (timerHandlerPtr->deleteProc) { - (*timerHandlerPtr->deleteProc)(timerHandlerPtr->clientData); + if (entryPtr->deleteProc) { + (*entryPtr->deleteProc)(entryPtr->clientData); } - ckfree((char *)timerHandlerPtr); + if (entryPtr->flags & (TCL_PROMPT_EVENT|TCL_IDLE_EVENT)) { + ckfree((char *)entryPtr); + } else { + /* shift to the allocated pointer */ + ckfree((char *)TimerEntry2TimerHandler(entryPtr)); + } } /* @@ -580,10 +599,12 @@ TimerSetupProc( ClientData data, /* Not used. */ int flags) /* Event flags as passed to Tcl_DoOneEvent. */ { - Tcl_Time blockTime; - ThreadSpecificData *tsdPtr = InitTimer(); + Tcl_Time blockTime, *firstTime; + ThreadSpecificData *tsdPtr = (ThreadSpecificData *)data; - if (((flags & TCL_IDLE_EVENTS) && tsdPtr->idleList) + if (tsdPtr == NULL) { tsdPtr = InitTimer(); }; + + if (((flags & TCL_IDLE_EVENTS) && tsdPtr->idleList ) || ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerPending)) { /* * There is an idle handler or a pending timer event, so just poll. @@ -598,9 +619,9 @@ TimerSetupProc( */ Tcl_GetTime(&blockTime); - blockTime.sec = tsdPtr->timerList->time.sec - blockTime.sec; - blockTime.usec = tsdPtr->timerList->time.usec - - blockTime.usec; + firstTime = &(TimerEntry2TimerHandler(tsdPtr->timerList)->time); + blockTime.sec = firstTime->sec - blockTime.sec; + blockTime.usec = firstTime->usec - blockTime.usec; if (blockTime.usec < 0) { blockTime.sec -= 1; blockTime.usec += 1000000; @@ -609,6 +630,16 @@ TimerSetupProc( blockTime.sec = 0; blockTime.usec = 0; } + + /* + * If the first timer has expired, stick an event on the queue. + */ + + if (blockTime.sec == 0 && blockTime.usec == 0) { + tsdPtr->timerPending = 1; + QueueTimerHandlerEvent(); + } + } else { return; } @@ -616,6 +647,7 @@ TimerSetupProc( Tcl_SetMaxBlockTime(&blockTime); } +#if 0 /* *---------------------------------------------------------------------- * @@ -639,20 +671,27 @@ TimerCheckProc( ClientData data, /* Not used. */ int flags) /* Event flags as passed to Tcl_DoOneEvent. */ { - Tcl_Time blockTime; + Tcl_Time blockTime, *firstTime; ThreadSpecificData *tsdPtr = InitTimer(); - if ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerList + if ((flags & TCL_TIMER_EVENTS) && !tsdPtr->timerPending ) { /* * Compute the timeout for the next timer on the list. */ + if (tsdPtr->promptList) { + goto queuetmr; + } + + if (!tsdPtr->timerList) { + return; + } Tcl_GetTime(&blockTime); - blockTime.sec = tsdPtr->timerList->time.sec - blockTime.sec; - blockTime.usec = tsdPtr->timerList->time.usec - - blockTime.usec; + firstTime = &(TimerEntry2TimerHandler(tsdPtr->timerList)->time); + blockTime.sec = firstTime->sec - blockTime.sec; + blockTime.usec = firstTime->usec - blockTime.usec; if (blockTime.usec < 0) { blockTime.sec -= 1; blockTime.usec += 1000000; @@ -667,12 +706,14 @@ TimerCheckProc( */ if (blockTime.sec == 0 && blockTime.usec == 0) { + queuetmr: tsdPtr->timerPending = 1; QueueTimerHandlerEvent(); } } } +#endif /* *---------------------------------------------------------------------- * @@ -700,7 +741,7 @@ TimerHandlerEventProc( int flags) /* Flags that indicate what events to handle, * such as TCL_FILE_EVENTS. */ { - TimerHandler *timerHandlerPtr, *nextPtr; + TimerEntry *entryPtr, *nextPtr; Tcl_Time time; size_t currentGeneration, currentEpoch; ThreadSpecificData *tsdPtr = InitTimer(); @@ -739,14 +780,42 @@ TimerHandlerEventProc( */ currentGeneration = tsdPtr->timerGeneration++; + + /* First process all prompt (immediate) events */ + while ((entryPtr = tsdPtr->promptList) != NULL + && entryPtr->generation <= currentGeneration + ) { + /* detach entry from the owner's list */ + TclSpliceOutEx(entryPtr, tsdPtr->promptList, tsdPtr->lastPromptPtr); + + /* execute event */ + (*entryPtr->proc)(entryPtr->clientData); + + /* free it via deleteProc and ckfree */ + if (entryPtr->deleteProc) { + (*entryPtr->deleteProc)(entryPtr->clientData); + } + ckfree((char *) entryPtr); + } + + /* if pending prompt events (new generation) - repeat event cycle right now */ + if (tsdPtr->promptList) { + tsdPtr->timerPending = 1; + return 0; /* leave handler event in the queue */ + } + + /* Hereafter all timer events with time before now */ + if (!tsdPtr->timerList) { + goto done; + } Tcl_GetTime(&time); - for (timerHandlerPtr = tsdPtr->timerList; - timerHandlerPtr != NULL; - timerHandlerPtr = nextPtr + for (entryPtr = tsdPtr->timerList; + entryPtr != NULL; + entryPtr = nextPtr ) { - nextPtr = timerHandlerPtr->nextPtr; + nextPtr = entryPtr->nextPtr; - if (TCL_TIME_BEFORE(time, timerHandlerPtr->time)) { + if (TCL_TIME_BEFORE(time, TimerEntry2TimerHandler(entryPtr)->time)) { break; } @@ -754,7 +823,8 @@ TimerHandlerEventProc( * Bypass timers of newer generation. */ - if (timerHandlerPtr->generation > currentGeneration) { + if (entryPtr->generation > currentGeneration) { + /* increase pending to signal repeat */ tsdPtr->timerPending++; continue; } @@ -766,19 +836,19 @@ TimerHandlerEventProc( * potential reentrancy problems. */ - TclSpliceOutEx(timerHandlerPtr, + TclSpliceOutEx(entryPtr, tsdPtr->timerList, tsdPtr->lastTimerPtr); currentEpoch = tsdPtr->timerListEpoch; /* invoke timer proc */ - (*timerHandlerPtr->proc)(timerHandlerPtr->clientData); + (*entryPtr->proc)(entryPtr->clientData); /* free it via deleteProc or ckfree */ - if (timerHandlerPtr->deleteProc) { - (*timerHandlerPtr->deleteProc)(timerHandlerPtr->clientData); + if (entryPtr->deleteProc) { + (*entryPtr->deleteProc)(entryPtr->clientData); } - ckfree((char *) timerHandlerPtr); + ckfree((char *) TimerEntry2TimerHandler(entryPtr)); /* be sure that timer-list was not changed inside the proc call */ if (currentEpoch != tsdPtr->timerListEpoch) { @@ -788,121 +858,116 @@ TimerHandlerEventProc( } } +done: /* don't need to queue event again by pending timer events */ if (tsdPtr->timerPending > 1) { tsdPtr->timerPending = 1; return 0; /* leave handler event in the queue */ } - /* Reset generation */ + /* Reset generation if both timer queue are empty */ if (!tsdPtr->timerList) { - tsdPtr->timerGeneration = 0; + tsdPtr->timerGeneration = 0; } /* Compute the next timeout (later via TimerSetupProc using the first timer). */ tsdPtr->timerPending = 0; - return 1; /* processing done, again later via TimerCheckProc */ + return 1; /* processing done, again later via TimerSetupProc */ } /* *-------------------------------------------------------------- * - * TclCreateIdleHandlerEx --, Tcl_DoWhenIdle -- + * TclCreateTimerEntryEx -- * - * Arrange for proc to be invoked the next time the system is idle (i.e., - * just before the next time that Tcl_DoOneEvent would have to wait for + * Arrange for proc to be invoked delayed (but prompt) as timer event, + * without time ("after 0"). + * Or as idle event (the next time the system is idle i.e., just + * before the next time that Tcl_DoOneEvent would have to wait for * something to happen). * + * Providing the flag TCL_PROMPT_EVENT ensures that timer event-handler + * will be queued immediately to guarantee the execution of timer-event + * as soon as possible + * * Results: - * None. + * Returns the created timer entry. * * Side effects: - * Proc will eventually be called, with clientData as argument. See the - * manual entry for details. + * None. * *-------------------------------------------------------------- */ -IdleHandler * -TclCreateIdleHandlerEx( - Tcl_IdleProc *proc, /* Function to invoke. */ - Tcl_IdleDeleteProc *deleteProc, /* Function to cleanup */ - size_t extraDataSize) +TimerEntry * +TclCreateTimerEntryEx( + Tcl_TimerProc *proc, /* Function to invoke. */ + Tcl_TimerDeleteProc *deleteProc, /* Function to cleanup */ + size_t extraDataSize, + int flags) { - register IdleHandler *idlePtr; - Tcl_Time blockTime; + register TimerEntry *entryPtr; ThreadSpecificData *tsdPtr = InitTimer(); - idlePtr = (IdleHandler *) ckalloc(sizeof(IdleHandler) + extraDataSize); - if (idlePtr == NULL) { + entryPtr = (TimerEntry *) ckalloc(sizeof(TimerEntry) + extraDataSize); + if (entryPtr == NULL) { return NULL; } - idlePtr->proc = proc; - idlePtr->deleteProc = deleteProc; - idlePtr->clientData = IdleHandler2ClientData(idlePtr); - idlePtr->generation = tsdPtr->idleGeneration; - - /* attach to the idle queue */ - TclSpliceTailEx(idlePtr, tsdPtr->idleList, tsdPtr->lastIdlePtr); - - /* reset next block time */ - blockTime.sec = 0; - blockTime.usec = 0; - Tcl_SetMaxBlockTime(&blockTime); - - return idlePtr; -} - -void -Tcl_DoWhenIdle( - Tcl_IdleProc *proc, /* Function to invoke. */ - ClientData clientData) /* Arbitrary value to pass to proc. */ -{ - register IdleHandler *idlePtr = TclCreateIdleHandlerEx(proc, NULL, 0); - - if (idlePtr) { - idlePtr->clientData = clientData; + entryPtr->proc = proc; + entryPtr->deleteProc = deleteProc; + entryPtr->clientData = TimerEntry2ClientData(entryPtr); + entryPtr->flags = flags; + if (flags & TCL_PROMPT_EVENT) { + /* use timer generation, because usually no differences between + * call of "after 0" and "after 1" */ + entryPtr->generation = tsdPtr->timerGeneration; + /* attach to the prompt queue */ + TclSpliceTailEx(entryPtr, tsdPtr->promptList, tsdPtr->lastPromptPtr); + + /* execute immediately: queue handler event right now */ + if (!tsdPtr->timerPending) { + QueueTimerHandlerEvent(); + } + tsdPtr->timerPending++; /* queued and TimerSetupProc knows about */ + } else { + /* idle generation */ + entryPtr->generation = tsdPtr->idleGeneration; + /* attach to the idle queue */ + TclSpliceTailEx(entryPtr, tsdPtr->idleList, tsdPtr->lastIdlePtr); } + + return entryPtr; } /* *-------------------------------------------------------------- * - * TclDeleteIdleHandler -- + * Tcl_DoWhenIdle -- * - * Delete a previously-registered idle handler. + * Arrange for proc to be invoked the next time the system is idle (i.e., + * just before the next time that Tcl_DoOneEvent would have to wait for + * something to happen). * * Results: * None. * * Side effects: - * None. + * Proc will eventually be called, with clientData as argument. See the + * manual entry for details. * *-------------------------------------------------------------- */ - void -TclDeleteIdleHandler( - IdleHandler *idlePtr) /* Result previously returned by */ - /* TclCreateIdleHandlerEx */ +Tcl_DoWhenIdle( + Tcl_IdleProc *proc, /* Function to invoke. */ + ClientData clientData) /* Arbitrary value to pass to proc. */ { - ThreadSpecificData *tsdPtr; + TimerEntry *idlePtr = TclCreateTimerEntryEx(proc, NULL, 0, TCL_IDLE_EVENT); - if (idlePtr == NULL) { - return; - } - - tsdPtr = InitTimer(); - - /* detach entry from the owner list */ - TclSpliceOutEx(idlePtr, tsdPtr->idleList, tsdPtr->lastIdlePtr); - - /* free it via deleteProc and ckfree */ - if (idlePtr->deleteProc) { - (*idlePtr->deleteProc)(idlePtr->clientData); + if (idlePtr) { + idlePtr->clientData = clientData; } - ckfree((char *)idlePtr); } /* @@ -928,7 +993,7 @@ Tcl_CancelIdleCall( Tcl_IdleProc *proc, /* Function that was previously registered. */ ClientData clientData) /* Arbitrary value to pass to proc. */ { - register IdleHandler *idlePtr, *nextPtr; + register TimerEntry *idlePtr, *nextPtr; ThreadSpecificData *tsdPtr = InitTimer(); for (idlePtr = tsdPtr->idleList; @@ -972,7 +1037,7 @@ Tcl_CancelIdleCall( int TclServiceIdle(void) { - IdleHandler *idlePtr; + TimerEntry *idlePtr; size_t currentGeneration; Tcl_Time blockTime; ThreadSpecificData *tsdPtr = InitTimer(); @@ -1053,7 +1118,6 @@ Tcl_AfterObjCmd( Tcl_Obj *CONST objv[]) /* Argument objects. */ { Tcl_WideInt ms; /* Number of milliseconds to wait */ - Tcl_Time wakeup; AfterInfo *afterPtr; AfterAssocData *assocPtr; int length; @@ -1112,8 +1176,7 @@ Tcl_AfterObjCmd( switch (index) { case -1: { - Tcl_Time *timePtr = NULL; - TimerHandler *timerPtr; + TimerEntry *entryPtr; if (ms < 0) { ms = 0; } @@ -1122,7 +1185,7 @@ Tcl_AfterObjCmd( } if (ms) { - timePtr = &wakeup; + Tcl_Time wakeup; Tcl_GetTime(&wakeup); wakeup.sec += (long)(ms / 1000); wakeup.usec += ((long)(ms % 1000)) * 1000; @@ -1130,20 +1193,22 @@ Tcl_AfterObjCmd( wakeup.sec++; wakeup.usec -= 1000000; } + entryPtr = TclCreateAbsoluteTimerHandlerEx(&wakeup, AfterProc, + FreeAfterPtr, sizeof(AfterInfo)); + } else { + entryPtr = TclCreateTimerEntryEx(AfterProc, + FreeAfterPtr, sizeof(AfterInfo), TCL_PROMPT_EVENT); } - timerPtr = TclCreateAbsoluteTimerHandlerEx(timePtr, AfterProc, - FreeAfterPtr, sizeof(AfterInfo)); - if (timerPtr == NULL) { /* error handled in panic */ + if (entryPtr == NULL) { /* error handled in panic */ return TCL_ERROR; } - afterPtr = TimerHandler2AfterInfo(timerPtr); + afterPtr = TimerEntry2AfterInfo(entryPtr); /* attach to the list */ afterPtr->assocPtr = assocPtr; TclSpliceTailEx(afterPtr, assocPtr->firstAfterPtr, assocPtr->lastAfterPtr); - afterPtr->flags = 0; afterPtr->selfPtr = NULL; if (objc == 3) { @@ -1208,34 +1273,29 @@ Tcl_AfterObjCmd( } } if (afterPtr != NULL) { - if (!(afterPtr->flags & IDLE_EVENT)) { - TclDeleteTimerHandler(AfterInfo2TimerHandler(afterPtr)); - } else { - TclDeleteIdleHandler(AfterInfo2IdleHandler(afterPtr)); - } + TclDeleteTimerEntry(AfterInfo2TimerEntry(afterPtr)); } break; } case AFTER_IDLE: { - IdleHandler *idlePtr; + TimerEntry *idlePtr; if (objc < 3) { Tcl_WrongNumArgs(interp, 2, objv, "script script ..."); return TCL_ERROR; } - idlePtr = TclCreateIdleHandlerEx(AfterProc, - FreeAfterPtr, sizeof(AfterInfo)); + idlePtr = TclCreateTimerEntryEx(AfterProc, + FreeAfterPtr, sizeof(AfterInfo), TCL_IDLE_EVENT); if (idlePtr == NULL) { /* error handled in panic */ return TCL_ERROR; } - afterPtr = IdleHandler2AfterInfo(idlePtr); + afterPtr = TimerEntry2AfterInfo(idlePtr); /* attach to the list */ afterPtr->assocPtr = assocPtr; TclSpliceTailEx(afterPtr, assocPtr->firstAfterPtr, assocPtr->lastAfterPtr); - afterPtr->flags = IDLE_EVENT; afterPtr->selfPtr = NULL; if (objc == 3) { @@ -1285,7 +1345,8 @@ Tcl_AfterObjCmd( resultListPtr = Tcl_NewObj(); Tcl_ListObjAppendElement(interp, resultListPtr, afterPtr->commandPtr); Tcl_ListObjAppendElement(interp, resultListPtr, Tcl_NewStringObj( - (afterPtr->flags & IDLE_EVENT) ? "idle" : "timer", -1)); + (AfterInfo2TimerEntry(afterPtr)->flags & TCL_IDLE_EVENT) ? + "idle" : "timer", -1)); Tcl_SetObjResult(interp, resultListPtr); break; } @@ -1457,11 +1518,7 @@ AfterProc( */ /* remove delete proc from handler (we'll do cleanup here) */ - if (!(afterPtr->flags & IDLE_EVENT)) { - AfterInfo2TimerHandler(afterPtr)->deleteProc = NULL; - } else { - AfterInfo2IdleHandler(afterPtr)->deleteProc = NULL; - } + AfterInfo2TimerEntry(afterPtr)->deleteProc = NULL; /* release object (mark it was triggered) */ if (afterPtr->selfPtr) { @@ -1562,14 +1619,9 @@ AfterCleanupProc( Tcl_Interp *interp) /* Interpreter that is being deleted. */ { AfterAssocData *assocPtr = (AfterAssocData *) clientData; - AfterInfo *afterPtr; - while ( (afterPtr = assocPtr->lastAfterPtr) ) { - if (!(afterPtr->flags & IDLE_EVENT)) { - TclDeleteTimerHandler(AfterInfo2TimerHandler(afterPtr)); - } else { - TclDeleteIdleHandler(AfterInfo2IdleHandler(afterPtr)); - } + while ( assocPtr->lastAfterPtr ) { + TclDeleteTimerEntry(AfterInfo2TimerEntry(assocPtr->lastAfterPtr)); } } -- cgit v0.12 From df949554f991f8fb4a399bdd248437012deebe40 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:23:45 +0000 Subject: =?UTF-8?q?[performance]=20large=20performance=20increase=20by=20e?= =?UTF-8?q?vent=20servicing=20cycles=20(3x=20-=205x=20faster=20now);=20[wi?= =?UTF-8?q?n]=20prevent=20listen=20using=20PeekMessage=20twice,=20and=20no?= =?UTF-8?q?=20wait=20anymore=20for=20too=20short=20timeouts=20(because=20w?= =?UTF-8?q?indows=20can=20wait=20too=20long),=20compare=200=C2=B5s=20with?= =?UTF-8?q?=20up-to=20100=C2=B5s=20overhead=20within=20MsgWaitForMultipleO?= =?UTF-8?q?bjectsEx;=20[bad=20behavior]=20process=20idle=20events=20only?= =?UTF-8?q?=20as=20long=20as=20no=20other=20events=20available=20(now=20Tc?= =?UTF-8?q?lPeekEventQueued=20will=20be=20used=20to=20check=20new=20events?= =?UTF-8?q?=20are=20available=20in=20service=20idle=20cycle);=20[enhanceme?= =?UTF-8?q?nt]=20new=20option=20"noidletasks"=20for=20command=20"update",?= =?UTF-8?q?=20so=20"update=20noidle"=20means=20"process=20all=20events=20b?= =?UTF-8?q?ut=20not=20idle";?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generic/tclEvent.c | 14 ++-- generic/tclInt.h | 4 + generic/tclNotify.c | 119 +++++++++++++++++++++++++---- generic/tclTimer.c | 214 ++++++++++++++++++++++------------------------------ tests/event.test | 4 +- win/tclWinNotify.c | 47 +++++++----- 6 files changed, 238 insertions(+), 164 deletions(-) diff --git a/generic/tclEvent.c b/generic/tclEvent.c index 4db524c..85c564a 100644 --- a/generic/tclEvent.c +++ b/generic/tclEvent.c @@ -1410,26 +1410,28 @@ Tcl_UpdateObjCmd( Tcl_Obj *CONST objv[]) /* Argument objects. */ { int optionIndex; - int flags = 0; /* Initialized to avoid compiler warning. */ - static CONST char *updateOptions[] = {"idletasks", NULL}; - enum updateOptions {REGEXP_IDLETASKS}; + int flags = TCL_ALL_EVENTS|TCL_DONT_WAIT; + static CONST char *updateOptions[] = {"idletasks", "noidletasks", NULL}; + enum updateOptions {UPDATE_IDLETASKS, UPDATE_NOIDLETASKS}; if (objc == 1) { - flags = TCL_ALL_EVENTS|TCL_DONT_WAIT; } else if (objc == 2) { if (Tcl_GetIndexFromObj(interp, objv[1], updateOptions, "option", 0, &optionIndex) != TCL_OK) { return TCL_ERROR; } switch ((enum updateOptions) optionIndex) { - case REGEXP_IDLETASKS: + case UPDATE_IDLETASKS: flags = TCL_WINDOW_EVENTS|TCL_IDLE_EVENTS|TCL_DONT_WAIT; break; + case UPDATE_NOIDLETASKS: + flags &= ~TCL_IDLE_EVENTS; + break; default: Tcl_Panic("Tcl_UpdateObjCmd: bad option index to UpdateOptions"); } } else { - Tcl_WrongNumArgs(interp, 1, objv, "?idletasks?"); + Tcl_WrongNumArgs(interp, 1, objv, "?option?"); return TCL_ERROR; } diff --git a/generic/tclInt.h b/generic/tclInt.h index a0bab62..4151f83 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -2484,6 +2484,7 @@ MODULE_SCOPE Tcl_ObjType tclByteCodeType; MODULE_SCOPE Tcl_ObjType tclDoubleType; MODULE_SCOPE Tcl_ObjType tclEndOffsetType; MODULE_SCOPE Tcl_ObjType tclIntType; +MODULE_SCOPE Tcl_ObjType tclIndexType; MODULE_SCOPE Tcl_ObjType tclListType; MODULE_SCOPE Tcl_ObjType tclDictType; MODULE_SCOPE Tcl_ObjType tclProcBodyType; @@ -2919,6 +2920,8 @@ MODULE_SCOPE int Tcl_ConcatObjCmd(ClientData clientData, MODULE_SCOPE int Tcl_ContinueObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); +MODULE_SCOPE void TclSetTimerEventMarker(void); +MODULE_SCOPE int TclServiceTimerEvents(void); MODULE_SCOPE Tcl_TimerToken TclCreateAbsoluteTimerHandler( Tcl_Time *timePtr, Tcl_TimerProc *proc, ClientData clientData); @@ -2930,6 +2933,7 @@ MODULE_SCOPE TimerEntry* TclCreateTimerEntryEx( Tcl_TimerProc *proc, Tcl_TimerDeleteProc *deleteProc, size_t extraDataSize, int flags); MODULE_SCOPE void TclDeleteTimerEntry(TimerEntry *entryPtr); +MODULE_SCOPE int TclPeekEventQueued(int flags); MODULE_SCOPE int TclDefaultBgErrorHandlerObjCmd( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); diff --git a/generic/tclNotify.c b/generic/tclNotify.c index b45539a..e883071 100644 --- a/generic/tclNotify.c +++ b/generic/tclNotify.c @@ -49,6 +49,8 @@ typedef struct ThreadSpecificData { Tcl_Event *lastEventPtr; /* Last pending event, or NULL if none. */ Tcl_Event *markerEventPtr; /* Last high-priority event in queue, or NULL * if none. */ + Tcl_Event *timerMarkerPtr; /* Weak pointer to last event in the queue, + * before timer event generation */ Tcl_Mutex queueMutex; /* Mutex to protect access to the previous * three fields. */ int serviceMode; /* One of TCL_SERVICE_NONE or @@ -612,7 +614,7 @@ Tcl_ServiceEvent( { Tcl_Event *evPtr, *prevPtr; Tcl_EventProc *proc; - int result; + int result = 0; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); /* @@ -635,6 +637,13 @@ Tcl_ServiceEvent( } /* + * If timer marker reached, process timer events now. + */ + if (tsdPtr->timerMarkerPtr == INT2PTR(-1) || !tsdPtr->firstEventPtr) { + goto timer; + } + + /* * Loop through all the events in the queue until we find one that can * actually be handled. */ @@ -642,6 +651,14 @@ Tcl_ServiceEvent( Tcl_MutexLock(&(tsdPtr->queueMutex)); for (evPtr = tsdPtr->firstEventPtr; evPtr != NULL; evPtr = evPtr->nextPtr) { + + /* + * If timer marker reached, next cycle will process timer events. + */ + if (evPtr == tsdPtr->timerMarkerPtr) { + tsdPtr->timerMarkerPtr = INT2PTR(-1); + } + /* * Call the handler for the event. If it actually handles the event * then free the storage for the event. There are two tricky things @@ -721,7 +738,85 @@ Tcl_ServiceEvent( } } Tcl_MutexUnlock(&(tsdPtr->queueMutex)); - return 0; + +timer: + /* + * Process timer queue, if alloved and timers are enabled. + */ + if ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerMarkerPtr) { + /* reset marker */ + tsdPtr->timerMarkerPtr = NULL; + + result = TclServiceTimerEvents(); + if (result <= 0) { + /* events processed, but marker to process still pending timers */ + tsdPtr->timerMarkerPtr = INT2PTR(-1); + result = 1; + } + } + + + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TclPeekEventQueued -- + * + * Check whether some event (except idle) available (async, queued, timer). + * + * This will be used e. g. in TclServiceIdle to stop the processing of the + * the idle events if some "normal" event occurred. + * + * Results: + * Returns 1 if some event queued, 0 otherwise. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TclPeekEventQueued( + int flags) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + return Tcl_AsyncReady() + || (tsdPtr->firstEventPtr) + || ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerMarkerPtr); +} + +/* + *---------------------------------------------------------------------- + * + * TclSetTimerEventMarker -- + * + * Set timer event marker to the last pending event in the queue. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +TclSetTimerEventMarker(void) +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (tsdPtr->timerMarkerPtr == NULL) { + /* marker to last event in the queue */ + if (!(tsdPtr->timerMarkerPtr = tsdPtr->lastEventPtr)) { + /* marker as "now" - queue is empty, so timers events are first */ + tsdPtr->timerMarkerPtr = INT2PTR(-1); + }; + } } /* @@ -855,21 +950,12 @@ Tcl_DoOneEvent( * TCL_TIMER_EVENTS, TCL_IDLE_EVENTS, or * others defined by event sources. */ { - int result = 0, oldMode; + int result = 0, oldMode, i = 0; EventSource *sourcePtr; Tcl_Time *timePtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); /* - * The first thing we do is to service any asynchronous event handlers. - */ - - if (Tcl_AsyncReady()) { - (void) Tcl_AsyncInvoke(NULL, 0); - return 1; - } - - /* * No event flags is equivalent to TCL_ALL_EVENTS. */ @@ -904,7 +990,8 @@ Tcl_DoOneEvent( } /* - * Ask Tcl to service a queued event, if there are any. + * Ask Tcl to service any asynchronous event handlers or + * queued event, if there are any. */ if (Tcl_ServiceEvent(flags)) { @@ -921,6 +1008,8 @@ Tcl_DoOneEvent( tsdPtr->blockTime.sec = 0; tsdPtr->blockTime.usec = 0; tsdPtr->blockTimeSet = 1; + timePtr = &tsdPtr->blockTime; + goto wait; /* for notifier resp. system events */ } else { tsdPtr->blockTimeSet = 0; } @@ -939,7 +1028,7 @@ Tcl_DoOneEvent( } tsdPtr->inTraversal = 0; - if ((flags & TCL_DONT_WAIT) || tsdPtr->blockTimeSet) { + if (tsdPtr->blockTimeSet) { timePtr = &tsdPtr->blockTime; } else { timePtr = NULL; @@ -949,7 +1038,7 @@ Tcl_DoOneEvent( * Wait for a new event or a timeout. If Tcl_WaitForEvent returns -1, * we should abort Tcl_DoOneEvent. */ - + wait: result = Tcl_WaitForEvent(timePtr); if (result < 0) { result = 0; diff --git a/generic/tclTimer.c b/generic/tclTimer.c index 7eee89e..d3aa5aa 100644 --- a/generic/tclTimer.c +++ b/generic/tclTimer.c @@ -121,26 +121,13 @@ static void FreeAfterPtr(ClientData clientData); static AfterInfo * GetAfterEvent(AfterAssocData *assocPtr, Tcl_Obj *objPtr); static ThreadSpecificData *InitTimer(void); static void TimerExitProc(ClientData clientData); -static int TimerHandlerEventProc(Tcl_Event *evPtr, int flags); -#if 0 static void TimerCheckProc(ClientData clientData, int flags); -#endif static void TimerSetupProc(ClientData clientData, int flags); static void AfterObj_DupInternalRep(Tcl_Obj *, Tcl_Obj *); static void AfterObj_FreeInternalRep(Tcl_Obj *); static void AfterObj_UpdateString(Tcl_Obj *); -static inline void -QueueTimerHandlerEvent() -{ - Tcl_Event *timerEvPtr; - - timerEvPtr = (Tcl_Event *) ckalloc(sizeof(Tcl_Event)); - timerEvPtr->proc = TimerHandlerEventProc; - Tcl_QueueEvent(timerEvPtr, TCL_QUEUE_TAIL); -} - /* * Type definition. */ @@ -272,7 +259,7 @@ InitTimer(void) if (tsdPtr == NULL) { tsdPtr = TCL_TSD_INIT(&dataKey); - Tcl_CreateEventSource(TimerSetupProc, NULL, tsdPtr); + Tcl_CreateEventSource(TimerSetupProc, TimerCheckProc, tsdPtr); Tcl_CreateThreadExitHandler(TimerExitProc, NULL); } return tsdPtr; @@ -303,7 +290,7 @@ TimerExitProc( TclThreadDataKeyGet(&dataKey); if (tsdPtr != NULL) { - Tcl_DeleteEventSource(TimerSetupProc, NULL, tsdPtr); + Tcl_DeleteEventSource(TimerSetupProc, TimerCheckProc, tsdPtr); while ((tsdPtr->lastPromptPtr) != NULL) { TclDeleteTimerEntry(tsdPtr->lastPromptPtr); @@ -596,7 +583,7 @@ TclDeleteTimerEntry( static void TimerSetupProc( - ClientData data, /* Not used. */ + ClientData data, /* Specific data. */ int flags) /* Event flags as passed to Tcl_DoOneEvent. */ { Tcl_Time blockTime, *firstTime; @@ -604,10 +591,11 @@ TimerSetupProc( if (tsdPtr == NULL) { tsdPtr = InitTimer(); }; - if (((flags & TCL_IDLE_EVENTS) && tsdPtr->idleList ) - || ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerPending)) { + if ( ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerPending) + || ((flags & TCL_IDLE_EVENTS) && tsdPtr->idleList ) + ) { /* - * There is an idle handler or a pending timer event, so just poll. + * There is a pending timer event or an idle handler, so just poll. */ blockTime.sec = 0; @@ -630,15 +618,6 @@ TimerSetupProc( blockTime.sec = 0; blockTime.usec = 0; } - - /* - * If the first timer has expired, stick an event on the queue. - */ - - if (blockTime.sec == 0 && blockTime.usec == 0) { - tsdPtr->timerPending = 1; - QueueTimerHandlerEvent(); - } } else { return; @@ -647,15 +626,13 @@ TimerSetupProc( Tcl_SetMaxBlockTime(&blockTime); } -#if 0 /* *---------------------------------------------------------------------- * * TimerCheckProc -- * * This function is called by Tcl_DoOneEvent to check the timer event - * source for events. This routine checks both the idle and after timer - * lists. + * source for events. This routine checks the first timer in the list. * * Results: * None. @@ -668,66 +645,63 @@ TimerSetupProc( static void TimerCheckProc( - ClientData data, /* Not used. */ + ClientData data, /* Specific data. */ int flags) /* Event flags as passed to Tcl_DoOneEvent. */ { Tcl_Time blockTime, *firstTime; - ThreadSpecificData *tsdPtr = InitTimer(); - - if ((flags & TCL_TIMER_EVENTS) - && !tsdPtr->timerPending - ) { - /* - * Compute the timeout for the next timer on the list. - */ + ThreadSpecificData *tsdPtr = (ThreadSpecificData *)data; - if (tsdPtr->promptList) { - goto queuetmr; - } + if (!(flags & TCL_TIMER_EVENTS)) { + return; + } - if (!tsdPtr->timerList) { - return; - } - Tcl_GetTime(&blockTime); - firstTime = &(TimerEntry2TimerHandler(tsdPtr->timerList)->time); - blockTime.sec = firstTime->sec - blockTime.sec; - blockTime.usec = firstTime->usec - blockTime.usec; - if (blockTime.usec < 0) { - blockTime.sec -= 1; - blockTime.usec += 1000000; - } - if (blockTime.sec < 0) { - blockTime.sec = 0; - blockTime.usec = 0; - } + if (tsdPtr == NULL) { tsdPtr = InitTimer(); }; - /* - * If the first timer has expired, stick an event on the queue. - */ + /* If already pending */ + if (!tsdPtr->timerList || tsdPtr->timerPending) { + return; + } - if (blockTime.sec == 0 && blockTime.usec == 0) { - queuetmr: - tsdPtr->timerPending = 1; - QueueTimerHandlerEvent(); - } + /* + * Verify the first timer on the queue. + */ + Tcl_GetTime(&blockTime); + firstTime = &(TimerEntry2TimerHandler(tsdPtr->timerList)->time); + blockTime.sec = firstTime->sec - blockTime.sec; + blockTime.usec = firstTime->usec - blockTime.usec; + if (blockTime.usec < 0) { + blockTime.sec -= 1; + blockTime.usec += 1000000; + } + if (blockTime.sec < 0) { + blockTime.sec = 0; + blockTime.usec = 0; + } + + /* + * If the first timer has expired, stick an event on the queue. + */ + if (blockTime.sec == 0 && blockTime.usec == 0) { + TclSetTimerEventMarker(); + tsdPtr->timerPending = 1; } } -#endif /* *---------------------------------------------------------------------- * - * TimerHandlerEventProc -- + * TclServiceTimerEvents -- * - * This function is called by Tcl_ServiceEvent when a timer event reaches - * the front of the event queue. This function handles the event by + * This function is called by Tcl_ServiceEvent when a timer events should + * be processed. This function handles the event by * invoking the callbacks for all timers that are ready. * * Results: * Returns 1 if the event was handled, meaning it should be removed from - * the queue. Returns 0 if the event was not handled, meaning it should - * stay on the queue. The only time the event isn't handled is if the - * TCL_TIMER_EVENTS flag bit isn't set. + * the queue. + * Returns 0 if the event was not handled (no timer events). + * Returns -1 if pending timer events available, meaning the marker should + * stay on the queue. * * Side effects: * Whatever the timer handler callback functions do. @@ -735,25 +709,17 @@ TimerCheckProc( *---------------------------------------------------------------------- */ -static int -TimerHandlerEventProc( - Tcl_Event *evPtr, /* Event to service. */ - int flags) /* Flags that indicate what events to handle, - * such as TCL_FILE_EVENTS. */ +int +TclServiceTimerEvents(void) { TimerEntry *entryPtr, *nextPtr; Tcl_Time time; size_t currentGeneration, currentEpoch; ThreadSpecificData *tsdPtr = InitTimer(); - /* - * Do nothing if timers aren't enabled. This leaves the event on the - * queue, so we will get to it as soon as ServiceEvents() is called with - * timers enabled. - */ - if (!(flags & TCL_TIMER_EVENTS)) { - return 0; + if (!tsdPtr->timerPending) { + return 0; /* no timer events */ } /* @@ -798,10 +764,10 @@ TimerHandlerEventProc( ckfree((char *) entryPtr); } - /* if pending prompt events (new generation) - repeat event cycle right now */ + /* if stil pending prompt events (new generation) - repeat event cycle as + * soon as possible */ if (tsdPtr->promptList) { - tsdPtr->timerPending = 1; - return 0; /* leave handler event in the queue */ + return -1; } /* Hereafter all timer events with time before now */ @@ -859,10 +825,10 @@ TimerHandlerEventProc( } done: - /* don't need to queue event again by pending timer events */ + /* pending timer events, so mark (queue) timer events */ if (tsdPtr->timerPending > 1) { tsdPtr->timerPending = 1; - return 0; /* leave handler event in the queue */ + return -1; } /* Reset generation if both timer queue are empty */ @@ -873,7 +839,7 @@ done: /* Compute the next timeout (later via TimerSetupProc using the first timer). */ tsdPtr->timerPending = 0; - return 1; /* processing done, again later via TimerSetupProc */ + return 1; /* processing done, again later via TimerCheckProc */ } /* @@ -925,11 +891,9 @@ TclCreateTimerEntryEx( /* attach to the prompt queue */ TclSpliceTailEx(entryPtr, tsdPtr->promptList, tsdPtr->lastPromptPtr); - /* execute immediately: queue handler event right now */ - if (!tsdPtr->timerPending) { - QueueTimerHandlerEvent(); - } - tsdPtr->timerPending++; /* queued and TimerSetupProc knows about */ + /* execute immediately: signal pending and set timer marker */ + tsdPtr->timerPending++; + TclSetTimerEventMarker(); } else { /* idle generation */ entryPtr->generation = tsdPtr->idleGeneration; @@ -1018,7 +982,7 @@ Tcl_CancelIdleCall( /* *---------------------------------------------------------------------- * - * TclServiceIdle -- + * TclServiceIdle -- , TclServiceIdleEx -- * * This function is invoked by the notifier when it becomes idle. It will * invoke all idle handlers that are present at the time the call is @@ -1035,14 +999,14 @@ Tcl_CancelIdleCall( */ int -TclServiceIdle(void) +TclServiceIdleEx( + int count) { TimerEntry *idlePtr; size_t currentGeneration; - Tcl_Time blockTime; ThreadSpecificData *tsdPtr = InitTimer(); - if (tsdPtr->idleList == NULL) { + if ((idlePtr = tsdPtr->idleList) == NULL) { return 0; } @@ -1065,9 +1029,7 @@ TclServiceIdle(void) * during the call. */ - while ((idlePtr = tsdPtr->idleList) != NULL - && idlePtr->generation <= currentGeneration - ) { + while (idlePtr->generation <= currentGeneration) { /* detach entry from the owner's list */ TclSpliceOutEx(idlePtr, tsdPtr->idleList, tsdPtr->lastIdlePtr); @@ -1079,18 +1041,28 @@ TclServiceIdle(void) (*idlePtr->deleteProc)(idlePtr->clientData); } ckfree((char *) idlePtr); + + /* stop processing idle if no more idle, count reached or other queued */ + if ( (idlePtr = tsdPtr->idleList) == NULL + || !--count + || TclPeekEventQueued(TCL_ALL_EVENTS) + ) { + break; + } } - if (tsdPtr->idleList) { - blockTime.sec = 0; - blockTime.usec = 0; - Tcl_SetMaxBlockTime(&blockTime); - } + /* Reset generation */ if (!tsdPtr->idleList) { tsdPtr->idleGeneration = 0; } return 1; } + +int +TclServiceIdle(void) +{ + return TclServiceIdleEx(INT_MAX); +} /* *---------------------------------------------------------------------- @@ -1152,21 +1124,17 @@ Tcl_AfterObjCmd( * First lets see if the command was passed a number as the first argument. */ - if (objv[1]->typePtr == &tclIntType -#ifndef NO_WIDE_TYPE - || objv[1]->typePtr == &tclWideIntType -#endif - || objv[1]->typePtr == &tclBignumType - || ( Tcl_GetIndexFromObj(NULL, objv[1], afterSubCmds, "", 0, - &index) != TCL_OK )) { - index = -1; - if (Tcl_GetWideIntFromObj(NULL, objv[1], &ms) != TCL_OK) { - Tcl_AppendResult(interp, "bad argument \"", - Tcl_GetString(objv[1]), - "\": must be cancel, idle, info, or an integer", - NULL); - return TCL_ERROR; - } + index = -1; + if ( ( objv[1]->typePtr == &tclIndexType + || Tcl_GetWideIntFromObj(NULL, objv[1], &ms) != TCL_OK + ) + && Tcl_GetIndexFromObj(NULL, objv[1], afterSubCmds, "", 0, + &index) != TCL_OK + ) { + Tcl_AppendResult(interp, "bad argument \"", + Tcl_GetString(objv[1]), + "\": must be cancel, idle, info, or an integer", NULL); + return TCL_ERROR; } /* diff --git a/tests/event.test b/tests/event.test index b6d4144..526faf2 100644 --- a/tests/event.test +++ b/tests/event.test @@ -625,10 +625,10 @@ test event-11.7 {Bug 16828b3744} { test event-12.1 {Tcl_UpdateCmd procedure} { list [catch {update a b} msg] $msg -} {1 {wrong # args: should be "update ?idletasks?"}} +} {1 {wrong # args: should be "update ?option?"}} test event-12.2 {Tcl_UpdateCmd procedure} { list [catch {update bogus} msg] $msg -} {1 {bad option "bogus": must be idletasks}} +} {1 {bad option "bogus": must be idletasks or noidletasks}} test event-12.3 {Tcl_UpdateCmd procedure} { foreach i [after info] { after cancel $i diff --git a/win/tclWinNotify.c b/win/tclWinNotify.c index 1cd5823..26aa296 100644 --- a/win/tclWinNotify.c +++ b/win/tclWinNotify.c @@ -437,13 +437,19 @@ Tcl_WaitForEvent( */ if (timePtr) { + + Tcl_Time myTime; + + /* No wait if timeout too small (because windows may wait too long) */ + if (!timePtr->sec && timePtr->usec <= 10) { + goto peek; + } + /* * TIP #233 (Virtualized Time). Convert virtual domain delay to * real-time. */ - Tcl_Time myTime; - myTime.sec = timePtr->sec; myTime.usec = timePtr->usec; @@ -452,6 +458,7 @@ Tcl_WaitForEvent( } timeout = myTime.sec * 1000 + myTime.usec / 1000; + } else { timeout = INFINITE; } @@ -462,33 +469,38 @@ Tcl_WaitForEvent( * currently sitting in the queue. */ - if (!PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { - /* - * Wait for something to happen (a signal from another thread, a - * message, or timeout) or loop servicing asynchronous procedure calls - * queued to this thread. - */ + if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { + goto get; + } - again: - result = MsgWaitForMultipleObjectsEx(1, &tsdPtr->event, timeout, + /* + * Wait for something to happen (a signal from another thread, a + * message, or timeout) or loop servicing asynchronous procedure calls + * queued to this thread. + */ + + again: + result = MsgWaitForMultipleObjectsEx(1, &tsdPtr->event, timeout, QS_ALLINPUT, MWMO_ALERTABLE); - if (result == WAIT_IO_COMPLETION) { - goto again; - } else if (result == WAIT_FAILED) { - status = -1; - goto end; - } + if (result == WAIT_IO_COMPLETION) { + goto again; + } + ResetEvent(tsdPtr->event); + if (result == WAIT_FAILED) { + status = -1; + goto end; } /* * Check to see if there are any messages to process. */ - + peek: if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { /* * Retrieve and dispatch the first message. */ + get: result = GetMessage(&msg, NULL, 0, 0); if (result == 0) { /* @@ -515,7 +527,6 @@ Tcl_WaitForEvent( } end: - ResetEvent(tsdPtr->event); return status; } -- cgit v0.12 From 8a1d9a3be57af976d24c218db14975378927f125 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:24:04 +0000 Subject: [performance] much better handling for timer events within Tcl_ServiceEvent using timer marker in the queue and direct call of TclServiceTimerEvents if marker reached (instead of continuous adding handler event, polling it in the queue and removing hereafter); this provides double performance increase in the service cycle; --- generic/tclIndexObj.c | 16 +++---- generic/tclInt.h | 1 - generic/tclNotify.c | 54 +++++++---------------- generic/tclTimer.c | 118 ++++++++++++-------------------------------------- 4 files changed, 51 insertions(+), 138 deletions(-) diff --git a/generic/tclIndexObj.c b/generic/tclIndexObj.c index 73ba515..ced7bd9 100644 --- a/generic/tclIndexObj.c +++ b/generic/tclIndexObj.c @@ -27,7 +27,7 @@ static void FreeIndex(Tcl_Obj *objPtr); * that can be invoked by generic object code. */ -static Tcl_ObjType indexType = { +Tcl_ObjType tclIndexType = { "index", /* name */ FreeIndex, /* freeIntRepProc */ DupIndex, /* dupIntRepProc */ @@ -105,7 +105,7 @@ Tcl_GetIndexFromObj( * the common case where the result is cached). */ - if (objPtr->typePtr == &indexType) { + if (objPtr->typePtr == &tclIndexType) { IndexRep *indexRep = objPtr->internalRep.twoPtrValue.ptr1; /* @@ -179,7 +179,7 @@ Tcl_GetIndexFromObjStruct( * See if there is a valid cached result from a previous lookup. */ - if (objPtr->typePtr == &indexType) { + if (objPtr->typePtr == &tclIndexType) { indexRep = objPtr->internalRep.twoPtrValue.ptr1; if (indexRep->tablePtr==tablePtr && indexRep->offset==offset) { *indexPtr = indexRep->index; @@ -240,13 +240,13 @@ Tcl_GetIndexFromObjStruct( * operation. */ - if (objPtr->typePtr == &indexType) { + if (objPtr->typePtr == &tclIndexType) { indexRep = objPtr->internalRep.twoPtrValue.ptr1; } else { TclFreeIntRep(objPtr); indexRep = (IndexRep *) ckalloc(sizeof(IndexRep)); objPtr->internalRep.twoPtrValue.ptr1 = indexRep; - objPtr->typePtr = &indexType; + objPtr->typePtr = &tclIndexType; } indexRep->tablePtr = (void *) tablePtr; indexRep->offset = offset; @@ -382,7 +382,7 @@ DupIndex( memcpy(dupIndexRep, srcIndexRep, sizeof(IndexRep)); dupPtr->internalRep.twoPtrValue.ptr1 = dupIndexRep; - dupPtr->typePtr = &indexType; + dupPtr->typePtr = &tclIndexType; } /* @@ -532,7 +532,7 @@ Tcl_WrongNumArgs( * Add the element, quoting it if necessary. */ - if (origObjv[i]->typePtr == &indexType) { + if (origObjv[i]->typePtr == &tclIndexType) { register IndexRep *indexRep = origObjv[i]->internalRep.twoPtrValue.ptr1; @@ -588,7 +588,7 @@ Tcl_WrongNumArgs( * Otherwise, just use the string rep. */ - if (objv[i]->typePtr == &indexType) { + if (objv[i]->typePtr == &tclIndexType) { register IndexRep *indexRep = objv[i]->internalRep.twoPtrValue.ptr1; Tcl_AppendStringsToObj(objPtr, EXPAND_OF(indexRep), NULL); diff --git a/generic/tclInt.h b/generic/tclInt.h index 4151f83..d0d1240 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -2933,7 +2933,6 @@ MODULE_SCOPE TimerEntry* TclCreateTimerEntryEx( Tcl_TimerProc *proc, Tcl_TimerDeleteProc *deleteProc, size_t extraDataSize, int flags); MODULE_SCOPE void TclDeleteTimerEntry(TimerEntry *entryPtr); -MODULE_SCOPE int TclPeekEventQueued(int flags); MODULE_SCOPE int TclDefaultBgErrorHandlerObjCmd( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); diff --git a/generic/tclNotify.c b/generic/tclNotify.c index e883071..89182ea 100644 --- a/generic/tclNotify.c +++ b/generic/tclNotify.c @@ -639,7 +639,7 @@ Tcl_ServiceEvent( /* * If timer marker reached, process timer events now. */ - if (tsdPtr->timerMarkerPtr == INT2PTR(-1) || !tsdPtr->firstEventPtr) { + if (tsdPtr->timerMarkerPtr == INT2PTR(-1)) { goto timer; } @@ -743,7 +743,7 @@ timer: /* * Process timer queue, if alloved and timers are enabled. */ - if ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerMarkerPtr) { + if (flags & TCL_TIMER_EVENTS && tsdPtr->timerMarkerPtr) { /* reset marker */ tsdPtr->timerMarkerPtr = NULL; @@ -762,36 +762,6 @@ timer: /* *---------------------------------------------------------------------- * - * TclPeekEventQueued -- - * - * Check whether some event (except idle) available (async, queued, timer). - * - * This will be used e. g. in TclServiceIdle to stop the processing of the - * the idle events if some "normal" event occurred. - * - * Results: - * Returns 1 if some event queued, 0 otherwise. - * - * Side effects: - * None. - * - *---------------------------------------------------------------------- - */ - -int -TclPeekEventQueued( - int flags) -{ - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); - - return Tcl_AsyncReady() - || (tsdPtr->firstEventPtr) - || ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerMarkerPtr); -} - -/* - *---------------------------------------------------------------------- - * * TclSetTimerEventMarker -- * * Set timer event marker to the last pending event in the queue. @@ -950,12 +920,21 @@ Tcl_DoOneEvent( * TCL_TIMER_EVENTS, TCL_IDLE_EVENTS, or * others defined by event sources. */ { - int result = 0, oldMode, i = 0; + int result = 0, oldMode; EventSource *sourcePtr; Tcl_Time *timePtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); /* + * The first thing we do is to service any asynchronous event handlers. + */ + + if (Tcl_AsyncReady()) { + (void) Tcl_AsyncInvoke(NULL, 0); + return 1; + } + + /* * No event flags is equivalent to TCL_ALL_EVENTS. */ @@ -990,8 +969,7 @@ Tcl_DoOneEvent( } /* - * Ask Tcl to service any asynchronous event handlers or - * queued event, if there are any. + * Ask Tcl to service a queued event, if there are any. */ if (Tcl_ServiceEvent(flags)) { @@ -1008,8 +986,6 @@ Tcl_DoOneEvent( tsdPtr->blockTime.sec = 0; tsdPtr->blockTime.usec = 0; tsdPtr->blockTimeSet = 1; - timePtr = &tsdPtr->blockTime; - goto wait; /* for notifier resp. system events */ } else { tsdPtr->blockTimeSet = 0; } @@ -1028,7 +1004,7 @@ Tcl_DoOneEvent( } tsdPtr->inTraversal = 0; - if (tsdPtr->blockTimeSet) { + if ((flags & TCL_DONT_WAIT) || tsdPtr->blockTimeSet) { timePtr = &tsdPtr->blockTime; } else { timePtr = NULL; @@ -1038,7 +1014,7 @@ Tcl_DoOneEvent( * Wait for a new event or a timeout. If Tcl_WaitForEvent returns -1, * we should abort Tcl_DoOneEvent. */ - wait: + result = Tcl_WaitForEvent(timePtr); if (result < 0) { result = 0; diff --git a/generic/tclTimer.c b/generic/tclTimer.c index d3aa5aa..12b039f 100644 --- a/generic/tclTimer.c +++ b/generic/tclTimer.c @@ -121,7 +121,6 @@ static void FreeAfterPtr(ClientData clientData); static AfterInfo * GetAfterEvent(AfterAssocData *assocPtr, Tcl_Obj *objPtr); static ThreadSpecificData *InitTimer(void); static void TimerExitProc(ClientData clientData); -static void TimerCheckProc(ClientData clientData, int flags); static void TimerSetupProc(ClientData clientData, int flags); static void AfterObj_DupInternalRep(Tcl_Obj *, Tcl_Obj *); @@ -259,7 +258,7 @@ InitTimer(void) if (tsdPtr == NULL) { tsdPtr = TCL_TSD_INIT(&dataKey); - Tcl_CreateEventSource(TimerSetupProc, TimerCheckProc, tsdPtr); + Tcl_CreateEventSource(TimerSetupProc, NULL, tsdPtr); Tcl_CreateThreadExitHandler(TimerExitProc, NULL); } return tsdPtr; @@ -290,7 +289,7 @@ TimerExitProc( TclThreadDataKeyGet(&dataKey); if (tsdPtr != NULL) { - Tcl_DeleteEventSource(TimerSetupProc, TimerCheckProc, tsdPtr); + Tcl_DeleteEventSource(TimerSetupProc, NULL, tsdPtr); while ((tsdPtr->lastPromptPtr) != NULL) { TclDeleteTimerEntry(tsdPtr->lastPromptPtr); @@ -583,7 +582,7 @@ TclDeleteTimerEntry( static void TimerSetupProc( - ClientData data, /* Specific data. */ + ClientData data, /* Not used. */ int flags) /* Event flags as passed to Tcl_DoOneEvent. */ { Tcl_Time blockTime, *firstTime; @@ -591,11 +590,10 @@ TimerSetupProc( if (tsdPtr == NULL) { tsdPtr = InitTimer(); }; - if ( ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerPending) - || ((flags & TCL_IDLE_EVENTS) && tsdPtr->idleList ) - ) { + if (((flags & TCL_IDLE_EVENTS) && tsdPtr->idleList ) + || ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerPending)) { /* - * There is a pending timer event or an idle handler, so just poll. + * There is an idle handler or a pending timer event, so just poll. */ blockTime.sec = 0; @@ -618,6 +616,15 @@ TimerSetupProc( blockTime.sec = 0; blockTime.usec = 0; } + + /* + * If the first timer has expired, stick an event on the queue. + */ + + if (!tsdPtr->timerPending && blockTime.sec == 0 && blockTime.usec == 0) { + TclSetTimerEventMarker(); + tsdPtr->timerPending = 1; + } } else { return; @@ -629,67 +636,6 @@ TimerSetupProc( /* *---------------------------------------------------------------------- * - * TimerCheckProc -- - * - * This function is called by Tcl_DoOneEvent to check the timer event - * source for events. This routine checks the first timer in the list. - * - * Results: - * None. - * - * Side effects: - * May queue an event and update the maximum notifier block time. - * - *---------------------------------------------------------------------- - */ - -static void -TimerCheckProc( - ClientData data, /* Specific data. */ - int flags) /* Event flags as passed to Tcl_DoOneEvent. */ -{ - Tcl_Time blockTime, *firstTime; - ThreadSpecificData *tsdPtr = (ThreadSpecificData *)data; - - if (!(flags & TCL_TIMER_EVENTS)) { - return; - } - - if (tsdPtr == NULL) { tsdPtr = InitTimer(); }; - - /* If already pending */ - if (!tsdPtr->timerList || tsdPtr->timerPending) { - return; - } - - /* - * Verify the first timer on the queue. - */ - Tcl_GetTime(&blockTime); - firstTime = &(TimerEntry2TimerHandler(tsdPtr->timerList)->time); - blockTime.sec = firstTime->sec - blockTime.sec; - blockTime.usec = firstTime->usec - blockTime.usec; - if (blockTime.usec < 0) { - blockTime.sec -= 1; - blockTime.usec += 1000000; - } - if (blockTime.sec < 0) { - blockTime.sec = 0; - blockTime.usec = 0; - } - - /* - * If the first timer has expired, stick an event on the queue. - */ - if (blockTime.sec == 0 && blockTime.usec == 0) { - TclSetTimerEventMarker(); - tsdPtr->timerPending = 1; - } -} - -/* - *---------------------------------------------------------------------- - * * TclServiceTimerEvents -- * * This function is called by Tcl_ServiceEvent when a timer events should @@ -839,7 +785,7 @@ done: /* Compute the next timeout (later via TimerSetupProc using the first timer). */ tsdPtr->timerPending = 0; - return 1; /* processing done, again later via TimerCheckProc */ + return 1; /* processing done, again later via TimerSetupProc */ } /* @@ -982,7 +928,7 @@ Tcl_CancelIdleCall( /* *---------------------------------------------------------------------- * - * TclServiceIdle -- , TclServiceIdleEx -- + * TclServiceIdle -- * * This function is invoked by the notifier when it becomes idle. It will * invoke all idle handlers that are present at the time the call is @@ -999,14 +945,14 @@ Tcl_CancelIdleCall( */ int -TclServiceIdleEx( - int count) +TclServiceIdle(void) { TimerEntry *idlePtr; size_t currentGeneration; + Tcl_Time blockTime; ThreadSpecificData *tsdPtr = InitTimer(); - if ((idlePtr = tsdPtr->idleList) == NULL) { + if (tsdPtr->idleList == NULL) { return 0; } @@ -1029,7 +975,9 @@ TclServiceIdleEx( * during the call. */ - while (idlePtr->generation <= currentGeneration) { + while ((idlePtr = tsdPtr->idleList) != NULL + && idlePtr->generation <= currentGeneration + ) { /* detach entry from the owner's list */ TclSpliceOutEx(idlePtr, tsdPtr->idleList, tsdPtr->lastIdlePtr); @@ -1041,28 +989,18 @@ TclServiceIdleEx( (*idlePtr->deleteProc)(idlePtr->clientData); } ckfree((char *) idlePtr); - - /* stop processing idle if no more idle, count reached or other queued */ - if ( (idlePtr = tsdPtr->idleList) == NULL - || !--count - || TclPeekEventQueued(TCL_ALL_EVENTS) - ) { - break; - } } - + if (tsdPtr->idleList) { + blockTime.sec = 0; + blockTime.usec = 0; + Tcl_SetMaxBlockTime(&blockTime); + } /* Reset generation */ if (!tsdPtr->idleList) { tsdPtr->idleGeneration = 0; } return 1; } - -int -TclServiceIdle(void) -{ - return TclServiceIdleEx(INT_MAX); -} /* *---------------------------------------------------------------------- -- cgit v0.12 From ee8dccacb4206a0ac43f8cb427b86775cedc4684 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:24:12 +0000 Subject: command "vwait" extended with timeout argument (in ms), 0 could be used to process pending events only (without wait), negative value equivalent execution of "vwait" without timeout (infinite); test cases fixed and extended; --- generic/tclEvent.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++------ tests/event.test | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 140 insertions(+), 10 deletions(-) diff --git a/generic/tclEvent.c b/generic/tclEvent.c index 85c564a..27953a4 100644 --- a/generic/tclEvent.c +++ b/generic/tclEvent.c @@ -1299,6 +1299,14 @@ TclInThreadExit(void) } } +static void +VwaitTimeOutProc( + ClientData clientData) +{ + int *donePtr = (int *) clientData; + + *donePtr = -1; +} /* *---------------------------------------------------------------------- * @@ -1324,31 +1332,82 @@ Tcl_VwaitObjCmd( int objc, /* Number of arguments. */ Tcl_Obj *CONST objv[]) /* Argument objects. */ { - int done, foundEvent; + int done, foundEvent, flags = TCL_ALL_EVENTS; char *nameString; + TimerEntry *timerEvent = NULL; - if (objc != 2) { - Tcl_WrongNumArgs(interp, 1, objv, "name"); + if (objc < 2 || objc > 3) { + Tcl_WrongNumArgs(interp, 1, objv, "name ?timeout?"); return TCL_ERROR; } + + /* if timeout specified - create timer event or no-wait by 0ms */ + if (objc == 3) { + Tcl_Time wakeup; + Tcl_WideInt ms; + if (Tcl_GetWideIntFromObj(interp, objv[2], &ms) != TCL_OK) { + return TCL_ERROR; + } + if (ms > 0) { + Tcl_GetTime(&wakeup); + wakeup.sec += (long)(ms / 1000); + wakeup.usec += ((long)(ms % 1000)) * 1000; + if (wakeup.usec > 1000000) { + wakeup.sec++; + wakeup.usec -= 1000000; + } + timerEvent = TclCreateAbsoluteTimerHandlerEx(&wakeup, VwaitTimeOutProc, NULL, 0); + timerEvent->clientData = &done; + } else if (ms == 0) { + flags |= TCL_DONT_WAIT; + } else { + /* infinite vait */ + objc = 2; + } + } + nameString = Tcl_GetString(objv[1]); if (Tcl_TraceVar(interp, nameString, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, VwaitVarProc, (ClientData) &done) != TCL_OK) { return TCL_ERROR; }; + done = 0; - foundEvent = 1; - while (!done && foundEvent) { - foundEvent = Tcl_DoOneEvent(TCL_ALL_EVENTS); + do { + if ((foundEvent = Tcl_DoOneEvent(flags)) == 0) { + /* no wait, no error - just stop waiting (no more events) */ + if (flags |= TCL_DONT_WAIT) { + foundEvent = 1; + done = -2; + } + break; + } if (Tcl_LimitExceeded(interp)) { + foundEvent = -1; break; } - } + } while (!done); + Tcl_UntraceVar(interp, nameString, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, VwaitVarProc, (ClientData) &done); + /* if timeout-timer and no timeout fired, cancel timer event */ + if (timerEvent && done != -1) { + TclDeleteTimerEntry(timerEvent); + } + + /* if timeout specified (and no errors) */ + if (objc == 3 && foundEvent > 0) { + Tcl_Obj *objPtr; + + /* done - true, timeout false */ + TclNewLongObj(objPtr, (done > 0)); + Tcl_SetObjResult(interp, objPtr); + return TCL_OK; + } + /* * Clear out the interpreter's result, since it may have been set by event * handlers. @@ -1360,7 +1419,7 @@ Tcl_VwaitObjCmd( "\": would wait forever", NULL); return TCL_ERROR; } - if (!done) { + if (foundEvent == -1) { Tcl_AppendResult(interp, "limit exceeded", NULL); return TCL_ERROR; } diff --git a/tests/event.test b/tests/event.test index 526faf2..c905489 100644 --- a/tests/event.test +++ b/tests/event.test @@ -488,10 +488,10 @@ test event-10.1 {Tcl_Exit procedure} {stdio} { test event-11.1 {Tcl_VwaitCmd procedure} { list [catch {vwait} msg] $msg -} {1 {wrong # args: should be "vwait name"}} +} {1 {wrong # args: should be "vwait name ?timeout?"}} test event-11.2 {Tcl_VwaitCmd procedure} { list [catch {vwait a b} msg] $msg -} {1 {wrong # args: should be "vwait name"}} +} {1 {expected integer but got "b"}} test event-11.3 {Tcl_VwaitCmd procedure} { catch {unset x} set x 1 @@ -513,6 +513,77 @@ test event-11.4 {Tcl_VwaitCmd procedure} {} { list [vwait y] $x $y $z $q } {{} x-done y-done before q-done} +test event-11.4.0 {vwait - interp limit precedence} {} { + foreach i [after info] { + after cancel $i + } + set result {} + set i [interp create] + $i bgerror {lappend errors}; # prevent stdout background errors; + + # limit should be exceeded (wait infinite): + $i limit time -milliseconds 0 + lappend result 1. [catch {$i eval {vwait x}} msg] $msg + + # no limit in between: + $i limit time -seconds {} -milliseconds {} + lappend result 2. [catch {$i eval {vwait x 0}} msg] $msg + + # limit should be exceeded: (wait infinite by -1) + $i limit time -milliseconds 0 + lappend result 3. [catch {$i eval {vwait x -1}} msg] $msg + # limit should be exceeded (wait too long - 1000ms): + $i limit time -milliseconds 0 + lappend result 4. [catch {$i eval {vwait x 1000}} msg] $msg + + set tout [clock seconds]; incr tout 10 + # wait timeout (before limit): + $i limit time -seconds $tout + lappend result 5. [catch {$i eval {vwait x 0}} msg] $msg + # wait timeout (before limit): + $i limit time -seconds $tout + lappend result 6. [catch {$i eval {vwait x 10}} msg] $msg + + # wait successful (before limit): + $i limit time -seconds $tout + lappend result 7. [catch {$i eval {after 0 {set x ""}; vwait x 10}} msg] $msg + + interp delete $i + set result +} [list \ + 1. 1 {limit exceeded} \ + 2. 0 0 \ + 3. 1 {limit exceeded} \ + 4. 1 {limit exceeded} \ + 5. 0 0 \ + 6. 0 0 \ + 7. 0 1 \ +] + +test event-11.4.1 {vwait with timeout} {} { + foreach i [after info] { + after cancel $i + } + set z {} + set x {} + # success cases: + after 0 {lappend z 0} + after 20 {lappend x 1} + after 30 {lappend x 2} + after 100 {lappend x 3} + after 1000 {lappend x "error-too-slow"} + vwait x 0; # no-wait + lappend z $x; # 0 {} - (x still empty) + vwait x 50; # wait up-to 50ms + lappend z $x; # 0 {} {1 2} + vwait x -1; # wait without timeout + lappend z $x; # 0 {} {1 2} {1 2 3} + foreach i [after info] { + after cancel $i + } + set z +} {0 {} {1 2} {1 2 3}} + test event-11.4.2 {cancel} {} { foreach i [after info] { after cancel $i -- cgit v0.12 From 5daa7f610ab6e2ea43bca023cb3cfe96811b48b4 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:24:36 +0000 Subject: [performance] do one event (update / event servicing) cycle optimized (introduced threshold to prevent sourcing resp. waiting for new events by no-wait). [enhancement] new event type introduced: TCL_ASYNC_EVENTS, command "update" becomes options to process only specified types, resp. to bypass some event types (including -idle/-noidle that in opposite to "idletasks" does not included window events); test cases extended. --- generic/tcl.h | 1 + generic/tclEvent.c | 55 ++++++++++++------- generic/tclInt.h | 2 + generic/tclNotify.c | 154 ++++++++++++++++++++++++++++++++++++++++++++-------- generic/tclTimer.c | 122 +++++++++++++++++++++++++++++++++-------- tests/event.test | 61 +++++++++++++++++++-- 6 files changed, 326 insertions(+), 69 deletions(-) diff --git a/generic/tcl.h b/generic/tcl.h index 6a3c66a..b65a5cb 100644 --- a/generic/tcl.h +++ b/generic/tcl.h @@ -1297,6 +1297,7 @@ typedef struct { * events: */ +#define TCL_ASYNC_EVENTS (1<<0) #define TCL_DONT_WAIT (1<<1) #define TCL_WINDOW_EVENTS (1<<2) #define TCL_FILE_EVENTS (1<<3) diff --git a/generic/tclEvent.c b/generic/tclEvent.c index 27953a4..d18836b 100644 --- a/generic/tclEvent.c +++ b/generic/tclEvent.c @@ -1468,30 +1468,42 @@ Tcl_UpdateObjCmd( int objc, /* Number of arguments. */ Tcl_Obj *CONST objv[]) /* Argument objects. */ { - int optionIndex; - int flags = TCL_ALL_EVENTS|TCL_DONT_WAIT; - static CONST char *updateOptions[] = {"idletasks", "noidletasks", NULL}; - enum updateOptions {UPDATE_IDLETASKS, UPDATE_NOIDLETASKS}; - - if (objc == 1) { - } else if (objc == 2) { - if (Tcl_GetIndexFromObj(interp, objv[1], updateOptions, + int i, optionIndex; + static CONST defUpdateFlags = TCL_ALL_EVENTS|TCL_DONT_WAIT; + int flags = defUpdateFlags; + static CONST char *updateOptions[] = {"idletasks", /* backwards compat. */ + "-nowait", "-wait", /* new options */ + "-idle", "-noidle", "-timer", "-notimer", + "-file", "-nofile", "-window", "-nowindow", + "-async", "-noasync", + NULL}; + static CONST struct { + int minus; + int plus; + } *updateFlag, updateFlags[] = { + {TCL_ALL_EVENTS, + TCL_WINDOW_EVENTS|TCL_IDLE_EVENTS}, /* idletasks */ + {0, TCL_DONT_WAIT}, {TCL_DONT_WAIT, 0}, /* -nowait, -wait */ + {0, TCL_IDLE_EVENTS}, {TCL_IDLE_EVENTS, 0}, /* -idle, -noidle */ + {0, TCL_TIMER_EVENTS}, {TCL_TIMER_EVENTS, 0}, /* -file, -nofile */ + {0, TCL_FILE_EVENTS}, {TCL_FILE_EVENTS, 0}, /* -file, -nofile */ + {0, TCL_WINDOW_EVENTS}, {TCL_WINDOW_EVENTS, 0}, /* -window, -nowindow */ + {0, TCL_ASYNC_EVENTS}, {TCL_ASYNC_EVENTS, 0}, /* -async, -noasync */ + {0, 0} /* dummy / place holder */ + }; + + for (i = 1; i < objc; i++) { + if (Tcl_GetIndexFromObj(interp, objv[i], updateOptions, "option", 0, &optionIndex) != TCL_OK) { return TCL_ERROR; } - switch ((enum updateOptions) optionIndex) { - case UPDATE_IDLETASKS: - flags = TCL_WINDOW_EVENTS|TCL_IDLE_EVENTS|TCL_DONT_WAIT; - break; - case UPDATE_NOIDLETASKS: - flags &= ~TCL_IDLE_EVENTS; - break; - default: - Tcl_Panic("Tcl_UpdateObjCmd: bad option index to UpdateOptions"); + updateFlag = &updateFlags[optionIndex]; + /* pure positive option and still default - reset all events */ + if (flags == defUpdateFlags && !updateFlag->minus) { + flags &= ~TCL_ALL_EVENTS; } - } else { - Tcl_WrongNumArgs(interp, 1, objv, "?option?"); - return TCL_ERROR; + flags &= ~updateFlag->minus; + flags |= updateFlag->plus; } while (Tcl_DoOneEvent(flags) != 0) { @@ -1500,6 +1512,9 @@ Tcl_UpdateObjCmd( Tcl_AppendResult(interp, "limit exceeded", NULL); return TCL_ERROR; } + + /* be sure not to produce infinite wait (wait only once) */ + flags |= TCL_DONT_WAIT; } /* diff --git a/generic/tclInt.h b/generic/tclInt.h index d0d1240..dd73eac 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -2922,6 +2922,7 @@ MODULE_SCOPE int Tcl_ContinueObjCmd(ClientData clientData, Tcl_Obj *const objv[]); MODULE_SCOPE void TclSetTimerEventMarker(void); MODULE_SCOPE int TclServiceTimerEvents(void); +MODULE_SCOPE int TclServiceIdleEx(int flags, int count); MODULE_SCOPE Tcl_TimerToken TclCreateAbsoluteTimerHandler( Tcl_Time *timePtr, Tcl_TimerProc *proc, ClientData clientData); @@ -2933,6 +2934,7 @@ MODULE_SCOPE TimerEntry* TclCreateTimerEntryEx( Tcl_TimerProc *proc, Tcl_TimerDeleteProc *deleteProc, size_t extraDataSize, int flags); MODULE_SCOPE void TclDeleteTimerEntry(TimerEntry *entryPtr); +MODULE_SCOPE int TclPeekEventQueued(int flags); MODULE_SCOPE int TclDefaultBgErrorHandlerObjCmd( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); diff --git a/generic/tclNotify.c b/generic/tclNotify.c index 89182ea..f13fca3 100644 --- a/generic/tclNotify.c +++ b/generic/tclNotify.c @@ -72,11 +72,25 @@ typedef struct ThreadSpecificData { /* Next notifier in global list of notifiers. * Access is controlled by the listLock global * mutex. */ +#ifndef TCL_WIDE_CLICKS /* Last "time" source checked, used as threshold */ + unsigned long lastCheckClicks; +#else + Tcl_WideInt lastCheckClicks; +#endif } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* + * Used for performance purposes, threshold to bypass check source (if don't wait) + * Values under 1000 should be approximately under 1ms, e. g. 10 is ca. 0.01ms + */ +#ifndef CHECK_EVENT_SOURCE_THRESHOLD + #define CHECK_EVENT_SOURCE_THRESHOLD 10 +#endif + + +/* * Global list of notifiers. Access to this list is controlled by the listLock * mutex. If this becomes a performance bottleneck, this could be replaced * with a hashtable. @@ -618,28 +632,36 @@ Tcl_ServiceEvent( ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); /* + * No event flags is equivalent to TCL_ALL_EVENTS. + */ + + if ((flags & TCL_ALL_EVENTS) == 0) { + flags |= TCL_ALL_EVENTS; + } + + /* * Asynchronous event handlers are considered to be the highest priority * events, and so must be invoked before we process events on the event * queue. */ - if (Tcl_AsyncReady()) { + if ((flags & TCL_ASYNC_EVENTS) && Tcl_AsyncReady()) { (void) Tcl_AsyncInvoke(NULL, 0); return 1; } - /* - * No event flags is equivalent to TCL_ALL_EVENTS. - */ - - if ((flags & TCL_ALL_EVENTS) == 0) { - flags |= TCL_ALL_EVENTS; + /* Async only */ + if ((flags & TCL_ALL_EVENTS) == TCL_ASYNC_EVENTS) { + return 0; } /* * If timer marker reached, process timer events now. */ - if (tsdPtr->timerMarkerPtr == INT2PTR(-1)) { + if ( tsdPtr->timerMarkerPtr == INT2PTR(-1) + || !tsdPtr->firstEventPtr + || ((flags & TCL_ALL_EVENTS) == TCL_TIMER_EVENTS) + ) { goto timer; } @@ -743,7 +765,7 @@ timer: /* * Process timer queue, if alloved and timers are enabled. */ - if (flags & TCL_TIMER_EVENTS && tsdPtr->timerMarkerPtr) { + if ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerMarkerPtr) { /* reset marker */ tsdPtr->timerMarkerPtr = NULL; @@ -762,6 +784,73 @@ timer: /* *---------------------------------------------------------------------- * + * TclPeekEventQueued -- + * + * Check whether some event (except idle) available (async, queued, timer). + * + * This will be used e. g. in TclServiceIdle to stop the processing of the + * the idle events if some "normal" event occurred. + * + * Results: + * Returns 1 if some event queued, 0 otherwise. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TclPeekEventQueued( + int flags) +{ + EventSource *sourcePtr; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + int repeat = 1; + + do { + /* + * Events already pending ? + */ + if ( Tcl_AsyncReady() + || (tsdPtr->firstEventPtr) + || ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerMarkerPtr) + ) { + return 1; + } + + if (flags & TCL_DONT_WAIT) { + /* don't need to wait/check for events too often */ +#ifndef TCL_WIDE_CLICKS + unsigned long clicks = TclpGetClicks(); +#else + Tcl_WideInt clicks = TclpGetWideClicks(); +#endif + + if ((clicks - tsdPtr->lastCheckClicks) <= CHECK_EVENT_SOURCE_THRESHOLD) { + return 0; + } + tsdPtr->lastCheckClicks = clicks; + } + + /* + * Check all the event sources for new events. + */ + for (sourcePtr = tsdPtr->firstEventSourcePtr; sourcePtr != NULL; + sourcePtr = sourcePtr->nextPtr) { + if (sourcePtr->checkProc) { + (sourcePtr->checkProc)(sourcePtr->clientData, flags); + } + } + + } while (repeat--); + + return 0; +} + +/* + *---------------------------------------------------------------------- + * * TclSetTimerEventMarker -- * * Set timer event marker to the last pending event in the queue. @@ -920,26 +1009,33 @@ Tcl_DoOneEvent( * TCL_TIMER_EVENTS, TCL_IDLE_EVENTS, or * others defined by event sources. */ { - int result = 0, oldMode; + int result = 0, oldMode, i = 0; EventSource *sourcePtr; Tcl_Time *timePtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); /* - * The first thing we do is to service any asynchronous event handlers. + * No event flags is equivalent to TCL_ALL_EVENTS. */ - if (Tcl_AsyncReady()) { - (void) Tcl_AsyncInvoke(NULL, 0); - return 1; + if ((flags & TCL_ALL_EVENTS) == 0) { + flags |= TCL_ALL_EVENTS; } /* - * No event flags is equivalent to TCL_ALL_EVENTS. + * Asynchronous event handlers are considered to be the highest priority + * events, and so must be invoked before we process events on the event + * queue. */ - if ((flags & TCL_ALL_EVENTS) == 0) { - flags |= TCL_ALL_EVENTS; + if ((flags & TCL_ASYNC_EVENTS) && Tcl_AsyncReady()) { + (void) Tcl_AsyncInvoke(NULL, 0); + return 1; + } + + /* Async only */ + if ((flags & TCL_ALL_EVENTS) == TCL_ASYNC_EVENTS) { + return 0; } /* @@ -964,12 +1060,13 @@ Tcl_DoOneEvent( */ if ((flags & TCL_ALL_EVENTS) == TCL_IDLE_EVENTS) { - flags = TCL_IDLE_EVENTS | TCL_DONT_WAIT; + flags |= TCL_DONT_WAIT; goto idleEvents; } /* - * Ask Tcl to service a queued event, if there are any. + * Ask Tcl to service any asynchronous event handlers or + * queued event, if there are any. */ if (Tcl_ServiceEvent(flags)) { @@ -983,9 +1080,22 @@ Tcl_DoOneEvent( */ if (flags & TCL_DONT_WAIT) { + /* don't need to wait/check for events too often */ +#ifndef TCL_WIDE_CLICKS + unsigned long clicks = TclpGetClicks(); +#else + Tcl_WideInt clicks = TclpGetWideClicks(); +#endif + if ((clicks - tsdPtr->lastCheckClicks) <= CHECK_EVENT_SOURCE_THRESHOLD) { + goto idleEvents; + } + tsdPtr->lastCheckClicks = clicks; + tsdPtr->blockTime.sec = 0; tsdPtr->blockTime.usec = 0; tsdPtr->blockTimeSet = 1; + timePtr = &tsdPtr->blockTime; + goto wait; /* for notifier resp. system events */ } else { tsdPtr->blockTimeSet = 0; } @@ -1004,7 +1114,7 @@ Tcl_DoOneEvent( } tsdPtr->inTraversal = 0; - if ((flags & TCL_DONT_WAIT) || tsdPtr->blockTimeSet) { + if (tsdPtr->blockTimeSet) { timePtr = &tsdPtr->blockTime; } else { timePtr = NULL; @@ -1014,7 +1124,7 @@ Tcl_DoOneEvent( * Wait for a new event or a timeout. If Tcl_WaitForEvent returns -1, * we should abort Tcl_DoOneEvent. */ - + wait: result = Tcl_WaitForEvent(timePtr); if (result < 0) { result = 0; @@ -1049,7 +1159,7 @@ Tcl_DoOneEvent( idleEvents: if (flags & TCL_IDLE_EVENTS) { - if (TclServiceIdle()) { + if (TclServiceIdleEx(flags, INT_MAX)) { result = 1; break; } diff --git a/generic/tclTimer.c b/generic/tclTimer.c index 12b039f..52a3073 100644 --- a/generic/tclTimer.c +++ b/generic/tclTimer.c @@ -121,6 +121,7 @@ static void FreeAfterPtr(ClientData clientData); static AfterInfo * GetAfterEvent(AfterAssocData *assocPtr, Tcl_Obj *objPtr); static ThreadSpecificData *InitTimer(void); static void TimerExitProc(ClientData clientData); +static void TimerCheckProc(ClientData clientData, int flags); static void TimerSetupProc(ClientData clientData, int flags); static void AfterObj_DupInternalRep(Tcl_Obj *, Tcl_Obj *); @@ -258,7 +259,7 @@ InitTimer(void) if (tsdPtr == NULL) { tsdPtr = TCL_TSD_INIT(&dataKey); - Tcl_CreateEventSource(TimerSetupProc, NULL, tsdPtr); + Tcl_CreateEventSource(TimerSetupProc, TimerCheckProc, tsdPtr); Tcl_CreateThreadExitHandler(TimerExitProc, NULL); } return tsdPtr; @@ -289,7 +290,7 @@ TimerExitProc( TclThreadDataKeyGet(&dataKey); if (tsdPtr != NULL) { - Tcl_DeleteEventSource(TimerSetupProc, NULL, tsdPtr); + Tcl_DeleteEventSource(TimerSetupProc, TimerCheckProc, tsdPtr); while ((tsdPtr->lastPromptPtr) != NULL) { TclDeleteTimerEntry(tsdPtr->lastPromptPtr); @@ -582,7 +583,7 @@ TclDeleteTimerEntry( static void TimerSetupProc( - ClientData data, /* Not used. */ + ClientData data, /* Specific data. */ int flags) /* Event flags as passed to Tcl_DoOneEvent. */ { Tcl_Time blockTime, *firstTime; @@ -590,10 +591,11 @@ TimerSetupProc( if (tsdPtr == NULL) { tsdPtr = InitTimer(); }; - if (((flags & TCL_IDLE_EVENTS) && tsdPtr->idleList ) - || ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerPending)) { + if ( ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerPending) + || ((flags & TCL_IDLE_EVENTS) && tsdPtr->idleList ) + ) { /* - * There is an idle handler or a pending timer event, so just poll. + * There is a pending timer event or an idle handler, so just poll. */ blockTime.sec = 0; @@ -616,11 +618,10 @@ TimerSetupProc( blockTime.sec = 0; blockTime.usec = 0; } - + /* - * If the first timer has expired, stick an event on the queue. - */ - + * If the first timer has expired, stick an event on the queue right now. + */ if (!tsdPtr->timerPending && blockTime.sec == 0 && blockTime.usec == 0) { TclSetTimerEventMarker(); tsdPtr->timerPending = 1; @@ -636,6 +637,67 @@ TimerSetupProc( /* *---------------------------------------------------------------------- * + * TimerCheckProc -- + * + * This function is called by Tcl_DoOneEvent to check the timer event + * source for events. This routine checks the first timer in the list. + * + * Results: + * None. + * + * Side effects: + * May queue an event and update the maximum notifier block time. + * + *---------------------------------------------------------------------- + */ + +static void +TimerCheckProc( + ClientData data, /* Specific data. */ + int flags) /* Event flags as passed to Tcl_DoOneEvent. */ +{ + Tcl_Time blockTime, *firstTime; + ThreadSpecificData *tsdPtr = (ThreadSpecificData *)data; + + if (!(flags & TCL_TIMER_EVENTS)) { + return; + } + + if (tsdPtr == NULL) { tsdPtr = InitTimer(); }; + + /* If already pending */ + if (!tsdPtr->timerList || tsdPtr->timerPending) { + return; + } + + /* + * Verify the first timer on the queue. + */ + Tcl_GetTime(&blockTime); + firstTime = &(TimerEntry2TimerHandler(tsdPtr->timerList)->time); + blockTime.sec = firstTime->sec - blockTime.sec; + blockTime.usec = firstTime->usec - blockTime.usec; + if (blockTime.usec < 0) { + blockTime.sec -= 1; + blockTime.usec += 1000000; + } + if (blockTime.sec < 0) { + blockTime.sec = 0; + blockTime.usec = 0; + } + + /* + * If the first timer has expired, stick an event on the queue. + */ + if (blockTime.sec == 0 && blockTime.usec == 0) { + TclSetTimerEventMarker(); + tsdPtr->timerPending = 1; + } +} + +/* + *---------------------------------------------------------------------- + * * TclServiceTimerEvents -- * * This function is called by Tcl_ServiceEvent when a timer events should @@ -785,7 +847,7 @@ done: /* Compute the next timeout (later via TimerSetupProc using the first timer). */ tsdPtr->timerPending = 0; - return 1; /* processing done, again later via TimerSetupProc */ + return 1; /* processing done, again later via TimerCheckProc */ } /* @@ -928,7 +990,7 @@ Tcl_CancelIdleCall( /* *---------------------------------------------------------------------- * - * TclServiceIdle -- + * TclServiceIdle -- , TclServiceIdleEx -- * * This function is invoked by the notifier when it becomes idle. It will * invoke all idle handlers that are present at the time the call is @@ -945,14 +1007,15 @@ Tcl_CancelIdleCall( */ int -TclServiceIdle(void) +TclServiceIdleEx( + int flags, + int count) { TimerEntry *idlePtr; size_t currentGeneration; - Tcl_Time blockTime; ThreadSpecificData *tsdPtr = InitTimer(); - if (tsdPtr->idleList == NULL) { + if ((idlePtr = tsdPtr->idleList) == NULL) { return 0; } @@ -975,9 +1038,7 @@ TclServiceIdle(void) * during the call. */ - while ((idlePtr = tsdPtr->idleList) != NULL - && idlePtr->generation <= currentGeneration - ) { + while (idlePtr->generation <= currentGeneration) { /* detach entry from the owner's list */ TclSpliceOutEx(idlePtr, tsdPtr->idleList, tsdPtr->lastIdlePtr); @@ -989,18 +1050,33 @@ TclServiceIdle(void) (*idlePtr->deleteProc)(idlePtr->clientData); } ckfree((char *) idlePtr); + + /* + * Stop processing idle if idle queue empty, count reached or other + * events queued (only if not idle events only to service). + */ + + if ( (idlePtr = tsdPtr->idleList) == NULL + || !--count + || ((flags & TCL_ALL_EVENTS) != TCL_IDLE_EVENTS + && TclPeekEventQueued(flags)) + ) { + break; + } } - if (tsdPtr->idleList) { - blockTime.sec = 0; - blockTime.usec = 0; - Tcl_SetMaxBlockTime(&blockTime); - } + /* Reset generation */ if (!tsdPtr->idleList) { tsdPtr->idleGeneration = 0; } return 1; } + +int +TclServiceIdle(void) +{ + return TclServiceIdleEx(TCL_ALL_EVENTS, INT_MAX); +} /* *---------------------------------------------------------------------- diff --git a/tests/event.test b/tests/event.test index c905489..cf65ae17 100644 --- a/tests/event.test +++ b/tests/event.test @@ -694,12 +694,9 @@ test event-11.7 {Bug 16828b3744} { } {} -test event-12.1 {Tcl_UpdateCmd procedure} { - list [catch {update a b} msg] $msg -} {1 {wrong # args: should be "update ?option?"}} test event-12.2 {Tcl_UpdateCmd procedure} { list [catch {update bogus} msg] $msg -} {1 {bad option "bogus": must be idletasks or noidletasks}} +} {1 {bad option "bogus": must be idletasks, -nowait, -wait, -idle, -noidle, -timer, -notimer, -file, -nofile, -window, -nowindow, -async, or -noasync}} test event-12.3 {Tcl_UpdateCmd procedure} { foreach i [after info] { after cancel $i @@ -729,6 +726,62 @@ test event-12.4 {Tcl_UpdateCmd procedure} { list $x $y $z } {x-done before z-done} +test event-12.5 {update -idle, update -noidle} { + foreach i [after info] { + after cancel $i + } + set x {} + after idle {lappend x idle} + update -noidle + after 0 {lappend x 0} + update -noidle + after 50 {lappend x 1} + update -noidle + lappend x 2 + update -idle + lappend x 3 + after idle {lappend x idle} + after 0 {lappend x 4} + after 0 {lappend x 5} + update -idle + lappend x 6 + update + lappend x res:[vwait x 500] + set x +} {0 2 idle 3 idle 6 4 5 1 res:1} + +test event-12.6 {update -timer, update -notimer} { + foreach i [after info] { + after cancel $i + } + set x {} + after idle {lappend x idle.0} + update -timer + after 0 {lappend x 0a} + update -notimer + after idle { + lappend x idle.1a; + after 0 {lappend x 0b}; + after idle {lappend x idle.1b} + } + after 50 {lappend x 1; after idle {lappend x idle.2}} + update -timer + lappend x 2 + update -timer -idle + lappend x 3 + after idle {lappend x idle.3} + after 0 {lappend x 4} + after 0 {lappend x 5} + update -timer -idle + lappend x 6 + update + lappend x res:[vwait x 500] + update -noidle + lappend x 7 + update + set x +} {idle.0 0a 2 idle.1a 0b idle.1b 3 4 5 idle.3 6 1 res:1 7 idle.2} + test event-13.1 {Tcl_WaitForFile procedure, readable} {testfilehandler} { foreach i [after info] { after cancel $i -- cgit v0.12 From 0e11ffaa99da39ffd0a3eac314a1f9f848641b83 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:24:42 +0000 Subject: interim commit: try to extend "vwait" with same options as "update" --- generic/tclEvent.c | 166 ++++++++++++++++++++++++++++++++++++----------------- tests/event.test | 16 +++--- 2 files changed, 121 insertions(+), 61 deletions(-) diff --git a/generic/tclEvent.c b/generic/tclEvent.c index d18836b..6413d10 100644 --- a/generic/tclEvent.c +++ b/generic/tclEvent.c @@ -1298,6 +1298,61 @@ TclInThreadExit(void) return tsdPtr->inExit; } } + + +static int +GetEventFlagsFromOpts( + Tcl_Interp *interp, /* Used for error reporting if not NULL. */ + int objc, /* Number of arguments. */ + Tcl_Obj *CONST objv[], /* Arguments containing the option to lookup. */ + int *flagsPtr) /* Input and resulting flags. */ +{ + int i, optionIndex, result = TCL_ERROR; + int flags = *flagsPtr; /* default flags */ + static CONST char *updateOptions[] = { + "-idle", "-noidle", /* new options */ + "-timer", "-notimer", + "-file", "-nofile", + "-window", "-nowindow", + "-async", "-noasync", + "-nowait", "-wait", + "idletasks", /* backwards compat. */ + NULL}; + static CONST struct { + int mask; + int flags; + } *updateFlag, updateFlags[] = { + {0, TCL_IDLE_EVENTS}, {TCL_IDLE_EVENTS, 0}, /* -idle, -noidle */ + {0, TCL_TIMER_EVENTS}, {TCL_TIMER_EVENTS, 0}, /* -timer, -notimer */ + {0, TCL_FILE_EVENTS}, {TCL_FILE_EVENTS, 0}, /* -file, -nofile */ + {0, TCL_WINDOW_EVENTS}, {TCL_WINDOW_EVENTS, 0}, /* -window, -nowindow */ + {0, TCL_ASYNC_EVENTS}, {TCL_ASYNC_EVENTS, 0}, /* -async, -noasync */ + {0, TCL_DONT_WAIT}, {TCL_DONT_WAIT, 0}, /* -nowait, -wait */ + {TCL_ALL_EVENTS, + TCL_WINDOW_EVENTS|TCL_IDLE_EVENTS}, /* idletasks */ + {0, 0} /* dummy / place holder */ + }; + + for (i = 0; i < objc; i++) { + if (Tcl_GetIndexFromObj(interp, objv[i], updateOptions, + "option", 0, &optionIndex) != TCL_OK) { + goto done; + } + updateFlag = &updateFlags[optionIndex]; + /* pure positive option and still default, + * reset all events (only this flag) */ + if (!updateFlag->mask && flags == *flagsPtr) { + flags &= ~TCL_ALL_EVENTS; + } + flags &= ~updateFlag->mask; + flags |= updateFlag->flags; + } + result = TCL_OK; + + done: + *flagsPtr = flags; + return result; +} static void VwaitTimeOutProc( @@ -1332,57 +1387,90 @@ Tcl_VwaitObjCmd( int objc, /* Number of arguments. */ Tcl_Obj *CONST objv[]) /* Argument objects. */ { - int done, foundEvent, flags = TCL_ALL_EVENTS; + int done = 0, foundEvent; + int flags = TCL_ALL_EVENTS; /* default flags */ char *nameString; + int opti = 1, /* start option index (and index of varname later) */ + optc = objc - 2; /* options count without cmd and varname */ TimerEntry *timerEvent = NULL; + Tcl_WideInt ms = -1; - if (objc < 2 || objc > 3) { - Tcl_WrongNumArgs(interp, 1, objv, "name ?timeout?"); + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "?options? name ?timeout?"); return TCL_ERROR; } + /* if arguments available - wrap options to flags */ + if (objc >= 3) { + /* first try to recognize options up to the possible end, thereby + * we assume that varname is not integer, try to get numeric timeout, + * (just assume precidence of option fewer as timeout) + */ + if (Tcl_GetWideIntFromObj(NULL, objv[objc-1], &ms) == TCL_OK) { + objc--; + optc--; + } + + /* now try to parse options (if available) */ + if ( optc > 0 + && GetEventFlagsFromOpts(interp, optc, objv+1, &flags) != TCL_OK + ) { + return TCL_ERROR; + } + /* opti points to varname */ + opti += optc; + } + + done = 0; + /* if timeout specified - create timer event or no-wait by 0ms */ - if (objc == 3) { - Tcl_Time wakeup; - Tcl_WideInt ms; - if (Tcl_GetWideIntFromObj(interp, objv[2], &ms) != TCL_OK) { - return TCL_ERROR; - } - if (ms > 0) { + if (ms != -1) { + Tcl_Time wakeup; + + if (ms > 0) { Tcl_GetTime(&wakeup); wakeup.sec += (long)(ms / 1000); wakeup.usec += ((long)(ms % 1000)) * 1000; if (wakeup.usec > 1000000) { - wakeup.sec++; - wakeup.usec -= 1000000; + wakeup.sec++; + wakeup.usec -= 1000000; } timerEvent = TclCreateAbsoluteTimerHandlerEx(&wakeup, VwaitTimeOutProc, NULL, 0); timerEvent->clientData = &done; } else if (ms == 0) { flags |= TCL_DONT_WAIT; - } else { - /* infinite vait */ - objc = 2; } } - nameString = Tcl_GetString(objv[1]); + nameString = Tcl_GetString(objv[opti]); if (Tcl_TraceVar(interp, nameString, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, VwaitVarProc, (ClientData) &done) != TCL_OK) { + + /* if timeout-timer and no timeout fired, cancel timer event */ + if (timerEvent && done != -1) { + TclDeleteTimerEntry(timerEvent); + } return TCL_ERROR; }; - done = 0; do { if ((foundEvent = Tcl_DoOneEvent(flags)) == 0) { - /* no wait, no error - just stop waiting (no more events) */ - if (flags |= TCL_DONT_WAIT) { + /* + * If don't wait flag set - no error, and two cases: + * option -nowait for vwait means - we don't wait for events + * if no timeout (0) - just stop waiting (no more events) + */ + if (flags & TCL_DONT_WAIT) { foundEvent = 1; + if (ms != 0) { + goto checkLimit; /* continue waiting */ + } done = -2; } break; } + checkLimit: if (Tcl_LimitExceeded(interp)) { foundEvent = -1; break; @@ -1399,7 +1487,7 @@ Tcl_VwaitObjCmd( } /* if timeout specified (and no errors) */ - if (objc == 3 && foundEvent > 0) { + if (ms != -1 && foundEvent > 0) { Tcl_Obj *objPtr; /* done - true, timeout false */ @@ -1468,42 +1556,14 @@ Tcl_UpdateObjCmd( int objc, /* Number of arguments. */ Tcl_Obj *CONST objv[]) /* Argument objects. */ { - int i, optionIndex; - static CONST defUpdateFlags = TCL_ALL_EVENTS|TCL_DONT_WAIT; - int flags = defUpdateFlags; - static CONST char *updateOptions[] = {"idletasks", /* backwards compat. */ - "-nowait", "-wait", /* new options */ - "-idle", "-noidle", "-timer", "-notimer", - "-file", "-nofile", "-window", "-nowindow", - "-async", "-noasync", - NULL}; - static CONST struct { - int minus; - int plus; - } *updateFlag, updateFlags[] = { - {TCL_ALL_EVENTS, - TCL_WINDOW_EVENTS|TCL_IDLE_EVENTS}, /* idletasks */ - {0, TCL_DONT_WAIT}, {TCL_DONT_WAIT, 0}, /* -nowait, -wait */ - {0, TCL_IDLE_EVENTS}, {TCL_IDLE_EVENTS, 0}, /* -idle, -noidle */ - {0, TCL_TIMER_EVENTS}, {TCL_TIMER_EVENTS, 0}, /* -file, -nofile */ - {0, TCL_FILE_EVENTS}, {TCL_FILE_EVENTS, 0}, /* -file, -nofile */ - {0, TCL_WINDOW_EVENTS}, {TCL_WINDOW_EVENTS, 0}, /* -window, -nowindow */ - {0, TCL_ASYNC_EVENTS}, {TCL_ASYNC_EVENTS, 0}, /* -async, -noasync */ - {0, 0} /* dummy / place holder */ - }; + int flags = TCL_ALL_EVENTS|TCL_DONT_WAIT; /* default flags */ - for (i = 1; i < objc; i++) { - if (Tcl_GetIndexFromObj(interp, objv[i], updateOptions, - "option", 0, &optionIndex) != TCL_OK) { + /* if arguments available - wrap options to flags */ + if (objc > 1) { + int i = 1; + if (GetEventFlagsFromOpts(interp, objc-1, objv+1, &flags) != TCL_OK) { return TCL_ERROR; } - updateFlag = &updateFlags[optionIndex]; - /* pure positive option and still default - reset all events */ - if (flags == defUpdateFlags && !updateFlag->minus) { - flags &= ~TCL_ALL_EVENTS; - } - flags &= ~updateFlag->minus; - flags |= updateFlag->plus; } while (Tcl_DoOneEvent(flags) != 0) { diff --git a/tests/event.test b/tests/event.test index cf65ae17..cce486a 100644 --- a/tests/event.test +++ b/tests/event.test @@ -488,10 +488,10 @@ test event-10.1 {Tcl_Exit procedure} {stdio} { test event-11.1 {Tcl_VwaitCmd procedure} { list [catch {vwait} msg] $msg -} {1 {wrong # args: should be "vwait name ?timeout?"}} +} {1 {wrong # args: should be "vwait ?options? name ?timeout?"}} test event-11.2 {Tcl_VwaitCmd procedure} { list [catch {vwait a b} msg] $msg -} {1 {expected integer but got "b"}} +} {1 {bad option "a": must be -idle, -noidle, -timer, -notimer, -file, -nofile, -window, -nowindow, -async, -noasync, -nowait, -wait, or idletasks}} test event-11.3 {Tcl_VwaitCmd procedure} { catch {unset x} set x 1 @@ -568,15 +568,15 @@ test event-11.4.1 {vwait with timeout} {} { set x {} # success cases: after 0 {lappend z 0} - after 20 {lappend x 1} - after 30 {lappend x 2} - after 100 {lappend x 3} + after 100 {lappend x 1} + after 100 {lappend x 2} + after 500 {lappend x 3} after 1000 {lappend x "error-too-slow"} vwait x 0; # no-wait lappend z $x; # 0 {} - (x still empty) - vwait x 50; # wait up-to 50ms + vwait x 200; # wait up-to 200ms lappend z $x; # 0 {} {1 2} - vwait x -1; # wait without timeout + vwait x -1; # infinite wait lappend z $x; # 0 {} {1 2} {1 2 3} foreach i [after info] { after cancel $i @@ -696,7 +696,7 @@ test event-11.7 {Bug 16828b3744} { test event-12.2 {Tcl_UpdateCmd procedure} { list [catch {update bogus} msg] $msg -} {1 {bad option "bogus": must be idletasks, -nowait, -wait, -idle, -noidle, -timer, -notimer, -file, -nofile, -window, -nowindow, -async, or -noasync}} +} {1 {bad option "bogus": must be -idle, -noidle, -timer, -notimer, -file, -nofile, -window, -nowindow, -async, -noasync, -nowait, -wait, or idletasks}} test event-12.3 {Tcl_UpdateCmd procedure} { foreach i [after info] { after cancel $i -- cgit v0.12 From d2d76748809298daff2f10a63b2999d559d129dd Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:24:53 +0000 Subject: [enhancement] extend "vwait" with same options as "update", new syntax "vwait ?options? ?timeout? varname". some small improvements and fixing: - Tcl_DoOneEvent can wait for block time that was set with Tcl_SetMaxBlockTime outside an event source traversal, and stop waiting if Tcl_SetMaxBlockTime was called outside an event source (another event occurs and interrupt waiting loop), etc; - safer more precise pre-lookup by options (use TclObjIsIndexOfTable instead of simply comparison of type with tclIndexType); test cases extended to cover conditional "vwait" usage; --- generic/tclEvent.c | 90 +++++++++++++++++++++++++-------------------------- generic/tclIndexObj.c | 37 +++++++++++++++++++++ generic/tclInt.h | 6 ++++ generic/tclNotify.c | 54 +++++++++++++++---------------- generic/tclTimer.c | 2 +- tests/event.test | 51 ++++++++++++++++++++--------- 6 files changed, 151 insertions(+), 89 deletions(-) diff --git a/generic/tclEvent.c b/generic/tclEvent.c index 6413d10..84e4637 100644 --- a/generic/tclEvent.c +++ b/generic/tclEvent.c @@ -1300,6 +1300,17 @@ TclInThreadExit(void) } +static CONST char *updateEventOptions[] = { + "-idle", "-noidle", /* new options */ + "-timer", "-notimer", + "-file", "-nofile", + "-window", "-nowindow", + "-async", "-noasync", + "-nowait", "-wait", + "idletasks", /* backwards compat. */ + NULL +}; + static int GetEventFlagsFromOpts( Tcl_Interp *interp, /* Used for error reporting if not NULL. */ @@ -1309,15 +1320,6 @@ GetEventFlagsFromOpts( { int i, optionIndex, result = TCL_ERROR; int flags = *flagsPtr; /* default flags */ - static CONST char *updateOptions[] = { - "-idle", "-noidle", /* new options */ - "-timer", "-notimer", - "-file", "-nofile", - "-window", "-nowindow", - "-async", "-noasync", - "-nowait", "-wait", - "idletasks", /* backwards compat. */ - NULL}; static CONST struct { int mask; int flags; @@ -1334,7 +1336,7 @@ GetEventFlagsFromOpts( }; for (i = 0; i < objc; i++) { - if (Tcl_GetIndexFromObj(interp, objv[i], updateOptions, + if (Tcl_GetIndexFromObj(interp, objv[i], updateEventOptions, "option", 0, &optionIndex) != TCL_OK) { goto done; } @@ -1354,14 +1356,6 @@ GetEventFlagsFromOpts( return result; } -static void -VwaitTimeOutProc( - ClientData clientData) -{ - int *donePtr = (int *) clientData; - - *donePtr = -1; -} /* *---------------------------------------------------------------------- * @@ -1387,27 +1381,26 @@ Tcl_VwaitObjCmd( int objc, /* Number of arguments. */ Tcl_Obj *CONST objv[]) /* Argument objects. */ { - int done = 0, foundEvent; + int done = 0, foundEvent = 1; int flags = TCL_ALL_EVENTS; /* default flags */ char *nameString; int opti = 1, /* start option index (and index of varname later) */ optc = objc - 2; /* options count without cmd and varname */ - TimerEntry *timerEvent = NULL; Tcl_WideInt ms = -1; + Tcl_Time wakeup; if (objc < 2) { - Tcl_WrongNumArgs(interp, 1, objv, "?options? name ?timeout?"); + Tcl_WrongNumArgs(interp, 1, objv, "?options? ?timeout? name"); return TCL_ERROR; } /* if arguments available - wrap options to flags */ if (objc >= 3) { /* first try to recognize options up to the possible end, thereby - * we assume that varname is not integer, try to get numeric timeout, - * (just assume precidence of option fewer as timeout) + * we assume that option is not an integer, try to get numeric timeout */ - if (Tcl_GetWideIntFromObj(NULL, objv[objc-1], &ms) == TCL_OK) { - objc--; + if (!TclObjIsIndexOfTable(objv[optc], updateEventOptions) + && Tcl_GetWideIntFromObj(NULL, objv[optc], &ms) == TCL_OK) { optc--; } @@ -1417,16 +1410,12 @@ Tcl_VwaitObjCmd( ) { return TCL_ERROR; } - /* opti points to varname */ - opti += optc; } done = 0; /* if timeout specified - create timer event or no-wait by 0ms */ if (ms != -1) { - Tcl_Time wakeup; - if (ms > 0) { Tcl_GetTime(&wakeup); wakeup.sec += (long)(ms / 1000); @@ -1435,26 +1424,38 @@ Tcl_VwaitObjCmd( wakeup.sec++; wakeup.usec -= 1000000; } - timerEvent = TclCreateAbsoluteTimerHandlerEx(&wakeup, VwaitTimeOutProc, NULL, 0); - timerEvent->clientData = &done; } else if (ms == 0) { flags |= TCL_DONT_WAIT; } } - nameString = Tcl_GetString(objv[opti]); + nameString = Tcl_GetString(objv[objc-1]); if (Tcl_TraceVar(interp, nameString, TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, VwaitVarProc, (ClientData) &done) != TCL_OK) { - - /* if timeout-timer and no timeout fired, cancel timer event */ - if (timerEvent && done != -1) { - TclDeleteTimerEntry(timerEvent); - } return TCL_ERROR; }; do { + /* if wait - set blocking time */ + if (ms > 0) { + Tcl_Time blockTime; + Tcl_GetTime(&blockTime); + blockTime.sec = wakeup.sec - blockTime.sec; + blockTime.usec = wakeup.usec - blockTime.usec; + if (blockTime.usec < 0) { + blockTime.sec -= 1; + blockTime.usec += 1000000; + } + if ( blockTime.sec < 0 + || (blockTime.sec == 0 && blockTime.usec <= 0) + ) { + /* timeout occurs */ + done = -1; + break; + } + Tcl_SetMaxBlockTime(&blockTime); + } if ((foundEvent = Tcl_DoOneEvent(flags)) == 0) { /* * If don't wait flag set - no error, and two cases: @@ -1463,14 +1464,16 @@ Tcl_VwaitObjCmd( */ if (flags & TCL_DONT_WAIT) { foundEvent = 1; - if (ms != 0) { - goto checkLimit; /* continue waiting */ - } done = -2; } + if (ms > 0) { + foundEvent = 1; + goto checkWait; /* continue waiting */ + } break; } - checkLimit: + checkWait: + /* check interpreter limit exceeded */ if (Tcl_LimitExceeded(interp)) { foundEvent = -1; break; @@ -1481,11 +1484,6 @@ Tcl_VwaitObjCmd( TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS, VwaitVarProc, (ClientData) &done); - /* if timeout-timer and no timeout fired, cancel timer event */ - if (timerEvent && done != -1) { - TclDeleteTimerEntry(timerEvent); - } - /* if timeout specified (and no errors) */ if (ms != -1 && foundEvent > 0) { Tcl_Obj *objPtr; diff --git a/generic/tclIndexObj.c b/generic/tclIndexObj.c index ced7bd9..4caba7a 100644 --- a/generic/tclIndexObj.c +++ b/generic/tclIndexObj.c @@ -63,6 +63,43 @@ typedef struct { /* *---------------------------------------------------------------------- * + * TclObjIsIndexOfStruct -- + * + * This function looks up an object's is a index of given table. + * + * Used for fast lookup by dynamic options count to check for other + * object types. + * + * Results: + * 1 if object is an option of table, otherwise 0. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ +int +TclObjIsIndexOfStruct( + Tcl_Obj *objPtr, /* Object containing the string to lookup. */ + const void *tablePtr) /* Array of strings to compare against the + * value of objPtr; last entry must be NULL + * and there must not be duplicate entries. */ +{ + IndexRep *indexRep; + if (objPtr->typePtr != &tclIndexType) { + return 0; + } + indexRep = objPtr->internalRep.twoPtrValue.ptr1; + + if (indexRep->tablePtr != (void *) tablePtr) { + return 0; + } + return 1; +} + +/* + *---------------------------------------------------------------------- + * * Tcl_GetIndexFromObj -- * * This function looks up an object's value in a table of strings and diff --git a/generic/tclInt.h b/generic/tclInt.h index dd73eac..42223e4 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -2561,6 +2561,12 @@ MODULE_SCOPE char tclEmptyString; *---------------------------------------------------------------- */ +MODULE_SCOPE int TclObjIsIndexOfStruct(Tcl_Obj *objPtr, + const void *tablePtr); +#define TclObjIsIndexOfTable(objPtr, tablePtr) \ + ((objPtr->typePtr == &tclIndexType) \ + && TclObjIsIndexOfStruct(objPtr, tablePtr)) + MODULE_SCOPE void TclAppendBytesToByteArray(Tcl_Obj *objPtr, const unsigned char *bytes, int len); MODULE_SCOPE void TclAdvanceContinuations(int* line, int** next, int loc); diff --git a/generic/tclNotify.c b/generic/tclNotify.c index f13fca3..fb251b7 100644 --- a/generic/tclNotify.c +++ b/generic/tclNotify.c @@ -989,14 +989,18 @@ Tcl_SetMaxBlockTime( * Results: * The return value is 1 if the function actually found an event to * process. If no processing occurred, then 0 is returned (this can - * happen if the TCL_DONT_WAIT flag is set or if there are no event - * handlers to wait for in the set specified by flags). + * happen if the TCL_DONT_WAIT flag is set or block time was set using + * Tcl_SetMaxBlockTime before or if there are no event handlers to wait + * for in the set specified by flags). * * Side effects: * May delay execution of process while waiting for an event, unless * TCL_DONT_WAIT is set in the flags argument. Event sources are invoked * to check for and queue events. Event handlers may produce arbitrary * side effects. + * If block time was set (Tcl_SetMaxBlockTime) but another event occurs + * and interrupt wait, the function can return early, thereby it resets + * the block time (caller should use Tcl_SetMaxBlockTime again). * *---------------------------------------------------------------------- */ @@ -1013,6 +1017,7 @@ Tcl_DoOneEvent( EventSource *sourcePtr; Tcl_Time *timePtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + int stopWait; /* * No event flags is equivalent to TCL_ALL_EVENTS. @@ -1022,20 +1027,26 @@ Tcl_DoOneEvent( flags |= TCL_ALL_EVENTS; } + /* Block time was set outside an event source traversal or no wait */ + stopWait = tsdPtr->blockTimeSet || (flags & TCL_DONT_WAIT); + /* * Asynchronous event handlers are considered to be the highest priority * events, and so must be invoked before we process events on the event * queue. */ - if ((flags & TCL_ASYNC_EVENTS) && Tcl_AsyncReady()) { - (void) Tcl_AsyncInvoke(NULL, 0); - return 1; - } + if (flags & TCL_ASYNC_EVENTS) { + if (Tcl_AsyncReady()) { + (void) Tcl_AsyncInvoke(NULL, 0); + return 1; + } - /* Async only */ - if ((flags & TCL_ALL_EVENTS) == TCL_ASYNC_EVENTS) { - return 0; + /* Async only and don't wait - return */ + if ( (flags & (TCL_ALL_EVENTS|TCL_DONT_WAIT)) + == (TCL_ASYNC_EVENTS|TCL_DONT_WAIT) ) { + return 0; + } } /* @@ -1047,12 +1058,10 @@ Tcl_DoOneEvent( tsdPtr->serviceMode = TCL_SERVICE_NONE; /* - * The core of this function is an infinite loop, even though we only - * service one event. The reason for this is that we may be processing - * events that don't do anything inside of Tcl. + * Main loop until servicing exact one event or block time resp. + * TCL_DONT_WAIT specified (infinite loop if stopWait = 0). */ - - while (1) { + do { /* * If idle events are the only things to service, skip the main part * of the loop and go directly to handle idle events (i.e. don't wait @@ -1060,7 +1069,6 @@ Tcl_DoOneEvent( */ if ((flags & TCL_ALL_EVENTS) == TCL_IDLE_EVENTS) { - flags |= TCL_DONT_WAIT; goto idleEvents; } @@ -1096,8 +1104,6 @@ Tcl_DoOneEvent( tsdPtr->blockTimeSet = 1; timePtr = &tsdPtr->blockTime; goto wait; /* for notifier resp. system events */ - } else { - tsdPtr->blockTimeSet = 0; } /* @@ -1164,9 +1170,6 @@ Tcl_DoOneEvent( break; } } - if (flags & TCL_DONT_WAIT) { - break; - } /* * If Tcl_WaitForEvent has returned 1, indicating that one system @@ -1176,16 +1179,13 @@ Tcl_DoOneEvent( * had the side effect of changing the variable (so the vwait can * return and unwind properly). * - * NB: We will process idle events if any first, because otherwise we - * might never do the idle events if the notifier always gets - * system events. + * We can stop also if block time was set outside an event source, + * that means timeout was set (so exit loop also without event/result). */ - if (result) { - break; - } - } + } while (!stopWait); + tsdPtr->blockTimeSet = 0; tsdPtr->serviceMode = oldMode; return result; } diff --git a/generic/tclTimer.c b/generic/tclTimer.c index 52a3073..63346a1 100644 --- a/generic/tclTimer.c +++ b/generic/tclTimer.c @@ -1139,7 +1139,7 @@ Tcl_AfterObjCmd( */ index = -1; - if ( ( objv[1]->typePtr == &tclIndexType + if ( ( TclObjIsIndexOfTable(objv[1], afterSubCmds) || Tcl_GetWideIntFromObj(NULL, objv[1], &ms) != TCL_OK ) && Tcl_GetIndexFromObj(NULL, objv[1], afterSubCmds, "", 0, diff --git a/tests/event.test b/tests/event.test index cce486a..d2dd2fc 100644 --- a/tests/event.test +++ b/tests/event.test @@ -488,7 +488,7 @@ test event-10.1 {Tcl_Exit procedure} {stdio} { test event-11.1 {Tcl_VwaitCmd procedure} { list [catch {vwait} msg] $msg -} {1 {wrong # args: should be "vwait ?options? name ?timeout?"}} +} {1 {wrong # args: should be "vwait ?options? ?timeout? name"}} test event-11.2 {Tcl_VwaitCmd procedure} { list [catch {vwait a b} msg] $msg } {1 {bad option "a": must be -idle, -noidle, -timer, -notimer, -file, -nofile, -window, -nowindow, -async, -noasync, -nowait, -wait, or idletasks}} @@ -527,26 +527,26 @@ test event-11.4.0 {vwait - interp limit precedence} {} { # no limit in between: $i limit time -seconds {} -milliseconds {} - lappend result 2. [catch {$i eval {vwait x 0}} msg] $msg + lappend result 2. [catch {$i eval {vwait 0 x}} msg] $msg # limit should be exceeded: (wait infinite by -1) $i limit time -milliseconds 0 - lappend result 3. [catch {$i eval {vwait x -1}} msg] $msg + lappend result 3. [catch {$i eval {vwait -1 x}} msg] $msg # limit should be exceeded (wait too long - 1000ms): $i limit time -milliseconds 0 - lappend result 4. [catch {$i eval {vwait x 1000}} msg] $msg + lappend result 4. [catch {$i eval {vwait 1000 x}} msg] $msg set tout [clock seconds]; incr tout 10 # wait timeout (before limit): $i limit time -seconds $tout - lappend result 5. [catch {$i eval {vwait x 0}} msg] $msg + lappend result 5. [catch {$i eval {vwait 0 x}} msg] $msg # wait timeout (before limit): $i limit time -seconds $tout - lappend result 6. [catch {$i eval {vwait x 10}} msg] $msg + lappend result 6. [catch {$i eval {vwait 10 x}} msg] $msg # wait successful (before limit): $i limit time -seconds $tout - lappend result 7. [catch {$i eval {after 0 {set x ""}; vwait x 10}} msg] $msg + lappend result 7. [catch {$i eval {after 0 {set x ""}; vwait 10 x}} msg] $msg interp delete $i set result @@ -560,6 +560,27 @@ test event-11.4.0 {vwait - interp limit precedence} {} { 7. 0 1 \ ] +test event-11.4.0 {vwait conditional with timeout (bypass timer)} {} { + set x {} + after 1000 {lappend x "error-too-slow"} + after 0 {lappend x 1-timer} + after 1 {lappend x 2-timer} + after idle {lappend x 3-idle} + vwait -async 50 x; # ignore all except async (timer also) + lappend x 4-async + vwait -idle 50 x; # ignore all except idle (timer also) + lappend x 5-idle + after idle {lappend x 6-idle} + vwait 100 x; # now we accept timer events + lappend x 7-idle + vwait 100 x; + # cleanup: + foreach i [after info] { + after cancel $i + } + set x +} {4-async 3-idle 5-idle 1-timer 2-timer 7-idle 6-idle} + test event-11.4.1 {vwait with timeout} {} { foreach i [after info] { after cancel $i @@ -568,15 +589,15 @@ test event-11.4.1 {vwait with timeout} {} { set x {} # success cases: after 0 {lappend z 0} - after 100 {lappend x 1} - after 100 {lappend x 2} - after 500 {lappend x 3} + after 50 {lappend x 1} + after 50 {lappend x 2} + after 250 {lappend x 3} after 1000 {lappend x "error-too-slow"} - vwait x 0; # no-wait + vwait 0 x; # no-wait lappend z $x; # 0 {} - (x still empty) - vwait x 200; # wait up-to 200ms + vwait 200 x; # wait up-to 200ms lappend z $x; # 0 {} {1 2} - vwait x -1; # infinite wait + vwait -1 x; # infinite wait lappend z $x; # 0 {} {1 2} {1 2 3} foreach i [after info] { after cancel $i @@ -746,7 +767,7 @@ test event-12.5 {update -idle, update -noidle} { update -idle lappend x 6 update - lappend x res:[vwait x 500] + lappend x res:[vwait 500 x] set x } {0 2 idle 3 idle 6 4 5 1 res:1} @@ -775,7 +796,7 @@ test event-12.6 {update -timer, update -notimer} { update -timer -idle lappend x 6 update - lappend x res:[vwait x 500] + lappend x res:[vwait 500 x] update -noidle lappend x 7 update -- cgit v0.12 From c63851a26ab53586d457dfcaf73a9afda52092ec Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:25:01 +0000 Subject: dynamic increase of timer resolution corresponding wait-time; non-blocking wait for event - if block-time set outside an event source traversal, use it as timeout, so can return with result 0 (no events); --- generic/tclEvent.c | 38 +++---- generic/tclInt.h | 15 ++- generic/tclNotify.c | 36 +++--- generic/tclTimer.c | 44 ++++---- win/tclWinNotify.c | 317 +++++++++++++++++++++++++++++++++++++++++++++++++--- win/tclWinTime.c | 10 ++ 6 files changed, 386 insertions(+), 74 deletions(-) diff --git a/generic/tclEvent.c b/generic/tclEvent.c index 84e4637..299f1f8 100644 --- a/generic/tclEvent.c +++ b/generic/tclEvent.c @@ -1330,8 +1330,7 @@ GetEventFlagsFromOpts( {0, TCL_WINDOW_EVENTS}, {TCL_WINDOW_EVENTS, 0}, /* -window, -nowindow */ {0, TCL_ASYNC_EVENTS}, {TCL_ASYNC_EVENTS, 0}, /* -async, -noasync */ {0, TCL_DONT_WAIT}, {TCL_DONT_WAIT, 0}, /* -nowait, -wait */ - {TCL_ALL_EVENTS, - TCL_WINDOW_EVENTS|TCL_IDLE_EVENTS}, /* idletasks */ + {TCL_ALL_EVENTS, TCL_WINDOW_EVENTS|TCL_IDLE_EVENTS}, /* idletasks */ {0, 0} /* dummy / place holder */ }; @@ -1381,12 +1380,12 @@ Tcl_VwaitObjCmd( int objc, /* Number of arguments. */ Tcl_Obj *CONST objv[]) /* Argument objects. */ { - int done = 0, foundEvent = 1; + int done = 0, foundEvent = 1, limit = 0; int flags = TCL_ALL_EVENTS; /* default flags */ char *nameString; int opti = 1, /* start option index (and index of varname later) */ optc = objc - 2; /* options count without cmd and varname */ - Tcl_WideInt ms = -1; + double ms = -1; Tcl_Time wakeup; if (objc < 2) { @@ -1400,7 +1399,7 @@ Tcl_VwaitObjCmd( * we assume that option is not an integer, try to get numeric timeout */ if (!TclObjIsIndexOfTable(objv[optc], updateEventOptions) - && Tcl_GetWideIntFromObj(NULL, objv[optc], &ms) == TCL_OK) { + && Tcl_GetDoubleFromObj(NULL, objv[optc], &ms) == TCL_OK) { optc--; } @@ -1418,12 +1417,7 @@ Tcl_VwaitObjCmd( if (ms != -1) { if (ms > 0) { Tcl_GetTime(&wakeup); - wakeup.sec += (long)(ms / 1000); - wakeup.usec += ((long)(ms % 1000)) * 1000; - if (wakeup.usec > 1000000) { - wakeup.sec++; - wakeup.usec -= 1000000; - } + TclTimeAddMilliseconds(&wakeup, ms); } else if (ms == 0) { flags |= TCL_DONT_WAIT; } @@ -1444,7 +1438,7 @@ Tcl_VwaitObjCmd( blockTime.sec = wakeup.sec - blockTime.sec; blockTime.usec = wakeup.usec - blockTime.usec; if (blockTime.usec < 0) { - blockTime.sec -= 1; + blockTime.sec--; blockTime.usec += 1000000; } if ( blockTime.sec < 0 @@ -1459,7 +1453,7 @@ Tcl_VwaitObjCmd( if ((foundEvent = Tcl_DoOneEvent(flags)) == 0) { /* * If don't wait flag set - no error, and two cases: - * option -nowait for vwait means - we don't wait for events + * option -nowait for vwait means - we don't wait for events; * if no timeout (0) - just stop waiting (no more events) */ if (flags & TCL_DONT_WAIT) { @@ -1468,14 +1462,14 @@ Tcl_VwaitObjCmd( } if (ms > 0) { foundEvent = 1; - goto checkWait; /* continue waiting */ } - break; + /* don't stop wait - no event expected here + * (stop only on error case foundEvent < 0). */ } - checkWait: /* check interpreter limit exceeded */ if (Tcl_LimitExceeded(interp)) { - foundEvent = -1; + limit = 1; + foundEvent = 0; break; } } while (!done); @@ -1500,13 +1494,13 @@ Tcl_VwaitObjCmd( */ Tcl_ResetResult(interp); - if (!foundEvent) { - Tcl_AppendResult(interp, "can't wait for variable \"", nameString, - "\": would wait forever", NULL); + if (limit) { + Tcl_AppendResult(interp, "limit exceeded", NULL); return TCL_ERROR; } - if (foundEvent == -1) { - Tcl_AppendResult(interp, "limit exceeded", NULL); + if (foundEvent <= 0) { + Tcl_AppendResult(interp, "can't wait for variable \"", nameString, + "\": would wait forever", NULL); return TCL_ERROR; } return TCL_OK; diff --git a/generic/tclInt.h b/generic/tclInt.h index 42223e4..e7fbb01 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -2870,6 +2870,19 @@ MODULE_SCOPE double TclpWideClickInMicrosec(void); #endif MODULE_SCOPE Tcl_WideInt TclpGetMicroseconds(void); +static inline void +TclTimeAddMilliseconds( + register Tcl_Time *timePtr, + register double ms +) { + timePtr->sec += (long)(ms / 1000); + timePtr->usec += (((long)ms) % 1000) * 1000 + (((long)(ms*1000)) % 1000); + if (timePtr->usec > 1000000) { + timePtr->sec++; + timePtr->usec -= 1000000; + } +} + MODULE_SCOPE Tcl_Obj * TclDisassembleByteCodeObj(Tcl_Obj *objPtr); MODULE_SCOPE int TclUtfCasecmp(CONST char *cs, CONST char *ct); @@ -2926,7 +2939,7 @@ MODULE_SCOPE int Tcl_ConcatObjCmd(ClientData clientData, MODULE_SCOPE int Tcl_ContinueObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); -MODULE_SCOPE void TclSetTimerEventMarker(void); +MODULE_SCOPE void TclSetTimerEventMarker(int head); MODULE_SCOPE int TclServiceTimerEvents(void); MODULE_SCOPE int TclServiceIdleEx(int flags, int count); MODULE_SCOPE Tcl_TimerToken TclCreateAbsoluteTimerHandler( diff --git a/generic/tclNotify.c b/generic/tclNotify.c index fb251b7..3c4e4de 100644 --- a/generic/tclNotify.c +++ b/generic/tclNotify.c @@ -770,7 +770,7 @@ timer: tsdPtr->timerMarkerPtr = NULL; result = TclServiceTimerEvents(); - if (result <= 0) { + if (result < 0) { /* events processed, but marker to process still pending timers */ tsdPtr->timerMarkerPtr = INT2PTR(-1); result = 1; @@ -865,13 +865,14 @@ TclPeekEventQueued( */ void -TclSetTimerEventMarker(void) +TclSetTimerEventMarker( + int head) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (tsdPtr->timerMarkerPtr == NULL) { /* marker to last event in the queue */ - if (!(tsdPtr->timerMarkerPtr = tsdPtr->lastEventPtr)) { + if (head || !(tsdPtr->timerMarkerPtr = tsdPtr->lastEventPtr)) { /* marker as "now" - queue is empty, so timers events are first */ tsdPtr->timerMarkerPtr = INT2PTR(-1); }; @@ -1017,7 +1018,7 @@ Tcl_DoOneEvent( EventSource *sourcePtr; Tcl_Time *timePtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); - int stopWait; + int blockTimeWasSet; /* * No event flags is equivalent to TCL_ALL_EVENTS. @@ -1027,9 +1028,9 @@ Tcl_DoOneEvent( flags |= TCL_ALL_EVENTS; } - /* Block time was set outside an event source traversal or no wait */ - stopWait = tsdPtr->blockTimeSet || (flags & TCL_DONT_WAIT); - + /* Block time was set outside an event source traversal, caller has specified a waittime */ + blockTimeWasSet = tsdPtr->blockTimeSet; + /* * Asynchronous event handlers are considered to be the highest priority * events, and so must be invoked before we process events on the event @@ -1059,7 +1060,7 @@ Tcl_DoOneEvent( /* * Main loop until servicing exact one event or block time resp. - * TCL_DONT_WAIT specified (infinite loop if stopWait = 0). + * TCL_DONT_WAIT specified (infinite loop otherwise). */ do { /* @@ -1133,7 +1134,9 @@ Tcl_DoOneEvent( wait: result = Tcl_WaitForEvent(timePtr); if (result < 0) { - result = 0; + if (blockTimeWasSet) { + result = 0; + } break; } @@ -1179,13 +1182,20 @@ Tcl_DoOneEvent( * had the side effect of changing the variable (so the vwait can * return and unwind properly). * - * We can stop also if block time was set outside an event source, - * that means timeout was set (so exit loop also without event/result). + * We can stop also if works in block to event mode (e. g. block time was + * set outside an event source, that means timeout was set so exit loop + * also without event/result). */ - } while (!stopWait); - + result = 0; + if (blockTimeWasSet) { + break; + } + } while ( !(flags & TCL_DONT_WAIT) ); + + /* Reset block time earliest at the end of event cycle */ tsdPtr->blockTimeSet = 0; + tsdPtr->serviceMode = oldMode; return result; } diff --git a/generic/tclTimer.c b/generic/tclTimer.c index 63346a1..aa78b22 100644 --- a/generic/tclTimer.c +++ b/generic/tclTimer.c @@ -115,7 +115,7 @@ static Tcl_ThreadDataKey dataKey; static void AfterCleanupProc(ClientData clientData, Tcl_Interp *interp); -static int AfterDelay(Tcl_Interp *interp, Tcl_WideInt ms); +static int AfterDelay(Tcl_Interp *interp, double ms); static void AfterProc(ClientData clientData); static void FreeAfterPtr(ClientData clientData); static AfterInfo * GetAfterEvent(AfterAssocData *assocPtr, Tcl_Obj *objPtr); @@ -623,7 +623,7 @@ TimerSetupProc( * If the first timer has expired, stick an event on the queue right now. */ if (!tsdPtr->timerPending && blockTime.sec == 0 && blockTime.usec == 0) { - TclSetTimerEventMarker(); + TclSetTimerEventMarker(0); tsdPtr->timerPending = 1; } @@ -690,7 +690,7 @@ TimerCheckProc( * If the first timer has expired, stick an event on the queue. */ if (blockTime.sec == 0 && blockTime.usec == 0) { - TclSetTimerEventMarker(); + TclSetTimerEventMarker(0); tsdPtr->timerPending = 1; } } @@ -709,7 +709,7 @@ TimerCheckProc( * the queue. * Returns 0 if the event was not handled (no timer events). * Returns -1 if pending timer events available, meaning the marker should - * stay on the queue. + * stay on the head of queue. * * Side effects: * Whatever the timer handler callback functions do. @@ -723,6 +723,7 @@ TclServiceTimerEvents(void) TimerEntry *entryPtr, *nextPtr; Tcl_Time time; size_t currentGeneration, currentEpoch; + int prevTmrPending; ThreadSpecificData *tsdPtr = InitTimer(); @@ -762,8 +763,13 @@ TclServiceTimerEvents(void) /* detach entry from the owner's list */ TclSpliceOutEx(entryPtr, tsdPtr->promptList, tsdPtr->lastPromptPtr); + /* reset current timer pending (correct process nested wait event) */ + prevTmrPending = tsdPtr->timerPending; + tsdPtr->timerPending = 0; /* execute event */ (*entryPtr->proc)(entryPtr->clientData); + /* restore current timer pending */ + tsdPtr->timerPending += prevTmrPending; /* free it via deleteProc and ckfree */ if (entryPtr->deleteProc) { @@ -775,6 +781,7 @@ TclServiceTimerEvents(void) /* if stil pending prompt events (new generation) - repeat event cycle as * soon as possible */ if (tsdPtr->promptList) { + tsdPtr->timerPending = 1; return -1; } @@ -815,8 +822,14 @@ TclServiceTimerEvents(void) currentEpoch = tsdPtr->timerListEpoch; + /* reset current timer pending (correct process nested wait event) */ + prevTmrPending = tsdPtr->timerPending; + tsdPtr->timerPending = 0; /* invoke timer proc */ (*entryPtr->proc)(entryPtr->clientData); + /* restore current timer pending */ + tsdPtr->timerPending += prevTmrPending; + /* free it via deleteProc or ckfree */ if (entryPtr->deleteProc) { (*entryPtr->deleteProc)(entryPtr->clientData); @@ -836,6 +849,7 @@ done: /* pending timer events, so mark (queue) timer events */ if (tsdPtr->timerPending > 1) { tsdPtr->timerPending = 1; + return -1; } @@ -901,7 +915,7 @@ TclCreateTimerEntryEx( /* execute immediately: signal pending and set timer marker */ tsdPtr->timerPending++; - TclSetTimerEventMarker(); + TclSetTimerEventMarker(0); } else { /* idle generation */ entryPtr->generation = tsdPtr->idleGeneration; @@ -1103,7 +1117,7 @@ Tcl_AfterObjCmd( int objc, /* Number of arguments. */ Tcl_Obj *CONST objv[]) /* Argument objects. */ { - Tcl_WideInt ms; /* Number of milliseconds to wait */ + double ms; /* Number of milliseconds to wait */ AfterInfo *afterPtr; AfterAssocData *assocPtr; int length; @@ -1140,7 +1154,7 @@ Tcl_AfterObjCmd( index = -1; if ( ( TclObjIsIndexOfTable(objv[1], afterSubCmds) - || Tcl_GetWideIntFromObj(NULL, objv[1], &ms) != TCL_OK + || Tcl_GetDoubleFromObj(NULL, objv[1], &ms) != TCL_OK ) && Tcl_GetIndexFromObj(NULL, objv[1], afterSubCmds, "", 0, &index) != TCL_OK @@ -1169,12 +1183,7 @@ Tcl_AfterObjCmd( if (ms) { Tcl_Time wakeup; Tcl_GetTime(&wakeup); - wakeup.sec += (long)(ms / 1000); - wakeup.usec += ((long)(ms % 1000)) * 1000; - if (wakeup.usec > 1000000) { - wakeup.sec++; - wakeup.usec -= 1000000; - } + TclTimeAddMilliseconds(&wakeup, ms); entryPtr = TclCreateAbsoluteTimerHandlerEx(&wakeup, AfterProc, FreeAfterPtr, sizeof(AfterInfo)); } else { @@ -1359,7 +1368,7 @@ Tcl_AfterObjCmd( static int AfterDelay( Tcl_Interp *interp, - Tcl_WideInt ms) + double ms) { Interp *iPtr = (Interp *) interp; @@ -1367,12 +1376,7 @@ AfterDelay( Tcl_WideInt diff; Tcl_GetTime(&endTime); - endTime.sec += (long)(ms/1000); - endTime.usec += ((int)(ms%1000))*1000; - if (endTime.usec >= 1000000) { - endTime.sec++; - endTime.usec -= 1000000; - } + TclTimeAddMilliseconds(&endTime, ms); do { Tcl_GetTime(&now); diff --git a/win/tclWinNotify.c b/win/tclWinNotify.c index 26aa296..f9ff5b4 100644 --- a/win/tclWinNotify.c +++ b/win/tclWinNotify.c @@ -21,6 +21,8 @@ #define WM_WAKEUP WM_USER /* Message that is send by * Tcl_AlertNotifier. */ + +//#define WIN_TIMER_PRECISION 15000 /* Handle of interval timer. */ /* * The following static structure contains the state information for the * Windows implementation of the Tcl notifier. One of these structures is @@ -396,6 +398,216 @@ NotifierProc( } /* + * Timer resolution primitives + */ + +typedef int (CALLBACK* LPFN_NtQueryTimerResolution)(PULONG,PULONG,PULONG); +typedef int (CALLBACK* LPFN_NtSetTimerResolution)(ULONG,BOOLEAN,PULONG); + +static LPFN_NtQueryTimerResolution NtQueryTimerResolution = NULL; +static LPFN_NtSetTimerResolution NtSetTimerResolution = NULL; + +#define TMR_RES_MICROSEC (1000 / 100) +#define TMR_RES_TOLERANCE 2.5 /* Tolerance (in percent), prevents entering + * busy wait, but has fewer accuracy because + * can wait a bit longer as wanted */ + +static struct { + int available; /* Availability of timer resolution functions */ + ULONG minRes; /* Lowest possible resolution (in 100-ns) */ + ULONG maxRes; /* Highest possible resolution (in 100-ns) */ + ULONG curRes; /* Current resolution (in 100-ns units). */ + ULONG resRes; /* Resolution to be restored (delayed restore) */ + LONG minDelay; /* Lowest delay by max resolution (in microsecs) */ + LONG maxDelay; /* Highest delay by min resolution (in microsecs) */ + size_t count; /* Waiter count (used to restore the resolution) */ + CRITICAL_SECTION cs; /* Mutex guarding this structure. */ +} timerResolution = { + -1, + 15600 * TMR_RES_MICROSEC, 500 * TMR_RES_MICROSEC, + 15600 * TMR_RES_MICROSEC, 0, + 500, 15600, + 0 +}; + +/* + *---------------------------------------------------------------------- + * + * InitTimerResolution -- + * + * This function initializes the timer resolution functionality. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +InitTimerResolution(void) +{ + if (timerResolution.available == -1) { + TclpInitLock(); + if (timerResolution.available == -1) { + HMODULE hLib = GetModuleHandle("Ntdll"); + if (hLib) { + NtQueryTimerResolution = + (LPFN_NtQueryTimerResolution)GetProcAddress(hLib, + "NtQueryTimerResolution"); + NtSetTimerResolution = + (LPFN_NtSetTimerResolution)GetProcAddress(hLib, + "NtSetTimerResolution"); + + if ( NtSetTimerResolution && NtQueryTimerResolution + && NtQueryTimerResolution(&timerResolution.minRes, + &timerResolution.maxRes, &timerResolution.curRes) == 0 + ) { + InitializeCriticalSection(&timerResolution.cs); + timerResolution.resRes = timerResolution.curRes; + timerResolution.minRes -= (timerResolution.minRes % TMR_RES_MICROSEC); + timerResolution.minDelay = timerResolution.maxRes / TMR_RES_MICROSEC; + timerResolution.maxDelay = timerResolution.minRes / TMR_RES_MICROSEC; + if (timerResolution.maxRes <= 1000 * TMR_RES_MICROSEC) { + timerResolution.available = 1; + } + } + } + if (timerResolution.available <= 0) { + /* not available, set min/max to typical values on windows */ + timerResolution.minRes = 15600 * TMR_RES_MICROSEC; + timerResolution.maxRes = 500 * TMR_RES_MICROSEC; + timerResolution.minDelay = timerResolution.maxRes / TMR_RES_MICROSEC; + timerResolution.maxDelay = timerResolution.minRes / TMR_RES_MICROSEC; + timerResolution.available = 0; + } + } + TclpInitUnlock(); + } +} + +/* + *---------------------------------------------------------------------- + * + * SetTimerResolution -- + * + * This is called by Tcl_WaitForEvent to increase timer resolution if wait + * for events with time smaller as the typical windows value (ca. 15ms). + * + * Results: + * Returns previous value of timer resolution, used for restoring with + * RestoreTimerResolution. + * + * Side effects: + * Note that timer resolution takes affect for the whole process (accross + * all threads). + * + *---------------------------------------------------------------------- + */ + +static unsigned long +SetTimerResolution( + unsigned long newResolution, + unsigned long actualResolution +) { + /* if available */ + if (timerResolution.available > 0) + { + if (newResolution < timerResolution.maxRes) { + newResolution = timerResolution.maxRes; + } + EnterCriticalSection(&timerResolution.cs); + if (!actualResolution) { + timerResolution.count++; + actualResolution = timerResolution.curRes; + } + if (newResolution < timerResolution.curRes) { + ULONG curRes; + if (NtSetTimerResolution(newResolution, TRUE, &curRes) == 0) { + timerResolution.curRes = curRes; + } + } + LeaveCriticalSection(&timerResolution.cs); + return actualResolution; + } + + /* resolution unchanged (and counter not increased) */ + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * RestoreTimerResolution -- + * + * This is called by Tcl_WaitForEvent to restore timer resolution to + * previous value. + * + * Results: + * None. + * + * Side effects: + * Because timer resolution takes affect for the whole process, it can + * remain max resolution after execution of this function (if some thread + * still waits with the highest timer resolution). + * + *---------------------------------------------------------------------- + */ + +static void +RestoreTimerResolution( + unsigned long newResolution +) { + /* if available */ + if (timerResolution.available > 0 && newResolution) { + EnterCriticalSection(&timerResolution.cs); + if (timerResolution.count-- <= 1) { + if (newResolution > timerResolution.resRes) { + timerResolution.resRes = newResolution; + }; + } + LeaveCriticalSection(&timerResolution.cs); + } +} + +/* + *---------------------------------------------------------------------- + * + * TclWinResetTimerResolution -- + * + * This is called by CalibrationThread to delayed reset of the timer + * resolution (once per second), if no more waiting workers using + * precise timer resolution. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +TclWinResetTimerResolution(void) +{ + if ( timerResolution.available > 0 + && timerResolution.count == 0 && timerResolution.resRes > timerResolution.curRes + ) { + EnterCriticalSection(&timerResolution.cs); + if (timerResolution.count == 0 && timerResolution.resRes > timerResolution.curRes) { + ULONG curRes; + if (NtSetTimerResolution(timerResolution.resRes, TRUE, &curRes) == 0) { + timerResolution.curRes = curRes; + }; + } + LeaveCriticalSection(&timerResolution.cs); + } +} + +/* *---------------------------------------------------------------------- * * Tcl_WaitForEvent -- @@ -420,8 +632,11 @@ Tcl_WaitForEvent( { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); MSG msg; - DWORD timeout, result; - int status; + DWORD timeout, result = WAIT_TIMEOUT; + int status = 0; + Tcl_Time waitTime = {0, 0}; + Tcl_Time endTime; + unsigned long actualResolution = 0; /* * Allow the notifier to be hooked. This may not make sense on windows, @@ -438,26 +653,72 @@ Tcl_WaitForEvent( if (timePtr) { - Tcl_Time myTime; + waitTime.sec = timePtr->sec; + waitTime.usec = timePtr->usec; - /* No wait if timeout too small (because windows may wait too long) */ - if (!timePtr->sec && timePtr->usec <= 10) { + /* if no wait */ + if (waitTime.sec == 0 && waitTime.usec == 0) { + result = 0; goto peek; } - /* - * TIP #233 (Virtualized Time). Convert virtual domain delay to - * real-time. - */ + /* calculate end of wait */ + Tcl_GetTime(&endTime); + endTime.sec += waitTime.sec; + endTime.usec += waitTime.usec; + if (endTime.usec > 1000000) { + endTime.usec -= 1000000; + endTime.sec++; + } - myTime.sec = timePtr->sec; - myTime.usec = timePtr->usec; + /* + * TIP #233 (Virtualized Time). Convert virtual domain delay to + * real-time. + */ + (*tclScaleTimeProcPtr) (&waitTime, tclTimeClientData); - if (myTime.sec != 0 || myTime.usec != 0) { - (*tclScaleTimeProcPtr) (&myTime, tclTimeClientData); + if (timerResolution.available == -1) { + InitTimerResolution(); + } + + repeat: + /* add possible tolerance in percent, so "round" to full ms (-overhead) */ + waitTime.usec += waitTime.usec * (TMR_RES_TOLERANCE / 100); + /* No wait if timeout too small (because windows may wait too long) */ + if (!waitTime.sec && waitTime.usec < (long)timerResolution.minDelay) { + /* prevent busy wait */ + if (waitTime.usec >= 10) { + timeout = 0; + goto wait; + } + goto peek; + } + + if (timerResolution.available) { + if (waitTime.sec || waitTime.usec > timerResolution.maxDelay) { + long usec; + timeout = waitTime.sec * 1000; + usec = ((timeout * 1000) + waitTime.usec) % 1000000; + timeout += (usec - (usec % timerResolution.maxDelay)) / 1000; + } else { + /* calculate resolution up to 1000 microseconds + * (don't use highest, because of too large CPU load) */ + ULONG res; + if (waitTime.usec >= 10000) { + res = 10000 * TMR_RES_MICROSEC; + } else { + res = 1000 * TMR_RES_MICROSEC; + } + timeout = waitTime.usec / 1000; + /* set more precise timer resolution for minimal delay */ + if (!actualResolution || res < timerResolution.curRes) { + actualResolution = SetTimerResolution( + res, actualResolution); + } + } + } else { + timeout = waitTime.sec * 1000 + waitTime.usec / 1000; } - - timeout = myTime.sec * 1000 + myTime.usec / 1000; } else { timeout = INFINITE; @@ -468,7 +729,7 @@ Tcl_WaitForEvent( * because MsgWaitForMultipleObjects will not wake up if there are events * currently sitting in the queue. */ - + wait: if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { goto get; } @@ -522,11 +783,31 @@ Tcl_WaitForEvent( DispatchMessage(&msg); status = 1; } - } else { - status = 0; + } + else + if (result == WAIT_TIMEOUT && timeout != INFINITE) { + /* Check the wait should be repeated, and correct time for wait */ + Tcl_Time now; + + Tcl_GetTime(&now); + waitTime.sec = (endTime.sec - now.sec); + if ((waitTime.usec = (endTime.usec - now.usec)) < 0) { + waitTime.usec += 1000000; + waitTime.sec--; + } + if (waitTime.sec < 0 || !waitTime.sec && waitTime.usec <= 0) { + goto end; + } + /* Repeat wait with more precise timer resolution (or using sleep) */ + goto repeat; } end: + + /* restore timer resolution */ + if (actualResolution) { + RestoreTimerResolution(actualResolution); + } return status; } diff --git a/win/tclWinTime.c b/win/tclWinTime.c index 93f62b8..def4548 100644 --- a/win/tclWinTime.c +++ b/win/tclWinTime.c @@ -731,6 +731,11 @@ StopCalibration( WaitForSingleObject(timeInfo.calibrationThread, 100); CloseHandle(timeInfo.exitEvent); CloseHandle(timeInfo.calibrationThread); + + /* + * Reset timer resolution (shutdown case) + */ + (void)TclWinResetTimerResolution(); } /* @@ -1111,6 +1116,11 @@ CalibrationThread( break; } UpdateTimeEachSecond(); + + /* + * Reset timer resolution if expected (check waiter count once per second) + */ + (void)TclWinResetTimerResolution(); } /* lint */ -- cgit v0.12 From f8149b6c94e868c891f7116ba4a18bd43aa62718 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:25:11 +0000 Subject: optimization of Tcl_LimitExceeded by internal usage (tclInt header) --- generic/tclInt.h | 8 ++++++++ generic/tclInterp.c | 2 ++ 2 files changed, 10 insertions(+) diff --git a/generic/tclInt.h b/generic/tclInt.h index e7fbb01..ba15f95 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -4070,6 +4070,14 @@ MODULE_SCOPE void TclBNInitBignumFromWideUInt(mp_int *bignum, #define TclLimitExceeded(limit) ((limit).exceeded != 0) +static inline int +TclInlLimitExceeded( + register Tcl_Interp *interp) +{ + return (((Interp *)interp)->limit.exceeded != 0); +} +#define Tcl_LimitExceeded(interp) TclInlLimitExceeded(interp) + #define TclLimitReady(limit) \ (((limit).active == 0) ? 0 : \ (++(limit).granularityTicker, \ diff --git a/generic/tclInterp.c b/generic/tclInterp.c index 50537de..5f1b958 100644 --- a/generic/tclInterp.c +++ b/generic/tclInterp.c @@ -3042,6 +3042,8 @@ Tcl_MakeSafe( *---------------------------------------------------------------------- */ +#undef Tcl_LimitExceeded + int Tcl_LimitExceeded( Tcl_Interp *interp) -- cgit v0.12 From fdc24c5c31c074dce2539aad1c9cc7facb565099 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:25:20 +0000 Subject: Use auto-reset event object (system automatically resets the event state to nonsignaled after wake-up), avoids unwanted reset if wake-up for some other reasons (timeout/aio/message). --- win/tclWinNotify.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/win/tclWinNotify.c b/win/tclWinNotify.c index f9ff5b4..d9d0f5d 100644 --- a/win/tclWinNotify.c +++ b/win/tclWinNotify.c @@ -118,7 +118,7 @@ Tcl_InitNotifier(void) tsdPtr->hwnd = NULL; tsdPtr->thread = GetCurrentThreadId(); - tsdPtr->event = CreateEvent(NULL, TRUE /* manual */, + tsdPtr->event = CreateEvent(NULL, FALSE /* !manual */, FALSE /* !signaled */, NULL); return (ClientData) tsdPtr; @@ -746,7 +746,6 @@ Tcl_WaitForEvent( if (result == WAIT_IO_COMPLETION) { goto again; } - ResetEvent(tsdPtr->event); if (result == WAIT_FAILED) { status = -1; goto end; -- cgit v0.12 From 1b814ecdc54b17f604790d2242e3249dbf38d068 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:25:25 +0000 Subject: [win32] use timer resolution handling in Tcl_Sleep also; --- generic/tclTimer.c | 22 ++++++++-------- win/tclWinNotify.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 75 insertions(+), 20 deletions(-) diff --git a/generic/tclTimer.c b/generic/tclTimer.c index aa78b22..f1235be 100644 --- a/generic/tclTimer.c +++ b/generic/tclTimer.c @@ -611,7 +611,7 @@ TimerSetupProc( blockTime.sec = firstTime->sec - blockTime.sec; blockTime.usec = firstTime->usec - blockTime.usec; if (blockTime.usec < 0) { - blockTime.sec -= 1; + blockTime.sec--; blockTime.usec += 1000000; } if (blockTime.sec < 0) { @@ -665,8 +665,8 @@ TimerCheckProc( if (tsdPtr == NULL) { tsdPtr = InitTimer(); }; - /* If already pending */ - if (!tsdPtr->timerList || tsdPtr->timerPending) { + /* If already pending (or no timer-events) */ + if (tsdPtr->timerPending || !tsdPtr->timerList) { return; } @@ -678,18 +678,14 @@ TimerCheckProc( blockTime.sec = firstTime->sec - blockTime.sec; blockTime.usec = firstTime->usec - blockTime.usec; if (blockTime.usec < 0) { - blockTime.sec -= 1; + blockTime.sec--; blockTime.usec += 1000000; } - if (blockTime.sec < 0) { - blockTime.sec = 0; - blockTime.usec = 0; - } /* * If the first timer has expired, stick an event on the queue. */ - if (blockTime.sec == 0 && blockTime.usec == 0) { + if (blockTime.sec < 0 || blockTime.sec == 0 && blockTime.usec <= 0) { TclSetTimerEventMarker(0); tsdPtr->timerPending = 1; } @@ -726,7 +722,6 @@ TclServiceTimerEvents(void) int prevTmrPending; ThreadSpecificData *tsdPtr = InitTimer(); - if (!tsdPtr->timerPending) { return 0; /* no timer events */ } @@ -1375,6 +1370,13 @@ AfterDelay( Tcl_Time endTime, now; Tcl_WideInt diff; + if (ms <= 0) { + /* to cause a context switch only */ + Tcl_Sleep(0); + return TCL_OK; + } + + Tcl_GetTime(&endTime); TclTimeAddMilliseconds(&endTime, ms); diff --git a/win/tclWinNotify.c b/win/tclWinNotify.c index d9d0f5d..9c13ff2 100644 --- a/win/tclWinNotify.c +++ b/win/tclWinNotify.c @@ -671,17 +671,17 @@ Tcl_WaitForEvent( endTime.sec++; } + if (timerResolution.available == -1) { + InitTimerResolution(); + } + + repeat: /* * TIP #233 (Virtualized Time). Convert virtual domain delay to * real-time. */ (*tclScaleTimeProcPtr) (&waitTime, tclTimeClientData); - if (timerResolution.available == -1) { - InitTimerResolution(); - } - - repeat: /* add possible tolerance in percent, so "round" to full ms (-overhead) */ waitTime.usec += waitTime.usec * (TMR_RES_TOLERANCE / 100); /* No wait if timeout too small (because windows may wait too long) */ @@ -846,6 +846,18 @@ Tcl_Sleep( * real. */ DWORD sleepTime; /* Time to sleep, real-time */ + unsigned long actualResolution = 0; + + if (ms <= 0) { + /* causes context switch only */ + Sleep(0); + return; + } + + if (timerResolution.available == -1) { + InitTimerResolution(); + } + vdelay.sec = ms / 1000; vdelay.usec = (ms % 1000) * 1000; @@ -861,10 +873,45 @@ Tcl_Sleep( * TIP #233: Scale delay from virtual to real-time. */ - (*tclScaleTimeProcPtr) (&vdelay, tclTimeClientData); - sleepTime = vdelay.sec * 1000 + vdelay.usec / 1000; - for (;;) { + + (*tclScaleTimeProcPtr) (&vdelay, tclTimeClientData); + + /* add possible tolerance in percent, so "round" to full ms (-overhead) */ + vdelay.usec += vdelay.usec * (TMR_RES_TOLERANCE / 100); + /* No wait if sleep time too small (because windows may wait too long) */ + if (!vdelay.sec && vdelay.usec < (long)timerResolution.minDelay) { + sleepTime = 0; + goto wait; + } + + if (timerResolution.available) { + if (vdelay.sec || vdelay.usec > timerResolution.maxDelay) { + long usec; + sleepTime = vdelay.sec * 1000; + usec = ((sleepTime * 1000) + vdelay.usec) % 1000000; + sleepTime += (usec - (usec % timerResolution.maxDelay)) / 1000; + } else { + /* calculate resolution up to 1000 microseconds + * (don't use highest, because of too large CPU load) */ + ULONG res; + if (vdelay.usec >= 10000) { + res = 10000 * TMR_RES_MICROSEC; + } else { + res = 1000 * TMR_RES_MICROSEC; + } + sleepTime = vdelay.usec / 1000; + /* set more precise timer resolution for minimal delay */ + if (!actualResolution || res < timerResolution.curRes) { + actualResolution = SetTimerResolution( + res, actualResolution); + } + } + } else { + sleepTime = vdelay.sec * 1000 + vdelay.usec / 1000; + } + + wait: Sleep(sleepTime); Tcl_GetTime(&now); if (now.sec > desired.sec) { @@ -875,9 +922,15 @@ Tcl_Sleep( vdelay.sec = desired.sec - now.sec; vdelay.usec = desired.usec - now.usec; + if (vdelay.usec < 0) { + vdelay.sec--; + vdelay.usec += 1000000; + } + } - (*tclScaleTimeProcPtr) (&vdelay, tclTimeClientData); - sleepTime = vdelay.sec * 1000 + vdelay.usec / 1000; + /* restore timer resolution */ + if (actualResolution) { + RestoreTimerResolution(actualResolution); } } -- cgit v0.12 From 951cfc22688728ad9615a07682bd2406a3f0db2e Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:25:32 +0000 Subject: [win] fallback to replace C++ keyword "inline" with C keyword "__inline" Otherwise depending on the VC-version, context, include-order it can cause: error C2054: expected '(' to follow 'inline' --- generic/tclInt.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/generic/tclInt.h b/generic/tclInt.h index ba15f95..991ffc3 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -55,6 +55,16 @@ typedef int ptrdiff_t; #endif +/* + * [MSVC] fallback to replace C++ keyword "inline" with C keyword "__inline" + * Otherwise depending on the VC-version, context, include-order it can cause: + * error C2054: expected '(' to follow 'inline' + */ +#if defined(_MSC_VER) && !defined(inline) +# define inline __inline +#endif + + /* * Ensure WORDS_BIGENDIAN is defined correctly: * Needs to happen here in addition to configure to work with fat compiles on -- cgit v0.12 From eccfe47689dda049eadecf74e70346d13a6126e1 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:25:40 +0000 Subject: code review + better usage of the waiting tolerance (fewer CPU-greedy now, avoid busy-wait if the rest of wait-time too small and can be neglected); TMR_RES_TOLERANCE can be defined to use wait-tolerance on *nix platforms (currently windows only as relation resp. deviation between default timer resolution 15.600 in exact milliseconds, means 15600/15000 + small overhead); Decreasing of TMR_RES_TOLERANCE (up to 0) makes tcl more "RTS" resp. NRT-capable (very precise wait-intervals, but more CPU-hungry). --- generic/tclEvent.c | 36 +++++++++++++++++++++++------------- generic/tclInt.h | 6 ++++++ generic/tclTimer.c | 38 +++++++++++++++++++++++++++++++++++--- win/tclWinNotify.c | 49 +++++++++++++++++++++++++++++++------------------ 4 files changed, 95 insertions(+), 34 deletions(-) diff --git a/generic/tclEvent.c b/generic/tclEvent.c index 299f1f8..1a5b9d5 100644 --- a/generic/tclEvent.c +++ b/generic/tclEvent.c @@ -1380,13 +1380,13 @@ Tcl_VwaitObjCmd( int objc, /* Number of arguments. */ Tcl_Obj *CONST objv[]) /* Argument objects. */ { - int done = 0, foundEvent = 1, limit = 0; + int done = 0, foundEvent = 1, limit = 0, checktime = 0; int flags = TCL_ALL_EVENTS; /* default flags */ char *nameString; - int opti = 1, /* start option index (and index of varname later) */ - optc = objc - 2; /* options count without cmd and varname */ + int optc = objc - 2; /* options count without cmd and varname */ double ms = -1; Tcl_Time wakeup; + long tolerance = 0; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "?options? ?timeout? name"); @@ -1418,6 +1418,10 @@ Tcl_VwaitObjCmd( if (ms > 0) { Tcl_GetTime(&wakeup); TclTimeAddMilliseconds(&wakeup, ms); + #ifdef TMR_RES_TOLERANCE + tolerance = (ms < 1000 ? ms : 1000) * + (1000 * TMR_RES_TOLERANCE / 100); + #endif } else if (ms == 0) { flags |= TCL_DONT_WAIT; } @@ -1441,16 +1445,20 @@ Tcl_VwaitObjCmd( blockTime.sec--; blockTime.usec += 1000000; } - if ( blockTime.sec < 0 - || (blockTime.sec == 0 && blockTime.usec <= 0) - ) { - /* timeout occurs */ - done = -1; - break; + /* be sure process at least one event */ + if (checktime) { + if ( blockTime.sec < 0 + || (blockTime.sec == 0 && blockTime.usec <= tolerance) + ) { + /* timeout occurs */ + done = -1; + break; + } } + checktime = 1; Tcl_SetMaxBlockTime(&blockTime); } - if ((foundEvent = Tcl_DoOneEvent(flags)) == 0) { + if ((foundEvent = Tcl_DoOneEvent(flags)) <= 0) { /* * If don't wait flag set - no error, and two cases: * option -nowait for vwait means - we don't wait for events; @@ -1459,12 +1467,14 @@ Tcl_VwaitObjCmd( if (flags & TCL_DONT_WAIT) { foundEvent = 1; done = -2; - } - if (ms > 0) { + } else if (ms > 0 && foundEvent == 0) { foundEvent = 1; - } + } /* don't stop wait - no event expected here * (stop only on error case foundEvent < 0). */ + if (foundEvent < 0) { + done = -2; + } } /* check interpreter limit exceeded */ if (Tcl_LimitExceeded(interp)) { diff --git a/generic/tclInt.h b/generic/tclInt.h index 991ffc3..f13af82 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -2876,6 +2876,12 @@ MODULE_SCOPE Tcl_WideInt TclpGetWideClicks(void); MODULE_SCOPE double TclpWideClickInMicrosec(void); # define TclpWideClicksToNanoseconds(clicks) \ ((double)(clicks) * TclpWideClickInMicrosec() * 1000) + /* Tolerance (in percent), prevents entering busy wait, but has fewer accuracy + * because can wait a bit shorter as wanted. Currently experimental value + * (4.5% equivalent to 15600 / 15000 with small overhead) */ +# ifndef TMR_RES_TOLERANCE +# define TMR_RES_TOLERANCE 4.5 +# endif # endif #endif MODULE_SCOPE Tcl_WideInt TclpGetMicroseconds(void); diff --git a/generic/tclTimer.c b/generic/tclTimer.c index f1235be..c135ffb 100644 --- a/generic/tclTimer.c +++ b/generic/tclTimer.c @@ -588,6 +588,7 @@ TimerSetupProc( { Tcl_Time blockTime, *firstTime; ThreadSpecificData *tsdPtr = (ThreadSpecificData *)data; + long tolerance = 0; if (tsdPtr == NULL) { tsdPtr = InitTimer(); }; @@ -619,10 +620,15 @@ TimerSetupProc( blockTime.usec = 0; } + #ifdef TMR_RES_TOLERANCE + /* consider timer resolution tolerance (avoid busy wait) */ + tolerance = ((blockTime.sec <= 0) ? blockTime.usec : 1000000) * + (TMR_RES_TOLERANCE / 100); + #endif /* * If the first timer has expired, stick an event on the queue right now. */ - if (!tsdPtr->timerPending && blockTime.sec == 0 && blockTime.usec == 0) { + if (!tsdPtr->timerPending && blockTime.sec == 0 && blockTime.usec <= tolerance) { TclSetTimerEventMarker(0); tsdPtr->timerPending = 1; } @@ -717,7 +723,7 @@ int TclServiceTimerEvents(void) { TimerEntry *entryPtr, *nextPtr; - Tcl_Time time; + Tcl_Time time, entrytm; size_t currentGeneration, currentEpoch; int prevTmrPending; ThreadSpecificData *tsdPtr = InitTimer(); @@ -791,7 +797,16 @@ TclServiceTimerEvents(void) ) { nextPtr = entryPtr->nextPtr; - if (TCL_TIME_BEFORE(time, TimerEntry2TimerHandler(entryPtr)->time)) { + entrytm = TimerEntry2TimerHandler(entryPtr)->time; + #ifdef TMR_RES_TOLERANCE + entrytm.usec -= ((entrytm.sec <= 0) ? entrytm.usec : 1000000) * + (TMR_RES_TOLERANCE / 100); + if (entrytm.usec < 0) { + entrytm.usec += 1000000; + entrytm.sec--; + } + #endif + if (TCL_TIME_BEFORE(time, entrytm)) { break; } @@ -1369,6 +1384,9 @@ AfterDelay( Tcl_Time endTime, now; Tcl_WideInt diff; +#ifdef TMR_RES_TOLERANCE + long tolerance; +#endif if (ms <= 0) { /* to cause a context switch only */ @@ -1376,6 +1394,10 @@ AfterDelay( return TCL_OK; } + /* calculate possible maximal tolerance (in usec) of original wait-time */ +#ifdef TMR_RES_TOLERANCE + tolerance = ((ms < 1000) ? ms : 1000) * (1000 * TMR_RES_TOLERANCE / 100); +#endif Tcl_GetTime(&endTime); TclTimeAddMilliseconds(&endTime, ms); @@ -1399,6 +1421,7 @@ AfterDelay( #endif if (diff > 0) { Tcl_Sleep((long)diff); + Tcl_GetTime(&now); } } else { diff = TCL_TIME_DIFF_MS(iPtr->limit.time, now); @@ -1409,11 +1432,20 @@ AfterDelay( #endif if (diff > 0) { Tcl_Sleep((long)diff); + Tcl_GetTime(&now); } if (Tcl_LimitCheck(interp) != TCL_OK) { return TCL_ERROR; } } + /* consider timer resolution tolerance (avoid busy wait) */ + #ifdef TMR_RES_TOLERANCE + now.usec += tolerance; + if (now.usec > 1000000) { + now.usec -= 1000000; + now.sec++; + } + #endif } while (TCL_TIME_BEFORE(now, endTime)); return TCL_OK; } diff --git a/win/tclWinNotify.c b/win/tclWinNotify.c index 9c13ff2..f92fe2f 100644 --- a/win/tclWinNotify.c +++ b/win/tclWinNotify.c @@ -22,7 +22,6 @@ #define WM_WAKEUP WM_USER /* Message that is send by * Tcl_AlertNotifier. */ -//#define WIN_TIMER_PRECISION 15000 /* Handle of interval timer. */ /* * The following static structure contains the state information for the * Windows implementation of the Tcl notifier. One of these structures is @@ -408,9 +407,6 @@ static LPFN_NtQueryTimerResolution NtQueryTimerResolution = NULL; static LPFN_NtSetTimerResolution NtSetTimerResolution = NULL; #define TMR_RES_MICROSEC (1000 / 100) -#define TMR_RES_TOLERANCE 2.5 /* Tolerance (in percent), prevents entering - * busy wait, but has fewer accuracy because - * can wait a bit longer as wanted */ static struct { int available; /* Availability of timer resolution functions */ @@ -636,6 +632,7 @@ Tcl_WaitForEvent( int status = 0; Tcl_Time waitTime = {0, 0}; Tcl_Time endTime; + long tolerance = 0; unsigned long actualResolution = 0; /* @@ -657,11 +654,17 @@ Tcl_WaitForEvent( waitTime.usec = timePtr->usec; /* if no wait */ - if (waitTime.sec == 0 && waitTime.usec == 0) { + if (waitTime.sec <= 0 && waitTime.usec <= 0) { result = 0; goto peek; } + #ifdef TMR_RES_TOLERANCE + /* calculate possible maximal tolerance (in usec) of original wait-time */ + tolerance = ((waitTime.sec <= 0) ? waitTime.usec : 1000000) * + (TMR_RES_TOLERANCE / 100); + #endif + /* calculate end of wait */ Tcl_GetTime(&endTime); endTime.sec += waitTime.sec; @@ -682,8 +685,6 @@ Tcl_WaitForEvent( */ (*tclScaleTimeProcPtr) (&waitTime, tclTimeClientData); - /* add possible tolerance in percent, so "round" to full ms (-overhead) */ - waitTime.usec += waitTime.usec * (TMR_RES_TOLERANCE / 100); /* No wait if timeout too small (because windows may wait too long) */ if (!waitTime.sec && waitTime.usec < (long)timerResolution.minDelay) { /* prevent busy wait */ @@ -695,14 +696,18 @@ Tcl_WaitForEvent( } if (timerResolution.available) { - if (waitTime.sec || waitTime.usec > timerResolution.maxDelay) { - long usec; - timeout = waitTime.sec * 1000; - usec = ((timeout * 1000) + waitTime.usec) % 1000000; + if (waitTime.sec || waitTime.usec + tolerance > timerResolution.maxDelay) { + long usec; + timeout = waitTime.sec * 1000; + usec = (timeout * 1000) + waitTime.usec + tolerance; + if (usec > 1000000) { + usec -= 1000000; + timeout += 1000; + } timeout += (usec - (usec % timerResolution.maxDelay)) / 1000; } else { - /* calculate resolution up to 1000 microseconds - * (don't use highest, because of too large CPU load) */ + /* calculate resolution up to 1000 microseconds + * (don't use highest, because of too large CPU load) */ ULONG res; if (waitTime.usec >= 10000) { res = 10000 * TMR_RES_MICROSEC; @@ -794,7 +799,7 @@ Tcl_WaitForEvent( waitTime.usec += 1000000; waitTime.sec--; } - if (waitTime.sec < 0 || !waitTime.sec && waitTime.usec <= 0) { + if (waitTime.sec < 0 || !waitTime.sec && waitTime.usec <= tolerance) { goto end; } /* Repeat wait with more precise timer resolution (or using sleep) */ @@ -845,7 +850,6 @@ Tcl_Sleep( Tcl_Time vdelay; /* Time to sleep, for scaling virtual -> * real. */ DWORD sleepTime; /* Time to sleep, real-time */ - unsigned long actualResolution = 0; if (ms <= 0) { @@ -865,9 +869,20 @@ Tcl_Sleep( desired.sec = now.sec + vdelay.sec; desired.usec = now.usec + vdelay.usec; if (desired.usec > 1000000) { - ++desired.sec; desired.usec -= 1000000; + desired.sec++; + } + +#ifdef TMR_RES_TOLERANCE + /* calculate possible maximal tolerance (in usec) of original wait-time */ + if (vdelay.sec <= 0) { + desired.usec -= vdelay.usec * (TMR_RES_TOLERANCE / 100); + if (desired.usec < 0) { + desired.usec += 1000000; + desired.sec--; + } } +#endif /* * TIP #233: Scale delay from virtual to real-time. @@ -877,8 +892,6 @@ Tcl_Sleep( (*tclScaleTimeProcPtr) (&vdelay, tclTimeClientData); - /* add possible tolerance in percent, so "round" to full ms (-overhead) */ - vdelay.usec += vdelay.usec * (TMR_RES_TOLERANCE / 100); /* No wait if sleep time too small (because windows may wait too long) */ if (!vdelay.sec && vdelay.usec < (long)timerResolution.minDelay) { sleepTime = 0; -- cgit v0.12 From 251c15c11767bc6e33809b3f13eabc1771a0ba85 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:25:45 +0000 Subject: resolved some warnings / fixed unix resp. x64 compilation --- generic/tclEvent.c | 1 - generic/tclNotify.c | 2 +- generic/tclTimer.c | 8 ++++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/generic/tclEvent.c b/generic/tclEvent.c index 1a5b9d5..186b8ae 100644 --- a/generic/tclEvent.c +++ b/generic/tclEvent.c @@ -1562,7 +1562,6 @@ Tcl_UpdateObjCmd( /* if arguments available - wrap options to flags */ if (objc > 1) { - int i = 1; if (GetEventFlagsFromOpts(interp, objc-1, objv+1, &flags) != TCL_OK) { return TCL_ERROR; } diff --git a/generic/tclNotify.c b/generic/tclNotify.c index 3c4e4de..6e67ed9 100644 --- a/generic/tclNotify.c +++ b/generic/tclNotify.c @@ -1014,7 +1014,7 @@ Tcl_DoOneEvent( * TCL_TIMER_EVENTS, TCL_IDLE_EVENTS, or * others defined by event sources. */ { - int result = 0, oldMode, i = 0; + int result = 0, oldMode; EventSource *sourcePtr; Tcl_Time *timePtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); diff --git a/generic/tclTimer.c b/generic/tclTimer.c index c135ffb..4d26742 100644 --- a/generic/tclTimer.c +++ b/generic/tclTimer.c @@ -25,7 +25,7 @@ typedef struct AfterInfo { * executed. */ Tcl_Obj *commandPtr; /* Command to execute. */ Tcl_Obj *selfPtr; /* Points to the handle object (self) */ - size_t id; /* Integer identifier for command */ + unsigned int id; /* Integer identifier for command */ struct AfterInfo *nextPtr; /* Next in list of all "after" commands for * this interpreter. */ struct AfterInfo *prevPtr; /* Prev in list of all "after" commands for @@ -77,7 +77,7 @@ typedef struct { * a new loop, so that all old handlers can be * called without calling any of the new ones * created by old ones. */ - size_t afterId; /* For unique identifiers of after events. */ + unsigned int afterId; /* For unique identifiers of after events. */ } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; @@ -203,7 +203,7 @@ AfterObj_UpdateString(objPtr) return; } - len = sprintf(buf, "after#%d", afterPtr->id); + len = sprintf(buf, "after#%u", afterPtr->id); objPtr->length = len; objPtr->bytes = ckalloc((size_t)++len); @@ -691,7 +691,7 @@ TimerCheckProc( /* * If the first timer has expired, stick an event on the queue. */ - if (blockTime.sec < 0 || blockTime.sec == 0 && blockTime.usec <= 0) { + if (blockTime.sec < 0 || (blockTime.sec == 0 && blockTime.usec <= 0)) { TclSetTimerEventMarker(0); tsdPtr->timerPending = 1; } -- cgit v0.12 From b39f779d1efe91fb83782a84d75e0e6bc055a597 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:25:52 +0000 Subject: after info, after cancel: compare interpreter of the timer-events by direct retrieving via internal representation (ignore foreign events), test cases extended. --- generic/tclTimer.c | 4 ++-- tests/timer.test | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/generic/tclTimer.c b/generic/tclTimer.c index 4d26742..df974ab 100644 --- a/generic/tclTimer.c +++ b/generic/tclTimer.c @@ -1273,7 +1273,7 @@ Tcl_AfterObjCmd( Tcl_DecrRefCount(commandPtr); } } - if (afterPtr != NULL) { + if (afterPtr != NULL && afterPtr->assocPtr->interp == interp) { TclDeleteTimerEntry(AfterInfo2TimerEntry(afterPtr)); } break; @@ -1338,7 +1338,7 @@ Tcl_AfterObjCmd( } afterPtr = GetAfterEvent(assocPtr, objv[2]); - if (afterPtr == NULL) { + if (afterPtr == NULL || afterPtr->assocPtr->interp != interp) { Tcl_AppendResult(interp, "event \"", TclGetString(objv[2]), "\" doesn't exist", NULL); return TCL_ERROR; diff --git a/tests/timer.test b/tests/timer.test index db508e5..d9679e1 100644 --- a/tests/timer.test +++ b/tests/timer.test @@ -326,15 +326,41 @@ set childEvent [x eval {after idle event in child}] test timer-6.19 {Tcl_AfterCmd, info option} { lsort [after info] } [lsort "$event1 $event2"] +test timer-6.19.1 {Tcl_AfterCmd, info option (all events in child)} { + x eval {after info} +} [list $childEvent] test timer-6.20 {Tcl_AfterCmd, info option} { list [catch {after info a b} msg] $msg } {1 {wrong # args: should be "after info ?id?"}} test timer-6.21 {Tcl_AfterCmd, info option} { list [catch {after info $childEvent} msg] $msg } "1 {event \"$childEvent\" doesn't exist}" +test timer-6.21.1 {Tcl_AfterCmd, info option (internal representation)} { + list [catch {x eval [list after info $childEvent]} msg] $msg +} {0 {{event in child} idle}} +test timer-6.21.1 {Tcl_AfterCmd, info option (internal representation)} { + list [catch {x eval [list after info $childEvent]} msg] $msg +} {0 {{event in child} idle}} +test timer-6.21.2 {Tcl_AfterCmd, info option (search using string representation)} { + list [catch {x eval [list after info [string trim " $childEvent "]]} msg] $msg +} {0 {{event in child} idle}} test timer-6.22 {Tcl_AfterCmd, info option} { list [after info $event1] [after info $event2] } {{{event 1} idle} {{event 2} timer}} +test timer-6.22.1 {Tcl_AfterCmd, cancel option (internal representation)} { + after cancel $childEvent; # foreign event - does nothing + # check still available: + list [catch {x eval [list after info $childEvent]} msg] $msg +} {0 {{event in child} idle}} +test timer-6.22.2 {Tcl_AfterCmd, cancel option (search using string representation)} { + after cancel [string trim " $childEvent "]; # foreign event - does nothing + # check still available: + set lst [list [catch {x eval [list after info $childEvent]} msg] $msg] + # cancel again but in child: + x eval [list after cancel [string trim " $childEvent "]] + # check it was canceled: + lappend lst {*}[list [catch {x eval [list after info $childEvent]} msg] $msg] +} [list 0 {{event in child} idle} 1 "event \"$childEvent\" doesn't exist"] after cancel $event1 after cancel $event2 -- cgit v0.12 From 85e45c26583e38ad80c7d1ad6edbeab0e2db7a22 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:26:06 +0000 Subject: make timer test-case more precise and time-independent, ignores short tolerance (deviation by waiting); several time-independent test-cases optimized (wait shorter now) + some new cases to cover more situations. --- tests/timer.test | 257 ++++++++++++++++++++++++++----------------------------- 1 file changed, 119 insertions(+), 138 deletions(-) diff --git a/tests/timer.test b/tests/timer.test index d9679e1..5a63d54 100644 --- a/tests/timer.test +++ b/tests/timer.test @@ -17,32 +17,48 @@ if {[lsearch [namespace children] ::tcltest] == -1} { package require tcltest namespace import -force ::tcltest::* } +# On some platforms the short tolerance (in percent to wait-time) used to avoid +# busy waiting, so it may cause on the fast mashines, that waiting interupts a bit +# earlier as expected ("rounded" to this tolerance boundary). +# +# Following routines guarantee the timer event always occured in such cases. +variable tolerance 5 +proc tl-after {ms args} { + variable tolerance + uplevel [list after [expr {$ms + double($ms*$tolerance/100)}] {*}$args] +} +proc tl-vwait {args} { + variable tolerance + set ms [lindex $args end-1] + set vn [lindex $args end] + uplevel [list vwait [expr {$ms + double($ms*$tolerance/100)}] {*}[lrange $args 0 end-2] $vn] +} -test timer-1.1 {Tcl_CreateTimerHandler procedure} { +proc clean-up-events {} { foreach i [after info] { after cancel $i } +} + +test timer-1.1 {Tcl_CreateTimerHandler procedure} { + clean-up-events set x "" foreach i {100 200 1000 50 150} { after $i lappend x $i } - after 200 set done 1 - vwait done + tl-vwait 200 done set x } {50 100 150 200} test timer-2.1 {Tcl_DeleteTimerHandler procedure} { - foreach i [after info] { - after cancel $i - } + clean-up-events set x "" foreach i {100 200 1000 50 150} { after $i lappend x $i } after cancel lappend x 150 after cancel lappend x 50 - after 200 set done 1 - vwait done + tl-vwait 200 done set x } {100 200} @@ -59,65 +75,55 @@ test timer-3.1 {TimerHandlerEventProc procedure: event masks} { lappend result $x } {start fired} test timer-3.2 {TimerHandlerEventProc procedure: multiple timers} { - foreach i [after info] { - after cancel $i - } + clean-up-events foreach i {200 600 1000} { after $i lappend x $i } - after 200 + tl-after 200 set result "" set x "" update lappend result $x - after 400 + tl-after 400 update lappend result $x - after 400 + tl-after 400 update lappend result $x } {200 {200 600} {200 600 1000}} test timer-3.3 {TimerHandlerEventProc procedure: reentrant timer deletion} { - foreach i [after info] { - after cancel $i - } + clean-up-events set x {} after 100 lappend x 100 set i [after 300 lappend x 300] after 200 after cancel $i - after 400 + tl-after 400 update set x } 100 test timer-3.4 {TimerHandlerEventProc procedure: all expired timers fire} { - foreach i [after info] { - after cancel $i - } + clean-up-events set x {} after 100 lappend x a after 200 lappend x b after 300 lappend x c - after 300 + tl-after 300 vwait x set x } {a b c} test timer-3.5 {TimerHandlerEventProc procedure: reentrantly added timers don't fire} { - foreach i [after info] { - after cancel $i - } + clean-up-events set x {} - after 100 {lappend x a; after 0 lappend x b} - after 100 + after 10 {lappend x a; after 0 lappend x b} + after 50 vwait x set x } a test timer-3.6 {TimerHandlerEventProc procedure: reentrantly added timers don't fire} { - foreach i [after info] { - after cancel $i - } + clean-up-events set x {} after 100 {lappend x a; after 100 lappend x b; after 100} - after 100 + tl-after 100 vwait x set result $x vwait x @@ -128,9 +134,7 @@ test timer-3.6 {TimerHandlerEventProc procedure: reentrantly added timers don't # below. test timer-4.1 {Tcl_CancelIdleCall procedure} { - foreach i [after info] { - after cancel $i - } + clean-up-events set x before set y before set z before @@ -142,9 +146,7 @@ test timer-4.1 {Tcl_CancelIdleCall procedure} { concat $x $y $z } {after1 before after3} test timer-4.2 {Tcl_CancelIdleCall procedure} { - foreach i [after info] { - after cancel $i - } + clean-up-events set x before set y before set z before @@ -157,9 +159,7 @@ test timer-4.2 {Tcl_CancelIdleCall procedure} { } {before after2 after3} test timer-5.1 {Tcl_ServiceIdle, self-rescheduling handlers} { - foreach i [after info] { - after cancel $i - } + clean-up-events set x 1 set y 23 after idle {incr x; after idle {incr x; after idle {incr x}}} @@ -181,18 +181,25 @@ test timer-6.3 {Tcl_AfterCmd procedure, basics} { } {1 {bad argument "gorp": must be cancel, idle, info, or an integer}} test timer-6.4 {Tcl_AfterCmd procedure, ms argument} { set x before - after 400 {set x after} - after 200 + # cover prompt events also (immediate handling differs now): + after 0 {lappend x immediate} + lappend x before-immediate update - set y $x - after 400 + lappend x after-immediate + after 100 {lappend x after} + after 10 update - list $y $x -} {before after} + lappend x nothing + after 200 + lappend x before-after + update + lappend x after-after + set x +} {before before-immediate immediate after-immediate nothing before-after after after-after} test timer-6.5 {Tcl_AfterCmd procedure, ms argument} { set x before - after 300 set x after - after 200 + after 100 set x after + after 10 update set y $x after 200 @@ -209,47 +216,39 @@ test timer-6.8 {Tcl_AfterCmd procedure, cancel option} { after cancel {foo bar} } {} test timer-6.9 {Tcl_AfterCmd procedure, cancel option} { - foreach i [after info] { - after cancel $i - } + clean-up-events set x before - set y [after 100 set x after] + set y [after 10 set x after] after cancel $y - after 200 + after 100 update set x } {before} test timer-6.10 {Tcl_AfterCmd procedure, cancel option} { - foreach i [after info] { - after cancel $i - } + clean-up-events set x before - after 100 set x after + after 10 set x after after cancel {set x after} - after 200 + after 100 update set x } {before} test timer-6.11 {Tcl_AfterCmd procedure, cancel option} { - foreach i [after info] { - after cancel $i - } + clean-up-events set x before - after 100 set x after - set id [after 300 set x after] + after 10 set x after-10 + set id [after 30 set x after-30] after cancel $id - after 200 + after 100 update set y $x set x cleared - after 200 + after 100 update list $y $x -} {after cleared} +} {after-10 cleared} test timer-6.12 {Tcl_AfterCmd procedure, cancel option} { - foreach i [after info] { - after cancel $i - } + clean-up-events set x first after idle lappend x second after idle lappend x third @@ -260,9 +259,7 @@ test timer-6.12 {Tcl_AfterCmd procedure, cancel option} { set x } {first third} test timer-6.13 {Tcl_AfterCmd procedure, cancel option, multiple arguments for command} { - foreach i [after info] { - after cancel $i - } + clean-up-events set x first after idle lappend x second after idle lappend x third @@ -273,21 +270,18 @@ test timer-6.13 {Tcl_AfterCmd procedure, cancel option, multiple arguments for c set x } {first third} test timer-6.14 {Tcl_AfterCmd procedure, cancel option, cancel during handler, used to dump core} { - foreach i [after info] { - after cancel $i - } + clean-up-events set id [ - after 100 { + after 10 { set x done after cancel $id + after idle {set y done} } ] - vwait x -} {} + list [tl-vwait 1000 x] [tl-vwait 100 y] +} {1 1} test timer-6.15 {Tcl_AfterCmd procedure, cancel option, multiple interps} { - foreach i [after info] { - after cancel $i - } + clean-up-events interp create x x eval {set a before; set b before; after idle {set a a-after}; after idle {set b b-after}} @@ -367,9 +361,7 @@ after cancel $event2 interp delete x test timer-6.23 {Tcl_AfterCmd procedure, no option, script with NULL} { - foreach i [after info] { - after cancel $i - } + clean-up-events set x "hello world" after 1 "set x ab\0cd" after 10 @@ -377,9 +369,7 @@ test timer-6.23 {Tcl_AfterCmd procedure, no option, script with NULL} { string length $x } {5} test timer-6.24 {Tcl_AfterCmd procedure, no option, script with NULL} { - foreach i [after info] { - after cancel $i - } + clean-up-events set x "hello world" after 1 set x ab\0cd after 10 @@ -387,61 +377,45 @@ test timer-6.24 {Tcl_AfterCmd procedure, no option, script with NULL} { string length $x } {5} test timer-6.25 {Tcl_AfterCmd procedure, cancel option, script with NULL} { - foreach i [after info] { - after cancel $i - } + clean-up-events set x "hello world" after 1 set x ab\0cd after cancel "set x ab\0ef" set x [llength [after info]] - foreach i [after info] { - after cancel $i - } + clean-up-events set x } {1} test timer-6.26 {Tcl_AfterCmd procedure, cancel option, script with NULL} { - foreach i [after info] { - after cancel $i - } + clean-up-events set x "hello world" after 1 set x ab\0cd after cancel set x ab\0ef set y [llength [after info]] - foreach i [after info] { - after cancel $i - } + clean-up-events set y } {1} test timer-6.27 {Tcl_AfterCmd procedure, idle option, script with NULL} { - foreach i [after info] { - after cancel $i - } + clean-up-events set x "hello world" after idle "set x ab\0cd" update string length $x } {5} test timer-6.28 {Tcl_AfterCmd procedure, idle option, script with NULL} { - foreach i [after info] { - after cancel $i - } + clean-up-events set x "hello world" after idle set x ab\0cd update string length $x } {5} test timer-6.29 {Tcl_AfterCmd procedure, info option, script with NULL} { - foreach i [after info] { - after cancel $i - } + clean-up-events set x "hello world" set id junk set id [after 10 set x ab\0cd] update set y [string length [lindex [lindex [after info $id] 0] 2]] - foreach i [after info] { - after cancel $i - } + clean-up-events set y } {5} @@ -472,8 +446,8 @@ test timer-8.1 {AfterProc procedure} { set x before proc foo {} { set x untouched - after 100 {set x after} - after 200 + after 10 {set x after} + after 100 update return $x } @@ -487,8 +461,8 @@ test timer-8.2 {AfterProc procedure} -setup { set handler [interp bgerror {}] interp bgerror {} [namespace which myHandler] } -body { - after 100 {error "After error"} - after 200 + after 10 {error "After error"} + after 100 set y $x update list $y $x @@ -499,9 +473,7 @@ test timer-8.2 {AfterProc procedure} -setup { "error "After error"" ("after" script)}}} test timer-8.3 {AfterProc procedure, deleting handler from itself} { - foreach i [after info] { - after cancel $i - } + clean-up-events proc foo {} { global x set x {} @@ -511,14 +483,13 @@ test timer-8.3 {AfterProc procedure, deleting handler from itself} { after cancel foo } after idle foo - after 1000 {error "I shouldn't ever have executed"} + after 0 {error "I shouldn't ever have executed"} update idletasks + clean-up-events set x } {{{error "I shouldn't ever have executed"} timer}} test timer-8.4 {AfterProc procedure, deleting handler from itself} { - foreach i [after info] { - after cancel $i - } + clean-up-events proc foo {} { global x set x {} @@ -527,15 +498,14 @@ test timer-8.4 {AfterProc procedure, deleting handler from itself} { } after cancel foo } - after 1000 {error "I shouldn't ever have executed"} + after 0 {error "I shouldn't ever have executed"} + after 1 {error "I also shouldn't ever have executed"} after idle foo update idletasks - set x -} {{{error "I shouldn't ever have executed"} timer}} + lsort $x +} [lsort {{{error "I shouldn't ever have executed"} timer} {{error "I also shouldn't ever have executed"} timer}}] -foreach i [after info] { - after cancel $i -} +clean-up-events # No test for FreeAfterPtr, since it is already tested above. @@ -543,23 +513,23 @@ foreach i [after info] { test timer-9.1 {AfterCleanupProc procedure} { catch {interp delete x} interp create x - x eval {after 200 { + x eval {after 10 { lappend x after puts "part 1: this message should not appear" }} - after 200 {lappend x after2} - x eval {after 200 { + after 10 {lappend x after2} + x eval {after 10 { lappend x after3 puts "part 2: this message should not appear" }} - after 200 {lappend x after4} - x eval {after 200 { + after 10 {lappend x after4} + x eval {after 10 { lappend x after5 puts "part 3: this message should not appear" }} interp delete x set x before - after 300 + after 100 update set x } {before after2 after4} @@ -580,17 +550,16 @@ test timer-11.1 {Bug 1350291: [after] overflowing 32-bit field} \ -body { set b ok set a [after 0x100000001 {set b "after fired early"}] - after 100 set done 1 - vwait done - set b + list [vwait 100 b] [set b] } \ -cleanup { catch {after cancel $a} } \ - -result ok + -result {0 ok} test timer-11.2 {Bug 1350293: [after] negative argument} \ -body { + clean-up-events set l {} after 100 {lappend l 100; set done 1} after -1 {lappend l -1} @@ -599,6 +568,18 @@ test timer-11.2 {Bug 1350293: [after] negative argument} \ } \ -result {-1 100} +test timer-11.3 {[after] correct timer ordering (insert ahead)} \ + -body { + clean-up-events + after 10 {set done 1} + foreach l {1 0.75 0.5 0.25 0.1 0} { + after $l [list lappend l "ev:$l"] + } + set l {} + vwait done + set l + } \ + -result {ev:0 ev:0.1 ev:0.25 ev:0.5 ev:0.75 ev:1} # cleanup ::tcltest::cleanupTests -- cgit v0.12 From a07a5d700e82162dc377db840df58e437da1a8f9 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:26:19 +0000 Subject: fix sporadic errors on some fast cpu/platforms (because bgerror executed in background and it is an idle-event, give enough time to process it (resp. wait until last idle event is done); --- tests/interp.test | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/tests/interp.test b/tests/interp.test index 510ab4a..2649b47 100644 --- a/tests/interp.test +++ b/tests/interp.test @@ -3476,24 +3476,31 @@ test interp-36.7 {SlaveBgerror sets error handler of slave [1999035]} -setup { interp create slave slave alias handler handler slave bgerror handler - variable result {untouched} + variable result {} proc handler {args} { variable result - set result [lindex $args 0] + lappend result [lindex $args 0] } } -body { - slave eval { - variable done {} - after 0 error foo - after 10 [list ::set [namespace which -variable done] {}] - vwait [namespace which -variable done] + # because bgerror executed in background and it is idle-event, give enough + # time to process it (resp. wait until last idle event is done). + slave eval {; # via vwait + after 0 error foo-1 + after 10 {after idle {set done {}}} + vwait done + } + lappend result between + slave eval {; # via update + after 1 error foo-2 + after 10 + update } set result } -cleanup { variable result {} unset result interp delete slave -} -result foo +} -result {foo-1 between foo-2} test interp-37.1 {safe interps and min() and max(): Bug 2895741} -setup { catch {interp delete a} -- cgit v0.12 From d21bae7857c761d57f933bcc4b2256edd5fe7e11 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:27:20 +0000 Subject: added performance test-cases to cover timer-events speed resp. event-driven tcl-handling (cherry-picked and back-ported from tclSE-9) --- tests-perf/test-performance.tcl | 121 ++++++++++++++++++++++++++++++++ tests-perf/timer-event.perf.tcl | 149 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 270 insertions(+) create mode 100644 tests-perf/test-performance.tcl create mode 100644 tests-perf/timer-event.perf.tcl diff --git a/tests-perf/test-performance.tcl b/tests-perf/test-performance.tcl new file mode 100644 index 0000000..b0cbb17 --- /dev/null +++ b/tests-perf/test-performance.tcl @@ -0,0 +1,121 @@ +# ------------------------------------------------------------------------ +# +# test-performance.tcl -- +# +# This file provides common performance tests for comparison of tcl-speed +# degradation or regression by switching between branches. +# +# To execute test case evaluate direct corresponding file "tests-perf\*.perf.tcl". +# +# ------------------------------------------------------------------------ +# +# Copyright (c) 2014 Serg G. Brester (aka sebres) +# +# See the file "license.terms" for information on usage and redistribution +# of this file. +# + +namespace eval ::tclTestPerf { +# warm-up interpeter compiler env, calibrate timerate measurement functionality: + +# if no timerate here - import from unsupported: +if {[namespace which -command timerate] eq {}} { + namespace inscope ::tcl::unsupported {namespace export timerate} + namespace import ::tcl::unsupported::timerate +} + +# if not yet calibrated: +if {[lindex [timerate {} 10] 6] >= (10-1)} { + puts -nonewline "Calibration ... "; flush stdout + puts "done: [lrange \ + [timerate -calibrate {}] \ + 0 1]" +} + +proc {**STOP**} {args} { + return -code error -level 4 "**STOP** in [info level [expr {[info level]-2}]] [join $args { }]" +} + +proc _test_get_commands {lst} { + regsub -all {(?:^|\n)[ \t]*(\#[^\n]*|\msetup\M[^\n]*|\mcleanup\M[^\n]*)(?=\n\s*(?:[\{\#]|setup|cleanup|$))} $lst "\n{\\1}" +} + +proc _test_out_total {} { + upvar _ _ + + set tcnt [llength $_(itm)] + if {!$tcnt} { + puts "" + return + } + + set mintm 0x7fffffff + set maxtm 0 + set nett 0 + set wtm 0 + set wcnt 0 + set i 0 + foreach tm $_(itm) { + if {[llength $tm] > 6} { + set nett [expr {$nett + [lindex $tm 6]}] + } + set wtm [expr {$wtm + [lindex $tm 0]}] + set wcnt [expr {$wcnt + [lindex $tm 2]}] + set tm [lindex $tm 0] + if {$tm > $maxtm} {set maxtm $tm; set maxi $i} + if {$tm < $mintm} {set mintm $tm; set mini $i} + incr i + } + + puts [string repeat ** 40] + set s [format "%d cases in %.2f sec." $tcnt [expr {([clock milliseconds] - $_(starttime)) / 1000.0}]] + if {$nett > 0} { + append s [format " (%.2f nett-sec.)" [expr {$nett / 1000.0}]] + } + puts "Total $s:" + lset _(m) 0 [format %.6f $wtm] + lset _(m) 2 $wcnt + lset _(m) 4 [format %.3f [expr {$wcnt / (($nett ? $nett : ($tcnt * $_(reptime))) / 1000.0)}]] + if {[llength $_(m)] > 6} { + lset _(m) 6 [format %.3f $nett] + } + puts $_(m) + puts "Average:" + lset _(m) 0 [format %.6f [expr {[lindex $_(m) 0] / $tcnt}]] + lset _(m) 2 [expr {[lindex $_(m) 2] / $tcnt}] + if {[llength $_(m)] > 6} { + lset _(m) 6 [format %.3f [expr {[lindex $_(m) 6] / $tcnt}]] + lset _(m) 4 [format %.0f [expr {[lindex $_(m) 2] / [lindex $_(m) 6] * 1000}]] + } + puts $_(m) + puts "Min:" + puts [lindex $_(itm) $mini] + puts "Max:" + puts [lindex $_(itm) $maxi] + puts [string repeat ** 40] + puts "" +} + +proc _test_run {reptime lst {outcmd {puts $_(r)}}} { + upvar _ _ + array set _ [list itm {} reptime $reptime starttime [clock milliseconds]] + + foreach _(c) [_test_get_commands $lst] { + puts "% [regsub -all {\n[ \t]*} $_(c) {; }]" + if {[regexp {^\s*\#} $_(c)]} continue + if {[regexp {^\s*(?:setup|cleanup)\s+} $_(c)]} { + puts [if 1 [lindex $_(c) 1]] + continue + } + if {$reptime > 1} {; #if not once: + set _(r) [if 1 $_(c)] + if {$outcmd ne {}} $outcmd + } + puts [set _(m) [timerate $_(c) $reptime]] + lappend _(itm) $_(m) + puts "" + } + _test_out_total +} + +}; # end of namespace ::tclTestPerf diff --git a/tests-perf/timer-event.perf.tcl b/tests-perf/timer-event.perf.tcl new file mode 100644 index 0000000..fdca695 --- /dev/null +++ b/tests-perf/timer-event.perf.tcl @@ -0,0 +1,149 @@ +#!/usr/bin/tclsh + +# ------------------------------------------------------------------------ +# +# timer-event.perf.tcl -- +# +# This file provides performance tests for comparison of tcl-speed +# of timer events (event-driven tcl-handling). +# +# ------------------------------------------------------------------------ +# +# Copyright (c) 2014 Serg G. Brester (aka sebres) +# +# See the file "license.terms" for information on usage and redistribution +# of this file. +# + + +if {![namespace exists ::tclTestPerf]} { + source [file join [file dirname [info script]] test-performance.tcl] +} + + +namespace eval ::tclTestPerf-Timer-Event { + +namespace path {::tclTestPerf} + +proc test-queue {howmuch} { + + # because of extremely short measurement times by tests below, wait a little bit (warming-up), + # to minimize influence of the time-gradation (just for better dispersion resp. result-comparison) + timerate {after 0} 156 + + puts "*** $howmuch events ***" + _test_run 0 [string map [list \$howmuch $howmuch \\# \#] { + # update / after idle: + setup {puts [time {after idle {set foo bar}} $howmuch]} + {update; \# $howmuch idle-events} + # update idletasks / after idle: + setup {puts [time {after idle {set foo bar}} $howmuch]} + {update idletasks; \# $howmuch idle-events} + + # update / after 0: + setup {puts [time {after 0 {set foo bar}} $howmuch]} + {update; \# $howmuch timer-events} + # update / after 1: + setup {puts [time {after 1 {set foo bar}} $howmuch]; after 1} + {update; \# $howmuch timer-events} + + # cancel forwards "after idle" / $howmuch idle-events in queue: + setup {set i 0; time {set ev([incr i]) [after idle {set foo bar}]} $howmuch} + {set i 0; time {after cancel $ev([incr i])} $howmuch} + cleanup {update} + # cancel backwards "after idle" / $howmuch idle-events in queue: + setup {set i 0; time {set ev([incr i]) [after idle {set foo bar}]} $howmuch} + {incr i; time {after cancel $ev([incr i -1])} $howmuch} + cleanup {update} + + # cancel forwards "after 0" / $howmuch timer-events in queue: + setup {set i 0; time {set ev([incr i]) [after 0 {set foo bar}]} $howmuch} + {set i 0; time {after cancel $ev([incr i])} $howmuch} + cleanup {update} + # cancel backwards "after 0" / $howmuch timer-events in queue: + setup {set i 0; time {set ev([incr i]) [after 0 {set foo bar}]} $howmuch} + {incr i; time {after cancel $ev([incr i -1])} $howmuch} + cleanup {update} + # end $howmuch events. + }] +} + +proc test-exec {{reptime 1000}} { + _test_run $reptime { + # after idle + after cancel + {after cancel [after idle {set foo bar}]} + # after 0 + after cancel + {after cancel [after 0 {set foo bar}]} + # after idle + update idletasks + {after idle {set foo bar}; update idletasks} + # after idle + update + {after idle {set foo bar}; update} + # immediate: after 0 + update + {after 0 {set foo bar}; update} + # delayed: after 1 + update + {after 1 {set foo bar}; update} + # empty update: + {update} + # empty update idle tasks: + {update idletasks} + } +} + +proc test-exec-new {{reptime 1000}} { + _test_run $reptime { + # conditional update pure idle only (without window): + {update -idle} + # conditional update without idle events: + {update -noidle} + # conditional update timers only: + {update -timer} + # conditional update AIO only: + {update -async} + + # conditional vwait with zero timeout: pure idle only (without window): + {vwait -idle 0 x} + # conditional vwait with zero timeout: without idle events: + {vwait -noidle 0 x} + # conditional vwait with zero timeout: timers only: + {vwait -timer 0 x} + # conditional vwait with zero timeout: AIO only: + {vwait -async 0 x} + } +} + +proc test-long {{reptime 1000}} { + _test_run $reptime { + # in-between important event by amount of idle events: + {time {after idle {after 30}} 10; after 1 {set important 1}; vwait important;} + cleanup {foreach i [after info] {after cancel $i}} + # in-between important event (of new generation) by amount of idle events: + {time {after idle {after 30}} 10; after 1 {after 0 {set important 1}}; vwait important;} + cleanup {foreach i [after info] {after cancel $i}} + } +} + +proc test {{reptime 1000}} { + test-exec $reptime + if {![catch {update -noidle}]} { + test-exec-new $reptime + } + test-long $reptime + + puts "" + foreach howmuch { 10000 20000 40000 60000 } { + test-queue $howmuch + } + + puts \n**OK** +} + +}; # end of ::tclTestPerf-Timer-Event + +# ------------------------------------------------------------------------ + +# if calling direct: +if {[info exists ::argv0] && [file tail $::argv0] eq [file tail [info script]]} { + array set in {-time 500} + array set in $argv + ::tclTestPerf-Timer-Event::test $in(-time) +} -- cgit v0.12 From 86897b9570309f5109b17429741bf6ce57f69d2b Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:27:33 +0000 Subject: =?UTF-8?q?[unix]=20optimized=20Tcl=5FWaitForEvent=20similar=20to?= =?UTF-8?q?=20windows=20changes=20(makes=20Tcl=20for=20*nix=20more=20"RTS"?= =?UTF-8?q?=20resp.=20NRT-capable):=20-=20more=20precise=20waiting=20now?= =?UTF-8?q?=20(e.g.=20still=20microseconds=20by=20time=20up=20to=200.005?= =?UTF-8?q?=20ms),=20important=20since=20after/vwait=20accepting=20microse?= =?UTF-8?q?conds=20(double);=20-=20avoids=20too=20long=20waiting=20on=20*n?= =?UTF-8?q?ix=20wait/sleep=20primitives,=20e.=20g.=20by=20`timerate=20{vwa?= =?UTF-8?q?it=200=20a}`=20-=201.5=C2=B5s=20now=20vs.=2031.9=C2=B5s=20befor?= =?UTF-8?q?e;=20-=20extended=20with=20new=20internal=20function=20TclpSlee?= =?UTF-8?q?p=20(in=20contrast=20to=20Tcl=5FSleep=20accept=20Tcl=5FTime,=20?= =?UTF-8?q?so=20microseconds);?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- unix/tclUnixEvent.c | 58 ++---------- unix/tclUnixNotfy.c | 257 ++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 226 insertions(+), 89 deletions(-) diff --git a/unix/tclUnixEvent.c b/unix/tclUnixEvent.c index e4d922d..7e75357 100644 --- a/unix/tclUnixEvent.c +++ b/unix/tclUnixEvent.c @@ -12,7 +12,10 @@ #include "tclInt.h" #ifndef HAVE_COREFOUNDATION /* Darwin/Mac OS X CoreFoundation notifier is * in tclMacOSXNotify.c */ - + +/* declaration of TclpSleep() from tclUnixNotfy.c */ +void TclpSleep(const Tcl_Time *timePtr); + /* *---------------------------------------------------------------------- * @@ -33,56 +36,11 @@ void Tcl_Sleep( int ms) /* Number of milliseconds to sleep. */ { - struct timeval delay; - Tcl_Time before, after, vdelay; - - /* - * The only trick here is that select appears to return early under some - * conditions, so we have to check to make sure that the right amount of - * time really has elapsed. If it's too early, go back to sleep again. - */ - - Tcl_GetTime(&before); - after = before; - after.sec += ms/1000; - after.usec += (ms%1000)*1000; - if (after.usec > 1000000) { - after.usec -= 1000000; - after.sec += 1; - } - while (1) { - /* - * TIP #233: Scale from virtual time to real-time for select. - */ - - vdelay.sec = after.sec - before.sec; - vdelay.usec = after.usec - before.usec; - - if (vdelay.usec < 0) { - vdelay.usec += 1000000; - vdelay.sec -= 1; - } - - if ((vdelay.sec != 0) || (vdelay.usec != 0)) { - (*tclScaleTimeProcPtr) (&vdelay, tclTimeClientData); - } - - delay.tv_sec = vdelay.sec; - delay.tv_usec = vdelay.usec; - - /* - * Special note: must convert delay.tv_sec to int before comparing to - * zero, since delay.tv_usec is unsigned on some platforms. - */ + Tcl_Time delay; - if ((((int) delay.tv_sec) < 0) - || ((delay.tv_usec == 0) && (delay.tv_sec == 0))) { - break; - } - (void) select(0, (SELECT_MASK *) 0, (SELECT_MASK *) 0, - (SELECT_MASK *) 0, &delay); - Tcl_GetTime(&before); - } + delay.sec = ms/1000; + delay.usec = (ms%1000)*1000; + TclpSleep(&delay); } #endif /* HAVE_COREFOUNDATION */ diff --git a/unix/tclUnixNotfy.c b/unix/tclUnixNotfy.c index e7ea7a1..a5ee159 100644 --- a/unix/tclUnixNotfy.c +++ b/unix/tclUnixNotfy.c @@ -112,7 +112,7 @@ typedef struct ThreadSpecificData { * this condition variable. */ #endif /* __CYGWIN__ */ int waitCVinitialized; /* Variable to flag initialization of the structure */ - int eventReady; /* True if an event is ready to be processed. + int eventReady; /* > 0 if an event is ready to be processed. * Used as condition flag together with waitCV * above. */ #endif /* TCL_THREADS */ @@ -492,7 +492,7 @@ Tcl_AlertNotifier( ThreadSpecificData *tsdPtr = (ThreadSpecificData *) clientData; pthread_mutex_lock(¬ifierMutex); - tsdPtr->eventReady = 1; + tsdPtr->eventReady++; #ifdef __CYGWIN__ PostMessageW(tsdPtr->hwnd, 1024, 0, 0); #else /* __CYGWIN__ */ @@ -822,12 +822,107 @@ NotifierProc( * Process all of the runnable events. */ - tsdPtr->eventReady = 1; + tsdPtr->eventReady++; Tcl_ServiceAll(); return 0; } #endif /* TCL_THREADS && __CYGWIN__ */ + +/* + * Several minimal sleep and wait values, corresponding unix timer resolution + * + * Note: Adjusting of this values may increase NRT-capability, but also may + * increase CPU load (resp. busy waits on brief intervals). + */ + +#ifndef TCL_TMR_MIN_DELAY +# define TCL_TMR_MIN_DELAY 100 +# define TCL_TMR_MIN_SLEEP 50 +# define TCL_TMR_OVERHEAD 50 +#endif + +/* + *---------------------------------------------------------------------- + * + * TclpSleep -- + * + * Delay execution for the specified time. + * + * Results: + * None. + * + * Side effects: + * Time passes. + * + *---------------------------------------------------------------------- + */ + +void +TclpSleep( + const Tcl_Time *timePtr) /* Time to sleep. */ +{ + struct timeval delay; + Tcl_Time before, after, vdelay; + + /* + * The only trick here is that select appears to return early under some + * conditions, so we have to check to make sure that the right amount of + * time really has elapsed. If it's too early, go back to sleep again. + */ + + Tcl_GetTime(&before); + after = before; + if (timePtr) { /* if given calculate, otherwise - 0 usec */ + + /* + * TIP #233: Scale from virtual time to real-time for select/usleep. + */ + + vdelay = *timePtr; + if ((vdelay.sec != 0) || (vdelay.usec != 0)) { + tclScaleTimeProcPtr(&vdelay, tclTimeClientData); + } + + after.sec += vdelay.sec; + after.usec += vdelay.usec; + if (after.usec > 1000000) { + after.usec -= 1000000; + after.sec += 1; + } + } + while (1) { + vdelay.sec = after.sec - before.sec; + vdelay.usec = after.usec - before.usec; + + if (vdelay.usec < 0) { + vdelay.usec += 1000000; + vdelay.sec -= 1; + } + + delay.tv_sec = vdelay.sec; + delay.tv_usec = vdelay.usec; + + /* + * Special note: must convert delay.tv_sec to int before comparing to + * zero, since delay.tv_usec is unsigned on some platforms. + */ + + if ((((int) delay.tv_sec) < 0) + || ((delay.tv_usec == 0) && (delay.tv_sec == 0))) { + break; + } + + if (delay.tv_sec || delay.tv_usec >= TCL_TMR_MIN_DELAY) { + (void) select(0, (SELECT_MASK *) 0, (SELECT_MASK *) 0, + (SELECT_MASK *) 0, &delay); + } else if (delay.tv_usec >= TCL_TMR_MIN_SLEEP) { + usleep(delay.tv_usec - TCL_TMR_MIN_SLEEP); + } + Tcl_GetTime(&before); + } +} + /* *---------------------------------------------------------------------- * @@ -851,10 +946,10 @@ Tcl_WaitForEvent( Tcl_Time *timePtr) /* Maximum block time, or NULL. */ { FileHandler *filePtr; - int mask; - Tcl_Time vTime; + int mask, canWait = 1; + Tcl_Time now, endTime, waitTime; #ifdef TCL_THREADS - int waitForFiles; + int waitForFiles = 0; #ifdef __CYGWIN__ MSG msg; #endif /* __CYGWIN__ */ @@ -868,12 +963,16 @@ Tcl_WaitForEvent( struct timeval timeout, *timeoutPtr; int numFound; #endif /* TCL_THREADS */ - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + ThreadSpecificData *tsdPtr; if (tclStubs.tcl_WaitForEvent != tclOriginalNotifier.waitForEventProc) { return tclStubs.tcl_WaitForEvent(timePtr); } + Tcl_GetTime(&now); + + tsdPtr = TCL_TSD_INIT(&dataKey); + /* * Set up the timeout structure. Note that if there are no events to * check for, we return with a negative result rather than blocking @@ -881,17 +980,22 @@ Tcl_WaitForEvent( */ if (timePtr != NULL) { - /* - * TIP #233 (Virtualized Time). Is virtual time in effect? And do - * we actually have something to scale? If yes to both then we - * call the handler to do this scaling. - */ - if (timePtr->sec != 0 || timePtr->usec != 0) { - vTime = *timePtr; - tclScaleTimeProcPtr(&vTime, tclTimeClientData); - timePtr = &vTime; + endTime = now; + endTime.sec += timePtr->sec; + endTime.usec += timePtr->usec; + if (endTime.usec > 1000000) { + endTime.usec -= 1000000; + endTime.sec++; + } + /* + * If short wait or no wait at all, just process events already available + * right now, avoid waiting too long somewhere (NRT-capability fix). + */ + if (!timePtr->sec && timePtr->usec < TCL_TMR_MIN_DELAY) { + canWait = 0; } + #ifndef TCL_THREADS timeout.tv_sec = timePtr->sec; timeout.tv_usec = timePtr->usec; @@ -921,18 +1025,12 @@ Tcl_WaitForEvent( pthread_mutex_lock(¬ifierMutex); - if (timePtr != NULL && timePtr->sec == 0 && (timePtr->usec == 0 -#if defined(__APPLE__) && defined(__LP64__) - /* - * On 64-bit Darwin, pthread_cond_timedwait() appears to have - * a bug that causes it to wait forever when passed an - * absolute time which has already been exceeded by the system - * time; as a workaround, when given a very brief timeout, - * just do a poll. [Bug 1457797] - */ - || timePtr->usec < 10 -#endif /* __APPLE__ && __LP64__ */ - )) { + /* if cannot wait (but not really necessary to wait), bypass triggering pipe */ + if (!canWait && (!tsdPtr->numFdBits || tsdPtr->eventReady)) { + goto nowait; + } + + if (!canWait) { /* * Cannot emulate a polling select with a polling condition * variable. Instead, pretend to wait for files and tell the @@ -943,7 +1041,6 @@ Tcl_WaitForEvent( waitForFiles = 1; tsdPtr->pollState = POLL_WANT; - timePtr = NULL; } else { waitForFiles = (tsdPtr->numFdBits > 0); tsdPtr->pollState = 0; @@ -970,17 +1067,46 @@ Tcl_WaitForEvent( } } + nowait: + FD_ZERO(&tsdPtr->readyMasks.readable); FD_ZERO(&tsdPtr->readyMasks.writable); FD_ZERO(&tsdPtr->readyMasks.exception); - if (!tsdPtr->eventReady) { + while (!tsdPtr->eventReady) { + + if (timePtr) { + Tcl_GetTime(&now); + waitTime = endTime; + waitTime.sec -= now.sec; + waitTime.usec -= now.usec; + if (waitTime.usec < 0) { + waitTime.usec += 1000000; + waitTime.sec--; + } + + if (now.sec > endTime.sec) { + break; /* end of wait */ + } + if (now.sec == endTime.sec) { + if (now.usec > endTime.usec) { + break; /* end of wait */ + } + if (now.usec > endTime.usec + TCL_TMR_OVERHEAD) { + canWait = 0; + } + } + } + #ifdef __CYGWIN__ if (!PeekMessageW(&msg, NULL, 0, 0, 0)) { DWORD timeout; if (timePtr) { - timeout = timePtr->sec * 1000 + timePtr->usec / 1000; + /* TIP #233: Scale from virtual time to real-time */ + tclScaleTimeProcPtr(&waitTime, tclTimeClientData); + + timeout = waitTime.sec * 1000 + waitTime.usec / 1000; } else { timeout = 0xFFFFFFFF; } @@ -989,21 +1115,74 @@ Tcl_WaitForEvent( pthread_mutex_lock(¬ifierMutex); } #else - if (timePtr != NULL) { - Tcl_Time now; + /* prevent too long waiting (NRT-capability) */ + if ( !canWait ) { + /* short sleep */ + TclpSleep(&waitTime); + break; /* end of wait */ + } + else + if (timePtr) { struct timespec ptime; - Tcl_GetTime(&now); - ptime.tv_sec = timePtr->sec + now.sec + (timePtr->usec + now.usec) / 1000000; - ptime.tv_nsec = 1000 * ((timePtr->usec + now.usec) % 1000000); +#if 1 + /* TIP #233: Scale from virtual time to real-time */ + tclScaleTimeProcPtr(&waitTime, tclTimeClientData); - pthread_cond_timedwait(&tsdPtr->waitCV, ¬ifierMutex, &ptime); + ptime.tv_sec = now.sec; + ptime.tv_nsec = (now.usec + waitTime.usec); + if (ptime.tv_nsec > 1000000) { + ptime.tv_nsec -= 1000000; + ptime.tv_sec++; + } + ptime.tv_nsec *= 1000; /* usec to nsec */ +#else + ptime.tv_sec = endTime.sec; + ptime.tv_nsec = 1000 * endTime.usec; +#endif + +#if defined(__APPLE__) && defined(__LP64__) + /* + * On 64-bit Darwin, pthread_cond_timedwait() appears to have + * a bug that causes it to wait forever when passed an + * absolute time which has already been exceeded by the system + * time; as a workaround, when given a very brief timeout, + * just increment a bit the waiting-time from now. [Bug 1457797] + */ + if ( now.sec > endTime.sec + || (now.sec == endTime.sec && now.usec > endTime.usec) + ) { + ptime.tv_sec = now.sec; + ptime.tv_nsec = 1000 * now.usec + 10; /* + 10 nanosecond */ + } +#else + /* remove overhead in nsec */ + if (ptime.tv_nsec < TCL_TMR_OVERHEAD * 1000) { + ptime.tv_nsec += 1000000000; + ptime.tv_sec--; + } + ptime.tv_nsec -= TCL_TMR_OVERHEAD * 1000; + +#endif /* __APPLE__ && __LP64__ */ + + if (ptime.tv_nsec > 1000000000) { + ptime.tv_nsec -= 1000000000; + ptime.tv_sec++; + } + if (pthread_cond_timedwait(&tsdPtr->waitCV, ¬ifierMutex, + &ptime) == ETIMEDOUT) { + continue; /* repeat wait (if not yet real timeout) */ + }; } else { pthread_cond_wait(&tsdPtr->waitCV, ¬ifierMutex); } #endif /* __CYGWIN__ */ + + break; /* end of wait */ + } + if (tsdPtr->eventReady > 0) { + tsdPtr->eventReady--; } - tsdPtr->eventReady = 0; #ifdef __CYGWIN__ while (PeekMessageW(&msg, NULL, 0, 0, 0)) { @@ -1272,7 +1451,7 @@ NotifierThreadProc( } if (found || (tsdPtr->pollState & POLL_DONE)) { - tsdPtr->eventReady = 1; + tsdPtr->eventReady++; if (tsdPtr->onList) { /* * Remove the ThreadSpecificData structure of this thread -- cgit v0.12 From 2ae514f620d54d6e84cd40faa493265aae2a91fe Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:27:47 +0000 Subject: extended performance test-cases (test-nrt-capability): covering of brief wait-times and other RTS-near constructs. --- tests-perf/timer-event.perf.tcl | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests-perf/timer-event.perf.tcl b/tests-perf/timer-event.perf.tcl index fdca695..12b3320 100644 --- a/tests-perf/timer-event.perf.tcl +++ b/tests-perf/timer-event.perf.tcl @@ -111,6 +111,34 @@ proc test-exec-new {{reptime 1000}} { } } +proc test-nrt-capability {{reptime 1000}} { + _test_run $reptime { + # comparison values: + {after 0 {set a 5}; update} + {after 0 {set a 5}; vwait a} + + # conditional vwait with very brief wait-time: + {vwait 1 a} + {vwait 0.5 a} + {vwait 0.2 a} + {vwait 0.1 a} + {vwait 0.05 a} + {vwait 0.02 a} + {vwait 0.01 a} + {vwait 0.005 a} + {vwait 0.001 a} + + # comparison of update's executing event: + {after idle {set a 5}; update -idle -timer} + {after 0 {set a 5}; update -idle -timer} + {after idle {set a 5}; update -idle} + # comparison of vwait's executing event: + {after idle {set a 5}; vwait -idle -timer a} + {after 0 {set a 5}; vwait -idle -timer a} + {after idle {set a 5}; vwait -idle a} + } +} + proc test-long {{reptime 1000}} { _test_run $reptime { # in-between important event by amount of idle events: @@ -126,6 +154,7 @@ proc test {{reptime 1000}} { test-exec $reptime if {![catch {update -noidle}]} { test-exec-new $reptime + test-nrt-capability $reptime } test-long $reptime -- cgit v0.12 From a82f50e67930df0328c5d0d6ddecf54c2548eced Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:28:04 +0000 Subject: bug fix: prevent setting of negative block-time by too few initial wait-time, that may expire immediately (for example `vwait 0.0001 test`). --- generic/tclEvent.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/generic/tclEvent.c b/generic/tclEvent.c index 186b8ae..05e3109 100644 --- a/generic/tclEvent.c +++ b/generic/tclEvent.c @@ -1446,16 +1446,18 @@ Tcl_VwaitObjCmd( blockTime.usec += 1000000; } /* be sure process at least one event */ - if (checktime) { - if ( blockTime.sec < 0 - || (blockTime.sec == 0 && blockTime.usec <= tolerance) - ) { - /* timeout occurs */ + if ( blockTime.sec < 0 + || (blockTime.sec == 0 && blockTime.usec <= tolerance) + ) { + /* timeout occurs */ + if (checktime) { done = -1; break; } + /* expired, be sure non-negative values here */ + blockTime.usec = blockTime.sec = 0; + checktime = 1; } - checktime = 1; Tcl_SetMaxBlockTime(&blockTime); } if ((foundEvent = Tcl_DoOneEvent(flags)) <= 0) { -- cgit v0.12 From fc99959a11991ca61d8713436b2a032f67559ae5 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:28:09 +0000 Subject: chanio.test: optimize several tests cases running too long (shorten unwanted large sleeps) --- tests/chanio.test | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/chanio.test b/tests/chanio.test index 2f2540e..9346a01 100644 --- a/tests/chanio.test +++ b/tests/chanio.test @@ -1771,7 +1771,7 @@ test chan-io-14.9 {reuse of stdio special channels} {stdio openpipe fileevent} { # Added delay to give Windows time to stop the spawned process and clean # up its grip on the file test1. Added delete as proper test cleanup. # The failing tests were 18.1 and 18.2 as first re-users of file "test1". - after 10000 + after 1000 file delete $path(script) file delete $path(test1) set c @@ -6892,17 +6892,17 @@ test chan-io-53.7 {CopyData: Flooding chan copy from pipe} {stdio openpipe fcopy set fcopyTestCount 0 set f1 [open $path(pipe) w] chan puts $f1 { - # Write 10 bytes / 10 msec + # Write 10 bytes / 1 msec proc Write {count} { chan puts -nonewline "1234567890" if {[incr count -1]} { - after 10 [list Write $count] + after 1 [list Write $count] } else { set ::ready 1 } } chan configure stdout -buffering none - Write 345 ;# 3450 bytes ~3.45 sec + Write 345 ;# 3450 bytes ~0.345 sec vwait ready exit 0 } @@ -7013,11 +7013,11 @@ test chan-io-53.9 {CopyData: -size and event interaction, Bug 780533} -setup { chan puts $pipe { chan configure stdout -translation binary -buffering line chan puts stderr Waiting... - after 1000 + after 100 foreach x {a b c} { chan puts stderr Looping... chan puts $x - after 500 + after 50 } proc bye args { if {[chan gets stdin line]<0} { -- cgit v0.12 From 97b969c05b650e02efd46fb0db7305c847d98230 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:28:17 +0000 Subject: extended performance test-cases (test-nrt-capability): RTS-near sleeps with very brief sleep-time. --- tests-perf/timer-event.perf.tcl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests-perf/timer-event.perf.tcl b/tests-perf/timer-event.perf.tcl index 12b3320..a5ea8e1 100644 --- a/tests-perf/timer-event.perf.tcl +++ b/tests-perf/timer-event.perf.tcl @@ -86,6 +86,9 @@ proc test-exec {{reptime 1000}} { {update} # empty update idle tasks: {update idletasks} + + # simple shortest sleep: + {after 0} } } @@ -128,6 +131,15 @@ proc test-nrt-capability {{reptime 1000}} { {vwait 0.005 a} {vwait 0.001 a} + # NRT sleep / very brief delays (0.5 - 0.005): + {after 0.5} + {after 0.05} + {after 0.005} + # NRT sleep / very brief delays (0.1 - 0.001): + {after 0.1} + {after 0.01} + {after 0.001} + # comparison of update's executing event: {after idle {set a 5}; update -idle -timer} {after 0 {set a 5}; update -idle -timer} -- cgit v0.12 From 418300bd6d16dcb6a02fad92ce471e69ce17fc7d Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:29:03 +0000 Subject: interim commit: try to fix time-drift backwards (too long offset after calibration? something else?)... --- generic/tclInt.h | 2 +- generic/tclTimer.c | 25 +++- win/tclWinTime.c | 376 +++++++++++++++++++++++++++++++++-------------------- 3 files changed, 260 insertions(+), 143 deletions(-) diff --git a/generic/tclInt.h b/generic/tclInt.h index f13af82..fad78f2 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -2894,8 +2894,8 @@ TclTimeAddMilliseconds( timePtr->sec += (long)(ms / 1000); timePtr->usec += (((long)ms) % 1000) * 1000 + (((long)(ms*1000)) % 1000); if (timePtr->usec > 1000000) { - timePtr->sec++; timePtr->usec -= 1000000; + timePtr->sec++; } } diff --git a/generic/tclTimer.c b/generic/tclTimer.c index df974ab..d9e25de 100644 --- a/generic/tclTimer.c +++ b/generic/tclTimer.c @@ -108,6 +108,9 @@ static Tcl_ThreadDataKey dataKey; #define TCL_TIME_DIFF_MS(t1, t2) \ (1000*((Tcl_WideInt)(t1).sec - (Tcl_WideInt)(t2).sec) + \ ((long)(t1).usec - (long)(t2).usec)/1000) +#define TCL_TIME_DIFF_US(t1, t2) \ + (1000000*((Tcl_WideInt)(t1).sec - (Tcl_WideInt)(t2).sec) + \ + ((long)(t1).usec - (long)(t2).usec)) /* * Prototypes for functions referenced only in this file: @@ -1382,8 +1385,8 @@ AfterDelay( { Interp *iPtr = (Interp *) interp; - Tcl_Time endTime, now; - Tcl_WideInt diff; + Tcl_Time endTime, now, prevNow; + Tcl_WideInt diff, prevUS, nowUS; #ifdef TMR_RES_TOLERANCE long tolerance; #endif @@ -1399,11 +1402,20 @@ AfterDelay( tolerance = ((ms < 1000) ? ms : 1000) * (1000 * TMR_RES_TOLERANCE / 100); #endif - Tcl_GetTime(&endTime); + prevUS = TclpGetMicroseconds(); + Tcl_GetTime(&endTime); now = endTime; + prevNow = now; TclTimeAddMilliseconds(&endTime, ms); do { + nowUS = TclpGetMicroseconds(); Tcl_GetTime(&now); + if (now.sec < prevNow.sec || (now.sec == prevNow.sec && now.usec < prevNow.usec) ) { + printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!long-diff!!!! %5.3f, prev: %d.%06d - now: %d.%06d (%d usec)\n", ms, prevNow.sec, prevNow.usec, now.sec, now.usec, now.usec - prevNow.usec); + printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!long-diff!!!! %5.3f, prev: %I64d - now: %I64d (%I64d usec)\n", ms, prevUS, nowUS, nowUS - prevUS); + //Tcl_Panic("Time running backwards!"); + //return TCL_ERROR; + } if (iPtr->limit.timeEvent != NULL && TCL_TIME_BEFORE(iPtr->limit.time, now)) { iPtr->limit.granularityTicker = 0; @@ -1414,6 +1426,9 @@ AfterDelay( if (iPtr->limit.timeEvent == NULL || TCL_TIME_BEFORE(endTime, iPtr->limit.time)) { diff = TCL_TIME_DIFF_MS(endTime, now); + if (TCL_TIME_DIFF_US(endTime, now) > 500 || TCL_TIME_DIFF_US(endTime, now) < -500) { + printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!drift!!!! %5.3f, diff: %d -- %d.%06d - %d.%06d (%d usec)\n", ms, (int)diff, endTime.sec, endTime.usec, now.sec, now.usec, TCL_TIME_DIFF_US(endTime, now)); + } #ifndef TCL_WIDE_INT_IS_LONG if (diff > LONG_MAX) { diff = LONG_MAX; @@ -1421,6 +1436,7 @@ AfterDelay( #endif if (diff > 0) { Tcl_Sleep((long)diff); + nowUS = TclpGetMicroseconds(); Tcl_GetTime(&now); } } else { @@ -1432,6 +1448,7 @@ AfterDelay( #endif if (diff > 0) { Tcl_Sleep((long)diff); + nowUS = TclpGetMicroseconds(); Tcl_GetTime(&now); } if (Tcl_LimitCheck(interp) != TCL_OK) { @@ -1439,6 +1456,8 @@ AfterDelay( } } /* consider timer resolution tolerance (avoid busy wait) */ + prevNow = now; + prevUS = nowUS; #ifdef TMR_RES_TOLERANCE now.usec += tolerance; if (now.usec > 1000000) { diff --git a/win/tclWinTime.c b/win/tclWinTime.c index def4548..8576656 100644 --- a/win/tclWinTime.c +++ b/win/tclWinTime.c @@ -51,7 +51,7 @@ typedef struct TimeInfo { * initialized. */ int perfCounterAvailable; /* Flag == 1 if the hardware has a performance * counter. */ - DWORD calibrationInterv; /* Calibration interval in seconds (start 1 sec) */ + Tcl_WideInt calibNextTime; /* Next time of calibration (in 100-ns ticks) */ HANDLE calibrationThread; /* Handle to the thread that keeps the virtual * clock calibrated. */ HANDLE readyEvent; /* System event used to trigger the requesting @@ -62,20 +62,24 @@ typedef struct TimeInfo { LARGE_INTEGER nominalFreq; /* Nominal frequency of the system performance * counter, that is, the value returned from * QueryPerformanceFrequency. */ + LARGE_INTEGER posixEpoch; /* Posix epoch expressed as 100-ns ticks since + * the windows epoch. */ /* * The following values are used for calculating virtual time. Virtual * time is always equal to: - * lastFileTime + (current perf counter - lastCounter) - * * 10000000 / curCounterFreq - * and lastFileTime and lastCounter are updated any time that virtual time - * is returned to a caller. + * fileTime + (current perf counter - lastCounter) + * * 10000000 / counterFreq */ - ULARGE_INTEGER fileTimeLastCall; - LARGE_INTEGER perfCounterLastCall; - LARGE_INTEGER curCounterFreq; - LARGE_INTEGER posixEpoch; /* Posix epoch expressed as 100-ns ticks since - * the windows epoch. */ + struct { + ULONGLONG fileTime; + volatile /* used also to compare calibration epoch */ + LONGLONG perfCounter; + LONGLONG counterFreq; + } lastCC; /* Last data updated in calibration cycle */ + + Tcl_WideInt lastUsedTime; /* Last known (caller) virtual time in 100-ns + * (used to avoid drifts after calibrate) */ /* * Data used in developing the estimate of performance counter frequency @@ -92,25 +96,25 @@ static TimeInfo timeInfo = { { NULL, 0, 0, NULL, NULL, 0 }, 0, 0, - 1, + (Tcl_WideInt) 0, (HANDLE) NULL, (HANDLE) NULL, (HANDLE) NULL, #ifdef HAVE_CAST_TO_UNION (LARGE_INTEGER) (Tcl_WideInt) 0, - (ULARGE_INTEGER) (DWORDLONG) 0, - (LARGE_INTEGER) (Tcl_WideInt) 0, - (LARGE_INTEGER) (Tcl_WideInt) 0, (LARGE_INTEGER) (Tcl_WideInt) 0, #else {0, 0}, {0, 0}, - {0, 0}, - {0, 0}, - {0, 0}, #endif - { 0 }, - { 0 }, + { + (ULONGLONG) 0, + (LONGLONG) 0, + (LONGLONG) 0 + }, + (Tcl_WideInt) 0, + { (Tcl_WideUInt) 0 }, + { (Tcl_WideInt) 0 }, 0 }; @@ -154,6 +158,50 @@ ClientData tclTimeClientData = NULL; /* *---------------------------------------------------------------------- * + * NativeCalc100NsTicks -- + * + * Calculate the current system time in 100-ns ticks since posix epoch, + * for current performance counter (curCounter), using given calibrated values. + * + * Results: + * Returns the wide integer with number of microseconds from the epoch. + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static inline Tcl_WideInt +NativeCalc100NsTicks( + ULONGLONG ccFileTime, + LONGLONG ccPerfCounter, + LONGLONG ccCounterFreq, + LONGLONG curCounter +) { + return ccFileTime + + ((curCounter - ccPerfCounter) * 10000000 / ccCounterFreq); +} + +/* + * Representing the number of 100-nanosecond intervals since posix epoch. + */ +static inline Tcl_WideInt +GetSystemTimeAsVirtual(void) +{ + FILETIME curSysTime; /* Current system time. */ + LARGE_INTEGER curFileTime; + + /* 100-ns ticks since since Jan 1, 1601 (UTC) */ + GetSystemTimeAsFileTime(&curSysTime); + curFileTime.LowPart = curSysTime.dwLowDateTime; + curFileTime.HighPart = curSysTime.dwHighDateTime; + return (Tcl_WideInt)(curFileTime.QuadPart - timeInfo.posixEpoch.QuadPart); +} + +/* + *---------------------------------------------------------------------- + * * TclpGetSeconds -- * * This procedure returns the number of seconds from the epoch. On most @@ -331,13 +379,19 @@ TclpWideClickInMicrosec(void) Tcl_WideInt TclpGetMicroseconds(void) { + static Tcl_WideInt prevUS = 0; + static Tcl_WideInt fileTimeLastCall, perfCounterLastCall, curCounterFreq; + static LARGE_INTEGER prevPerfCounter; + LARGE_INTEGER newPerfCounter; + Tcl_WideInt usecSincePosixEpoch; /* Try to use high resolution timer */ - if ( tclGetTimeProcPtr == NativeGetTime - && (usecSincePosixEpoch = NativeGetMicroseconds()) - ) { - return usecSincePosixEpoch; + if (tclGetTimeProcPtr == NativeGetTime) { + if ( !(usecSincePosixEpoch = NativeGetMicroseconds()) ) { + usecSincePosixEpoch = GetSystemTimeAsVirtual() / 10; /* in 100-ns */ + printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!no-native-ms!!!!!!!!!!!\n"); + } } else { /* * Use the Tcl_GetTime abstraction to get the time in microseconds, as @@ -347,8 +401,31 @@ TclpGetMicroseconds(void) Tcl_Time now; tclGetTimeProcPtr(&now, tclTimeClientData); /* Tcl_GetTime inlined */ - return (((Tcl_WideInt)now.sec) * 1000000) + now.usec; + usecSincePosixEpoch = (((Tcl_WideInt)now.sec) * 1000000) + now.usec; + printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!no-native-ms!!!!!!!!!!!\n"); } + + QueryPerformanceCounter(&newPerfCounter); + + if (prevUS && usecSincePosixEpoch < prevUS) { + printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!time-backwards!!!! pre-struct: %I64d, %I64d, %I64d, %I64d == %I64d \n", fileTimeLastCall, perfCounterLastCall, prevPerfCounter.QuadPart, curCounterFreq, + NativeCalc100NsTicks(fileTimeLastCall, + perfCounterLastCall, curCounterFreq, + prevPerfCounter.QuadPart)); + printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!time-backwards!!!! new-struct: %I64d, %I64d, %I64d, %I64d == %I64d \n", timeInfo.lastCC.fileTime, timeInfo.lastCC.perfCounter, newPerfCounter.QuadPart, timeInfo.lastCC.counterFreq, + NativeCalc100NsTicks(timeInfo.lastCC.fileTime, + timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq, + newPerfCounter.QuadPart)); + printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!time-backwards!!!! prev: %I64d - now: %I64d (%I64d usec)\n", prevUS, usecSincePosixEpoch, usecSincePosixEpoch - prevUS); + Tcl_Panic("Time running backwards!!!"); + } + prevUS = usecSincePosixEpoch; + fileTimeLastCall = timeInfo.lastCC.fileTime; + perfCounterLastCall = timeInfo.lastCC.perfCounter; + curCounterFreq = timeInfo.lastCC.counterFreq; + prevPerfCounter.QuadPart = newPerfCounter.QuadPart; + + return usecSincePosixEpoch; } /* @@ -469,20 +546,12 @@ NativeScaleTime( *---------------------------------------------------------------------- */ -static inline Tcl_WideInt -NativeCalc100NsTicks( - ULONGLONG fileTimeLastCall, - LONGLONG perfCounterLastCall, - LONGLONG curCounterFreq, - LONGLONG curCounter -) { - return fileTimeLastCall + - ((curCounter - perfCounterLastCall) * 10000000 / curCounterFreq); -} - static Tcl_WideInt NativeGetMicroseconds(void) { + Tcl_WideInt curTime; /* Current time in 100-ns ticks since epoch */ + Tcl_WideInt lastTime; /* Used to compare with last known time */ + /* * Initialize static storage on the first trip through. * @@ -595,42 +664,52 @@ NativeGetMicroseconds(void) TclpInitUnlock(); } - if (timeInfo.perfCounterAvailable && timeInfo.curCounterFreq.QuadPart!=0) { - /* - * Query the performance counter and use it to calculate the current - * time. - */ + if (timeInfo.perfCounterAvailable && timeInfo.lastCC.counterFreq!=0) { - ULONGLONG fileTimeLastCall; - LONGLONG perfCounterLastCall, curCounterFreq; - /* Copy with current data of calibration cycle */ + static struct { + ULONGLONG fileTime; + volatile /* don't optimize */ + LONGLONG perfCounter; + LONGLONG counterFreq; + Tcl_WideInt calibNextTime; + } cc = {0, 0, 0, 0}; /* Copy with current data of calibration cycle */ LARGE_INTEGER curCounter; /* Current performance counter. */ - QueryPerformanceCounter(&curCounter); - /* * Hold time section locked as short as possible */ - EnterCriticalSection(&timeInfo.cs); - - fileTimeLastCall = timeInfo.fileTimeLastCall.QuadPart; - perfCounterLastCall = timeInfo.perfCounterLastCall.QuadPart; - curCounterFreq = timeInfo.curCounterFreq.QuadPart; - - LeaveCriticalSection(&timeInfo.cs); + if (cc.perfCounter != timeInfo.lastCC.perfCounter) { + EnterCriticalSection(&timeInfo.cs); + if (cc.perfCounter != timeInfo.lastCC.perfCounter) { + cc.perfCounter = timeInfo.lastCC.perfCounter; + cc.fileTime = timeInfo.lastCC.fileTime; + cc.counterFreq = timeInfo.lastCC.counterFreq; + cc.calibNextTime = timeInfo.calibNextTime; + } + LeaveCriticalSection(&timeInfo.cs); + } /* - * If calibration cycle occurred after we get curCounter + * Query the performance counter and use it to calculate the current + * time. */ - if (curCounter.QuadPart <= perfCounterLastCall) { - /* Calibrated file-time is saved from posix in 100-ns ticks */ - return fileTimeLastCall / 10; + QueryPerformanceCounter(&curCounter); + + /* Calibrated file-time is saved from posix in 100-ns ticks */ + curTime = NativeCalc100NsTicks(cc.fileTime, + cc.perfCounter, cc.counterFreq, curCounter.QuadPart); + + /* Be sure the clock ticks never backwards (avoid backwards time-drifts) */ + if ( (lastTime = timeInfo.lastUsedTime) && lastTime > curTime + && lastTime - curTime < 1000000 /* bypass time-switch (drifts only) */ + ) { + curTime = timeInfo.lastUsedTime; } /* - * If it appears to be more than 1.1 seconds since the last trip + * If it appears to be more than 1.5 seconds since the last trip * through the calibration loop, the performance counter may have * jumped forward. (See MSDN Knowledge Base article Q274323 for a * description of the hardware problem that makes this test @@ -639,19 +718,26 @@ NativeGetMicroseconds(void) * loop should recover. */ - if (curCounter.QuadPart - perfCounterLastCall < - 11 * curCounterFreq * timeInfo.calibrationInterv / 10 - ) { - /* Calibrated file-time is saved from posix in 100-ns ticks */ - return NativeCalc100NsTicks(fileTimeLastCall, - perfCounterLastCall, curCounterFreq, curCounter.QuadPart) / 10; + if (curTime < cc.calibNextTime + 5000000 /* 500 millisec (in 100-ns ticks). */) { + /* save last used time */ + timeInfo.lastUsedTime = curTime; + return curTime / 10; } + printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!calibration-error!!!! cur: %I64d - call: %I64d (%I64d) -- prev: %I64d - now: %I64d (%I64d)\n", curTime, cc.calibNextTime, cc.calibNextTime - curTime, cc.perfCounter, curCounter.QuadPart, curCounter.QuadPart - cc.perfCounter); } /* * High resolution timer is not available. */ - return 0; + curTime = GetSystemTimeAsVirtual(); /* in 100-ns ticks */ + /* Be sure the clock ticks never backwards (avoid backwards time-drifts) */ + if ( (lastTime = timeInfo.lastUsedTime) && lastTime > curTime + && lastTime - curTime < 1000000 /* bypass time-switch (drifts only) */ + ) { + curTime = timeInfo.lastUsedTime; + } + timeInfo.lastUsedTime = curTime; + return curTime / 10; } /* @@ -1076,30 +1162,32 @@ static DWORD WINAPI CalibrationThread( LPVOID arg) { - FILETIME curFileTime; DWORD waitResult; /* * Get initial system time and performance counter. */ - GetSystemTimeAsFileTime(&curFileTime); - QueryPerformanceCounter(&timeInfo.perfCounterLastCall); - QueryPerformanceFrequency(&timeInfo.curCounterFreq); - timeInfo.fileTimeLastCall.LowPart = curFileTime.dwLowDateTime; - timeInfo.fileTimeLastCall.HighPart = curFileTime.dwHighDateTime; - /* Calibrated file-time will be saved from posix in 100-ns ticks */ - timeInfo.fileTimeLastCall.QuadPart -= timeInfo.posixEpoch.QuadPart; + LARGE_INTEGER curPerfCounter; + + QueryPerformanceCounter(&curPerfCounter); + timeInfo.lastCC.perfCounter = curPerfCounter.QuadPart; + timeInfo.lastCC.counterFreq = timeInfo.nominalFreq.QuadPart; + timeInfo.lastCC.fileTime = GetSystemTimeAsVirtual(); - ResetCounterSamples(timeInfo.fileTimeLastCall.QuadPart, - timeInfo.perfCounterLastCall.QuadPart, - timeInfo.curCounterFreq.QuadPart); + ResetCounterSamples(timeInfo.lastCC.fileTime, + timeInfo.lastCC.perfCounter, + timeInfo.lastCC.counterFreq); /* - * Wake up the calling thread. When it wakes up, it will release the - * initialization lock. + * Calibrate first time and wake up the calling thread. + * When it wakes up, it will release the initialization lock. */ + if (timeInfo.perfCounterAvailable) { + UpdateTimeEachSecond(); + } + SetEvent(timeInfo.readyEvent); /* @@ -1153,13 +1241,13 @@ UpdateTimeEachSecond(void) LARGE_INTEGER curPerfCounter; /* Current value returned from * QueryPerformanceCounter. */ - FILETIME curSysTime; /* Current system time. */ - static LARGE_INTEGER lastFileTime; /* File time of the previous calibration */ - LARGE_INTEGER curFileTime; /* File time at the time this callback was + static int calibrationInterv = 10000000; + /* Calibration interval in 100-ns ticks (starts from 1s) */ + Tcl_WideInt curFileTime; /* File time at the time this callback was * scheduled. */ Tcl_WideInt estFreq; /* Estimated perf counter frequency. */ Tcl_WideInt vt0; /* Tcl time right now. */ - Tcl_WideInt vt1; /* Tcl time one second from now. */ + Tcl_WideInt vt1, nt0, nt1; /* Interim virtual time used during adjustments */ Tcl_WideInt tdiff; /* Difference between system clock and Tcl * time. */ Tcl_WideInt driftFreq; /* Frequency needed to drift virtual time into @@ -1169,24 +1257,23 @@ UpdateTimeEachSecond(void) * Sample performance counter and system time (from posix epoch). */ - GetSystemTimeAsFileTime(&curSysTime); - curFileTime.LowPart = curSysTime.dwLowDateTime; - curFileTime.HighPart = curSysTime.dwHighDateTime; - curFileTime.QuadPart -= timeInfo.posixEpoch.QuadPart; - /* If calibration still not needed (check for possible time switch) */ - if ( curFileTime.QuadPart > lastFileTime.QuadPart - && curFileTime.QuadPart < lastFileTime.QuadPart + - (timeInfo.calibrationInterv * 10000000) + printf("-------------calibration start, prev-struct: %I64d, %I64d, %I64d\n", timeInfo.lastCC.fileTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq); + curFileTime = GetSystemTimeAsVirtual(); + /* + * If calibration still not needed (check for possible time-switch). Note, that + * NativeGetMicroseconds checks calibNextTime also, be sure it does not overflow. + */ + if ( curFileTime < timeInfo.calibNextTime - 10000000 /* 1 sec (in 100-ns ticks). */ + && timeInfo.calibNextTime - curFileTime < 10 * 10000000 /* max. 10 seconds in-between */ ) { /* again in next one second */ +// printf("-------------calibration end, not needed. ------ cur:%I64d - next:%I64d (d: %I64d) ------\n", curFileTime, timeInfo.calibNextTime, timeInfo.calibNextTime - curFileTime); return; } QueryPerformanceCounter(&curPerfCounter); - lastFileTime.QuadPart = curFileTime.QuadPart; - /* - * We devide by timeInfo.curCounterFreq.QuadPart in several places. That + * We devide by timeInfo.lastCC.counterFreq in several places. That * value should always be positive on a correctly functioning system. But * it is good to be defensive about such matters. So if something goes * wrong and the value does goes to zero, we clear the @@ -1194,7 +1281,7 @@ UpdateTimeEachSecond(void) * to shut itself down, then return without additional processing. */ - if (timeInfo.curCounterFreq.QuadPart == 0){ + if (timeInfo.lastCC.counterFreq == 0) { timeInfo.perfCounterAvailable = 0; return; } @@ -1213,16 +1300,15 @@ UpdateTimeEachSecond(void) * estimate the performance counter frequency. */ - estFreq = AccumulateSample(curPerfCounter.QuadPart, - (Tcl_WideUInt) curFileTime.QuadPart); + estFreq = AccumulateSample(curPerfCounter.QuadPart, curFileTime); /* * We want to adjust things so that time appears to be continuous. * Virtual file time, right now, is * - * vt0 = 10000000 * (curPerfCounter - perfCounterLastCall) - * / curCounterFreq - * + fileTimeLastCall + * vt0 = 10000000 * (curPerfCounter - lastCC.perfCounter) + * / lastCC.counterFreq + * + lastCC.fileTime * * Ideally, we would like to drift the clock into place over a period of 2 * sec, so that virtual time 2 sec from now will be @@ -1233,8 +1319,8 @@ UpdateTimeEachSecond(void) * is estFreq * 20000000 / (vt1 - vt0) */ - vt0 = NativeCalc100NsTicks(timeInfo.fileTimeLastCall.QuadPart, - timeInfo.perfCounterLastCall.QuadPart, timeInfo.curCounterFreq.QuadPart, + vt0 = NativeCalc100NsTicks(timeInfo.lastCC.fileTime, + timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq, curPerfCounter.QuadPart); /* * If we've gotten more than a second away from system time, then drifting @@ -1242,80 +1328,92 @@ UpdateTimeEachSecond(void) * compute the drift frequency and fill in everything. */ - tdiff = vt0 - curFileTime.QuadPart; + tdiff = vt0 - curFileTime; if (tdiff > 10000000 || tdiff < -10000000) { + /* More as a second difference, so could be a time-switch /* jump to current system time, use curent estimated frequency */ - vt0 = curFileTime.QuadPart; + vt0 = curFileTime; + timeInfo.lastUsedTime = 0; /* reset last used time */ } else { /* calculate new frequency and estimate drift to the next second */ - vt1 = 20000000 + curFileTime.QuadPart; + vt1 = 20000000 + curFileTime; driftFreq = (estFreq * 20000000 / (vt1 - vt0)); /* * Avoid too large drifts (only half of the current difference), * that allows also be more accurate (aspire to the smallest tdiff), * so then we can prolong calibration interval by tdiff < 100000 */ - driftFreq = timeInfo.curCounterFreq.QuadPart + - (driftFreq - timeInfo.curCounterFreq.QuadPart) / 2; + driftFreq = timeInfo.lastCC.counterFreq + + (driftFreq - timeInfo.lastCC.counterFreq) / 2; /* * Average between estimated, 2 current and 5 drifted frequencies, * (do the soft drifting as possible) */ - estFreq = (estFreq + 2 * timeInfo.curCounterFreq.QuadPart + 5 * driftFreq) / 8; + estFreq = (estFreq + 2 * timeInfo.lastCC.counterFreq + 5 * driftFreq) / 8; } - /* Avoid too large discrepancy from nominal frequency */ + /* + * Avoid too large discrepancy from nominal frequency + */ if (estFreq > 1003*timeInfo.nominalFreq.QuadPart/1000) { estFreq = 1003*timeInfo.nominalFreq.QuadPart/1000; - vt0 = curFileTime.QuadPart; + vt0 = curFileTime; } else if (estFreq < 997*timeInfo.nominalFreq.QuadPart/1000) { estFreq = 997*timeInfo.nominalFreq.QuadPart/1000; - vt0 = curFileTime.QuadPart; - } else if (vt0 != curFileTime.QuadPart) { + vt0 = curFileTime; + } + + /* If possible backwards time-drifts */ + if (estFreq > timeInfo.lastCC.counterFreq) { /* + * Calculate the time using new calibration values (and compare with old), + * to avoid possible backwards drifts (adjust current base time). + * This should affect at least next 10 ticks. + */ + vt1 = curPerfCounter.QuadPart + 10; + /* * Be sure the clock ticks never backwards (avoid it by negative drifting) * just compare native time (in 100-ns) before and hereafter using - * new calibrated values) and do a small adjustment (short time freeze) + * previous/new calibrated values) and do a small adjustment */ - LARGE_INTEGER newPerfCounter; - Tcl_WideInt nt0, nt1; - - QueryPerformanceCounter(&newPerfCounter); - nt0 = NativeCalc100NsTicks(timeInfo.fileTimeLastCall.QuadPart, - timeInfo.perfCounterLastCall.QuadPart, timeInfo.curCounterFreq.QuadPart, - newPerfCounter.QuadPart); - nt1 = NativeCalc100NsTicks(vt0, + nt0 = NativeCalc100NsTicks(timeInfo.lastCC.fileTime, + timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq, + vt1); + nt1 = NativeCalc100NsTicks(curFileTime, curPerfCounter.QuadPart, estFreq, - newPerfCounter.QuadPart); - if (nt0 > nt1) { /* drifted backwards, try to compensate with new base */ - /* first adjust with a micro jump (short frozen time is acceptable) */ - vt0 += nt0 - nt1; - /* if drift unavoidable (e. g. we had a time switch), then reset it */ - vt1 = vt0 - curFileTime.QuadPart; - if (vt1 > 10000000 || vt1 < -10000000) { - /* larger jump resp. shift relative new file-time */ - vt0 = curFileTime.QuadPart; - } + vt1); + vt1 = (nt0 - nt1); /* old time - new time */ + if (vt1 > 0 && vt1 < 10000000) { + /* base time should jump forwards (the same virtual time using current values) */ + vt0 += vt1; } } - /* In lock commit new values to timeInfo (hold lock as short as possible) */ - EnterCriticalSection(&timeInfo.cs); - /* grow calibration interval up to 10 seconds (if still precise enough) */ if (tdiff < -100000 || tdiff > 100000) { - /* too long drift - reset calibration interval to 1000 second */ - timeInfo.calibrationInterv = 1; - } else if (timeInfo.calibrationInterv < 10) { - timeInfo.calibrationInterv++; + /* too long drift - reset calibration interval to 1 second */ + calibrationInterv = 10000000; + } else if (calibrationInterv < 10*10000000) { + calibrationInterv += 10000000; } - timeInfo.fileTimeLastCall.QuadPart = vt0; - timeInfo.curCounterFreq.QuadPart = estFreq; - timeInfo.perfCounterLastCall.QuadPart = curPerfCounter.QuadPart; + /* In lock commit new values to timeInfo (hold lock as short as possible) */ + EnterCriticalSection(&timeInfo.cs); + + timeInfo.lastCC.perfCounter = curPerfCounter.QuadPart; + timeInfo.lastCC.fileTime = vt0; + timeInfo.lastCC.counterFreq = estFreq; + + timeInfo.calibNextTime = curFileTime + calibrationInterv; LeaveCriticalSection(&timeInfo.cs); +#if 1 + printf("-------------calibration adj -- nt1:%I64d - nt0:%I64d: adj: %I64d\n", nt1, nt0, vt1); + printf("-------------calibration end, tdiff %I64d, jump -- vt:%I64d - st:%I64d: %I64d, adj: %I64d\n", tdiff, + vt0, curFileTime, (vt0 - curFileTime), vt1); + printf("-------------calibration end , new-struct: %I64d, %I64d, %I64d\n", timeInfo.lastCC.fileTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq); +#endif } /* -- cgit v0.12 From f1d006af946ed800038bc2babde5d70ce347faab Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:29:07 +0000 Subject: interim commit: try to fix time-drift backwards (calibration? something else?)... --- generic/tclInt.h | 3 ++ generic/tclTimer.c | 2 +- win/tclWinTime.c | 120 +++++++++++++++++++++++++++++++++-------------------- 3 files changed, 80 insertions(+), 45 deletions(-) diff --git a/generic/tclInt.h b/generic/tclInt.h index fad78f2..7576a97 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -4092,6 +4092,9 @@ TclInlLimitExceeded( { return (((Interp *)interp)->limit.exceeded != 0); } +#ifdef Tcl_LimitExceeded +# undef Tcl_LimitExceeded +#endif #define Tcl_LimitExceeded(interp) TclInlLimitExceeded(interp) #define TclLimitReady(limit) \ diff --git a/generic/tclTimer.c b/generic/tclTimer.c index d9e25de..a7fd50b 100644 --- a/generic/tclTimer.c +++ b/generic/tclTimer.c @@ -1427,7 +1427,7 @@ AfterDelay( || TCL_TIME_BEFORE(endTime, iPtr->limit.time)) { diff = TCL_TIME_DIFF_MS(endTime, now); if (TCL_TIME_DIFF_US(endTime, now) > 500 || TCL_TIME_DIFF_US(endTime, now) < -500) { - printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!drift!!!! %5.3f, diff: %d -- %d.%06d - %d.%06d (%d usec)\n", ms, (int)diff, endTime.sec, endTime.usec, now.sec, now.usec, TCL_TIME_DIFF_US(endTime, now)); + //printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!drift!!!! %5.3f, diff: %d -- %d.%06d - %d.%06d (%d usec)\n", ms, (int)diff, endTime.sec, endTime.usec, now.sec, now.usec, TCL_TIME_DIFF_US(endTime, now)); } #ifndef TCL_WIDE_INT_IS_LONG if (diff > LONG_MAX) { diff --git a/win/tclWinTime.c b/win/tclWinTime.c index 8576656..6709335 100644 --- a/win/tclWinTime.c +++ b/win/tclWinTime.c @@ -163,6 +163,9 @@ ClientData tclTimeClientData = NULL; * Calculate the current system time in 100-ns ticks since posix epoch, * for current performance counter (curCounter), using given calibrated values. * + * vt = lastCC.fileTime + + * 10000000 * (curPerfCounter - lastCC.perfCounter) / lastCC.counterFreq + * * Results: * Returns the wide integer with number of microseconds from the epoch. * @@ -616,20 +619,32 @@ NativeGetMicroseconds(void) unsigned int regs[4]; GetSystemInfo(&systemInfo); + if (TclWinCPUID(1, regs) == TCL_OK) { + printf("********* system pen: %d, hyperthread: %d, cpu-count: %d\n, cpu-num: %d\n", + (regs[0]&0x00000F00) == 0x00000F00 /* Pentium 4 */ + ,((regs[0] & 0x0FF00000) /* Extended family (bits 20-27) */ + && (regs[3] & 0x10000000)), + ((regs[1]&0x00FF0000) >> 16)/* CPU count */, + systemInfo.dwNumberOfProcessors + );} + if (TclWinCPUID(0, regs) == TCL_OK && regs[1] == 0x756e6547 /* "Genu" */ && regs[3] == 0x49656e69 /* "ineI" */ && regs[2] == 0x6c65746e /* "ntel" */ && TclWinCPUID(1, regs) == TCL_OK - && ((regs[0]&0x00000F00) == 0x00000F00 /* Pentium 4 */ - || ((regs[0] & 0x00F00000) /* Extended family */ - && (regs[3] & 0x10000000))) /* Hyperthread */ - && (((regs[1]&0x00FF0000) >> 16)/* CPU count */ - == systemInfo.dwNumberOfProcessors)) { + && (( ((regs[0]&0x00000F00) == 0xF00) /* Pentium 4 */ + || ((regs[0]&0x00000F00) == 0x600) ) /* or compatible (VM) */ + && ((regs[0] & 0x0FF00000) /* Extended family (bits 20-27) */ + || (regs[3] & 0x10000000))) /* Hyperthread (bit 28) */ + || (((regs[1]&0x00FF0000) >> 16) >= 2 /* CPU count */ + || systemInfo.dwNumberOfProcessors >= 2)) { timeInfo.perfCounterAvailable = TRUE; } else { timeInfo.perfCounterAvailable = FALSE; } + printf("********* available %d\n", timeInfo.perfCounterAvailable); + } #endif /* above code is Win32 only */ @@ -702,14 +717,15 @@ NativeGetMicroseconds(void) cc.perfCounter, cc.counterFreq, curCounter.QuadPart); /* Be sure the clock ticks never backwards (avoid backwards time-drifts) */ - if ( (lastTime = timeInfo.lastUsedTime) && lastTime > curTime + if ( (lastTime = timeInfo.lastUsedTime) + && lastTime > curTime && lastTime - curTime < 1000000 /* bypass time-switch (drifts only) */ ) { curTime = timeInfo.lastUsedTime; } /* - * If it appears to be more than 1.5 seconds since the last trip + * If it appears to be more than 1 seconds since the last trip * through the calibration loop, the performance counter may have * jumped forward. (See MSDN Knowledge Base article Q274323 for a * description of the hardware problem that makes this test @@ -718,7 +734,7 @@ NativeGetMicroseconds(void) * loop should recover. */ - if (curTime < cc.calibNextTime + 5000000 /* 500 millisec (in 100-ns ticks). */) { + if (curTime < cc.calibNextTime + 10000000 /* 1 sec (in 100-ns ticks). */) { /* save last used time */ timeInfo.lastUsedTime = curTime; return curTime / 10; @@ -731,7 +747,8 @@ NativeGetMicroseconds(void) */ curTime = GetSystemTimeAsVirtual(); /* in 100-ns ticks */ /* Be sure the clock ticks never backwards (avoid backwards time-drifts) */ - if ( (lastTime = timeInfo.lastUsedTime) && lastTime > curTime + if ( (lastTime = timeInfo.lastUsedTime) + && lastTime > curTime && lastTime - curTime < 1000000 /* bypass time-switch (drifts only) */ ) { curTime = timeInfo.lastUsedTime; @@ -1246,31 +1263,43 @@ UpdateTimeEachSecond(void) Tcl_WideInt curFileTime; /* File time at the time this callback was * scheduled. */ Tcl_WideInt estFreq; /* Estimated perf counter frequency. */ + int driftBack; /* Sign the virtual time may drift backwards */ Tcl_WideInt vt0; /* Tcl time right now. */ - Tcl_WideInt vt1, nt0, nt1; /* Interim virtual time used during adjustments */ + Tcl_WideInt vt1; /* Interim virtual time used during adjustments */ Tcl_WideInt tdiff; /* Difference between system clock and Tcl * time. */ - Tcl_WideInt driftFreq; /* Frequency needed to drift virtual time into - * step over 1 second. */ + printf("-------------calibration start, prev-struct: %I64d, %I64d, %I64d\n", timeInfo.lastCC.fileTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq); /* - * Sample performance counter and system time (from posix epoch). + * Sample system time (from posix epoch) and performance counter. */ - printf("-------------calibration start, prev-struct: %I64d, %I64d, %I64d\n", timeInfo.lastCC.fileTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq); curFileTime = GetSystemTimeAsVirtual(); + QueryPerformanceCounter(&curPerfCounter); + + /* + * Current virtual time: + * vt0 = lastCC.fileTime + + * 10000000 * (curPerfCounter - lastCC.perfCounter) / lastCC.counterFreq + */ + vt0 = NativeCalc100NsTicks(timeInfo.lastCC.fileTime, + timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq, + curPerfCounter.QuadPart); + + tdiff = vt0 - curFileTime; /* discrepancy between virtual and real-time */ /* * If calibration still not needed (check for possible time-switch). Note, that * NativeGetMicroseconds checks calibNextTime also, be sure it does not overflow. + * Calibrate immediately if we've too large discrepancy to the real-time (15.6 ms). */ - if ( curFileTime < timeInfo.calibNextTime - 10000000 /* 1 sec (in 100-ns ticks). */ - && timeInfo.calibNextTime - curFileTime < 10 * 10000000 /* max. 10 seconds in-between */ + if ( curFileTime < timeInfo.calibNextTime - (10000000/2) /* 0.5 sec (in 100-ns ticks). */ + && timeInfo.calibNextTime - curFileTime < 10 * 10000000 /* max. 10 seconds in-between (time-switch?) */ + && tdiff > -156000 && tdiff < 156000 ) { /* again in next one second */ // printf("-------------calibration end, not needed. ------ cur:%I64d - next:%I64d (d: %I64d) ------\n", curFileTime, timeInfo.calibNextTime, timeInfo.calibNextTime - curFileTime); return; } - QueryPerformanceCounter(&curPerfCounter); /* * We devide by timeInfo.lastCC.counterFreq in several places. That @@ -1299,16 +1328,19 @@ UpdateTimeEachSecond(void) * Store the current sample into the circular buffer of samples, and * estimate the performance counter frequency. */ +#if 0 + estFreq = AccumulateSample(curPerfCounter.QuadPart, curFileTime); +#else + estFreq = 10000000 * (curPerfCounter.QuadPart - timeInfo.lastCC.perfCounter) / (vt0 - timeInfo.lastCC.fileTime); + printf("------**-----calibration estimated, tdiff: %I64d, cntrDiff:%I64d\n", tdiff, (curPerfCounter.QuadPart - timeInfo.lastCC.perfCounter)); + printf("------**-----calibration estimated Frequency %I64d, %I64d, %I64d, diff: %I64d\n", curFileTime, curPerfCounter.QuadPart, estFreq, estFreq - timeInfo.lastCC.counterFreq); +#endif - estFreq = AccumulateSample(curPerfCounter.QuadPart, curFileTime); + driftBack = 0; /* * We want to adjust things so that time appears to be continuous. - * Virtual file time, right now, is - * - * vt0 = 10000000 * (curPerfCounter - lastCC.perfCounter) - * / lastCC.counterFreq - * + lastCC.fileTime + * Virtual file time, right now, is vt0. * * Ideally, we would like to drift the clock into place over a period of 2 * sec, so that virtual time 2 sec from now will be @@ -1317,31 +1349,28 @@ UpdateTimeEachSecond(void) * * The frequency that we need to use to drift the counter back into place * is estFreq * 20000000 / (vt1 - vt0) - */ - - vt0 = NativeCalc100NsTicks(timeInfo.lastCC.fileTime, - timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq, - curPerfCounter.QuadPart); - /* + * * If we've gotten more than a second away from system time, then drifting * the clock is going to be pretty hopeless. Just let it jump. Otherwise, * compute the drift frequency and fill in everything. */ - tdiff = vt0 - curFileTime; if (tdiff > 10000000 || tdiff < -10000000) { /* More as a second difference, so could be a time-switch /* jump to current system time, use curent estimated frequency */ vt0 = curFileTime; timeInfo.lastUsedTime = 0; /* reset last used time */ } else { + Tcl_WideInt driftFreq; /* Frequency needed to drift virtual time into + * step over 1 second. */ + /* calculate new frequency and estimate drift to the next second */ vt1 = 20000000 + curFileTime; driftFreq = (estFreq * 20000000 / (vt1 - vt0)); /* * Avoid too large drifts (only half of the current difference), * that allows also be more accurate (aspire to the smallest tdiff), - * so then we can prolong calibration interval by tdiff < 100000 + * so then we can prolong calibration interval in such cases. */ driftFreq = timeInfo.lastCC.counterFreq + (driftFreq - timeInfo.lastCC.counterFreq) / 2; @@ -1354,18 +1383,20 @@ UpdateTimeEachSecond(void) } /* - * Avoid too large discrepancy from nominal frequency + * Avoid too large discrepancy from nominal frequency (0.6 %) */ - if (estFreq > 1003*timeInfo.nominalFreq.QuadPart/1000) { - estFreq = 1003*timeInfo.nominalFreq.QuadPart/1000; - vt0 = curFileTime; - } else if (estFreq < 997*timeInfo.nominalFreq.QuadPart/1000) { - estFreq = 997*timeInfo.nominalFreq.QuadPart/1000; - vt0 = curFileTime; + if ( estFreq > (vt1 = (1000+3)*timeInfo.nominalFreq.QuadPart/1000) + || estFreq < (vt1 = (1000-3)*timeInfo.nominalFreq.QuadPart/1000) + ) { + estFreq = vt1; + driftBack = vt0 > curFileTime; + vt0 = curFileTime; /* too large - just reset */ } - /* If possible backwards time-drifts */ - if (estFreq > timeInfo.lastCC.counterFreq) { + /* If possible backwards time-drifts (larger divider now) */ + if (driftBack || estFreq > timeInfo.lastCC.counterFreq) { + Tcl_WideInt nt0, nt1; + /* * Calculate the time using new calibration values (and compare with old), * to avoid possible backwards drifts (adjust current base time). @@ -1384,14 +1415,15 @@ UpdateTimeEachSecond(void) curPerfCounter.QuadPart, estFreq, vt1); vt1 = (nt0 - nt1); /* old time - new time */ - if (vt1 > 0 && vt1 < 10000000) { + if (vt1 > 0 && vt1 < 10000000 /* bypass time-switch */) { /* base time should jump forwards (the same virtual time using current values) */ vt0 += vt1; + tdiff += vt1; } } - /* grow calibration interval up to 10 seconds (if still precise enough) */ - if (tdiff < -100000 || tdiff > 100000) { + /* if still precise enough, grow calibration interval up to 10 seconds */ + if (tdiff < -156000 || tdiff > 156000 /* 15.6-ms */) { /* too long drift - reset calibration interval to 1 second */ calibrationInterv = 10000000; } else if (calibrationInterv < 10*10000000) { @@ -1409,7 +1441,7 @@ UpdateTimeEachSecond(void) LeaveCriticalSection(&timeInfo.cs); #if 1 - printf("-------------calibration adj -- nt1:%I64d - nt0:%I64d: adj: %I64d\n", nt1, nt0, vt1); + //printf("-------------calibration adj -- nt1:%I64d - nt0:%I64d: adj: %I64d\n", nt1, nt0, vt1); printf("-------------calibration end, tdiff %I64d, jump -- vt:%I64d - st:%I64d: %I64d, adj: %I64d\n", tdiff, vt0, curFileTime, (vt0 - curFileTime), vt1); printf("-------------calibration end , new-struct: %I64d, %I64d, %I64d\n", timeInfo.lastCC.fileTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq); -- cgit v0.12 From 68152b9280dd29d5a79dcbd75bd0b1e54998f141 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:29:12 +0000 Subject: interim commit: time-drift backwards fix... --- win/tclWinTime.c | 105 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 78 insertions(+), 27 deletions(-) diff --git a/win/tclWinTime.c b/win/tclWinTime.c index 6709335..a30a952 100644 --- a/win/tclWinTime.c +++ b/win/tclWinTime.c @@ -73,6 +73,7 @@ typedef struct TimeInfo { struct { ULONGLONG fileTime; + ULONGLONG virtTime; volatile /* used also to compare calibration epoch */ LONGLONG perfCounter; LONGLONG counterFreq; @@ -109,6 +110,7 @@ static TimeInfo timeInfo = { #endif { (ULONGLONG) 0, + (ULONGLONG) 0, (LONGLONG) 0, (LONGLONG) 0 }, @@ -163,7 +165,7 @@ ClientData tclTimeClientData = NULL; * Calculate the current system time in 100-ns ticks since posix epoch, * for current performance counter (curCounter), using given calibrated values. * - * vt = lastCC.fileTime + + * vt = lastCC.virtTime + * 10000000 * (curPerfCounter - lastCC.perfCounter) / lastCC.counterFreq * * Results: @@ -177,12 +179,12 @@ ClientData tclTimeClientData = NULL; static inline Tcl_WideInt NativeCalc100NsTicks( - ULONGLONG ccFileTime, + ULONGLONG ccVirtTime, LONGLONG ccPerfCounter, LONGLONG ccCounterFreq, LONGLONG curCounter ) { - return ccFileTime + + return ccVirtTime + ((curCounter - ccPerfCounter) * 10000000 / ccCounterFreq); } @@ -415,15 +417,15 @@ TclpGetMicroseconds(void) NativeCalc100NsTicks(fileTimeLastCall, perfCounterLastCall, curCounterFreq, prevPerfCounter.QuadPart)); - printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!time-backwards!!!! new-struct: %I64d, %I64d, %I64d, %I64d == %I64d \n", timeInfo.lastCC.fileTime, timeInfo.lastCC.perfCounter, newPerfCounter.QuadPart, timeInfo.lastCC.counterFreq, - NativeCalc100NsTicks(timeInfo.lastCC.fileTime, + printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!time-backwards!!!! new-struct: %I64d, %I64d, %I64d, %I64d == %I64d \n", timeInfo.lastCC.virtTime, timeInfo.lastCC.perfCounter, newPerfCounter.QuadPart, timeInfo.lastCC.counterFreq, + NativeCalc100NsTicks(timeInfo.lastCC.virtTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq, newPerfCounter.QuadPart)); printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!time-backwards!!!! prev: %I64d - now: %I64d (%I64d usec)\n", prevUS, usecSincePosixEpoch, usecSincePosixEpoch - prevUS); Tcl_Panic("Time running backwards!!!"); } prevUS = usecSincePosixEpoch; - fileTimeLastCall = timeInfo.lastCC.fileTime; + fileTimeLastCall = timeInfo.lastCC.virtTime; perfCounterLastCall = timeInfo.lastCC.perfCounter; curCounterFreq = timeInfo.lastCC.counterFreq; prevPerfCounter.QuadPart = newPerfCounter.QuadPart; @@ -682,7 +684,7 @@ NativeGetMicroseconds(void) if (timeInfo.perfCounterAvailable && timeInfo.lastCC.counterFreq!=0) { static struct { - ULONGLONG fileTime; + ULONGLONG virtTime; volatile /* don't optimize */ LONGLONG perfCounter; LONGLONG counterFreq; @@ -699,7 +701,7 @@ NativeGetMicroseconds(void) EnterCriticalSection(&timeInfo.cs); if (cc.perfCounter != timeInfo.lastCC.perfCounter) { cc.perfCounter = timeInfo.lastCC.perfCounter; - cc.fileTime = timeInfo.lastCC.fileTime; + cc.virtTime = timeInfo.lastCC.virtTime; cc.counterFreq = timeInfo.lastCC.counterFreq; cc.calibNextTime = timeInfo.calibNextTime; } @@ -713,7 +715,7 @@ NativeGetMicroseconds(void) QueryPerformanceCounter(&curCounter); /* Calibrated file-time is saved from posix in 100-ns ticks */ - curTime = NativeCalc100NsTicks(cc.fileTime, + curTime = NativeCalc100NsTicks(cc.virtTime, cc.perfCounter, cc.counterFreq, curCounter.QuadPart); /* Be sure the clock ticks never backwards (avoid backwards time-drifts) */ @@ -1190,7 +1192,7 @@ CalibrationThread( QueryPerformanceCounter(&curPerfCounter); timeInfo.lastCC.perfCounter = curPerfCounter.QuadPart; timeInfo.lastCC.counterFreq = timeInfo.nominalFreq.QuadPart; - timeInfo.lastCC.fileTime = GetSystemTimeAsVirtual(); + timeInfo.lastCC.fileTime = timeInfo.lastCC.virtTime = GetSystemTimeAsVirtual(); ResetCounterSamples(timeInfo.lastCC.fileTime, timeInfo.lastCC.perfCounter, @@ -1258,6 +1260,9 @@ UpdateTimeEachSecond(void) LARGE_INTEGER curPerfCounter; /* Current value returned from * QueryPerformanceCounter. */ + static Tcl_WideInt lastDiff = 0; + /* Last difference between system clock and Tcl + * time. */ static int calibrationInterv = 10000000; /* Calibration interval in 100-ns ticks (starts from 1s) */ Tcl_WideInt curFileTime; /* File time at the time this callback was @@ -1282,25 +1287,31 @@ UpdateTimeEachSecond(void) * vt0 = lastCC.fileTime + * 10000000 * (curPerfCounter - lastCC.perfCounter) / lastCC.counterFreq */ - vt0 = NativeCalc100NsTicks(timeInfo.lastCC.fileTime, + vt0 = NativeCalc100NsTicks((timeInfo.lastCC.fileTime/2 + timeInfo.lastCC.virtTime/2), timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq, curPerfCounter.QuadPart); tdiff = vt0 - curFileTime; /* discrepancy between virtual and real-time */ + if (tdiff >= 10000000 || tdiff <= -10000000) { + printf("---!!!!!!!---calibration ERR, tdiff %I64d\n", tdiff); + } /* * If calibration still not needed (check for possible time-switch). Note, that * NativeGetMicroseconds checks calibNextTime also, be sure it does not overflow. * Calibrate immediately if we've too large discrepancy to the real-time (15.6 ms). */ +#if 0 if ( curFileTime < timeInfo.calibNextTime - (10000000/2) /* 0.5 sec (in 100-ns ticks). */ && timeInfo.calibNextTime - curFileTime < 10 * 10000000 /* max. 10 seconds in-between (time-switch?) */ - && tdiff > -156000 && tdiff < 156000 + && tdiff > -156000 && tdiff < 156000 /* small discrepancy */ + && (tdiff >= 0 && tdiff <= lastDiff / 3 * 2 || tdiff < 0 && tdiff >= lastDiff / 3 * 2) /* more precise now */ ) { /* again in next one second */ -// printf("-------------calibration end, not needed. ------ cur:%I64d - next:%I64d (d: %I64d) ------\n", curFileTime, timeInfo.calibNextTime, timeInfo.calibNextTime - curFileTime); + printf("-------------calibration end, tdiff %I64d, *** not needed. (next in: %I64d) ------\n", tdiff, curFileTime, timeInfo.calibNextTime, timeInfo.calibNextTime - curFileTime); + lastDiff = tdiff; return; } - +#endif /* * We devide by timeInfo.lastCC.counterFreq in several places. That * value should always be positive on a correctly functioning system. But @@ -1331,9 +1342,26 @@ UpdateTimeEachSecond(void) #if 0 estFreq = AccumulateSample(curPerfCounter.QuadPart, curFileTime); #else - estFreq = 10000000 * (curPerfCounter.QuadPart - timeInfo.lastCC.perfCounter) / (vt0 - timeInfo.lastCC.fileTime); - printf("------**-----calibration estimated, tdiff: %I64d, cntrDiff:%I64d\n", tdiff, (curPerfCounter.QuadPart - timeInfo.lastCC.perfCounter)); - printf("------**-----calibration estimated Frequency %I64d, %I64d, %I64d, diff: %I64d\n", curFileTime, curPerfCounter.QuadPart, estFreq, estFreq - timeInfo.lastCC.counterFreq); + vt1 = curFileTime - timeInfo.lastCC.fileTime; + if (vt1) { + estFreq = 10000000 * (curPerfCounter.QuadPart - timeInfo.lastCC.perfCounter) / vt1; +#if 1 + + /* + * Minimize influence of estFreq if tdiff falls (in relation to last difference), + * with dual falling speed. This indicates better choice of lastCC.counterFreq. + */ + if (tdiff > 0 && tdiff < lastDiff / 2 || tdiff < 0 && tdiff > lastDiff / 2) { + printf("-----***-----calibration minimize %I64d, %I64d\n", estFreq, lastDiff); + estFreq = (estFreq + timeInfo.lastCC.counterFreq * 2) / 3; + printf("-----***-----calibration minimize %I64d, %I64d\n", estFreq, tdiff); + } +#endif + } else { + estFreq = timeInfo.lastCC.counterFreq; + } + //printf("------**-----calibration estimated, tdiff: %I64d, ** %s ** cntrDiff:%I64d\n", tdiff, (estFreq > timeInfo.lastCC.counterFreq) ? "^^^" : "vvv", (curPerfCounter.QuadPart - timeInfo.lastCC.perfCounter)); + //printf("------**-----calibration estimated Frequency %I64d, %I64d, %I64d, diff: %I64d\n", curFileTime, curPerfCounter.QuadPart, estFreq, estFreq - timeInfo.lastCC.counterFreq); #endif driftBack = 0; @@ -1360,6 +1388,7 @@ UpdateTimeEachSecond(void) /* jump to current system time, use curent estimated frequency */ vt0 = curFileTime; timeInfo.lastUsedTime = 0; /* reset last used time */ + estFreq = timeInfo.nominalFreq.QuadPart; } else { Tcl_WideInt driftFreq; /* Frequency needed to drift virtual time into * step over 1 second. */ @@ -1377,16 +1406,25 @@ UpdateTimeEachSecond(void) /* * Average between estimated, 2 current and 5 drifted frequencies, - * (do the soft drifting as possible) + * (do the soft drifting as possible). + * Minimize influence if tdiff falls (in relation to last difference) */ - estFreq = (estFreq + 2 * timeInfo.lastCC.counterFreq + 5 * driftFreq) / 8; +#if 0 + if (tdiff > 0 && tdiff < lastDiff / 2 || tdiff < 0 && tdiff > lastDiff / 2) { + estFreq = (1 * estFreq + 2 * timeInfo.lastCC.counterFreq + 5 * driftFreq) / 8; + } else { + estFreq = (3 * estFreq + 3 * timeInfo.lastCC.counterFreq + 2 * driftFreq) / 8; + } +#else + estFreq = (estFreq + timeInfo.lastCC.counterFreq + driftFreq) / 3; +#endif } /* - * Avoid too large discrepancy from nominal frequency (0.6 %) + * Avoid too large discrepancy from nominal frequency (0.5%) */ - if ( estFreq > (vt1 = (1000+3)*timeInfo.nominalFreq.QuadPart/1000) - || estFreq < (vt1 = (1000-3)*timeInfo.nominalFreq.QuadPart/1000) + if ( estFreq > (vt1 = (1000+5)*timeInfo.nominalFreq.QuadPart/1000) + || estFreq < (vt1 = (1000-5)*timeInfo.nominalFreq.QuadPart/1000) ) { estFreq = vt1; driftBack = vt0 > curFileTime; @@ -1394,7 +1432,8 @@ UpdateTimeEachSecond(void) } /* If possible backwards time-drifts (larger divider now) */ - if (driftBack || estFreq > timeInfo.lastCC.counterFreq) { + vt1 = 0; + if (1 || driftBack || estFreq > timeInfo.lastCC.counterFreq) { Tcl_WideInt nt0, nt1; /* @@ -1408,10 +1447,10 @@ UpdateTimeEachSecond(void) * just compare native time (in 100-ns) before and hereafter using * previous/new calibrated values) and do a small adjustment */ - nt0 = NativeCalc100NsTicks(timeInfo.lastCC.fileTime, + nt0 = NativeCalc100NsTicks(timeInfo.lastCC.virtTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq, vt1); - nt1 = NativeCalc100NsTicks(curFileTime, + nt1 = NativeCalc100NsTicks(vt0, curPerfCounter.QuadPart, estFreq, vt1); vt1 = (nt0 - nt1); /* old time - new time */ @@ -1419,6 +1458,7 @@ UpdateTimeEachSecond(void) /* base time should jump forwards (the same virtual time using current values) */ vt0 += vt1; tdiff += vt1; + //////////////////////////////////////////estFreq = 10000000 * (vt0 - timeInfo.lastCC.perfCounter) / vt1; } } @@ -1430,11 +1470,14 @@ UpdateTimeEachSecond(void) calibrationInterv += 10000000; } + lastDiff = tdiff; + /* In lock commit new values to timeInfo (hold lock as short as possible) */ EnterCriticalSection(&timeInfo.cs); timeInfo.lastCC.perfCounter = curPerfCounter.QuadPart; - timeInfo.lastCC.fileTime = vt0; + timeInfo.lastCC.fileTime = curFileTime; + timeInfo.lastCC.virtTime = vt0; timeInfo.lastCC.counterFreq = estFreq; timeInfo.calibNextTime = curFileTime + calibrationInterv; @@ -1444,7 +1487,7 @@ UpdateTimeEachSecond(void) //printf("-------------calibration adj -- nt1:%I64d - nt0:%I64d: adj: %I64d\n", nt1, nt0, vt1); printf("-------------calibration end, tdiff %I64d, jump -- vt:%I64d - st:%I64d: %I64d, adj: %I64d\n", tdiff, vt0, curFileTime, (vt0 - curFileTime), vt1); - printf("-------------calibration end , new-struct: %I64d, %I64d, %I64d\n", timeInfo.lastCC.fileTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq); + printf("-------------calibration end , new-struct: %I64d, %I64d, %I64d\n", timeInfo.lastCC.virtTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq); #endif } @@ -1545,6 +1588,14 @@ AccumulateSample( || FTdiff < 9000000 || FTdiff > 11000000) { ResetCounterSamples(fileTime, perfCounter, timeInfo.nominalFreq.QuadPart); +#if 0 + FTdiff = fileTime - timeInfo.lastCC.fileTime; + if (FTdiff) { + estFreq = 10000000 * (perfCounter - timeInfo.lastCC.perfCounter) / FTdiff; + printf("------**-----calibration estimated Frequency %I64d, %I64d, %I64d, diff: %I64d\n", fileTime, perfCounter, estFreq, estFreq - timeInfo.lastCC.counterFreq); + } + return (timeInfo.nominalFreq.QuadPart + timeInfo.lastCC.counterFreq) / 2; +#endif return timeInfo.nominalFreq.QuadPart; } else { /* -- cgit v0.12 From 4881937a78ead2f68f732520535e20097b33a4fc Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:29:16 +0000 Subject: interim commit: time-drift backwards fix... (try to resolve using frequency factor) --- win/tclWinTime.c | 308 ++++++++++++++++--------------------------------------- 1 file changed, 86 insertions(+), 222 deletions(-) diff --git a/win/tclWinTime.c b/win/tclWinTime.c index a30a952..c4206fe 100644 --- a/win/tclWinTime.c +++ b/win/tclWinTime.c @@ -17,12 +17,6 @@ #define SECSPER4YEAR (SECSPERYEAR * 4L + SECSPERDAY) /* - * Number of samples over which to estimate the performance counter. - */ - -#define SAMPLES 64 - -/* * The following arrays contain the day of year for the last day of each * month, where index 1 is January. */ @@ -75,22 +69,15 @@ typedef struct TimeInfo { ULONGLONG fileTime; ULONGLONG virtTime; volatile /* used also to compare calibration epoch */ - LONGLONG perfCounter; + LONGLONG perfCounter; LONGLONG counterFreq; } lastCC; /* Last data updated in calibration cycle */ + int freqFactor; /* Frequency factor (1000 - KHz, 1 - Hz) */ + Tcl_WideInt lastUsedTime; /* Last known (caller) virtual time in 100-ns * (used to avoid drifts after calibrate) */ - /* - * Data used in developing the estimate of performance counter frequency - */ - - Tcl_WideUInt fileTimeSample[SAMPLES]; - /* Last 64 samples of system time. */ - Tcl_WideInt perfCounterSample[SAMPLES]; - /* Last 64 samples of performance counter. */ - int sampleNo; /* Current sample number. */ } TimeInfo; static TimeInfo timeInfo = { @@ -114,10 +101,8 @@ static TimeInfo timeInfo = { (LONGLONG) 0, (LONGLONG) 0 }, - (Tcl_WideInt) 0, - { (Tcl_WideUInt) 0 }, - { (Tcl_WideInt) 0 }, - 0 + 1000, /* KHz */ + (Tcl_WideInt) 0 }; /* @@ -139,10 +124,6 @@ static struct tm * ComputeGMT(const time_t *tp); static void StopCalibration(ClientData clientData); static DWORD WINAPI CalibrationThread(LPVOID arg); static void UpdateTimeEachSecond(void); -static void ResetCounterSamples(Tcl_WideUInt fileTime, - Tcl_WideInt perfCounter, Tcl_WideInt perfFreq); -static Tcl_WideInt AccumulateSample(Tcl_WideInt perfCounter, - Tcl_WideUInt fileTime); static void NativeScaleTime(Tcl_Time* timebuf, ClientData clientData); static Tcl_WideInt NativeGetMicroseconds(void); @@ -658,6 +639,12 @@ NativeGetMicroseconds(void) if (timeInfo.perfCounterAvailable) { DWORD id; + /* Some systems having frequency in Hz, so save the factor here */ + if (timeInfo.nominalFreq.QuadPart >= 1000000000) { + /* assume that frequency in Hz, factor used only for tolerance */ + timeInfo.freqFactor = 1; + } + InitializeCriticalSection(&timeInfo.cs); timeInfo.readyEvent = CreateEvent(NULL, FALSE, FALSE, NULL); timeInfo.exitEvent = CreateEvent(NULL, FALSE, FALSE, NULL); @@ -1194,10 +1181,6 @@ CalibrationThread( timeInfo.lastCC.counterFreq = timeInfo.nominalFreq.QuadPart; timeInfo.lastCC.fileTime = timeInfo.lastCC.virtTime = GetSystemTimeAsVirtual(); - ResetCounterSamples(timeInfo.lastCC.fileTime, - timeInfo.lastCC.perfCounter, - timeInfo.lastCC.counterFreq); - /* * Calibrate first time and wake up the calling thread. * When it wakes up, it will release the initialization lock. @@ -1260,9 +1243,6 @@ UpdateTimeEachSecond(void) LARGE_INTEGER curPerfCounter; /* Current value returned from * QueryPerformanceCounter. */ - static Tcl_WideInt lastDiff = 0; - /* Last difference between system clock and Tcl - * time. */ static int calibrationInterv = 10000000; /* Calibration interval in 100-ns ticks (starts from 1s) */ Tcl_WideInt curFileTime; /* File time at the time this callback was @@ -1271,27 +1251,30 @@ UpdateTimeEachSecond(void) int driftBack; /* Sign the virtual time may drift backwards */ Tcl_WideInt vt0; /* Tcl time right now. */ Tcl_WideInt vt1; /* Interim virtual time used during adjustments */ - Tcl_WideInt tdiff; /* Difference between system clock and Tcl - * time. */ + Tcl_WideInt tdiff, /* Difference between system clock and Tcl time. */ + lastDiff; /* Difference of last calibration. */ - printf("-------------calibration start, prev-struct: %I64d, %I64d, %I64d\n", timeInfo.lastCC.fileTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq); /* * Sample system time (from posix epoch) and performance counter. */ curFileTime = GetSystemTimeAsVirtual(); QueryPerformanceCounter(&curPerfCounter); + printf("-------------calibration start, prev-struct: %I64d, %I64d, %I64d, pc-diff: %I64d\n", timeInfo.lastCC.fileTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq, curPerfCounter.QuadPart - timeInfo.lastCC.perfCounter); /* - * Current virtual time: - * vt0 = lastCC.fileTime + + * Current virtual time (using average between fileTime and virtTime): + * vt0 = (lastCC.fileTime + lastCC.virtTime) / 2 + * 10000000 * (curPerfCounter - lastCC.perfCounter) / lastCC.counterFreq */ - vt0 = NativeCalc100NsTicks((timeInfo.lastCC.fileTime/2 + timeInfo.lastCC.virtTime/2), + vt0 = NativeCalc100NsTicks( + (timeInfo.lastCC.fileTime/2 + timeInfo.lastCC.virtTime/2), timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq, curPerfCounter.QuadPart); - tdiff = vt0 - curFileTime; /* discrepancy between virtual and real-time */ + /* Differences between virtual and real-time */ + tdiff = vt0 - curFileTime; + lastDiff = timeInfo.lastCC.virtTime - timeInfo.lastCC.fileTime; if (tdiff >= 10000000 || tdiff <= -10000000) { printf("---!!!!!!!---calibration ERR, tdiff %I64d\n", tdiff); } @@ -1300,14 +1283,13 @@ UpdateTimeEachSecond(void) * NativeGetMicroseconds checks calibNextTime also, be sure it does not overflow. * Calibrate immediately if we've too large discrepancy to the real-time (15.6 ms). */ -#if 0 +#if 1 if ( curFileTime < timeInfo.calibNextTime - (10000000/2) /* 0.5 sec (in 100-ns ticks). */ && timeInfo.calibNextTime - curFileTime < 10 * 10000000 /* max. 10 seconds in-between (time-switch?) */ - && tdiff > -156000 && tdiff < 156000 /* small discrepancy */ - && (tdiff >= 0 && tdiff <= lastDiff / 3 * 2 || tdiff < 0 && tdiff >= lastDiff / 3 * 2) /* more precise now */ + && tdiff > -10000 && tdiff < 10000 /* very small discrepancy (1ms) */ ) { /* again in next one second */ - printf("-------------calibration end, tdiff %I64d, *** not needed. (next in: %I64d) ------\n", tdiff, curFileTime, timeInfo.calibNextTime, timeInfo.calibNextTime - curFileTime); + //printf("-------------calibration end, tdiff %I64d, *** not needed. (next in: %I64d) ------\n", tdiff, curFileTime, timeInfo.calibNextTime, timeInfo.calibNextTime - curFileTime); lastDiff = tdiff; return; } @@ -1330,39 +1312,7 @@ UpdateTimeEachSecond(void) * Several things may have gone wrong here that have to be checked for. * (1) The performance counter may have jumped. * (2) The system clock may have been reset. - * - * In either case, we'll need to reinitialize the circular buffer with - * samples relative to the current system time and the NOMINAL performance - * frequency (not the actual, because the actual has probably run slow in - * the first case). Our estimated frequency will be the nominal frequency. - * - * Store the current sample into the circular buffer of samples, and - * estimate the performance counter frequency. */ -#if 0 - estFreq = AccumulateSample(curPerfCounter.QuadPart, curFileTime); -#else - vt1 = curFileTime - timeInfo.lastCC.fileTime; - if (vt1) { - estFreq = 10000000 * (curPerfCounter.QuadPart - timeInfo.lastCC.perfCounter) / vt1; -#if 1 - - /* - * Minimize influence of estFreq if tdiff falls (in relation to last difference), - * with dual falling speed. This indicates better choice of lastCC.counterFreq. - */ - if (tdiff > 0 && tdiff < lastDiff / 2 || tdiff < 0 && tdiff > lastDiff / 2) { - printf("-----***-----calibration minimize %I64d, %I64d\n", estFreq, lastDiff); - estFreq = (estFreq + timeInfo.lastCC.counterFreq * 2) / 3; - printf("-----***-----calibration minimize %I64d, %I64d\n", estFreq, tdiff); - } -#endif - } else { - estFreq = timeInfo.lastCC.counterFreq; - } - //printf("------**-----calibration estimated, tdiff: %I64d, ** %s ** cntrDiff:%I64d\n", tdiff, (estFreq > timeInfo.lastCC.counterFreq) ? "^^^" : "vvv", (curPerfCounter.QuadPart - timeInfo.lastCC.perfCounter)); - //printf("------**-----calibration estimated Frequency %I64d, %I64d, %I64d, diff: %I64d\n", curFileTime, curPerfCounter.QuadPart, estFreq, estFreq - timeInfo.lastCC.counterFreq); -#endif driftBack = 0; @@ -1373,10 +1323,10 @@ UpdateTimeEachSecond(void) * Ideally, we would like to drift the clock into place over a period of 2 * sec, so that virtual time 2 sec from now will be * - * vt1 = 20000000 + curFileTime + * vt1 = 10000000 + curFileTime * * The frequency that we need to use to drift the counter back into place - * is estFreq * 20000000 / (vt1 - vt0) + * is estFreq * 10000000 / (vt1 - vt0) * * If we've gotten more than a second away from system time, then drifting * the clock is going to be pretty hopeless. Just let it jump. Otherwise, @@ -1384,18 +1334,47 @@ UpdateTimeEachSecond(void) */ if (tdiff > 10000000 || tdiff < -10000000) { - /* More as a second difference, so could be a time-switch + /* More as a second difference, so could be a time-switch (reset) /* jump to current system time, use curent estimated frequency */ vt0 = curFileTime; timeInfo.lastUsedTime = 0; /* reset last used time */ estFreq = timeInfo.nominalFreq.QuadPart; } else { + Tcl_WideInt driftFreq; /* Frequency needed to drift virtual time into * step over 1 second. */ - /* calculate new frequency and estimate drift to the next second */ - vt1 = 20000000 + curFileTime; - driftFreq = (estFreq * 20000000 / (vt1 - vt0)); + /* + * Estimate current frequency corresponding current time / counter. + */ + + vt1 = curFileTime - timeInfo.lastCC.fileTime; + if (vt1 > 0) { + estFreq = (curPerfCounter.QuadPart - timeInfo.lastCC.perfCounter) * 10000000 / vt1; + + /* + * Minimize influence of estFreq if tdiff falls (in relation to + * last difference), with dual falling speed. This indicates better + * choice of lastCC.counterFreq. + */ + if (tdiff > 0 && tdiff < lastDiff / 2 || tdiff < 0 && tdiff > lastDiff / 2) { + //printf("-----***-----calibration minimize %I64d, %I64d\n", estFreq, lastDiff); + estFreq = (estFreq + timeInfo.lastCC.counterFreq * 2) / 3; + //printf("-----***-----calibration minimize %I64d, %I64d\n", estFreq, tdiff); + } + } else { + estFreq = timeInfo.lastCC.counterFreq; + } + printf("------**-----calibration estimated, tdiff: %I64d, ** %s ** cntrDiff:%I64d\n", tdiff, (estFreq > timeInfo.lastCC.counterFreq) ? "^^^" : "vvv", (curPerfCounter.QuadPart - timeInfo.lastCC.perfCounter)); + printf("------**-----calibration estimated Frequency %I64d, %I64d, %I64d, diff: %I64d, to-nomin: %I64d\n", curFileTime, curPerfCounter.QuadPart, estFreq, estFreq - timeInfo.lastCC.counterFreq, estFreq - timeInfo.nominalFreq.QuadPart); + +#if 1 + /* + * Calculate new frequency and estimate drift to the next second + */ + + vt1 = 10000000 + curFileTime; + driftFreq = estFreq * 10000000 / (vt1 - vt0); /* * Avoid too large drifts (only half of the current difference), * that allows also be more accurate (aspire to the smallest tdiff), @@ -1404,6 +1383,10 @@ UpdateTimeEachSecond(void) driftFreq = timeInfo.lastCC.counterFreq + (driftFreq - timeInfo.lastCC.counterFreq) / 2; + printf("------**-----calibration lastFreq: %I64d\n", timeInfo.lastCC.counterFreq); + printf("------**-----calibration estFreq: %I64d\n", estFreq); + printf("------**-----calibration driftFreq:%I64d\n", driftFreq); + /* * Average between estimated, 2 current and 5 drifted frequencies, * (do the soft drifting as possible). @@ -1418,6 +1401,10 @@ UpdateTimeEachSecond(void) #else estFreq = (estFreq + timeInfo.lastCC.counterFreq + driftFreq) / 3; #endif + +#else + estFreq = (estFreq + timeInfo.lastCC.counterFreq) / 2; +#endif } /* @@ -1426,14 +1413,22 @@ UpdateTimeEachSecond(void) if ( estFreq > (vt1 = (1000+5)*timeInfo.nominalFreq.QuadPart/1000) || estFreq < (vt1 = (1000-5)*timeInfo.nominalFreq.QuadPart/1000) ) { - estFreq = vt1; - driftBack = vt0 > curFileTime; - vt0 = curFileTime; /* too large - just reset */ + /* Some systems having frequency in Hz, so fewer tolerant (1.5%) */ + if ( timeInfo.freqFactor == 1000 /* frequency in KHz */ + || ( estFreq > (vt1 = (1000+15)*timeInfo.nominalFreq.QuadPart/1000) + || estFreq < (vt1 = (1000-15)*timeInfo.nominalFreq.QuadPart/1000) + ) + ) { + estFreq = vt1; + driftBack = vt0 > curFileTime; + vt0 = curFileTime; /* too large - just reset */ + printf("************ too large: %I64d\n", estFreq); + } } /* If possible backwards time-drifts (larger divider now) */ vt1 = 0; - if (1 || driftBack || estFreq > timeInfo.lastCC.counterFreq) { + if (driftBack || estFreq > timeInfo.lastCC.counterFreq) { Tcl_WideInt nt0, nt1; /* @@ -1463,15 +1458,14 @@ UpdateTimeEachSecond(void) } /* if still precise enough, grow calibration interval up to 10 seconds */ - if (tdiff < -156000 || tdiff > 156000 /* 15.6-ms */) { - /* too long drift - reset calibration interval to 1 second */ - calibrationInterv = 10000000; - } else if (calibrationInterv < 10*10000000) { - calibrationInterv += 10000000; + if ( timeInfo.freqFactor == 1000 ) { /* frequency in KHz */ + if (tdiff < -100000 || tdiff > 100000 /* 10-ms */) { + /* too long drift - reset calibration interval to 1 second */ + calibrationInterv = 10000000; + } else if (calibrationInterv < 10*10000000) { + calibrationInterv += 10000000; + } } - - lastDiff = tdiff; - /* In lock commit new values to timeInfo (hold lock as short as possible) */ EnterCriticalSection(&timeInfo.cs); @@ -1494,136 +1488,6 @@ UpdateTimeEachSecond(void) /* *---------------------------------------------------------------------- * - * ResetCounterSamples -- - * - * Fills the sample arrays in 'timeInfo' with dummy values that will - * yield the current performance counter and frequency. - * - * Results: - * None. - * - * Side effects: - * The array of samples is filled in so that it appears that there are - * SAMPLES samples at one-second intervals, separated by precisely the - * given frequency. - * - *---------------------------------------------------------------------- - */ - -static void -ResetCounterSamples( - Tcl_WideUInt fileTime, /* Current file time */ - Tcl_WideInt perfCounter, /* Current performance counter */ - Tcl_WideInt perfFreq) /* Target performance frequency */ -{ - int i; - for (i=SAMPLES-1 ; i>=0 ; --i) { - timeInfo.perfCounterSample[i] = perfCounter; - timeInfo.fileTimeSample[i] = fileTime; - perfCounter -= perfFreq; - fileTime -= 10000000; - } - timeInfo.sampleNo = 0; -} - -/* - *---------------------------------------------------------------------- - * - * AccumulateSample -- - * - * Updates the circular buffer of performance counter and system time - * samples with a new data point. - * - * Results: - * None. - * - * Side effects: - * The new data point replaces the oldest point in the circular buffer, - * and the descriptive statistics are updated to accumulate the new - * point. - * - * Several things may have gone wrong here that have to be checked for. - * (1) The performance counter may have jumped. - * (2) The system clock may have been reset. - * - * In either case, we'll need to reinitialize the circular buffer with samples - * relative to the current system time and the NOMINAL performance frequency - * (not the actual, because the actual has probably run slow in the first - * case). - */ - -static Tcl_WideInt -AccumulateSample( - Tcl_WideInt perfCounter, - Tcl_WideUInt fileTime) -{ - Tcl_WideUInt workFTSample; /* File time sample being removed from or - * added to the circular buffer. */ - Tcl_WideInt workPCSample; /* Performance counter sample being removed - * from or added to the circular buffer. */ - Tcl_WideUInt lastFTSample; /* Last file time sample recorded */ - Tcl_WideInt lastPCSample; /* Last performance counter sample recorded */ - Tcl_WideInt FTdiff; /* Difference between last FT and current */ - Tcl_WideInt PCdiff; /* Difference between last PC and current */ - Tcl_WideInt estFreq; /* Estimated performance counter frequency */ - - /* - * Test for jumps and reset the samples if we have one. - */ - - if (timeInfo.sampleNo == 0) { - lastPCSample = - timeInfo.perfCounterSample[timeInfo.sampleNo + SAMPLES - 1]; - lastFTSample = - timeInfo.fileTimeSample[timeInfo.sampleNo + SAMPLES - 1]; - } else { - lastPCSample = timeInfo.perfCounterSample[timeInfo.sampleNo - 1]; - lastFTSample = timeInfo.fileTimeSample[timeInfo.sampleNo - 1]; - } - - PCdiff = perfCounter - lastPCSample; - FTdiff = fileTime - lastFTSample; - if (PCdiff < timeInfo.nominalFreq.QuadPart * 9 / 10 - || PCdiff > timeInfo.nominalFreq.QuadPart * 11 / 10 - || FTdiff < 9000000 || FTdiff > 11000000) { - ResetCounterSamples(fileTime, perfCounter, - timeInfo.nominalFreq.QuadPart); -#if 0 - FTdiff = fileTime - timeInfo.lastCC.fileTime; - if (FTdiff) { - estFreq = 10000000 * (perfCounter - timeInfo.lastCC.perfCounter) / FTdiff; - printf("------**-----calibration estimated Frequency %I64d, %I64d, %I64d, diff: %I64d\n", fileTime, perfCounter, estFreq, estFreq - timeInfo.lastCC.counterFreq); - } - return (timeInfo.nominalFreq.QuadPart + timeInfo.lastCC.counterFreq) / 2; -#endif - return timeInfo.nominalFreq.QuadPart; - } else { - /* - * Estimate the frequency. - */ - - workPCSample = timeInfo.perfCounterSample[timeInfo.sampleNo]; - workFTSample = timeInfo.fileTimeSample[timeInfo.sampleNo]; - estFreq = 10000000 * (perfCounter - workPCSample) - / (fileTime - workFTSample); - timeInfo.perfCounterSample[timeInfo.sampleNo] = perfCounter; - timeInfo.fileTimeSample[timeInfo.sampleNo] = (Tcl_WideInt) fileTime; - - /* - * Advance the sample number. - */ - - if (++timeInfo.sampleNo >= SAMPLES) { - timeInfo.sampleNo = 0; - } - - return estFreq; - } -} - -/* - *---------------------------------------------------------------------- - * * TclpGmtime -- * * Wrapper around the 'gmtime' library function to make it thread safe. -- cgit v0.12 From 91fe05d17b9acc7c807e3fdd32e068aa24426817 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:29:20 +0000 Subject: interim commit: time-drift backwards fix... (try to resolve using counter variance + frequency factor) --- win/tclWinTime.c | 355 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 187 insertions(+), 168 deletions(-) diff --git a/win/tclWinTime.c b/win/tclWinTime.c index c4206fe..f8b0712 100644 --- a/win/tclWinTime.c +++ b/win/tclWinTime.c @@ -36,6 +36,20 @@ typedef struct ThreadSpecificData { static Tcl_ThreadDataKey dataKey; /* + * The following values are used for calculating virtual time. Virtual + * time is always equal to: + * virtTime + (currentPerfCounter - perfCounter + cntrVariance) + * * 10000000 / nominalFreq + */ +typedef struct TimeCalibInfo { + ULONGLONG fileTime; /* Last real time (in 100-ns) */ + ULONGLONG virtTime; /* Last virtual time (in 100-ns) */ + LONGLONG perfCounter; /* QPC value of last calibration time */ + LONGLONG cntrVariance; /* Current calculated deviation (compensation) */ + Tcl_WideInt calibNextTime; /* Next time of calibration (in 100-ns ticks) */ +} TimeCalibInfo; + +/* * Data for managing high-resolution timers. */ @@ -45,7 +59,6 @@ typedef struct TimeInfo { * initialized. */ int perfCounterAvailable; /* Flag == 1 if the hardware has a performance * counter. */ - Tcl_WideInt calibNextTime; /* Next time of calibration (in 100-ns ticks) */ HANDLE calibrationThread; /* Handle to the thread that keeps the virtual * clock calibrated. */ HANDLE readyEvent; /* System event used to trigger the requesting @@ -53,27 +66,21 @@ typedef struct TimeInfo { * is initialized for the first time. */ HANDLE exitEvent; /* Event to signal out of an exit handler to * tell the calibration loop to terminate. */ - LARGE_INTEGER nominalFreq; /* Nominal frequency of the system performance + LONGLONG nominalFreq; /* Nominal frequency of the system performance * counter, that is, the value returned from * QueryPerformanceFrequency. */ + int freqFactor; /* Frequency factor (1 - KHz, 1000 - Hz) */ LARGE_INTEGER posixEpoch; /* Posix epoch expressed as 100-ns ticks since * the windows epoch. */ /* * The following values are used for calculating virtual time. Virtual * time is always equal to: - * fileTime + (current perf counter - lastCounter) - * * 10000000 / counterFreq + * virtTime + ( (currentPerfCounter - perfCounter) * 10000000 + * + cntrVariance) / nominalFreq */ - struct { - ULONGLONG fileTime; - ULONGLONG virtTime; - volatile /* used also to compare calibration epoch */ - LONGLONG perfCounter; - LONGLONG counterFreq; - } lastCC; /* Last data updated in calibration cycle */ - - int freqFactor; /* Frequency factor (1000 - KHz, 1 - Hz) */ + TimeCalibInfo lastCC; /* Last data updated in calibration cycle */ + volatile LONG calibEpoch; /* Calibration epoch */ Tcl_WideInt lastUsedTime; /* Last known (caller) virtual time in 100-ns * (used to avoid drifts after calibrate) */ @@ -84,24 +91,24 @@ static TimeInfo timeInfo = { { NULL, 0, 0, NULL, NULL, 0 }, 0, 0, - (Tcl_WideInt) 0, (HANDLE) NULL, (HANDLE) NULL, (HANDLE) NULL, + (LONGLONG) 0, + 1, /* for frequency in KHz */ #ifdef HAVE_CAST_TO_UNION (LARGE_INTEGER) (Tcl_WideInt) 0, - (LARGE_INTEGER) (Tcl_WideInt) 0, #else {0, 0}, - {0, 0}, #endif { + (Tcl_WideInt) 0, (ULONGLONG) 0, (ULONGLONG) 0, (LONGLONG) 0, (LONGLONG) 0 }, - 1000, /* KHz */ + (LONG) 0, (Tcl_WideInt) 0 }; @@ -141,16 +148,36 @@ ClientData tclTimeClientData = NULL; /* *---------------------------------------------------------------------- * + * NativePerformanceCounter -- + * + * Used instead of QueryPerformanceCounter to consider frequency factor. + * + * Results: + * Returns QPC corresponding current frequency factor. + * + *---------------------------------------------------------------------- + */ +static inline LONGLONG +NativePerformanceCounter(void) { + LARGE_INTEGER curCounter; + QueryPerformanceCounter(&curCounter); + return (curCounter.QuadPart / timeInfo.freqFactor); +} + +/* + *---------------------------------------------------------------------- + * * NativeCalc100NsTicks -- * * Calculate the current system time in 100-ns ticks since posix epoch, * for current performance counter (curCounter), using given calibrated values. * - * vt = lastCC.virtTime + - * 10000000 * (curPerfCounter - lastCC.perfCounter) / lastCC.counterFreq + * vt = lastCC.virtTime + * + ( (curPerfCounter - lastCC.perfCounter) * 10000000 + * + lastCC.cntrVariance ) / nominalFreq * * Results: - * Returns the wide integer with number of microseconds from the epoch. + * Returns the wide integer with number of 100-ns ticks from the epoch. * * Side effects: * None @@ -162,11 +189,11 @@ static inline Tcl_WideInt NativeCalc100NsTicks( ULONGLONG ccVirtTime, LONGLONG ccPerfCounter, - LONGLONG ccCounterFreq, + LONGLONG ccCntrVariance, LONGLONG curCounter ) { - return ccVirtTime + - ((curCounter - ccPerfCounter) * 10000000 / ccCounterFreq); + return ccVirtTime + ( (curCounter - ccPerfCounter) * 10000000 + + ccCntrVariance ) / timeInfo.nominalFreq; } /* @@ -366,9 +393,9 @@ Tcl_WideInt TclpGetMicroseconds(void) { static Tcl_WideInt prevUS = 0; - static Tcl_WideInt fileTimeLastCall, perfCounterLastCall, curCounterFreq; - static LARGE_INTEGER prevPerfCounter; - LARGE_INTEGER newPerfCounter; + static Tcl_WideInt fileTimeLastCall, perfCounterLastCall, curCntrVariance; + static LONGLONG prevPerfCounter; + LONGLONG newPerfCounter; Tcl_WideInt usecSincePosixEpoch; @@ -391,25 +418,25 @@ TclpGetMicroseconds(void) printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!no-native-ms!!!!!!!!!!!\n"); } - QueryPerformanceCounter(&newPerfCounter); + newPerfCounter = NativePerformanceCounter(); if (prevUS && usecSincePosixEpoch < prevUS) { - printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!time-backwards!!!! pre-struct: %I64d, %I64d, %I64d, %I64d == %I64d \n", fileTimeLastCall, perfCounterLastCall, prevPerfCounter.QuadPart, curCounterFreq, + printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!time-backwards!!!! pre-struct: %I64d, %I64d, %I64d, %I64d == %I64d \n", fileTimeLastCall, perfCounterLastCall, prevPerfCounter, curCntrVariance, NativeCalc100NsTicks(fileTimeLastCall, - perfCounterLastCall, curCounterFreq, - prevPerfCounter.QuadPart)); - printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!time-backwards!!!! new-struct: %I64d, %I64d, %I64d, %I64d == %I64d \n", timeInfo.lastCC.virtTime, timeInfo.lastCC.perfCounter, newPerfCounter.QuadPart, timeInfo.lastCC.counterFreq, + perfCounterLastCall, curCntrVariance, + prevPerfCounter)); + printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!time-backwards!!!! new-struct: %I64d, %I64d, %I64d, %I64d == %I64d \n", timeInfo.lastCC.virtTime, timeInfo.lastCC.perfCounter, newPerfCounter, timeInfo.lastCC.cntrVariance, NativeCalc100NsTicks(timeInfo.lastCC.virtTime, - timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq, - newPerfCounter.QuadPart)); + timeInfo.lastCC.perfCounter, timeInfo.lastCC.cntrVariance, + newPerfCounter)); printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!time-backwards!!!! prev: %I64d - now: %I64d (%I64d usec)\n", prevUS, usecSincePosixEpoch, usecSincePosixEpoch - prevUS); Tcl_Panic("Time running backwards!!!"); } prevUS = usecSincePosixEpoch; fileTimeLastCall = timeInfo.lastCC.virtTime; perfCounterLastCall = timeInfo.lastCC.perfCounter; - curCounterFreq = timeInfo.lastCC.counterFreq; - prevPerfCounter.QuadPart = newPerfCounter.QuadPart; + curCntrVariance = timeInfo.lastCC.cntrVariance; + prevPerfCounter = newPerfCounter; return usecSincePosixEpoch; } @@ -546,14 +573,32 @@ NativeGetMicroseconds(void) */ if (!timeInfo.initialized) { + LARGE_INTEGER nominalFreq; TclpInitLock(); if (!timeInfo.initialized) { timeInfo.posixEpoch.LowPart = 0xD53E8000; timeInfo.posixEpoch.HighPart = 0x019DB1DE; - timeInfo.perfCounterAvailable = - QueryPerformanceFrequency(&timeInfo.nominalFreq); + if ((timeInfo.perfCounterAvailable = + QueryPerformanceFrequency(&nominalFreq)) + ) { + timeInfo.nominalFreq = nominalFreq.QuadPart; + + /* + * We devide by timeInfo.nominalFreq in several places. + */ + if (timeInfo.nominalFreq == 0) { + timeInfo.perfCounterAvailable = FALSE; + } + /* Some systems having frequency in Hz, so save the factor here */ + if (timeInfo.nominalFreq >= 1000000000 + && (timeInfo.nominalFreq % 1000) == 0) { + timeInfo.nominalFreq /= 1000; + /* assume that frequency in Hz, factor used only for tolerance */ + timeInfo.freqFactor = 1000; + } + } /* * Some hardware abstraction layers use the CPU clock in place of @@ -587,10 +632,10 @@ NativeGetMicroseconds(void) /* * The following lines would do an exact match on crystal * frequency: - * && timeInfo.nominalFreq.QuadPart != (Tcl_WideInt)1193182 - * && timeInfo.nominalFreq.QuadPart != (Tcl_WideInt)3579545 + * && timeInfo.nominalFreq != 1193182 + * && timeInfo.nominalFreq != 3579545 */ - && timeInfo.nominalFreq.QuadPart > (Tcl_WideInt) 15000000){ + && timeInfo.nominalFreq > 15000000){ /* * As an exception, if every logical processor on the system * is on the same chip, we use the performance counter anyway, @@ -639,12 +684,6 @@ NativeGetMicroseconds(void) if (timeInfo.perfCounterAvailable) { DWORD id; - /* Some systems having frequency in Hz, so save the factor here */ - if (timeInfo.nominalFreq.QuadPart >= 1000000000) { - /* assume that frequency in Hz, factor used only for tolerance */ - timeInfo.freqFactor = 1; - } - InitializeCriticalSection(&timeInfo.cs); timeInfo.readyEvent = CreateEvent(NULL, FALSE, FALSE, NULL); timeInfo.exitEvent = CreateEvent(NULL, FALSE, FALSE, NULL); @@ -668,42 +707,47 @@ NativeGetMicroseconds(void) TclpInitUnlock(); } - if (timeInfo.perfCounterAvailable && timeInfo.lastCC.counterFreq!=0) { + if (timeInfo.perfCounterAvailable) { - static struct { - ULONGLONG virtTime; - volatile /* don't optimize */ - LONGLONG perfCounter; - LONGLONG counterFreq; - Tcl_WideInt calibNextTime; - } cc = {0, 0, 0, 0}; /* Copy with current data of calibration cycle */ + /* Copies with current data of calibration cycle */ + static TimeCalibInfo commonCC; + static volatile LONG calibEpoch; + TimeCalibInfo cc; + volatile LONG ccEpoch; - LARGE_INTEGER curCounter; - /* Current performance counter. */ + LONGLONG curCounter; /* Current performance counter. */ /* - * Hold time section locked as short as possible + * Try to acquire data without lock (same epoch at end of copy process). */ - if (cc.perfCounter != timeInfo.lastCC.perfCounter) { - EnterCriticalSection(&timeInfo.cs); - if (cc.perfCounter != timeInfo.lastCC.perfCounter) { - cc.perfCounter = timeInfo.lastCC.perfCounter; - cc.virtTime = timeInfo.lastCC.virtTime; - cc.counterFreq = timeInfo.lastCC.counterFreq; - cc.calibNextTime = timeInfo.calibNextTime; + do { + ccEpoch = calibEpoch; + memcpy(&cc, &commonCC, sizeof(cc)); + /* + * Hold time section locked as short as possible + */ + if (InterlockedCompareExchange(&timeInfo.calibEpoch, + calibEpoch, calibEpoch) != calibEpoch) { + EnterCriticalSection(&timeInfo.cs); + if (calibEpoch != timeInfo.calibEpoch) { + memcpy(&commonCC, &timeInfo.lastCC, sizeof(commonCC)); + } + calibEpoch = timeInfo.calibEpoch; + LeaveCriticalSection(&timeInfo.cs); } - LeaveCriticalSection(&timeInfo.cs); - } + + } while (InterlockedCompareExchange(&timeInfo.calibEpoch, + ccEpoch, ccEpoch) != ccEpoch); /* * Query the performance counter and use it to calculate the current * time. */ - QueryPerformanceCounter(&curCounter); + curCounter = NativePerformanceCounter(); /* Calibrated file-time is saved from posix in 100-ns ticks */ curTime = NativeCalc100NsTicks(cc.virtTime, - cc.perfCounter, cc.counterFreq, curCounter.QuadPart); + cc.perfCounter, cc.cntrVariance, curCounter); /* Be sure the clock ticks never backwards (avoid backwards time-drifts) */ if ( (lastTime = timeInfo.lastUsedTime) @@ -728,7 +772,7 @@ NativeGetMicroseconds(void) timeInfo.lastUsedTime = curTime; return curTime / 10; } - printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!calibration-error!!!! cur: %I64d - call: %I64d (%I64d) -- prev: %I64d - now: %I64d (%I64d)\n", curTime, cc.calibNextTime, cc.calibNextTime - curTime, cc.perfCounter, curCounter.QuadPart, curCounter.QuadPart - cc.perfCounter); + printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!calibration-error!!!! cur: %I64d - call: %I64d (%I64d) -- prev: %I64d - now: %I64d (%I64d)\n", curTime, cc.calibNextTime, cc.calibNextTime - curTime, cc.perfCounter, curCounter, curCounter - cc.perfCounter); } /* @@ -1174,11 +1218,7 @@ CalibrationThread( * Get initial system time and performance counter. */ - LARGE_INTEGER curPerfCounter; - - QueryPerformanceCounter(&curPerfCounter); - timeInfo.lastCC.perfCounter = curPerfCounter.QuadPart; - timeInfo.lastCC.counterFreq = timeInfo.nominalFreq.QuadPart; + timeInfo.lastCC.perfCounter = NativePerformanceCounter(); timeInfo.lastCC.fileTime = timeInfo.lastCC.virtTime = GetSystemTimeAsVirtual(); /* @@ -1240,15 +1280,15 @@ CalibrationThread( static void UpdateTimeEachSecond(void) { - LARGE_INTEGER curPerfCounter; + LONGLONG curPerfCounter; /* Current value returned from - * QueryPerformanceCounter. */ + * NativePerformanceCounter. */ static int calibrationInterv = 10000000; /* Calibration interval in 100-ns ticks (starts from 1s) */ Tcl_WideInt curFileTime; /* File time at the time this callback was * scheduled. */ - Tcl_WideInt estFreq; /* Estimated perf counter frequency. */ - int driftBack; /* Sign the virtual time may drift backwards */ + LONGLONG estVariance, /* Estimated variance to compensate ipmact of */ + driftVariance; /* deviations of perfomance counters. */ Tcl_WideInt vt0; /* Tcl time right now. */ Tcl_WideInt vt1; /* Interim virtual time used during adjustments */ Tcl_WideInt tdiff, /* Difference between system clock and Tcl time. */ @@ -1259,18 +1299,19 @@ UpdateTimeEachSecond(void) */ curFileTime = GetSystemTimeAsVirtual(); - QueryPerformanceCounter(&curPerfCounter); - printf("-------------calibration start, prev-struct: %I64d, %I64d, %I64d, pc-diff: %I64d\n", timeInfo.lastCC.fileTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq, curPerfCounter.QuadPart - timeInfo.lastCC.perfCounter); + curPerfCounter = NativePerformanceCounter(); + printf("-------------calibration start, prev-struct: %I64d, %I64d, %I64d / %I64d, pc-diff: %I64d\n", timeInfo.lastCC.fileTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.cntrVariance, timeInfo.nominalFreq, curPerfCounter - timeInfo.lastCC.perfCounter); /* - * Current virtual time (using average between fileTime and virtTime): - * vt0 = (lastCC.fileTime + lastCC.virtTime) / 2 + - * 10000000 * (curPerfCounter - lastCC.perfCounter) / lastCC.counterFreq + * Current virtual time (using average between last fileTime and virtTime): + * vt0 = (lastCC.fileTime + lastCC.virtTime) / 2 + * + ( (curPerfCounter - lastCC.perfCounter) * 10000000 + * + lastCC.cntrVariance) / nominalFreq */ vt0 = NativeCalc100NsTicks( (timeInfo.lastCC.fileTime/2 + timeInfo.lastCC.virtTime/2), - timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq, - curPerfCounter.QuadPart); + timeInfo.lastCC.perfCounter, timeInfo.lastCC.cntrVariance, + curPerfCounter); /* Differences between virtual and real-time */ tdiff = vt0 - curFileTime; @@ -1284,29 +1325,16 @@ UpdateTimeEachSecond(void) * Calibrate immediately if we've too large discrepancy to the real-time (15.6 ms). */ #if 1 - if ( curFileTime < timeInfo.calibNextTime - (10000000/2) /* 0.5 sec (in 100-ns ticks). */ - && timeInfo.calibNextTime - curFileTime < 10 * 10000000 /* max. 10 seconds in-between (time-switch?) */ + if ( curFileTime < timeInfo.lastCC.calibNextTime - (10000000/2) /* 0.5 sec (in 100-ns ticks). */ + && timeInfo.lastCC.calibNextTime - curFileTime < 10 * 10000000 /* max. 10 seconds in-between (time-switch?) */ && tdiff > -10000 && tdiff < 10000 /* very small discrepancy (1ms) */ ) { /* again in next one second */ - //printf("-------------calibration end, tdiff %I64d, *** not needed. (next in: %I64d) ------\n", tdiff, curFileTime, timeInfo.calibNextTime, timeInfo.calibNextTime - curFileTime); + printf("-------------calibration end, tdiff %I64d, *** not needed. (next in: %I64d) ------\n", tdiff, curFileTime, timeInfo.lastCC.calibNextTime, timeInfo.lastCC.calibNextTime - curFileTime); lastDiff = tdiff; return; } #endif - /* - * We devide by timeInfo.lastCC.counterFreq in several places. That - * value should always be positive on a correctly functioning system. But - * it is good to be defensive about such matters. So if something goes - * wrong and the value does goes to zero, we clear the - * timeInfo.perfCounterAvailable in order to cause the calibration thread - * to shut itself down, then return without additional processing. - */ - - if (timeInfo.lastCC.counterFreq == 0) { - timeInfo.perfCounterAvailable = 0; - return; - } /* * Several things may have gone wrong here that have to be checked for. @@ -1314,8 +1342,6 @@ UpdateTimeEachSecond(void) * (2) The system clock may have been reset. */ - driftBack = 0; - /* * We want to adjust things so that time appears to be continuous. * Virtual file time, right now, is vt0. @@ -1338,54 +1364,66 @@ UpdateTimeEachSecond(void) /* jump to current system time, use curent estimated frequency */ vt0 = curFileTime; timeInfo.lastUsedTime = 0; /* reset last used time */ - estFreq = timeInfo.nominalFreq.QuadPart; + estVariance = 0; } else { - Tcl_WideInt driftFreq; /* Frequency needed to drift virtual time into - * step over 1 second. */ - /* * Estimate current frequency corresponding current time / counter. */ - vt1 = curFileTime - timeInfo.lastCC.fileTime; - if (vt1 > 0) { - estFreq = (curPerfCounter.QuadPart - timeInfo.lastCC.perfCounter) * 10000000 / vt1; - - /* - * Minimize influence of estFreq if tdiff falls (in relation to - * last difference), with dual falling speed. This indicates better - * choice of lastCC.counterFreq. - */ - if (tdiff > 0 && tdiff < lastDiff / 2 || tdiff < 0 && tdiff > lastDiff / 2) { - //printf("-----***-----calibration minimize %I64d, %I64d\n", estFreq, lastDiff); - estFreq = (estFreq + timeInfo.lastCC.counterFreq * 2) / 3; - //printf("-----***-----calibration minimize %I64d, %I64d\n", estFreq, tdiff); - } + if ((curFileTime - timeInfo.lastCC.fileTime) > (10000000 / 2)) { + estVariance = (curFileTime - timeInfo.lastCC.fileTime) + * timeInfo.nominalFreq + - (curPerfCounter- timeInfo.lastCC.perfCounter) * 10000000; } else { - estFreq = timeInfo.lastCC.counterFreq; + estVariance = timeInfo.lastCC.cntrVariance; } - printf("------**-----calibration estimated, tdiff: %I64d, ** %s ** cntrDiff:%I64d\n", tdiff, (estFreq > timeInfo.lastCC.counterFreq) ? "^^^" : "vvv", (curPerfCounter.QuadPart - timeInfo.lastCC.perfCounter)); - printf("------**-----calibration estimated Frequency %I64d, %I64d, %I64d, diff: %I64d, to-nomin: %I64d\n", curFileTime, curPerfCounter.QuadPart, estFreq, estFreq - timeInfo.lastCC.counterFreq, estFreq - timeInfo.nominalFreq.QuadPart); + + /* + * Minimize influence of estVariance if tdiff falls (in relation to + * last difference), with dual falling speed. This indicates better + * choice of lastCC.cntrVariance. + */ + if (tdiff > 0 && tdiff < lastDiff / 2 || tdiff < 0 && tdiff > lastDiff / 2) { + //printf("-----***-----calibration minimize %I64d, %I64d\n", estFreq, lastDiff); + estVariance = (estVariance + timeInfo.lastCC.cntrVariance) / 2; + //printf("-----***-----calibration minimize %I64d, %I64d\n", estFreq, tdiff); + } + printf("------**-----calibration estimated, tdiff: %I64d, ** %s ** cntrDiff:%I64d\n", tdiff, (estVariance > timeInfo.lastCC.cntrVariance) ? "^^^" : "vvv", (curPerfCounter - timeInfo.lastCC.perfCounter)); + printf("------**-----calibration estimated %I64d, %I64d, %I64d, diff: %I64d\n", curFileTime, curPerfCounter, estVariance, estVariance - timeInfo.lastCC.cntrVariance); #if 1 /* - * Calculate new frequency and estimate drift to the next second + * Calculate new estimate drift variance to the next second */ - vt1 = 10000000 + curFileTime; - driftFreq = estFreq * 10000000 / (vt1 - vt0); + vt1 = vt0 - timeInfo.lastCC.virtTime; + if (vt1 > (10000000 / 2)) { + + LONGLONG driftPerfCounter = curPerfCounter + + + (curPerfCounter - timeInfo.lastCC.perfCounter) + / vt1 * (vt1 + 10000000); + + vt1 = NativeCalc100NsTicks(curFileTime, + curPerfCounter, estVariance, + driftPerfCounter); + driftVariance = (vt1 - vt0) * timeInfo.nominalFreq + //- estVariance + - (driftPerfCounter - curPerfCounter) * 10000000; + } else { + driftVariance = estVariance * 2; + } /* * Avoid too large drifts (only half of the current difference), * that allows also be more accurate (aspire to the smallest tdiff), * so then we can prolong calibration interval in such cases. */ - driftFreq = timeInfo.lastCC.counterFreq + - (driftFreq - timeInfo.lastCC.counterFreq) / 2; + driftVariance = timeInfo.lastCC.cntrVariance + + (driftVariance - timeInfo.lastCC.cntrVariance) / 2; - printf("------**-----calibration lastFreq: %I64d\n", timeInfo.lastCC.counterFreq); - printf("------**-----calibration estFreq: %I64d\n", estFreq); - printf("------**-----calibration driftFreq:%I64d\n", driftFreq); + printf("------**-----calibration cntrVariance: %I64d\n", timeInfo.lastCC.cntrVariance); + printf("------**-----calibration estVariance: %I64d\n", estVariance); + printf("------**-----calibration driftVariance:%I64d\n", driftVariance); /* * Average between estimated, 2 current and 5 drifted frequencies, @@ -1399,36 +1437,17 @@ UpdateTimeEachSecond(void) estFreq = (3 * estFreq + 3 * timeInfo.lastCC.counterFreq + 2 * driftFreq) / 8; } #else - estFreq = (estFreq + timeInfo.lastCC.counterFreq + driftFreq) / 3; + estVariance = (estVariance + timeInfo.lastCC.cntrVariance + driftVariance) / 3; #endif #else - estFreq = (estFreq + timeInfo.lastCC.counterFreq) / 2; + estVariance = (estVariance + timeInfo.lastCC.cntrVariance) / 2; #endif } - /* - * Avoid too large discrepancy from nominal frequency (0.5%) - */ - if ( estFreq > (vt1 = (1000+5)*timeInfo.nominalFreq.QuadPart/1000) - || estFreq < (vt1 = (1000-5)*timeInfo.nominalFreq.QuadPart/1000) - ) { - /* Some systems having frequency in Hz, so fewer tolerant (1.5%) */ - if ( timeInfo.freqFactor == 1000 /* frequency in KHz */ - || ( estFreq > (vt1 = (1000+15)*timeInfo.nominalFreq.QuadPart/1000) - || estFreq < (vt1 = (1000-15)*timeInfo.nominalFreq.QuadPart/1000) - ) - ) { - estFreq = vt1; - driftBack = vt0 > curFileTime; - vt0 = curFileTime; /* too large - just reset */ - printf("************ too large: %I64d\n", estFreq); - } - } - /* If possible backwards time-drifts (larger divider now) */ vt1 = 0; - if (driftBack || estFreq > timeInfo.lastCC.counterFreq) { + if (estVariance < timeInfo.lastCC.cntrVariance) { Tcl_WideInt nt0, nt1; /* @@ -1436,17 +1455,17 @@ UpdateTimeEachSecond(void) * to avoid possible backwards drifts (adjust current base time). * This should affect at least next 10 ticks. */ - vt1 = curPerfCounter.QuadPart + 10; + vt1 = curPerfCounter + 10; /* * Be sure the clock ticks never backwards (avoid it by negative drifting) * just compare native time (in 100-ns) before and hereafter using * previous/new calibrated values) and do a small adjustment */ nt0 = NativeCalc100NsTicks(timeInfo.lastCC.virtTime, - timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq, + timeInfo.lastCC.perfCounter, timeInfo.lastCC.cntrVariance, vt1); nt1 = NativeCalc100NsTicks(vt0, - curPerfCounter.QuadPart, estFreq, + curPerfCounter, estVariance, vt1); vt1 = (nt0 - nt1); /* old time - new time */ if (vt1 > 0 && vt1 < 10000000 /* bypass time-switch */) { @@ -1458,30 +1477,30 @@ UpdateTimeEachSecond(void) } /* if still precise enough, grow calibration interval up to 10 seconds */ - if ( timeInfo.freqFactor == 1000 ) { /* frequency in KHz */ - if (tdiff < -100000 || tdiff > 100000 /* 10-ms */) { - /* too long drift - reset calibration interval to 1 second */ - calibrationInterv = 10000000; - } else if (calibrationInterv < 10*10000000) { - calibrationInterv += 10000000; - } + if (tdiff < -100000 || tdiff > 100000 /* 10-ms */) { + /* too long drift - reset calibration interval to 1 second */ + calibrationInterv = 10000000; + } else if (calibrationInterv < 10*10000000) { + calibrationInterv += 10000000; } + /* In lock commit new values to timeInfo (hold lock as short as possible) */ EnterCriticalSection(&timeInfo.cs); - timeInfo.lastCC.perfCounter = curPerfCounter.QuadPart; + timeInfo.lastCC.perfCounter = curPerfCounter; timeInfo.lastCC.fileTime = curFileTime; timeInfo.lastCC.virtTime = vt0; - timeInfo.lastCC.counterFreq = estFreq; + timeInfo.lastCC.cntrVariance = estVariance; + timeInfo.lastCC.calibNextTime = curFileTime + calibrationInterv; - timeInfo.calibNextTime = curFileTime + calibrationInterv; + InterlockedIncrement(&timeInfo.calibEpoch); LeaveCriticalSection(&timeInfo.cs); #if 1 //printf("-------------calibration adj -- nt1:%I64d - nt0:%I64d: adj: %I64d\n", nt1, nt0, vt1); printf("-------------calibration end, tdiff %I64d, jump -- vt:%I64d - st:%I64d: %I64d, adj: %I64d\n", tdiff, vt0, curFileTime, (vt0 - curFileTime), vt1); - printf("-------------calibration end , new-struct: %I64d, %I64d, %I64d\n", timeInfo.lastCC.virtTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.counterFreq); + printf("-------------calibration end , new-struct: %I64d, %I64d, %I64d / %I64d\n", timeInfo.lastCC.virtTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.cntrVariance, timeInfo.nominalFreq); #endif } -- cgit v0.12 From 27392e7566cec8dc9ae2695644c153c55d565b08 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:29:24 +0000 Subject: interim commit: amend with optimization --- win/tclWinTime.c | 212 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 159 insertions(+), 53 deletions(-) diff --git a/win/tclWinTime.c b/win/tclWinTime.c index f8b0712..e956175 100644 --- a/win/tclWinTime.c +++ b/win/tclWinTime.c @@ -46,6 +46,7 @@ typedef struct TimeCalibInfo { ULONGLONG virtTime; /* Last virtual time (in 100-ns) */ LONGLONG perfCounter; /* QPC value of last calibration time */ LONGLONG cntrVariance; /* Current calculated deviation (compensation) */ + LONGLONG estFrequency; /* Current estimated frequency */ Tcl_WideInt calibNextTime; /* Next time of calibration (in 100-ns ticks) */ } TimeCalibInfo; @@ -190,10 +191,11 @@ NativeCalc100NsTicks( ULONGLONG ccVirtTime, LONGLONG ccPerfCounter, LONGLONG ccCntrVariance, + LONGLONG ccEstFrequency, LONGLONG curCounter ) { return ccVirtTime + ( (curCounter - ccPerfCounter) * 10000000 - + ccCntrVariance ) / timeInfo.nominalFreq; + + ccCntrVariance ) / ccEstFrequency; } /* @@ -393,7 +395,7 @@ Tcl_WideInt TclpGetMicroseconds(void) { static Tcl_WideInt prevUS = 0; - static Tcl_WideInt fileTimeLastCall, perfCounterLastCall, curCntrVariance; + static Tcl_WideInt fileTimeLastCall, perfCounterLastCall, curCntrVariance, prevEstFrequency; static LONGLONG prevPerfCounter; LONGLONG newPerfCounter; @@ -423,11 +425,11 @@ TclpGetMicroseconds(void) if (prevUS && usecSincePosixEpoch < prevUS) { printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!time-backwards!!!! pre-struct: %I64d, %I64d, %I64d, %I64d == %I64d \n", fileTimeLastCall, perfCounterLastCall, prevPerfCounter, curCntrVariance, NativeCalc100NsTicks(fileTimeLastCall, - perfCounterLastCall, curCntrVariance, + perfCounterLastCall, curCntrVariance, prevEstFrequency, prevPerfCounter)); printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!time-backwards!!!! new-struct: %I64d, %I64d, %I64d, %I64d == %I64d \n", timeInfo.lastCC.virtTime, timeInfo.lastCC.perfCounter, newPerfCounter, timeInfo.lastCC.cntrVariance, NativeCalc100NsTicks(timeInfo.lastCC.virtTime, - timeInfo.lastCC.perfCounter, timeInfo.lastCC.cntrVariance, + timeInfo.lastCC.perfCounter, timeInfo.lastCC.cntrVariance, timeInfo.lastCC.estFrequency, newPerfCounter)); printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!time-backwards!!!! prev: %I64d - now: %I64d (%I64d usec)\n", prevUS, usecSincePosixEpoch, usecSincePosixEpoch - prevUS); Tcl_Panic("Time running backwards!!!"); @@ -436,6 +438,7 @@ TclpGetMicroseconds(void) fileTimeLastCall = timeInfo.lastCC.virtTime; perfCounterLastCall = timeInfo.lastCC.perfCounter; curCntrVariance = timeInfo.lastCC.cntrVariance; + prevEstFrequency = timeInfo.lastCC.estFrequency; prevPerfCounter = newPerfCounter; return usecSincePosixEpoch; @@ -747,7 +750,7 @@ NativeGetMicroseconds(void) /* Calibrated file-time is saved from posix in 100-ns ticks */ curTime = NativeCalc100NsTicks(cc.virtTime, - cc.perfCounter, cc.cntrVariance, curCounter); + cc.perfCounter, cc.cntrVariance, cc.estFrequency, curCounter); /* Be sure the clock ticks never backwards (avoid backwards time-drifts) */ if ( (lastTime = timeInfo.lastUsedTime) @@ -767,6 +770,9 @@ NativeGetMicroseconds(void) * loop should recover. */ + + printf("********* %I64d\n", GetSystemTimeAsVirtual()); /* in 100-ns ticks */ + if (curTime < cc.calibNextTime + 10000000 /* 1 sec (in 100-ns ticks). */) { /* save last used time */ timeInfo.lastUsedTime = curTime; @@ -1220,6 +1226,7 @@ CalibrationThread( timeInfo.lastCC.perfCounter = NativePerformanceCounter(); timeInfo.lastCC.fileTime = timeInfo.lastCC.virtTime = GetSystemTimeAsVirtual(); + timeInfo.lastCC.estFrequency = timeInfo.nominalFreq; /* * Calibrate first time and wake up the calling thread. @@ -1288,7 +1295,8 @@ UpdateTimeEachSecond(void) Tcl_WideInt curFileTime; /* File time at the time this callback was * scheduled. */ LONGLONG estVariance, /* Estimated variance to compensate ipmact of */ - driftVariance; /* deviations of perfomance counters. */ + driftVariance, /* deviations of perfomance counters. */ + estFreq; /* Estimated frequency */ Tcl_WideInt vt0; /* Tcl time right now. */ Tcl_WideInt vt1; /* Interim virtual time used during adjustments */ Tcl_WideInt tdiff, /* Difference between system clock and Tcl time. */ @@ -1300,18 +1308,24 @@ UpdateTimeEachSecond(void) curFileTime = GetSystemTimeAsVirtual(); curPerfCounter = NativePerformanceCounter(); - printf("-------------calibration start, prev-struct: %I64d, %I64d, %I64d / %I64d, pc-diff: %I64d\n", timeInfo.lastCC.fileTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.cntrVariance, timeInfo.nominalFreq, curPerfCounter - timeInfo.lastCC.perfCounter); + printf("-------------calibration start, prev-struct: %I64d, %I64d, %I64d / %I64d, pc-diff: %I64d\n", timeInfo.lastCC.fileTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.cntrVariance, timeInfo.lastCC.estFrequency, curPerfCounter - timeInfo.lastCC.perfCounter); /* * Current virtual time (using average between last fileTime and virtTime): * vt0 = (lastCC.fileTime + lastCC.virtTime) / 2 * + ( (curPerfCounter - lastCC.perfCounter) * 10000000 - * + lastCC.cntrVariance) / nominalFreq + * + lastCC.cntrVariance) / lastCC.estFrequency + * vt1 = the same with nominalFreq */ vt0 = NativeCalc100NsTicks( (timeInfo.lastCC.fileTime/2 + timeInfo.lastCC.virtTime/2), timeInfo.lastCC.perfCounter, timeInfo.lastCC.cntrVariance, - curPerfCounter); + timeInfo.lastCC.estFrequency, curPerfCounter); + + vt1 = NativeCalc100NsTicks( + (timeInfo.lastCC.fileTime/2 + timeInfo.lastCC.virtTime/2), + timeInfo.lastCC.perfCounter, timeInfo.lastCC.cntrVariance, + timeInfo.nominalFreq, curPerfCounter); /* Differences between virtual and real-time */ tdiff = vt0 - curFileTime; @@ -1339,44 +1353,60 @@ UpdateTimeEachSecond(void) /* * Several things may have gone wrong here that have to be checked for. * (1) The performance counter may have jumped. - * (2) The system clock may have been reset. - */ - - /* - * We want to adjust things so that time appears to be continuous. - * Virtual file time, right now, is vt0. - * - * Ideally, we would like to drift the clock into place over a period of 2 - * sec, so that virtual time 2 sec from now will be - * - * vt1 = 10000000 + curFileTime - * - * The frequency that we need to use to drift the counter back into place - * is estFreq * 10000000 / (vt1 - vt0) - * - * If we've gotten more than a second away from system time, then drifting - * the clock is going to be pretty hopeless. Just let it jump. Otherwise, - * compute the drift frequency and fill in everything. + * (2) The system clock may have been reset. Try to compensate rather + * with adjustment of variance as of frequency. */ if (tdiff > 10000000 || tdiff < -10000000) { /* More as a second difference, so could be a time-switch (reset) /* jump to current system time, use curent estimated frequency */ - vt0 = curFileTime; timeInfo.lastUsedTime = 0; /* reset last used time */ + estFreq = timeInfo.nominalFreq; estVariance = 0; + vt0 = curFileTime; } else { + int repeatCnt = 2; + + estVariance = timeInfo.lastCC.cntrVariance; + estFreq = timeInfo.lastCC.estFrequency; + + /* Check nominal frequency would be better choice (nearby to curFileTime) */ + if ((tdiff >= 0 && vt1 < vt0) || (tdiff < 0 && vt1 > vt0)) { + estFreq = (estFreq + timeInfo.nominalFreq * 3) / 4; + } + + /* We want reduce tdiff, so slow drift to the time between vt0 and curFileTime */ + vt0 -= tdiff * 2 / 3; + + /* - * Estimate current frequency corresponding current time / counter. + * We want to adjust things so that time appears to be continuous. + * Virtual file time, right now, is vt0. + * + * Ideally, we would like to drift the clock into place over a period of 2 + * sec, so that virtual time 2 sec from now will be + * + * vt1 = 10000000 + curFileTime + * + * The frequency that we need to use to drift the counter back into place + * is estFreq * 10000000 / (vt1 - vt0) + * + * If we've gotten more than a second away from system time, then drifting + * the clock is going to be pretty hopeless. Just let it jump. Otherwise, + * compute the drift frequency and fill in everything. + */ + + repeatEstimate: + + /* + * Estimate current variance corresponding current time / counter. */ - if ((curFileTime - timeInfo.lastCC.fileTime) > (10000000 / 2)) { - estVariance = (curFileTime - timeInfo.lastCC.fileTime) - * timeInfo.nominalFreq + vt1 = vt0 - timeInfo.lastCC.virtTime; /* time since last calibration */ + if (vt1 > (10000000 / 2)) { + estVariance = vt1 * estFreq - (curPerfCounter- timeInfo.lastCC.perfCounter) * 10000000; - } else { - estVariance = timeInfo.lastCC.cntrVariance; } /* @@ -1384,47 +1414,101 @@ UpdateTimeEachSecond(void) * last difference), with dual falling speed. This indicates better * choice of lastCC.cntrVariance. */ +#if 1 + if (lastDiff / tdiff >= 2 || lastDiff / tdiff <= -2) { + estVariance = timeInfo.lastCC.cntrVariance + + (estVariance - timeInfo.lastCC.cntrVariance) / 2; + } +#else if (tdiff > 0 && tdiff < lastDiff / 2 || tdiff < 0 && tdiff > lastDiff / 2) { //printf("-----***-----calibration minimize %I64d, %I64d\n", estFreq, lastDiff); - estVariance = (estVariance + timeInfo.lastCC.cntrVariance) / 2; + estVariance = (estVariance + timeInfo.lastCC.cntrVariance * 3) / 2; //printf("-----***-----calibration minimize %I64d, %I64d\n", estFreq, tdiff); } +#endif + printf("------**-----calibration estimated, tdiff: %I64d, ** %s ** cntrDiff:%I64d\n", tdiff, (estVariance > timeInfo.lastCC.cntrVariance) ? "^^^" : "vvv", (curPerfCounter - timeInfo.lastCC.perfCounter)); printf("------**-----calibration estimated %I64d, %I64d, %I64d, diff: %I64d\n", curFileTime, curPerfCounter, estVariance, estVariance - timeInfo.lastCC.cntrVariance); #if 1 /* - * Calculate new estimate drift variance to the next second + * Calculate new estimate drift variance to the next second using new + * estimated values and approximated counter driftPerfCounter. */ + driftVariance = estVariance * 2; vt1 = vt0 - timeInfo.lastCC.virtTime; if (vt1 > (10000000 / 2)) { - LONGLONG driftPerfCounter = curPerfCounter + + /* approximated counter in 1s from now */ + LONGLONG driftPerfCounter = curPerfCounter + (curPerfCounter - timeInfo.lastCC.perfCounter) / vt1 * (vt1 + 10000000); - vt1 = NativeCalc100NsTicks(curFileTime, + /* virtual time in 1s from now */ + vt1 = NativeCalc100NsTicks(vt0, curPerfCounter, estVariance, - driftPerfCounter); - driftVariance = (vt1 - vt0) * timeInfo.nominalFreq - //- estVariance + estFreq, driftPerfCounter); + /* new value of variance for this time */ + driftVariance = (vt1 - vt0) * estFreq - (driftPerfCounter - curPerfCounter) * 10000000; - } else { - driftVariance = estVariance * 2; } /* * Avoid too large drifts (only half of the current difference), * that allows also be more accurate (aspire to the smallest tdiff), * so then we can prolong calibration interval in such cases. */ - driftVariance = timeInfo.lastCC.cntrVariance + - (driftVariance - timeInfo.lastCC.cntrVariance) / 2; + driftVariance = estVariance + + (driftVariance - estVariance) / 2; printf("------**-----calibration cntrVariance: %I64d\n", timeInfo.lastCC.cntrVariance); printf("------**-----calibration estVariance: %I64d\n", estVariance); printf("------**-----calibration driftVariance:%I64d\n", driftVariance); + + /* + * Average between estimated, current and drifted variance, + * (do the soft drifting as possible). + */ + + if (repeatCnt != 1 && tdiff > -10000000 && tdiff < 10000000) { /* bypass time-switch */ + estVariance = (estVariance * 2 + timeInfo.lastCC.cntrVariance + driftVariance) / 4; + } else { + estVariance = (estVariance + driftVariance) / 2; + } + + + if (repeatCnt != 1) { + /* + * Estimate current frequency corresponding current time / counter. + */ + +#if 1 + vt1 = vt0 - timeInfo.lastCC.virtTime; +#else + vt1 = ((curFileTime - timeInfo.lastCC.fileTime) / 2 + + (vt0 - timeInfo.lastCC.virtTime) / 2); +#endif + printf("------**-----calibration vt1: %I64d, estFrequency: %I64d\n", vt1, estFreq); + if (vt1 > (10000000 / 2)) { + estFreq = ( (curPerfCounter - timeInfo.lastCC.perfCounter) * 10000000 + + estVariance ) / vt1; + + /* + * Minimize influence of estFreq if tdiff falls (in relation to + * last difference), with dual falling speed. This indicates better + * choice of lastCC.estFrequency. + */ + if (tdiff > 0 && tdiff < lastDiff / 2 || tdiff < 0 && tdiff > lastDiff / 2) { + //printf("-----***-----calibration minimize %I64d, %I64d\n", estFreq, lastDiff); + estFreq = (estFreq + timeInfo.lastCC.estFrequency * 2) / 3; + //printf("-----***-----calibration minimize %I64d, %I64d\n", estFreq, tdiff); + } + } else { + estFreq = timeInfo.lastCC.estFrequency; + } + printf("------**-----calibration estVariance: %I64d, estFrequency: %I64d\n", estVariance, estFreq); + /* * Average between estimated, 2 current and 5 drifted frequencies, * (do the soft drifting as possible). @@ -1432,22 +1516,43 @@ UpdateTimeEachSecond(void) */ #if 0 if (tdiff > 0 && tdiff < lastDiff / 2 || tdiff < 0 && tdiff > lastDiff / 2) { - estFreq = (1 * estFreq + 2 * timeInfo.lastCC.counterFreq + 5 * driftFreq) / 8; + estFreq = (1 * estFreq + 2 * timeInfo.lastCC.estFrequency + 5 * driftFreq) / 8; } else { - estFreq = (3 * estFreq + 3 * timeInfo.lastCC.counterFreq + 2 * driftFreq) / 8; + estFreq = (3 * estFreq + 3 * timeInfo.lastCC.estFrequency + 2 * driftFreq) / 8; } #else - estVariance = (estVariance + timeInfo.lastCC.cntrVariance + driftVariance) / 3; + estFreq = (estFreq + timeInfo.lastCC.estFrequency * 2) / 3; #endif #else - estVariance = (estVariance + timeInfo.lastCC.cntrVariance) / 2; + if (tdiff > -10000000 && tdiff < 10000000) { /* bypass time-switch */ + estVariance = (estVariance + timeInfo.lastCC.cntrVariance) / 2; + } #endif + + printf("------**-----calibration estVariance: %I64d, estFrequency: %I64d\n", estVariance, estFreq); + + /* + * Avoid too large discrepancy from nominal frequency (0.5%) + */ + if ( estFreq > (vt1 = (1000+5)*timeInfo.nominalFreq/1000) + || estFreq < (vt1 = (1000-5)*timeInfo.nominalFreq/1000) + ) { + /* too different */ + estFreq = vt1; + printf("************ too large: %I64d\n", estFreq); + } + + } + + if (--repeatCnt) { + goto repeatEstimate; + } } /* If possible backwards time-drifts (larger divider now) */ vt1 = 0; - if (estVariance < timeInfo.lastCC.cntrVariance) { + if (estVariance < timeInfo.lastCC.cntrVariance || estFreq > timeInfo.lastCC.estFrequency) { Tcl_WideInt nt0, nt1; /* @@ -1463,10 +1568,10 @@ UpdateTimeEachSecond(void) */ nt0 = NativeCalc100NsTicks(timeInfo.lastCC.virtTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.cntrVariance, - vt1); + timeInfo.lastCC.estFrequency, vt1); nt1 = NativeCalc100NsTicks(vt0, curPerfCounter, estVariance, - vt1); + estFreq, vt1); vt1 = (nt0 - nt1); /* old time - new time */ if (vt1 > 0 && vt1 < 10000000 /* bypass time-switch */) { /* base time should jump forwards (the same virtual time using current values) */ @@ -1491,6 +1596,7 @@ UpdateTimeEachSecond(void) timeInfo.lastCC.fileTime = curFileTime; timeInfo.lastCC.virtTime = vt0; timeInfo.lastCC.cntrVariance = estVariance; + timeInfo.lastCC.estFrequency = estFreq; timeInfo.lastCC.calibNextTime = curFileTime + calibrationInterv; InterlockedIncrement(&timeInfo.calibEpoch); @@ -1500,7 +1606,7 @@ UpdateTimeEachSecond(void) //printf("-------------calibration adj -- nt1:%I64d - nt0:%I64d: adj: %I64d\n", nt1, nt0, vt1); printf("-------------calibration end, tdiff %I64d, jump -- vt:%I64d - st:%I64d: %I64d, adj: %I64d\n", tdiff, vt0, curFileTime, (vt0 - curFileTime), vt1); - printf("-------------calibration end , new-struct: %I64d, %I64d, %I64d / %I64d\n", timeInfo.lastCC.virtTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.cntrVariance, timeInfo.nominalFreq); + printf("-------------calibration end , new-struct: %I64d, %I64d, %I64d / %I64d\n", timeInfo.lastCC.virtTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.cntrVariance, timeInfo.lastCC.estFrequency); #endif } -- cgit v0.12 From 40b1d707436dba5c7e8fbcb877cd213d2eb509d0 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:29:29 +0000 Subject: win: calibration cycle completely rewritten (no calibration thread needed, soft drifts within 250ms intervals, fewer discrepancy and fewer virtual time gradation, etc). todo: implement resetting timer-resolution to original value (without calibration thread now). --- win/tclWinNotify.c | 2 +- win/tclWinTime.c | 833 ++++++++++++++--------------------------------------- 2 files changed, 222 insertions(+), 613 deletions(-) diff --git a/win/tclWinNotify.c b/win/tclWinNotify.c index f92fe2f..04d32a3 100644 --- a/win/tclWinNotify.c +++ b/win/tclWinNotify.c @@ -890,7 +890,7 @@ Tcl_Sleep( for (;;) { - (*tclScaleTimeProcPtr) (&vdelay, tclTimeClientData); + tclScaleTimeProcPtr(&vdelay, tclTimeClientData); /* No wait if sleep time too small (because windows may wait too long) */ if (!vdelay.sec && vdelay.usec < (long)timerResolution.minDelay) { diff --git a/win/tclWinTime.c b/win/tclWinTime.c index e956175..3a2ba8d 100644 --- a/win/tclWinTime.c +++ b/win/tclWinTime.c @@ -36,22 +36,51 @@ typedef struct ThreadSpecificData { static Tcl_ThreadDataKey dataKey; /* - * The following values are used for calculating virtual time. Virtual + * The following structure used for calculating virtual time. Virtual * time is always equal to: - * virtTime + (currentPerfCounter - perfCounter + cntrVariance) + * virtTimeBase + (currentPerfCounter - perfCounter) * * 10000000 / nominalFreq */ typedef struct TimeCalibInfo { - ULONGLONG fileTime; /* Last real time (in 100-ns) */ - ULONGLONG virtTime; /* Last virtual time (in 100-ns) */ - LONGLONG perfCounter; /* QPC value of last calibration time */ - LONGLONG cntrVariance; /* Current calculated deviation (compensation) */ - LONGLONG estFrequency; /* Current estimated frequency */ - Tcl_WideInt calibNextTime; /* Next time of calibration (in 100-ns ticks) */ + LONGLONG perfCounter; /* QPC value of last calibrated virtual time */ + Tcl_WideInt virtTimeBase; /* Last virtual time base (in 100-ns) */ + Tcl_WideInt sysTime; /* Last real system time (in 100-ns), + truncated to VT_SYSTMR_DIST (100ms) */ } TimeCalibInfo; +/* Milliseconds <-> 100-ns ticks */ +#define MsToT100ns(ms) (ms * 10000) +#define T100nsToMs(ms) (ms / 10000) +/* Microseconds <-> 100-ns ticks */ +#define UsToT100ns(ms) (ms * 10) +#define T100nsToUs(ms) (ms / 10) + + /* - * Data for managing high-resolution timers. + * Use factor 1000 for the frequencies of QPC if it ascertained in Hz: + * frequency = nominal frequency / 1000 + * native perf-counter = original perf-counter * 1000 + */ +#ifndef TCL_VT_FREQ_FACTOR +# define TCL_VT_FREQ_FACTOR 1 +#endif + +/* Distance in ms to obtain system timer (avoids unneeded syscalls). */ +#define VT_SYSTMR_MIN_DIST 50 +/* Resolution distance of the system-timer in milliseconds, + * should be greater as real resolution (normally 15.6ms) to make more + * accurate approximated part of virtual time */ +#define VT_SYSTMR_DIST 250 +/* Max discrepancy of virtual time to system time. Time can slow drift + * to the drift distance (+/-5ms), if reached this distance relative + * current system time. + * Note: it should be greater as real timer-resolution (> 15.6ms). */ +#define VT_MAX_DISCREPANCY 20 +/* Max virtual time drift to shorten current distance */ +#define VT_MAX_DRIFT_TIME 4 + +/* + * Data for managing high-resolution timers (virtual time). */ typedef struct TimeInfo { @@ -60,31 +89,20 @@ typedef struct TimeInfo { * initialized. */ int perfCounterAvailable; /* Flag == 1 if the hardware has a performance * counter. */ - HANDLE calibrationThread; /* Handle to the thread that keeps the virtual - * clock calibrated. */ - HANDLE readyEvent; /* System event used to trigger the requesting - * thread when the clock calibration procedure - * is initialized for the first time. */ - HANDLE exitEvent; /* Event to signal out of an exit handler to - * tell the calibration loop to terminate. */ LONGLONG nominalFreq; /* Nominal frequency of the system performance * counter, that is, the value returned from * QueryPerformanceFrequency. */ +#if TCL_VT_FREQ_FACTOR int freqFactor; /* Frequency factor (1 - KHz, 1000 - Hz) */ +#endif LARGE_INTEGER posixEpoch; /* Posix epoch expressed as 100-ns ticks since * the windows epoch. */ - /* - * The following values are used for calculating virtual time. Virtual - * time is always equal to: - * virtTime + ( (currentPerfCounter - perfCounter) * 10000000 - * + cntrVariance) / nominalFreq - */ - - TimeCalibInfo lastCC; /* Last data updated in calibration cycle */ - volatile LONG calibEpoch; /* Calibration epoch */ + TimeCalibInfo lastCI; /* Last virtual timer-data updated in the + * calibration process. */ + volatile LONG lastCIEpoch; /* Calibration epoch (increased each 100ms) */ - Tcl_WideInt lastUsedTime; /* Last known (caller) virtual time in 100-ns - * (used to avoid drifts after calibrate) */ + size_t lastUsedTime; /* Last known (caller) offset to virtual time + * (used to avoid back-drifts after calibrate) */ } TimeInfo; @@ -92,22 +110,19 @@ static TimeInfo timeInfo = { { NULL, 0, 0, NULL, NULL, 0 }, 0, 0, - (HANDLE) NULL, - (HANDLE) NULL, - (HANDLE) NULL, (LONGLONG) 0, +#if TCL_VT_FREQ_FACTOR 1, /* for frequency in KHz */ +#endif #ifdef HAVE_CAST_TO_UNION (LARGE_INTEGER) (Tcl_WideInt) 0, #else {0, 0}, #endif { - (Tcl_WideInt) 0, - (ULONGLONG) 0, - (ULONGLONG) 0, (LONGLONG) 0, - (LONGLONG) 0 + (Tcl_WideInt) 0, + (Tcl_WideInt) 0, }, (LONG) 0, (Tcl_WideInt) 0 @@ -129,9 +144,6 @@ static struct { */ static struct tm * ComputeGMT(const time_t *tp); -static void StopCalibration(ClientData clientData); -static DWORD WINAPI CalibrationThread(LPVOID arg); -static void UpdateTimeEachSecond(void); static void NativeScaleTime(Tcl_Time* timebuf, ClientData clientData); static Tcl_WideInt NativeGetMicroseconds(void); @@ -162,7 +174,15 @@ static inline LONGLONG NativePerformanceCounter(void) { LARGE_INTEGER curCounter; QueryPerformanceCounter(&curCounter); - return (curCounter.QuadPart / timeInfo.freqFactor); +#if TCL_VT_FREQ_FACTOR + if (timeInfo.freqFactor == 1) { + return curCounter.QuadPart; /* no factor */ + } + /* defactoring counter */ + return curCounter.QuadPart / timeInfo.freqFactor; +#else + return curCounter.QuadPart; /* no factor configured */ +#endif } /* @@ -173,9 +193,8 @@ NativePerformanceCounter(void) { * Calculate the current system time in 100-ns ticks since posix epoch, * for current performance counter (curCounter), using given calibrated values. * - * vt = lastCC.virtTime - * + ( (curPerfCounter - lastCC.perfCounter) * 10000000 - * + lastCC.cntrVariance ) / nominalFreq + * vt = lastCI.virtTimeBase + * + (curCounter - lastCI.perfCounter) * 10000000 / nominalFreq * * Results: * Returns the wide integer with number of 100-ns ticks from the epoch. @@ -188,14 +207,16 @@ NativePerformanceCounter(void) { static inline Tcl_WideInt NativeCalc100NsTicks( - ULONGLONG ccVirtTime, - LONGLONG ccPerfCounter, - LONGLONG ccCntrVariance, - LONGLONG ccEstFrequency, + ULONGLONG ciVirtTimeBase, + LONGLONG ciPerfCounter, LONGLONG curCounter ) { - return ccVirtTime + ( (curCounter - ccPerfCounter) * 10000000 - + ccCntrVariance ) / ccEstFrequency; + curCounter -= ciPerfCounter; /* current distance */ + if (!curCounter) { + return ciVirtTimeBase; /* virtual time without offset */ + } + /* virtual time with offset */ + return ciVirtTimeBase + curCounter * 10000000 / timeInfo.nominalFreq; } /* @@ -394,10 +415,24 @@ TclpWideClickInMicrosec(void) Tcl_WideInt TclpGetMicroseconds(void) { +#if 0 + /* Use high resolution timer if possible */ + if (tclGetTimeProcPtr == NativeGetTime) { + return NativeGetMicroseconds(); + } else { + /* + * Use the Tcl_GetTime abstraction to get the time in microseconds, as + * nearly as we can, and return it. + */ + + Tcl_Time now; + + tclGetTimeProcPtr(&now, tclTimeClientData); /* Tcl_GetTime inlined */ + return (((Tcl_WideInt)now.sec) * 1000000) + now.usec; + } + +#else static Tcl_WideInt prevUS = 0; - static Tcl_WideInt fileTimeLastCall, perfCounterLastCall, curCntrVariance, prevEstFrequency; - static LONGLONG prevPerfCounter; - LONGLONG newPerfCounter; Tcl_WideInt usecSincePosixEpoch; @@ -409,9 +444,9 @@ TclpGetMicroseconds(void) } } else { /* - * Use the Tcl_GetTime abstraction to get the time in microseconds, as - * nearly as we can, and return it. - */ + * Use the Tcl_GetTime abstraction to get the time in microseconds, as + * nearly as we can, and return it. + */ Tcl_Time now; @@ -420,28 +455,14 @@ TclpGetMicroseconds(void) printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!no-native-ms!!!!!!!!!!!\n"); } - newPerfCounter = NativePerformanceCounter(); - if (prevUS && usecSincePosixEpoch < prevUS) { - printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!time-backwards!!!! pre-struct: %I64d, %I64d, %I64d, %I64d == %I64d \n", fileTimeLastCall, perfCounterLastCall, prevPerfCounter, curCntrVariance, - NativeCalc100NsTicks(fileTimeLastCall, - perfCounterLastCall, curCntrVariance, prevEstFrequency, - prevPerfCounter)); - printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!time-backwards!!!! new-struct: %I64d, %I64d, %I64d, %I64d == %I64d \n", timeInfo.lastCC.virtTime, timeInfo.lastCC.perfCounter, newPerfCounter, timeInfo.lastCC.cntrVariance, - NativeCalc100NsTicks(timeInfo.lastCC.virtTime, - timeInfo.lastCC.perfCounter, timeInfo.lastCC.cntrVariance, timeInfo.lastCC.estFrequency, - newPerfCounter)); printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!time-backwards!!!! prev: %I64d - now: %I64d (%I64d usec)\n", prevUS, usecSincePosixEpoch, usecSincePosixEpoch - prevUS); Tcl_Panic("Time running backwards!!!"); } prevUS = usecSincePosixEpoch; - fileTimeLastCall = timeInfo.lastCC.virtTime; - perfCounterLastCall = timeInfo.lastCC.perfCounter; - curCntrVariance = timeInfo.lastCC.cntrVariance; - prevEstFrequency = timeInfo.lastCC.estFrequency; - prevPerfCounter = newPerfCounter; return usecSincePosixEpoch; +#endif } /* @@ -565,8 +586,10 @@ NativeScaleTime( static Tcl_WideInt NativeGetMicroseconds(void) { + static size_t nomObtainSTPerfCntrDist = 0; + /* Nominal distance in perf-counter ticks to + * obtain system timer (avoids unneeded syscalls). */ Tcl_WideInt curTime; /* Current time in 100-ns ticks since epoch */ - Tcl_WideInt lastTime; /* Used to compare with last known time */ /* * Initialize static storage on the first trip through. @@ -594,13 +617,19 @@ NativeGetMicroseconds(void) if (timeInfo.nominalFreq == 0) { timeInfo.perfCounterAvailable = FALSE; } +#if TCL_VT_FREQ_FACTOR /* Some systems having frequency in Hz, so save the factor here */ if (timeInfo.nominalFreq >= 1000000000 && (timeInfo.nominalFreq % 1000) == 0) { - timeInfo.nominalFreq /= 1000; /* assume that frequency in Hz, factor used only for tolerance */ timeInfo.freqFactor = 1000; + timeInfo.nominalFreq /= timeInfo.freqFactor; } +#endif + /* Distance in perf-counter ticks for VT_SYSTMR_MIN_DIST (ms) */ + nomObtainSTPerfCntrDist = (size_t) + (timeInfo.nominalFreq * MsToT100ns(VT_SYSTMR_MIN_DIST)) + / 10000000; } /* @@ -680,30 +709,17 @@ NativeGetMicroseconds(void) #endif /* above code is Win32 only */ /* - * If the performance counter is available, start a thread to - * calibrate it. + * If the performance counter is available, initialize */ if (timeInfo.perfCounterAvailable) { - DWORD id; - InitializeCriticalSection(&timeInfo.cs); - timeInfo.readyEvent = CreateEvent(NULL, FALSE, FALSE, NULL); - timeInfo.exitEvent = CreateEvent(NULL, FALSE, FALSE, NULL); - timeInfo.calibrationThread = CreateThread(NULL, 256, - CalibrationThread, (LPVOID) NULL, 0, &id); - SetThreadPriority(timeInfo.calibrationThread, - THREAD_PRIORITY_HIGHEST); - /* - * Wait for the thread just launched to start running, and - * create an exit handler that kills it so that it doesn't - * outlive unloading tclXX.dll - */ + timeInfo.lastCI.perfCounter = NativePerformanceCounter(); + timeInfo.lastCI.sysTime = + timeInfo.lastCI.virtTimeBase = GetSystemTimeAsVirtual(); + - WaitForSingleObject(timeInfo.readyEvent, INFINITE); - CloseHandle(timeInfo.readyEvent); - Tcl_CreateExitHandler(StopCalibration, (ClientData) NULL); } timeInfo.initialized = TRUE; } @@ -712,88 +728,144 @@ NativeGetMicroseconds(void) if (timeInfo.perfCounterAvailable) { - /* Copies with current data of calibration cycle */ - static TimeCalibInfo commonCC; - static volatile LONG calibEpoch; - TimeCalibInfo cc; - volatile LONG ccEpoch; - - LONGLONG curCounter; /* Current performance counter. */ + static LONGLONG lastObtainSTPerfCntr = 0; + /* Last perf-counter system timer was obtained. */ + TimeCalibInfo ci; /* Copy of common base/offset used to calc VT. */ + volatile LONG ciEpoch; /* Epoch of "ci", protecting this structure. */ + Tcl_WideInt sysTime, trSysTime; + /* System time and truncated (rounded) time. */ + LONGLONG curCounter; /* Current value of native QPC. */ /* * Try to acquire data without lock (same epoch at end of copy process). */ - do { - ccEpoch = calibEpoch; - memcpy(&cc, &commonCC, sizeof(cc)); - /* - * Hold time section locked as short as possible - */ - if (InterlockedCompareExchange(&timeInfo.calibEpoch, - calibEpoch, calibEpoch) != calibEpoch) { - EnterCriticalSection(&timeInfo.cs); - if (calibEpoch != timeInfo.calibEpoch) { - memcpy(&commonCC, &timeInfo.lastCC, sizeof(commonCC)); - } - calibEpoch = timeInfo.calibEpoch; - LeaveCriticalSection(&timeInfo.cs); - } - - } while (InterlockedCompareExchange(&timeInfo.calibEpoch, - ccEpoch, ccEpoch) != ccEpoch); - + ciEpoch = timeInfo.lastCIEpoch; + memcpy(&ci, &timeInfo.lastCI, sizeof(ci)); /* - * Query the performance counter and use it to calculate the current - * time. + * Lock on demand and hold time section locked as short as possible. */ - curCounter = NativePerformanceCounter(); - - /* Calibrated file-time is saved from posix in 100-ns ticks */ - curTime = NativeCalc100NsTicks(cc.virtTime, - cc.perfCounter, cc.cntrVariance, cc.estFrequency, curCounter); + if (InterlockedCompareExchange(&timeInfo.lastCIEpoch, + ciEpoch, ciEpoch) != ciEpoch) { + printf("**** not equal: %d != %d\n", ciEpoch, timeInfo.lastCIEpoch); + EnterCriticalSection(&timeInfo.cs); + if (ciEpoch != timeInfo.lastCIEpoch) { + memcpy(&ci, &timeInfo.lastCI, sizeof(ci)); + ciEpoch = timeInfo.lastCIEpoch; + } + LeaveCriticalSection(&timeInfo.cs); + } - /* Be sure the clock ticks never backwards (avoid backwards time-drifts) */ - if ( (lastTime = timeInfo.lastUsedTime) - && lastTime > curTime - && lastTime - curTime < 1000000 /* bypass time-switch (drifts only) */ + /* Query current performance counter. */ + curCounter = NativePerformanceCounter(); + + /* Avoid doing unneeded syscall too often */ + if ( curCounter >= lastObtainSTPerfCntr + && curCounter < lastObtainSTPerfCntr + nomObtainSTPerfCntrDist ) { - curTime = timeInfo.lastUsedTime; + goto calcVT; /* don't check system time (curCounter precise enough) */ } + lastObtainSTPerfCntr = curCounter; + /* Query non-precise system time */ + sysTime = GetSystemTimeAsVirtual(); /* - * If it appears to be more than 1 seconds since the last trip - * through the calibration loop, the performance counter may have - * jumped forward. (See MSDN Knowledge Base article Q274323 for a - * description of the hardware problem that makes this test - * necessary.) If the counter jumps, we don't want to use it directly. - * Instead, we must return system time. Eventually, the calibration - * loop should recover. + * Truncate non-precise part of the system time (to VT_SYSTMR_DIST ms) */ + trSysTime = sysTime; + trSysTime /= MsToT100ns(VT_SYSTMR_DIST); /* VT_SYSTMR_DIST ms (in 100ns)*/ + trSysTime *= MsToT100ns(VT_SYSTMR_DIST); + + /* + * If rounded system time is changed - recalibrate offsets/base values + */ + if (ci.sysTime != trSysTime) { /* next interval VT_SYSTMR_DIST ms */ + EnterCriticalSection(&timeInfo.cs); + if (ci.sysTime != trSysTime) { /* again in lock (done in other thread) */ + + /* + * Recalibration / Adjustment of base values. + */ + + Tcl_WideInt vt0; /* Desired virtual time */ + Tcl_WideInt tdiff; /* Time difference to the system time */ + Tcl_WideInt lastTime; /* Used to compare with last known time */ + + /* New desired virtual time using current base values */ + vt0 = NativeCalc100NsTicks(ci.virtTimeBase, ci.perfCounter, curCounter); + + tdiff = vt0 - sysTime; + /* If we can adjust offsets (not a jump to new system time) */ + if (MsToT100ns(-800) < tdiff && tdiff < MsToT100ns(800)) { + + /* Allow small drift if discrepancy larger as expected */ +//!!! printf("************* tdiff: %I64d\n", tdiff); + if (tdiff <= MsToT100ns(-VT_MAX_DISCREPANCY)) { + vt0 += MsToT100ns(VT_MAX_DRIFT_TIME); + } + else + if (tdiff >= MsToT100ns(VT_MAX_DISCREPANCY)) { + vt0 -= MsToT100ns(VT_MAX_DRIFT_TIME); + } + + /* + * Be sure the clock ticks never backwards (avoid backwards + * time-drifts). If time-reset (< 800ms) just use curent time + * (avoid time correction in such case). + */ + if ( (lastTime = (ci.virtTimeBase + timeInfo.lastUsedTime)) + && (lastTime -= vt0) > 0 /* offset to vt0 */ + && lastTime < MsToT100ns(800) /* bypass time-switch (drifts only) */ + ) { +//!!! printf("************* forwards 1: %I64d, last-time: %I64d, distance: %I64d\n", lastTime, vt0, (vt0 - trSysTime)); + vt0 += lastTime; /* hold on the time a bit */ +//!!! printf("************* forwards 1: %I64d, last-time: %I64d, distance: %I64d\n", lastTime, ci.virtTimeBase, (vt0 - trSysTime)); + } + } else { + /* + * The time-jump (reset or initial), we should use system time + * instead of virtual to recalibrate offsets (let the time jump). + */ + vt0 = sysTime; +//!!! printf("************* reset time: %I64d *****************\n", vt0); + } + + /* + * Adjustment of current base for virtual time. This will also + * prevent too large counter difference (resp. max distance ~ 100ms). + */ + ci.virtTimeBase = vt0; + ci.perfCounter = curCounter; + ci.sysTime = trSysTime; + /* base adjusted, so reset also last known offset */ + timeInfo.lastUsedTime = 0; + + /* Update global structure lastCI with new values */ + memcpy(&timeInfo.lastCI, &ci, sizeof(ci)); + /* Increase epoch, to inform all other threads about new data */ + InterlockedIncrement(&timeInfo.lastCIEpoch); + +//!!! printf("************* recalibrated: %I64d, %I64d adj. %I64d, distance: %I64d\n", vt0, ci.virtTimeBase, ci.perfCounter, (vt0 - trSysTime)); + + } /* end lock */ + LeaveCriticalSection(&timeInfo.cs); + } /* common info lastCI contains actual data */ - printf("********* %I64d\n", GetSystemTimeAsVirtual()); /* in 100-ns ticks */ + calcVT: + /* Calculate actual virtual time now using performance counter */ + curTime = NativeCalc100NsTicks(ci.virtTimeBase, ci.perfCounter, curCounter); - if (curTime < cc.calibNextTime + 10000000 /* 1 sec (in 100-ns ticks). */) { - /* save last used time */ - timeInfo.lastUsedTime = curTime; - return curTime / 10; - } - printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!calibration-error!!!! cur: %I64d - call: %I64d (%I64d) -- prev: %I64d - now: %I64d (%I64d)\n", curTime, cc.calibNextTime, cc.calibNextTime - curTime, cc.perfCounter, curCounter, curCounter - cc.perfCounter); + /* Save last used time (offset) and return virtual time */ + timeInfo.lastUsedTime = (size_t)(curTime - ci.virtTimeBase); + return T100nsToUs(curTime); /* 100-ns to microseconds */ } /* * High resolution timer is not available. */ + curTime = GetSystemTimeAsVirtual(); /* in 100-ns ticks */ - /* Be sure the clock ticks never backwards (avoid backwards time-drifts) */ - if ( (lastTime = timeInfo.lastUsedTime) - && lastTime > curTime - && lastTime - curTime < 1000000 /* bypass time-switch (drifts only) */ - ) { - curTime = timeInfo.lastUsedTime; - } - timeInfo.lastUsedTime = curTime; - return curTime / 10; + return T100nsToUs(curTime); /* 100-ns to microseconds */ } /* @@ -842,47 +914,6 @@ NativeGetTime( /* *---------------------------------------------------------------------- * - * StopCalibration -- - * - * Turns off the calibration thread in preparation for exiting the - * process. - * - * Results: - * None. - * - * Side effects: - * Sets the 'exitEvent' event in the 'timeInfo' structure to ask the - * thread in question to exit, and waits for it to do so. - * - *---------------------------------------------------------------------- - */ - -void TclWinResetTimerResolution(void); - -static void -StopCalibration( - ClientData unused) /* Client data is unused */ -{ - SetEvent(timeInfo.exitEvent); - - /* - * If Tcl_Finalize was called from DllMain, the calibration thread is in a - * paused state so we need to timeout and continue. - */ - - WaitForSingleObject(timeInfo.calibrationThread, 100); - CloseHandle(timeInfo.exitEvent); - CloseHandle(timeInfo.calibrationThread); - - /* - * Reset timer resolution (shutdown case) - */ - (void)TclWinResetTimerResolution(); -} - -/* - *---------------------------------------------------------------------- - * * TclpGetTZName -- * * Gets the current timezone string. @@ -1191,428 +1222,6 @@ ComputeGMT( /* *---------------------------------------------------------------------- * - * CalibrationThread -- - * - * Thread that manages calibration of the hi-resolution time derived from - * the performance counter, to keep it synchronized with the system - * clock. - * - * Parameters: - * arg - Client data from the CreateThread call. This parameter points to - * the static TimeInfo structure. - * - * Return value: - * None. This thread embeds an infinite loop. - * - * Side effects: - * At an interval of 1s, this thread performs virtual time discipline. - * - * Note: When this thread is entered, TclpInitLock has been called to - * safeguard the static storage. There is therefore no synchronization in the - * body of this procedure. - * - *---------------------------------------------------------------------- - */ - -static DWORD WINAPI -CalibrationThread( - LPVOID arg) -{ - DWORD waitResult; - - /* - * Get initial system time and performance counter. - */ - - timeInfo.lastCC.perfCounter = NativePerformanceCounter(); - timeInfo.lastCC.fileTime = timeInfo.lastCC.virtTime = GetSystemTimeAsVirtual(); - timeInfo.lastCC.estFrequency = timeInfo.nominalFreq; - - /* - * Calibrate first time and wake up the calling thread. - * When it wakes up, it will release the initialization lock. - */ - - if (timeInfo.perfCounterAvailable) { - UpdateTimeEachSecond(); - } - - SetEvent(timeInfo.readyEvent); - - /* - * Run the calibration once a second. - */ - - while (timeInfo.perfCounterAvailable) { - /* - * If the exitEvent is set, break out of the loop. - */ - - waitResult = WaitForSingleObjectEx(timeInfo.exitEvent, 1000, FALSE); - if (waitResult == WAIT_OBJECT_0) { - break; - } - UpdateTimeEachSecond(); - - /* - * Reset timer resolution if expected (check waiter count once per second) - */ - (void)TclWinResetTimerResolution(); - } - - /* lint */ - return (DWORD) 0; -} - -/* - *---------------------------------------------------------------------- - * - * UpdateTimeEachSecond -- - * - * Callback from the waitable timer in the clock calibration thread that - * updates system time. - * - * Parameters: - * info - Pointer to the static TimeInfo structure - * - * Results: - * None. - * - * Side effects: - * Performs virtual time calibration discipline. - * - *---------------------------------------------------------------------- - */ - -static void -UpdateTimeEachSecond(void) -{ - LONGLONG curPerfCounter; - /* Current value returned from - * NativePerformanceCounter. */ - static int calibrationInterv = 10000000; - /* Calibration interval in 100-ns ticks (starts from 1s) */ - Tcl_WideInt curFileTime; /* File time at the time this callback was - * scheduled. */ - LONGLONG estVariance, /* Estimated variance to compensate ipmact of */ - driftVariance, /* deviations of perfomance counters. */ - estFreq; /* Estimated frequency */ - Tcl_WideInt vt0; /* Tcl time right now. */ - Tcl_WideInt vt1; /* Interim virtual time used during adjustments */ - Tcl_WideInt tdiff, /* Difference between system clock and Tcl time. */ - lastDiff; /* Difference of last calibration. */ - - /* - * Sample system time (from posix epoch) and performance counter. - */ - - curFileTime = GetSystemTimeAsVirtual(); - curPerfCounter = NativePerformanceCounter(); - printf("-------------calibration start, prev-struct: %I64d, %I64d, %I64d / %I64d, pc-diff: %I64d\n", timeInfo.lastCC.fileTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.cntrVariance, timeInfo.lastCC.estFrequency, curPerfCounter - timeInfo.lastCC.perfCounter); - - /* - * Current virtual time (using average between last fileTime and virtTime): - * vt0 = (lastCC.fileTime + lastCC.virtTime) / 2 - * + ( (curPerfCounter - lastCC.perfCounter) * 10000000 - * + lastCC.cntrVariance) / lastCC.estFrequency - * vt1 = the same with nominalFreq - */ - vt0 = NativeCalc100NsTicks( - (timeInfo.lastCC.fileTime/2 + timeInfo.lastCC.virtTime/2), - timeInfo.lastCC.perfCounter, timeInfo.lastCC.cntrVariance, - timeInfo.lastCC.estFrequency, curPerfCounter); - - vt1 = NativeCalc100NsTicks( - (timeInfo.lastCC.fileTime/2 + timeInfo.lastCC.virtTime/2), - timeInfo.lastCC.perfCounter, timeInfo.lastCC.cntrVariance, - timeInfo.nominalFreq, curPerfCounter); - - /* Differences between virtual and real-time */ - tdiff = vt0 - curFileTime; - lastDiff = timeInfo.lastCC.virtTime - timeInfo.lastCC.fileTime; - if (tdiff >= 10000000 || tdiff <= -10000000) { - printf("---!!!!!!!---calibration ERR, tdiff %I64d\n", tdiff); - } - /* - * If calibration still not needed (check for possible time-switch). Note, that - * NativeGetMicroseconds checks calibNextTime also, be sure it does not overflow. - * Calibrate immediately if we've too large discrepancy to the real-time (15.6 ms). - */ -#if 1 - if ( curFileTime < timeInfo.lastCC.calibNextTime - (10000000/2) /* 0.5 sec (in 100-ns ticks). */ - && timeInfo.lastCC.calibNextTime - curFileTime < 10 * 10000000 /* max. 10 seconds in-between (time-switch?) */ - && tdiff > -10000 && tdiff < 10000 /* very small discrepancy (1ms) */ - ) { - /* again in next one second */ - printf("-------------calibration end, tdiff %I64d, *** not needed. (next in: %I64d) ------\n", tdiff, curFileTime, timeInfo.lastCC.calibNextTime, timeInfo.lastCC.calibNextTime - curFileTime); - lastDiff = tdiff; - return; - } -#endif - - /* - * Several things may have gone wrong here that have to be checked for. - * (1) The performance counter may have jumped. - * (2) The system clock may have been reset. Try to compensate rather - * with adjustment of variance as of frequency. - */ - - if (tdiff > 10000000 || tdiff < -10000000) { - /* More as a second difference, so could be a time-switch (reset) - /* jump to current system time, use curent estimated frequency */ - timeInfo.lastUsedTime = 0; /* reset last used time */ - estFreq = timeInfo.nominalFreq; - estVariance = 0; - vt0 = curFileTime; - } else { - - int repeatCnt = 2; - - estVariance = timeInfo.lastCC.cntrVariance; - estFreq = timeInfo.lastCC.estFrequency; - - /* Check nominal frequency would be better choice (nearby to curFileTime) */ - if ((tdiff >= 0 && vt1 < vt0) || (tdiff < 0 && vt1 > vt0)) { - estFreq = (estFreq + timeInfo.nominalFreq * 3) / 4; - } - - /* We want reduce tdiff, so slow drift to the time between vt0 and curFileTime */ - vt0 -= tdiff * 2 / 3; - - - /* - * We want to adjust things so that time appears to be continuous. - * Virtual file time, right now, is vt0. - * - * Ideally, we would like to drift the clock into place over a period of 2 - * sec, so that virtual time 2 sec from now will be - * - * vt1 = 10000000 + curFileTime - * - * The frequency that we need to use to drift the counter back into place - * is estFreq * 10000000 / (vt1 - vt0) - * - * If we've gotten more than a second away from system time, then drifting - * the clock is going to be pretty hopeless. Just let it jump. Otherwise, - * compute the drift frequency and fill in everything. - */ - - repeatEstimate: - - /* - * Estimate current variance corresponding current time / counter. - */ - - vt1 = vt0 - timeInfo.lastCC.virtTime; /* time since last calibration */ - if (vt1 > (10000000 / 2)) { - estVariance = vt1 * estFreq - - (curPerfCounter- timeInfo.lastCC.perfCounter) * 10000000; - } - - /* - * Minimize influence of estVariance if tdiff falls (in relation to - * last difference), with dual falling speed. This indicates better - * choice of lastCC.cntrVariance. - */ -#if 1 - if (lastDiff / tdiff >= 2 || lastDiff / tdiff <= -2) { - estVariance = timeInfo.lastCC.cntrVariance + - (estVariance - timeInfo.lastCC.cntrVariance) / 2; - } -#else - if (tdiff > 0 && tdiff < lastDiff / 2 || tdiff < 0 && tdiff > lastDiff / 2) { - //printf("-----***-----calibration minimize %I64d, %I64d\n", estFreq, lastDiff); - estVariance = (estVariance + timeInfo.lastCC.cntrVariance * 3) / 2; - //printf("-----***-----calibration minimize %I64d, %I64d\n", estFreq, tdiff); - } -#endif - - printf("------**-----calibration estimated, tdiff: %I64d, ** %s ** cntrDiff:%I64d\n", tdiff, (estVariance > timeInfo.lastCC.cntrVariance) ? "^^^" : "vvv", (curPerfCounter - timeInfo.lastCC.perfCounter)); - printf("------**-----calibration estimated %I64d, %I64d, %I64d, diff: %I64d\n", curFileTime, curPerfCounter, estVariance, estVariance - timeInfo.lastCC.cntrVariance); - -#if 1 - /* - * Calculate new estimate drift variance to the next second using new - * estimated values and approximated counter driftPerfCounter. - */ - - driftVariance = estVariance * 2; - vt1 = vt0 - timeInfo.lastCC.virtTime; - if (vt1 > (10000000 / 2)) { - - /* approximated counter in 1s from now */ - LONGLONG driftPerfCounter = curPerfCounter - + (curPerfCounter - timeInfo.lastCC.perfCounter) - / vt1 * (vt1 + 10000000); - - /* virtual time in 1s from now */ - vt1 = NativeCalc100NsTicks(vt0, - curPerfCounter, estVariance, - estFreq, driftPerfCounter); - /* new value of variance for this time */ - driftVariance = (vt1 - vt0) * estFreq - - (driftPerfCounter - curPerfCounter) * 10000000; - } - /* - * Avoid too large drifts (only half of the current difference), - * that allows also be more accurate (aspire to the smallest tdiff), - * so then we can prolong calibration interval in such cases. - */ - driftVariance = estVariance + - (driftVariance - estVariance) / 2; - - printf("------**-----calibration cntrVariance: %I64d\n", timeInfo.lastCC.cntrVariance); - printf("------**-----calibration estVariance: %I64d\n", estVariance); - printf("------**-----calibration driftVariance:%I64d\n", driftVariance); - - - /* - * Average between estimated, current and drifted variance, - * (do the soft drifting as possible). - */ - - if (repeatCnt != 1 && tdiff > -10000000 && tdiff < 10000000) { /* bypass time-switch */ - estVariance = (estVariance * 2 + timeInfo.lastCC.cntrVariance + driftVariance) / 4; - } else { - estVariance = (estVariance + driftVariance) / 2; - } - - - if (repeatCnt != 1) { - /* - * Estimate current frequency corresponding current time / counter. - */ - -#if 1 - vt1 = vt0 - timeInfo.lastCC.virtTime; -#else - vt1 = ((curFileTime - timeInfo.lastCC.fileTime) / 2 - + (vt0 - timeInfo.lastCC.virtTime) / 2); -#endif - printf("------**-----calibration vt1: %I64d, estFrequency: %I64d\n", vt1, estFreq); - if (vt1 > (10000000 / 2)) { - estFreq = ( (curPerfCounter - timeInfo.lastCC.perfCounter) * 10000000 - + estVariance ) / vt1; - - /* - * Minimize influence of estFreq if tdiff falls (in relation to - * last difference), with dual falling speed. This indicates better - * choice of lastCC.estFrequency. - */ - if (tdiff > 0 && tdiff < lastDiff / 2 || tdiff < 0 && tdiff > lastDiff / 2) { - //printf("-----***-----calibration minimize %I64d, %I64d\n", estFreq, lastDiff); - estFreq = (estFreq + timeInfo.lastCC.estFrequency * 2) / 3; - //printf("-----***-----calibration minimize %I64d, %I64d\n", estFreq, tdiff); - } - } else { - estFreq = timeInfo.lastCC.estFrequency; - } - printf("------**-----calibration estVariance: %I64d, estFrequency: %I64d\n", estVariance, estFreq); - - /* - * Average between estimated, 2 current and 5 drifted frequencies, - * (do the soft drifting as possible). - * Minimize influence if tdiff falls (in relation to last difference) - */ -#if 0 - if (tdiff > 0 && tdiff < lastDiff / 2 || tdiff < 0 && tdiff > lastDiff / 2) { - estFreq = (1 * estFreq + 2 * timeInfo.lastCC.estFrequency + 5 * driftFreq) / 8; - } else { - estFreq = (3 * estFreq + 3 * timeInfo.lastCC.estFrequency + 2 * driftFreq) / 8; - } -#else - estFreq = (estFreq + timeInfo.lastCC.estFrequency * 2) / 3; -#endif - -#else - if (tdiff > -10000000 && tdiff < 10000000) { /* bypass time-switch */ - estVariance = (estVariance + timeInfo.lastCC.cntrVariance) / 2; - } -#endif - - printf("------**-----calibration estVariance: %I64d, estFrequency: %I64d\n", estVariance, estFreq); - - /* - * Avoid too large discrepancy from nominal frequency (0.5%) - */ - if ( estFreq > (vt1 = (1000+5)*timeInfo.nominalFreq/1000) - || estFreq < (vt1 = (1000-5)*timeInfo.nominalFreq/1000) - ) { - /* too different */ - estFreq = vt1; - printf("************ too large: %I64d\n", estFreq); - } - - } - - if (--repeatCnt) { - goto repeatEstimate; - } - } - - /* If possible backwards time-drifts (larger divider now) */ - vt1 = 0; - if (estVariance < timeInfo.lastCC.cntrVariance || estFreq > timeInfo.lastCC.estFrequency) { - Tcl_WideInt nt0, nt1; - - /* - * Calculate the time using new calibration values (and compare with old), - * to avoid possible backwards drifts (adjust current base time). - * This should affect at least next 10 ticks. - */ - vt1 = curPerfCounter + 10; - /* - * Be sure the clock ticks never backwards (avoid it by negative drifting) - * just compare native time (in 100-ns) before and hereafter using - * previous/new calibrated values) and do a small adjustment - */ - nt0 = NativeCalc100NsTicks(timeInfo.lastCC.virtTime, - timeInfo.lastCC.perfCounter, timeInfo.lastCC.cntrVariance, - timeInfo.lastCC.estFrequency, vt1); - nt1 = NativeCalc100NsTicks(vt0, - curPerfCounter, estVariance, - estFreq, vt1); - vt1 = (nt0 - nt1); /* old time - new time */ - if (vt1 > 0 && vt1 < 10000000 /* bypass time-switch */) { - /* base time should jump forwards (the same virtual time using current values) */ - vt0 += vt1; - tdiff += vt1; - //////////////////////////////////////////estFreq = 10000000 * (vt0 - timeInfo.lastCC.perfCounter) / vt1; - } - } - - /* if still precise enough, grow calibration interval up to 10 seconds */ - if (tdiff < -100000 || tdiff > 100000 /* 10-ms */) { - /* too long drift - reset calibration interval to 1 second */ - calibrationInterv = 10000000; - } else if (calibrationInterv < 10*10000000) { - calibrationInterv += 10000000; - } - - /* In lock commit new values to timeInfo (hold lock as short as possible) */ - EnterCriticalSection(&timeInfo.cs); - - timeInfo.lastCC.perfCounter = curPerfCounter; - timeInfo.lastCC.fileTime = curFileTime; - timeInfo.lastCC.virtTime = vt0; - timeInfo.lastCC.cntrVariance = estVariance; - timeInfo.lastCC.estFrequency = estFreq; - timeInfo.lastCC.calibNextTime = curFileTime + calibrationInterv; - - InterlockedIncrement(&timeInfo.calibEpoch); - - LeaveCriticalSection(&timeInfo.cs); -#if 1 - //printf("-------------calibration adj -- nt1:%I64d - nt0:%I64d: adj: %I64d\n", nt1, nt0, vt1); - printf("-------------calibration end, tdiff %I64d, jump -- vt:%I64d - st:%I64d: %I64d, adj: %I64d\n", tdiff, - vt0, curFileTime, (vt0 - curFileTime), vt1); - printf("-------------calibration end , new-struct: %I64d, %I64d, %I64d / %I64d\n", timeInfo.lastCC.virtTime, timeInfo.lastCC.perfCounter, timeInfo.lastCC.cntrVariance, timeInfo.lastCC.estFrequency); -#endif -} - -/* - *---------------------------------------------------------------------- - * * TclpGmtime -- * * Wrapper around the 'gmtime' library function to make it thread safe. -- cgit v0.12 From 913dc38311eb8c0ecbf81444db4d7d0a9276c4e5 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:29:33 +0000 Subject: call TclWinResetTimerResolution at end of sleep resp. wait for event (no calibration thread anymore) --- win/tclWinNotify.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/win/tclWinNotify.c b/win/tclWinNotify.c index 04d32a3..6c7b3b7 100644 --- a/win/tclWinNotify.c +++ b/win/tclWinNotify.c @@ -417,13 +417,15 @@ static struct { LONG minDelay; /* Lowest delay by max resolution (in microsecs) */ LONG maxDelay; /* Highest delay by min resolution (in microsecs) */ size_t count; /* Waiter count (used to restore the resolution) */ + Tcl_WideInt timeToReset; /* Time to reset resolution (typically now + 5s) */ CRITICAL_SECTION cs; /* Mutex guarding this structure. */ } timerResolution = { -1, 15600 * TMR_RES_MICROSEC, 500 * TMR_RES_MICROSEC, 15600 * TMR_RES_MICROSEC, 0, 500, 15600, - 0 + 0, + (Tcl_WideInt)0x7FFFFFFFFFFFFFFFL }; /* @@ -532,7 +534,7 @@ SetTimerResolution( /* resolution unchanged (and counter not increased) */ return 0; } - + /* *---------------------------------------------------------------------- * @@ -560,22 +562,21 @@ RestoreTimerResolution( if (timerResolution.available > 0 && newResolution) { EnterCriticalSection(&timerResolution.cs); if (timerResolution.count-- <= 1) { - if (newResolution > timerResolution.resRes) { - timerResolution.resRes = newResolution; - }; + timerResolution.resRes = newResolution; + /* prolong time to reset resolution */ + timerResolution.timeToReset = TclpGetMicroseconds() + 1000000; } LeaveCriticalSection(&timerResolution.cs); } } - + /* *---------------------------------------------------------------------- * * TclWinResetTimerResolution -- * - * This is called by CalibrationThread to delayed reset of the timer - * resolution (once per second), if no more waiting workers using - * precise timer resolution. + * This is called to delayed reset (after 1 second) of the timer resolution + * to original value, if no more waiting workers using precise resolution. * * Results: * None. @@ -591,6 +592,7 @@ TclWinResetTimerResolution(void) { if ( timerResolution.available > 0 && timerResolution.count == 0 && timerResolution.resRes > timerResolution.curRes + && TclpGetMicroseconds() >= timerResolution.timeToReset ) { EnterCriticalSection(&timerResolution.cs); if (timerResolution.count == 0 && timerResolution.resRes > timerResolution.curRes) { @@ -598,6 +600,7 @@ TclWinResetTimerResolution(void) if (NtSetTimerResolution(timerResolution.resRes, TRUE, &curRes) == 0) { timerResolution.curRes = curRes; }; + timerResolution.timeToReset = 0x7FFFFFFFFFFFFFFFL; } LeaveCriticalSection(&timerResolution.cs); } @@ -692,6 +695,7 @@ Tcl_WaitForEvent( timeout = 0; goto wait; } + Sleep(0); goto peek; } @@ -810,8 +814,10 @@ Tcl_WaitForEvent( /* restore timer resolution */ if (actualResolution) { - RestoreTimerResolution(actualResolution); + RestoreTimerResolution(actualResolution); } + /* todo: move it to the service-thread (if available at some point) */ + TclWinResetTimerResolution(); return status; } @@ -945,6 +951,8 @@ Tcl_Sleep( if (actualResolution) { RestoreTimerResolution(actualResolution); } + /* todo: move it to the service-thread (if available at some point) */ + TclWinResetTimerResolution(); } /* -- cgit v0.12 From 62e00681cf398709d6a32eaa1ae0ccae3a5da9ef Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:29:42 +0000 Subject: interim commit: trying to resolve time-freezes with new facilities timeJump/timeJumpEpoch --- generic/tclEvent.c | 22 ++- generic/tclInt.h | 49 ++++- generic/tclInterp.c | 4 +- generic/tclTimer.c | 512 +++++++++++++++++++++++++++++++++++----------------- win/tclWinNotify.c | 61 +++++-- 5 files changed, 454 insertions(+), 194 deletions(-) diff --git a/generic/tclEvent.c b/generic/tclEvent.c index 05e3109..53668d0 100644 --- a/generic/tclEvent.c +++ b/generic/tclEvent.c @@ -1385,7 +1385,7 @@ Tcl_VwaitObjCmd( char *nameString; int optc = objc - 2; /* options count without cmd and varname */ double ms = -1; - Tcl_Time wakeup; + Tcl_Time lastNow, wakeup; long tolerance = 0; if (objc < 2) { @@ -1416,7 +1416,8 @@ Tcl_VwaitObjCmd( /* if timeout specified - create timer event or no-wait by 0ms */ if (ms != -1) { if (ms > 0) { - Tcl_GetTime(&wakeup); + Tcl_GetTime(&lastNow); + wakeup = lastNow; TclTimeAddMilliseconds(&wakeup, ms); #ifdef TMR_RES_TOLERANCE tolerance = (ms < 1000 ? ms : 1000) * @@ -1439,11 +1440,26 @@ Tcl_VwaitObjCmd( if (ms > 0) { Tcl_Time blockTime; Tcl_GetTime(&blockTime); + /* + * Note time can be switched backwards, certainly adjust end-time + * by possible time-jumps back. + */ + if (TCL_TIME_BEFORE(blockTime, lastNow)) { + /* backwards time-jump - simply shift wakeup-time */ + wakeup.sec -= (lastNow.sec - blockTime.sec); + wakeup.usec -= (lastNow.usec - blockTime.usec); + if (wakeup.usec < 0) { + wakeup.usec += 1000000; + wakeup.sec--; + } + } + /* calculate blocking time */ + lastNow = blockTime; blockTime.sec = wakeup.sec - blockTime.sec; blockTime.usec = wakeup.usec - blockTime.usec; if (blockTime.usec < 0) { - blockTime.sec--; blockTime.usec += 1000000; + blockTime.sec--; } /* be sure process at least one event */ if ( blockTime.sec < 0 diff --git a/generic/tclInt.h b/generic/tclInt.h index 7576a97..d270042 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -144,10 +144,12 @@ typedef int ptrdiff_t; */ #define TCL_PROMPT_EVENT (1 << 0) /* Mark immediate event */ +#define TCL_ABSTMR_EVENT (1 << 1) /* Mark absolute timer event (the time + * of TimerHandler is absolute). */ #define TCL_IDLE_EVENT (1 << 5) /* Mark idle event */ /* - * This structure used for handling of prompt timer events (without time to + * This structure used for handling of timer events (with or without time to * invoke, e. g. created with "after 0") or declared in a call to Tcl_DoWhenIdle * (created with "after idle"). All of the currently-active handlers are linked * together into corresponding list. @@ -173,7 +175,7 @@ typedef struct TimerEntry { typedef struct TimerHandler { - Tcl_Time time; /* When timer is to fire (if timer event). */ + Tcl_WideInt time; /* When timer is to fire (absolute/relative). */ Tcl_TimerToken token; /* Identifies handler so it can be deleted. */ struct TimerEntry entry; /* ExtraData */ @@ -2886,6 +2888,38 @@ MODULE_SCOPE double TclpWideClickInMicrosec(void); #endif MODULE_SCOPE Tcl_WideInt TclpGetMicroseconds(void); +/* + * Helper macros for working with times. TCL_TIME_BEFORE encodes how to write + * the ordering relation on (normalized) times, and TCL_TIME_DIFF_MS resp. + * TCL_TIME_DIFF_US compute the number of milliseconds or microseconds difference + * between two times. Both macros use both of their arguments multiple times, + * so make sure they are cheap and side-effect free. + * The "prototypes" for these macros are: + * + * static int TCL_TIME_BEFORE(Tcl_Time t1, Tcl_Time t2); + * static Tcl_WideInt TCL_TIME_DIFF_MS(Tcl_Time t1, Tcl_Time t2); + * static Tcl_WideInt TCL_TIME_DIFF_US(Tcl_Time t1, Tcl_Time t2); + */ + +#define TCL_TIME_BEFORE(t1, t2) \ + (((t1).sec<(t2).sec) || ((t1).sec==(t2).sec && (t1).usec<(t2).usec)) + +#define TCL_TIME_DIFF_MS(t1, t2) \ + (1000*((Tcl_WideInt)(t1).sec - (Tcl_WideInt)(t2).sec) + \ + ((long)(t1).usec - (long)(t2).usec)/1000) +#define TCL_TIME_DIFF_US(t1, t2) \ + (1000000*((Tcl_WideInt)(t1).sec - (Tcl_WideInt)(t2).sec) + \ + ((long)(t1).usec - (long)(t2).usec)) + +static inline void +TclTimeSetMilliseconds( + register Tcl_Time *timePtr, + register double ms +) { + timePtr->sec = (long)(ms / 1000); + timePtr->usec = (((long)ms) % 1000) * 1000 + (((long)(ms*1000)) % 1000); +} + static inline void TclTimeAddMilliseconds( register Tcl_Time *timePtr, @@ -2958,13 +2992,16 @@ MODULE_SCOPE int Tcl_ContinueObjCmd(ClientData clientData, MODULE_SCOPE void TclSetTimerEventMarker(int head); MODULE_SCOPE int TclServiceTimerEvents(void); MODULE_SCOPE int TclServiceIdleEx(int flags, int count); +MODULE_SCOPE TimerEntry* TclpCreateTimerHandlerEx( + Tcl_WideInt usec, + Tcl_TimerProc *proc, Tcl_TimerDeleteProc *deleteProc, + size_t extraDataSize, int flags); +MODULE_SCOPE Tcl_TimerToken TclCreateRelativeTimerHandler( + Tcl_Time *timeOffsPtr, Tcl_TimerProc *proc, + ClientData clientData); MODULE_SCOPE Tcl_TimerToken TclCreateAbsoluteTimerHandler( Tcl_Time *timePtr, Tcl_TimerProc *proc, ClientData clientData); -MODULE_SCOPE TimerEntry* TclCreateAbsoluteTimerHandlerEx( - Tcl_Time *timePtr, - Tcl_TimerProc *proc, Tcl_TimerDeleteProc *deleteProc, - size_t extraDataSize); MODULE_SCOPE TimerEntry* TclCreateTimerEntryEx( Tcl_TimerProc *proc, Tcl_TimerDeleteProc *deleteProc, size_t extraDataSize, int flags); diff --git a/generic/tclInterp.c b/generic/tclInterp.c index 5f1b958..b461f40 100644 --- a/generic/tclInterp.c +++ b/generic/tclInterp.c @@ -3730,8 +3730,8 @@ Tcl_LimitSetTime( nextMoment.sec++; nextMoment.usec -= 1000000; } - iPtr->limit.timeEvent = TclCreateAbsoluteTimerHandlerEx(&nextMoment, - TimeLimitCallback, TimeLimitDeleteCallback, 0); + iPtr->limit.timeEvent = TclCreateTimerHandlerEx(&nextMoment, + TimeLimitCallback, TimeLimitDeleteCallback, 0, TCL_ABSTMR_EVENT); iPtr->limit.timeEvent->clientData = interp; iPtr->limit.exceeded &= ~TCL_LIMIT_TIME; } diff --git a/generic/tclTimer.c b/generic/tclTimer.c index a7fd50b..9eaf944 100644 --- a/generic/tclTimer.c +++ b/generic/tclTimer.c @@ -60,17 +60,22 @@ typedef struct AfterAssocData { */ typedef struct { - TimerEntry *timerList; /* First event in queue. */ - TimerEntry *lastTimerPtr; /* Last event in queue. */ + Tcl_WideInt relTimerBase; /* Time base (offset) of the last known relative, + * timer, used to revert all events to the new + * base after possible time-jump (adjustment).*/ + TimerEntry *relTimerList; /* First event in queue of relative timers. */ + TimerEntry *relTimerTail; /* Last event in queue of relative timers. */ TimerEntry *promptList; /* First immediate event in queue. */ - TimerEntry *lastPromptPtr; /* Last immediate event in queue. */ + TimerEntry *promptTail; /* Last immediate event in queue. */ + TimerEntry *absTimerList; /* First event in queue of absolute timers. */ + TimerEntry *absTimerTail; /* Last event in queue of absolute timers. */ size_t timerListEpoch; /* Used for safe process of event queue (stop * the cycle after modifying of event queue) */ int lastTimerId; /* Timer identifier of most recently created - * timer. */ + * timer event. */ int timerPending; /* 1 if a timer event is in the queue. */ TimerEntry *idleList; /* First in list of all idle handlers. */ - TimerEntry *lastIdlePtr; /* Last in list (or NULL for empty list). */ + TimerEntry *idleTail; /* Last in list (or NULL for empty list). */ size_t timerGeneration; /* Used to fill in the "generation" fields of */ size_t idleGeneration; /* timer or idle structures. Increments each * time we place a new handler to queue inside, @@ -92,27 +97,6 @@ static Tcl_ThreadDataKey dataKey; ClientData2TimerEntry(ptr) /* - * Helper macros for working with times. TCL_TIME_BEFORE encodes how to write - * the ordering relation on (normalized) times, and TCL_TIME_DIFF_MS computes - * the number of milliseconds difference between two times. Both macros use - * both of their arguments multiple times, so make sure they are cheap and - * side-effect free. The "prototypes" for these macros are: - * - * static int TCL_TIME_BEFORE(Tcl_Time t1, Tcl_Time t2); - * static long TCL_TIME_DIFF_MS(Tcl_Time t1, Tcl_Time t2); - */ - -#define TCL_TIME_BEFORE(t1, t2) \ - (((t1).sec<(t2).sec) || ((t1).sec==(t2).sec && (t1).usec<(t2).usec)) - -#define TCL_TIME_DIFF_MS(t1, t2) \ - (1000*((Tcl_WideInt)(t1).sec - (Tcl_WideInt)(t2).sec) + \ - ((long)(t1).usec - (long)(t2).usec)/1000) -#define TCL_TIME_DIFF_US(t1, t2) \ - (1000000*((Tcl_WideInt)(t1).sec - (Tcl_WideInt)(t2).sec) + \ - ((long)(t1).usec - (long)(t2).usec)) - -/* * Prototypes for functions referenced only in this file: */ @@ -295,14 +279,14 @@ TimerExitProc( if (tsdPtr != NULL) { Tcl_DeleteEventSource(TimerSetupProc, TimerCheckProc, tsdPtr); - while ((tsdPtr->lastPromptPtr) != NULL) { - TclDeleteTimerEntry(tsdPtr->lastPromptPtr); + while ((tsdPtr->promptTail) != NULL) { + TclDeleteTimerEntry(tsdPtr->promptTail); } - while ((tsdPtr->lastTimerPtr) != NULL) { - TclDeleteTimerEntry(tsdPtr->lastTimerPtr); + while ((tsdPtr->absTimerTail) != NULL) { + TclDeleteTimerEntry(tsdPtr->absTimerTail); } - while ((tsdPtr->lastIdlePtr) != NULL) { - TclDeleteTimerEntry(tsdPtr->lastIdlePtr); + while ((tsdPtr->idleTail) != NULL) { + TclDeleteTimerEntry(tsdPtr->idleTail); } } } @@ -333,21 +317,19 @@ Tcl_CreateTimerHandler( ClientData clientData) /* Arbitrary data to pass to proc. */ { register TimerEntry *entryPtr; - Tcl_Time time; + Tcl_WideInt usec; /* - * Compute when the event should fire. + * Compute when the event should fire (avoid overflow). */ - Tcl_GetTime(&time); - time.sec += milliseconds/1000; - time.usec += (milliseconds%1000)*1000; - if (time.usec >= 1000000) { - time.usec -= 1000000; - time.sec += 1; + if (milliseconds < 0x7FFFFFFFFFFFFFFFL / 1000) { + usec = (Tcl_WideInt)milliseconds*1000; + } else { + usec = 0x7FFFFFFFFFFFFFFFL; } - entryPtr = TclCreateAbsoluteTimerHandlerEx(&time, proc, NULL, 0); + entryPtr = TclpCreateTimerHandlerEx(usec, proc, NULL, 0, 0); if (entryPtr == NULL) { return NULL; } @@ -359,31 +341,32 @@ Tcl_CreateTimerHandler( /* *-------------------------------------------------------------- * - * TclCreateAbsoluteTimerHandlerEx -- , TclCreateAbsoluteTimerHandler -- + * TclpCreateTimerHandlerEx -- * - * Arrange for a given function to be invoked at a particular time in the - * future. + * Arrange for a given function to be invoked at or in a particular time + * in the future (microseconds). * * Results: - * The return value is a handler entry or token of the timer event, which - * may be used to delete the event before it fires. + * The return value is a handler entry of the timer event, which may be + * used to access the event entry, e. g. delete the event before it fires. * * Side effects: - * When the time in timePtr has been reached, proc will be invoked + * When the time or offset in timePtr has been reached, proc will be invoked * exactly once. * *-------------------------------------------------------------- */ TimerEntry* -TclCreateAbsoluteTimerHandlerEx( - Tcl_Time *timePtr, /* Time to be invoked */ - Tcl_TimerProc *proc, /* Function to invoke */ - Tcl_TimerDeleteProc *deleteProc, /* Function to cleanup */ - size_t extraDataSize) +TclpCreateTimerHandlerEx( + Tcl_WideInt usec, /* Time to be invoked (absolute/relative) */ + Tcl_TimerProc *proc, /* Function to invoke */ + Tcl_TimerDeleteProc *deleteProc,/* Function to cleanup */ + size_t extraDataSize, /* Size of extra data to allocate */ + int flags) /* If TCL_ABSTMR_EVENT, time is absolute */ { register TimerEntry *entryPtr, *entryPtrPos; - register TimerHandler *timerPtr; + register TimerHandler *timerPtr, **tmrList, **tmrTail; ThreadSpecificData *tsdPtr; tsdPtr = InitTimer(); @@ -397,35 +380,49 @@ TclCreateAbsoluteTimerHandlerEx( * Fill in fields for the event. */ - memcpy((void *)&(timerPtr->time), (void *)timePtr, sizeof(*timePtr)); entryPtr->proc = proc; entryPtr->deleteProc = deleteProc; entryPtr->clientData = TimerEntry2ClientData(entryPtr); - entryPtr->flags = 0; + entryPtr->flags = flags & TCL_ABSTMR_EVENT; entryPtr->generation = tsdPtr->timerGeneration; tsdPtr->timerListEpoch++; /* signal-timer list was changed */ tsdPtr->lastTimerId++; timerPtr->token = (Tcl_TimerToken) INT2PTR(tsdPtr->lastTimerId); + if (flags & TCL_ABSTMR_EVENT) { + tmrList = &tsdPtr->absTimerList; + tmrTail = &tsdPte->absTimerTail; + } else { + Tcl_WideInt now = TclpGetMicroseconds(); + + tmrList = &tsdPtr->relTimerList; + tmrTail = &tsdPtr->relTimerTail; + if (tsdPtr->relTimerList) { + /* usec is relative current base (to now) */ + usec -= now - tsdPtr->relTimerBase; + } else { + tsdPtr->relTimerBase = now; + } + } + + timerPtr->time = usec; + /* * Add the event to the queue in the correct position * (ordered by event firing time). */ /* if before current first (e. g. "after 0" before first "after 1000") */ - if ( !(entryPtrPos = tsdPtr->timerList) - || TCL_TIME_BEFORE(timerPtr->time, - TimerEntry2TimerHandler(entryPtrPos)->time) + if ( !(entryPtrPos = *tmrList) + || usec < TimerEntry2TimerHandler(entryPtrPos)->time ) { /* splice to the head */ - TclSpliceInEx(entryPtr, - tsdPtr->timerList, tsdPtr->lastTimerPtr); + TclSpliceInEx(entryPtr, *tmrList, *tmrTail); } else { /* search from end as long as one with time before not found */ - for (entryPtrPos = tsdPtr->lastTimerPtr; entryPtrPos != NULL; + for (entryPtrPos = *tmrTail; entryPtrPos != NULL; entryPtrPos = entryPtrPos->prevPtr) { - if (!TCL_TIME_BEFORE(timerPtr->time, - TimerEntry2TimerHandler(entryPtrPos)->time)) { + if (usec >= TimerEntry2TimerHandler(entryPtrPos)->time) { break; } } @@ -436,19 +433,38 @@ TclCreateAbsoluteTimerHandlerEx( if ((entryPtr->nextPtr = entryPtrPos->nextPtr)) { entryPtrPos->nextPtr->prevPtr = entryPtr; } else { - tsdPtr->lastTimerPtr = entryPtr; + *tmrTail = entryPtr; } entryPtrPos->nextPtr = entryPtr; } else { /* unexpected case, but ... splice to the head */ - TclSpliceInEx(entryPtr, - tsdPtr->timerList, tsdPtr->lastTimerPtr); + TclSpliceInEx(entryPtr, *tmrList, *tmrTail); } } return entryPtr; } + +/* + *-------------------------------------------------------------- + * + * TclCreateAbsoluteTimerHandler -- + * + * Arrange for a given function to be invoked at a particular time in the + * future. + * + * Results: + * The return value is a token of the timer event, which + * may be used to delete the event before it fires. + * + * Side effects: + * When the time in timePtr has been reached, proc will be invoked + * exactly once. + * + *-------------------------------------------------------------- + */ + Tcl_TimerToken TclCreateAbsoluteTimerHandler( Tcl_Time *timePtr, @@ -456,8 +472,65 @@ TclCreateAbsoluteTimerHandler( ClientData clientData) { register TimerEntry *entryPtr; + Tcl_WideInt usec; + + /* + * Compute when the event should fire (avoid overflow). + */ - entryPtr = TclCreateAbsoluteTimerHandlerEx(timePtr, proc, NULL, 0); + if (timePtr->sec < 0x7FFFFFFFFFFFFFFFL / 1000000) { + usec = (((Tcl_WideInt)timePtr->sec) * 1000000) + timePtr->usec; + } else { + usec = 0x7FFFFFFFFFFFFFFFL; + } + + entryPtr = TclpCreateTimerHandlerEx(usec, proc, NULL, 0, TCL_ABSTMR_EVENT); + if (entryPtr == NULL) { + return NULL; + } + entryPtr->clientData = clientData; + + return TimerEntry2TimerHandler(entryPtr)->token; +} + +/* + *-------------------------------------------------------------- + * + * TclCreateRelativeTimerHandler -- + * + * Arrange for a given function to be invoked in a particular time offset + * in the future. + * + * Results: + * The return value is token of the timer event, which + * may be used to delete the event before it fires. + * + * Side effects: + * In contrary to absolute timer functions operate on relative time. + * + *-------------------------------------------------------------- + */ + +Tcl_TimerToken +TclCreateRelativeTimerHandler( + Tcl_Time *timePtr, + Tcl_TimerProc *proc, + ClientData clientData) +{ + register TimerEntry *entryPtr; + Tcl_WideInt usec; + + /* + * Compute when the event should fire (avoid overflow). + */ + + if (timePtr->sec < 0x7FFFFFFFFFFFFFFFL / 1000000) { + usec = (((Tcl_WideInt)timePtr->sec) * 1000000) + timePtr->usec; + } else { + usec = 0x7FFFFFFFFFFFFFFFL; + } + + entryPtr = TclpCreateTimerHandlerEx(usec, proc, NULL, 0, TCL_ABSTMR_EVENT); if (entryPtr == NULL) { return NULL; } @@ -496,7 +569,7 @@ Tcl_DeleteTimerHandler( return; } - for (entryPtr = tsdPtr->lastTimerPtr; + for (entryPtr = tsdPtr->absTimerTail; entryPtr != NULL; entryPtr = entryPtr->prevPtr ) { @@ -531,7 +604,8 @@ Tcl_DeleteTimerHandler( void TclDeleteTimerEntry( TimerEntry *entryPtr) /* Result previously returned by */ - /* TclCreateAbsoluteTimerHandlerEx or TclCreateTimerEntryEx. */ + /* TclCreateRelativeTimerHandlerEx, TclCreateAbsoluteTimerHandlerEx + * or TclCreateTimerEntryEx. */ { ThreadSpecificData *tsdPtr; @@ -542,15 +616,19 @@ TclDeleteTimerEntry( tsdPtr = InitTimer(); if (entryPtr->flags & TCL_PROMPT_EVENT) { - /* prompt handler */ - TclSpliceOutEx(entryPtr, tsdPtr->promptList, tsdPtr->lastPromptPtr); + /* prompt handler */ + TclSpliceOutEx(entryPtr, tsdPtr->promptList, tsdPtr->promptTail); } else if (entryPtr->flags & TCL_IDLE_EVENT) { - /* idle handler */ - TclSpliceOutEx(entryPtr, tsdPtr->idleList, tsdPtr->lastIdlePtr); + /* idle handler */ + TclSpliceOutEx(entryPtr, tsdPtr->idleList, tsdPtr->idleTail); } else { - /* timer event-handler */ + /* timer event-handler */ tsdPtr->timerListEpoch++; /* signal-timer list was changed */ - TclSpliceOutEx(entryPtr, tsdPtr->timerList, tsdPtr->lastTimerPtr); + if (entryPtr->flags & TCL_ABSTMR_EVENT) { + TclSpliceOutEx(entryPtr, tsdPtr->absTimerList, tsdPtr->absTimerTail); + } else e + TclSpliceOutEx(entryPtr, tsdPtr->relTimerList, tsdPtr->relTimerTail); + } } /* free it via deleteProc or ckfree */ @@ -559,13 +637,45 @@ TclDeleteTimerEntry( } if (entryPtr->flags & (TCL_PROMPT_EVENT|TCL_IDLE_EVENT)) { - ckfree((char *)entryPtr); + ckfree((char *)entryPtr); } else { - /* shift to the allocated pointer */ - ckfree((char *)TimerEntry2TimerHandler(entryPtr)); + /* shift to the allocated pointer */ + ckfree((char *)TimerEntry2TimerHandler(entryPtr)); } } +static Tcl_WideInt +TimerGetFirstTimeOffs( + ThreadSpecificData *tsdPtr, + TimerEntry **entryPtr) +{ + Tcl_WideInt firstTime = -0x7FFFFFFFFFFFFFFFL; + Tcl_WideInt now = TclpGetMicroseconds(); + + /* consider time-jump back */ + if (tsdPtr->relTimerList) { + if (now < tsdPtr->relTimerBase) { /* switched back */ + /* + * Because the real jump is unknown (resp. too complex to retrieve + * accross all threads), we simply accept possible small increment + * of the real wait-time. + */ + tsdPtr->relTimerBase = now; /* just shift the base back */ + } + firstTime = tsdPtr->relTimerBase + + TimerEntry2TimerHandler(tsdPtr->absTimerList)->time; + if (entryPtr) { *entryPtr = tsdPtr->relTimerBase; } + } + + if ( tsdPtr->absTimerList + && firstTime < TimerEntry2TimerHandler(tsdPtr->absTimerList)->time + ) { + firstTime = TimerEntry2TimerHandler(tsdPtr->absTimerList)->time; + if (entryPtr) { *entryPtr = tsdPtr->absTimerList; } + } + + return firstTime - now; +} /* *---------------------------------------------------------------------- * @@ -589,7 +699,7 @@ TimerSetupProc( ClientData data, /* Specific data. */ int flags) /* Event flags as passed to Tcl_DoOneEvent. */ { - Tcl_Time blockTime, *firstTime; + Tcl_Time blockTime; ThreadSpecificData *tsdPtr = (ThreadSpecificData *)data; long tolerance = 0; @@ -605,33 +715,34 @@ TimerSetupProc( blockTime.sec = 0; blockTime.usec = 0; - } else if ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerList) { + } else if ( + (flags & TCL_TIMER_EVENTS) + && (tsdPtr->relTimerList || tsdPtr->absTimerList) + ) { /* * Compute the timeout for the next timer on the list. */ - Tcl_GetTime(&blockTime); - firstTime = &(TimerEntry2TimerHandler(tsdPtr->timerList)->time); - blockTime.sec = firstTime->sec - blockTime.sec; - blockTime.usec = firstTime->usec - blockTime.usec; - if (blockTime.usec < 0) { - blockTime.sec--; - blockTime.usec += 1000000; - } - if (blockTime.sec < 0) { + Tcl_WideInt timeOffs = TimerGetFirstTimeOffs(tsdPtr, NULL); + + if (timeOffs > 0) { + blockTime.sec = (long) (timeOffs / 1000000); + blockTime.usec = (unsigned long) (timeOffs % 1000000); + + #ifdef TMR_RES_TOLERANCE + /* consider timer resolution tolerance (avoid busy wait) */ + tolerance = ((timeOffs <= 1000000) ? timeOffs : 1000000) * + TMR_RES_TOLERANCE / 100; + #endif + } else { blockTime.sec = 0; blockTime.usec = 0; } - #ifdef TMR_RES_TOLERANCE - /* consider timer resolution tolerance (avoid busy wait) */ - tolerance = ((blockTime.sec <= 0) ? blockTime.usec : 1000000) * - (TMR_RES_TOLERANCE / 100); - #endif /* * If the first timer has expired, stick an event on the queue right now. */ - if (!tsdPtr->timerPending && blockTime.sec == 0 && blockTime.usec <= tolerance) { + if (!tsdPtr->timerPending && timeOffs <= tolerance) { TclSetTimerEventMarker(0); tsdPtr->timerPending = 1; } @@ -665,8 +776,9 @@ TimerCheckProc( ClientData data, /* Specific data. */ int flags) /* Event flags as passed to Tcl_DoOneEvent. */ { - Tcl_Time blockTime, *firstTime; + Tcl_WideInt timeOffs; ThreadSpecificData *tsdPtr = (ThreadSpecificData *)data; + long tolerance = 0; if (!(flags & TCL_TIMER_EVENTS)) { return; @@ -675,26 +787,25 @@ TimerCheckProc( if (tsdPtr == NULL) { tsdPtr = InitTimer(); }; /* If already pending (or no timer-events) */ - if (tsdPtr->timerPending || !tsdPtr->timerList) { + if (tsdPtr->timerPending || !(tsdPtr->relTimerList || tsdPtr->absTimerList)) { return; } /* * Verify the first timer on the queue. */ - Tcl_GetTime(&blockTime); - firstTime = &(TimerEntry2TimerHandler(tsdPtr->timerList)->time); - blockTime.sec = firstTime->sec - blockTime.sec; - blockTime.usec = firstTime->usec - blockTime.usec; - if (blockTime.usec < 0) { - blockTime.sec--; - blockTime.usec += 1000000; - } - + timeOffs = TimerGetFirstTimeOffs(tsdPtr, NULL); + +#ifdef TMR_RES_TOLERANCE + /* consider timer resolution tolerance (avoid busy wait) */ + tolerance = ((timeOffs <= 1000000) ? timeOffs : 1000000) * + TMR_RES_TOLERANCE / 100; +#endif + /* * If the first timer has expired, stick an event on the queue. */ - if (blockTime.sec < 0 || (blockTime.sec == 0 && blockTime.usec <= 0)) { + if (timeOffs <= tolerance) { TclSetTimerEventMarker(0); tsdPtr->timerPending = 1; } @@ -765,7 +876,7 @@ TclServiceTimerEvents(void) && entryPtr->generation <= currentGeneration ) { /* detach entry from the owner's list */ - TclSpliceOutEx(entryPtr, tsdPtr->promptList, tsdPtr->lastPromptPtr); + TclSpliceOutEx(entryPtr, tsdPtr->promptList, tsdPtr->promptTail); /* reset current timer pending (correct process nested wait event) */ prevTmrPending = tsdPtr->timerPending; @@ -790,12 +901,12 @@ TclServiceTimerEvents(void) } /* Hereafter all timer events with time before now */ - if (!tsdPtr->timerList) { + if (!tsdPtr->absTimerList) { goto done; } Tcl_GetTime(&time); - for (entryPtr = tsdPtr->timerList; - entryPtr != NULL; + for (entryPtr = tsdPtr->absTimerList; + entryPtr != NULLe entryPtr = nextPtr ) { nextPtr = entryPtr->nextPtr; @@ -831,7 +942,7 @@ TclServiceTimerEvents(void) */ TclSpliceOutEx(entryPtr, - tsdPtr->timerList, tsdPtr->lastTimerPtr); + tsdPtr->absTimerLise, tsdPtr->absTimerTail); currentEpoch = tsdPtr->timerListEpoch; @@ -867,8 +978,8 @@ done: } /* Reset generation if both timer queue are empty */ - if (!tsdPtr->timerList) { - tsdPtr->timerGeneration = 0; + if (!tsdPtr->absTimerList) { + tsdPtr->timerGeneratioe = 0; } /* Compute the next timeout (later via TimerSetupProc using the first timer). */ @@ -924,7 +1035,7 @@ TclCreateTimerEntryEx( * call of "after 0" and "after 1" */ entryPtr->generation = tsdPtr->timerGeneration; /* attach to the prompt queue */ - TclSpliceTailEx(entryPtr, tsdPtr->promptList, tsdPtr->lastPromptPtr); + TclSpliceTailEx(entryPtr, tsdPtr->promptList, tsdPtr->promptTail); /* execute immediately: signal pending and set timer marker */ tsdPtr->timerPending++; @@ -933,7 +1044,7 @@ TclCreateTimerEntryEx( /* idle generation */ entryPtr->generation = tsdPtr->idleGeneration; /* attach to the idle queue */ - TclSpliceTailEx(entryPtr, tsdPtr->idleList, tsdPtr->lastIdlePtr); + TclSpliceTailEx(entryPtr, tsdPtr->idleList, tsdPtr->idleTail); } return entryPtr; @@ -1003,7 +1114,7 @@ Tcl_CancelIdleCall( if ((idlePtr->proc == proc) && (idlePtr->clientData == clientData)) { /* detach entry from the owner list */ - TclSpliceOutEx(idlePtr, tsdPtr->idleList, tsdPtr->lastIdlePtr); + TclSpliceOutEx(idlePtr, tsdPtr->idleList, tsdPtr->idleTail); /* free it via deleteProc and ckfree */ if (idlePtr->deleteProc) { @@ -1067,7 +1178,7 @@ TclServiceIdleEx( while (idlePtr->generation <= currentGeneration) { /* detach entry from the owner's list */ - TclSpliceOutEx(idlePtr, tsdPtr->idleList, tsdPtr->lastIdlePtr); + TclSpliceOutEx(idlePtr, tsdPtr->idleList, tsdPtr->idleTail); /* execute event */ (*idlePtr->proc)(idlePtr->clientData); @@ -1108,6 +1219,58 @@ TclServiceIdle(void) /* *---------------------------------------------------------------------- * + * TclGetTimeFromObj -- + * + * This function converts numeric tcl-object contains decimal milliseconds, + * (using milliseconds base) to time offset in microseconds, + * + * If input object contains double, the return time has microsecond + * precision. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * If possible leaves internal representation unchanged (e. g. integer). + * + *---------------------------------------------------------------------- + */ + +int +TclpGetUTimeFromObj( + Tcl_Interp *interp, /* Current interpreter or NULL. */ + Tcl_Obj *CONST objPtr, /* Object to read numeric time (in milliseconds). */ + Tcl_WideInt *timePtr) /* Resulting time if converted (in microseconds). */ +{ + if (objPtr->typePtr != &tclDoubleType) { + Tcl_WideInt ms; + if (Tcl_GetWideIntFromObj(NULL, objPtr, &ms) == TCL_OK) { + if (ms < 0x7FFFFFFFFFFFFFFFL / 1000) { /* avoid overflow */ + *timePtr = (ms * 1000); + return TCL_OK; + } + *timePtr = 0x7FFFFFFFFFFFFFFFL; + return TCL_OK; + } + } + if (1) { + double ms; + if (Tcl_GetDoubleFromObj(interp, objv[1], &ms) == TCL_OK) { + if (ms < 0x7FFFFFFFFFFFFFFFL / 1000) { /* avoid overflow */ + /* use precise as possible calculation by double (microseconds) */ + *timePtr = ((Tcl_WideInt)ms) * 1000 + (((long)(ms*1000)) % 1000); + return TCL_OK; + } + *timePtr = 0x7FFFFFFFFFFFFFFFL; + return TCL_OK; + } + } + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * * Tcl_AfterObjCmd -- * * This function is invoked to process the "after" Tcl command. See the @@ -1130,15 +1293,15 @@ Tcl_AfterObjCmd( int objc, /* Number of arguments. */ Tcl_Obj *CONST objv[]) /* Argument objects. */ { - double ms; /* Number of milliseconds to wait */ + Tcl_WideInt usec; /* Number of microseconds to wait (or time to wakeup) */ AfterInfo *afterPtr; AfterAssocData *assocPtr; int length; int index; static CONST char *afterSubCmds[] = { - "cancel", "idle", "info", NULL + "at", "cancel", "idle", "info", NULL }; - enum afterSubCmds {AFTER_CANCEL, AFTER_IDLE, AFTER_INFO}; + enum afterSubCmds {AFTER_AT, AFTER_CANCEL, AFTER_IDLE, AFTER_INFO}; ThreadSpecificData *tsdPtr = InitTimer(); if (objc < 2) { @@ -1167,39 +1330,58 @@ Tcl_AfterObjCmd( index = -1; if ( ( TclObjIsIndexOfTable(objv[1], afterSubCmds) - || Tcl_GetDoubleFromObj(NULL, objv[1], &ms) != TCL_OK + || TclpGetUTimeFromObj(NULL, objv[1], &usec) != TCL_OK ) && Tcl_GetIndexFromObj(NULL, objv[1], afterSubCmds, "", 0, &index) != TCL_OK ) { Tcl_AppendResult(interp, "bad argument \"", Tcl_GetString(objv[1]), - "\": must be cancel, idle, info, or an integer", NULL); + "\": must be at, cancel, idle, info, or a time", NULL); return TCL_ERROR; } /* - * At this point, either index = -1 and ms contains the number of ms + * At this point, either index = -1 and usec contains the time * to wait, or else index is the index of a subcommand. */ switch (index) { - case -1: { - TimerEntry *entryPtr; - if (ms < 0) { - ms = 0; + case -1: + /* usec already contains time-offset from objv[1] */ + /* relative time offset should be positive */ + if (usec < 0) { + usec = 0; } if (objc == 2) { - return AfterDelay(interp, ms); + /* after */ + return AfterDelay(interp, usec, 0); + } + case AFTER_AT: { + TimerEntry *entryPtr; + int flags = 0; + if (index == AFTER_AT) { + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "time"); + return TCL_ERROR; + } + /* get time from objv[2] */ + if (TclpGetUTimeFromObj(interp, objv[2], &usec) != TCL_OK) { + return TCL_ERROR; + } + if (objc == 3) { + /* after at