From 345ea34a7e160b62787cd3397d80dce03a4d1ee6 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:29:50 +0000 Subject: interim commit: trying to resolve time-freezes with new facilities timeJump/timeJumpEpoch --- generic/tclEvent.c | 67 +++++++-------- generic/tclInt.h | 11 +++ generic/tclInterp.c | 11 +-- generic/tclTimer.c | 239 +++++++++++++++++++++++++++++----------------------- unix/tclUnixTime.c | 28 ++++++ win/tclWinNotify.c | 212 ++++++++++++++++++++++------------------------ win/tclWinTime.c | 64 ++++++++++++-- 7 files changed, 362 insertions(+), 270 deletions(-) diff --git a/generic/tclEvent.c b/generic/tclEvent.c index 53668d0..9a20cc2 100644 --- a/generic/tclEvent.c +++ b/generic/tclEvent.c @@ -1384,9 +1384,10 @@ Tcl_VwaitObjCmd( int flags = TCL_ALL_EVENTS; /* default flags */ char *nameString; int optc = objc - 2; /* options count without cmd and varname */ - double ms = -1; - Tcl_Time lastNow, wakeup; + Tcl_WideInt usec = -1; + Tcl_WideInt lastNow = 0, wakeup = 0; long tolerance = 0; + size_t timeJumpEpoch = 0; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "?options? ?timeout? name"); @@ -1399,7 +1400,8 @@ Tcl_VwaitObjCmd( * we assume that option is not an integer, try to get numeric timeout */ if (!TclObjIsIndexOfTable(objv[optc], updateEventOptions) - && Tcl_GetDoubleFromObj(NULL, objv[optc], &ms) == TCL_OK) { + && TclpGetUTimeFromObj(NULL, objv[optc], &usec) == TCL_OK) { + if (usec < 0) { usec = 0; }; optc--; } @@ -1414,16 +1416,17 @@ Tcl_VwaitObjCmd( done = 0; /* if timeout specified - create timer event or no-wait by 0ms */ - if (ms != -1) { - if (ms > 0) { - Tcl_GetTime(&lastNow); - wakeup = lastNow; - TclTimeAddMilliseconds(&wakeup, ms); + if (usec != -1) { + if (usec > 0) { + lastNow = TclpGetMicroseconds(); + timeJumpEpoch = TclpGetLastTimeJumpEpoch(); #ifdef TMR_RES_TOLERANCE - tolerance = (ms < 1000 ? ms : 1000) * - (1000 * TMR_RES_TOLERANCE / 100); + tolerance = (usec < 1000000 ? usec : 1000000) * + TMR_RES_TOLERANCE / 100; + usec += tolerance / 3; #endif - } else if (ms == 0) { + wakeup = lastNow + usec; + } else { flags |= TCL_DONT_WAIT; } } @@ -1437,43 +1440,37 @@ Tcl_VwaitObjCmd( do { /* if wait - set blocking time */ - if (ms > 0) { + if (usec > 0) { Tcl_Time blockTime; - Tcl_GetTime(&blockTime); + Tcl_WideInt diff, now = TclpGetMicroseconds(); /* * 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--; - } + + if ( (diff = TclpGetLastTimeJump(&timeJumpEpoch)) != 0 + || (diff = (now - lastNow)) < 0 + ) { + /* recognized time-jump - simply shift wakeup-time */ + wakeup += diff; } /* calculate blocking time */ - lastNow = blockTime; - blockTime.sec = wakeup.sec - blockTime.sec; - blockTime.usec = wakeup.usec - blockTime.usec; - if (blockTime.usec < 0) { - blockTime.usec += 1000000; - blockTime.sec--; - } + lastNow = now; + diff = wakeup - now; + diff -= 1; /* overhead for Tcl_TraceVar / Tcl_UntraceVar */ /* be sure process at least one event */ - if ( blockTime.sec < 0 - || (blockTime.sec == 0 && blockTime.usec <= tolerance) - ) { + if (diff <= tolerance) { /* timeout occurs */ if (checktime) { done = -1; break; } /* expired, be sure non-negative values here */ - blockTime.usec = blockTime.sec = 0; + diff = 0; checktime = 1; } + blockTime.sec = diff / 1000000; + blockTime.usec = diff % 1000000; Tcl_SetMaxBlockTime(&blockTime); } if ((foundEvent = Tcl_DoOneEvent(flags)) <= 0) { @@ -1485,9 +1482,9 @@ Tcl_VwaitObjCmd( if (flags & TCL_DONT_WAIT) { foundEvent = 1; done = -2; - } else if (ms > 0 && foundEvent == 0) { + } else if (usec > 0 && foundEvent == 0) { foundEvent = 1; - } + } /* don't stop wait - no event expected here * (stop only on error case foundEvent < 0). */ if (foundEvent < 0) { @@ -1507,7 +1504,7 @@ Tcl_VwaitObjCmd( VwaitVarProc, (ClientData) &done); /* if timeout specified (and no errors) */ - if (ms != -1 && foundEvent > 0) { + if (usec != -1 && foundEvent > 0) { Tcl_Obj *objPtr; /* done - true, timeout false */ diff --git a/generic/tclInt.h b/generic/tclInt.h index d270042..fcb4752 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -2886,19 +2886,28 @@ MODULE_SCOPE double TclpWideClickInMicrosec(void); # endif # endif #endif +MODULE_SCOPE Tcl_WideInt TclpGetLastTimeJump(size_t *epoch); +MODULE_SCOPE size_t TclpGetLastTimeJumpEpoch(void); MODULE_SCOPE Tcl_WideInt TclpGetMicroseconds(void); +MODULE_SCOPE int TclpGetUTimeFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + Tcl_WideInt *timePtr); +MODULE_SCOPE Tcl_WideInt TclpScaleUTime(Tcl_WideInt usec); + +MODULE_SCOPE void TclpUSleep(Tcl_WideInt usec); /* * 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. + * Macro TCL_TIME_TO_USEC converts Tcl_Time to microseconds. * 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); + * static Tcl_WideInt TCL_TIME_TO_USEC(Tcl_Time t) */ #define TCL_TIME_BEFORE(t1, t2) \ @@ -2910,6 +2919,8 @@ MODULE_SCOPE Tcl_WideInt TclpGetMicroseconds(void); #define TCL_TIME_DIFF_US(t1, t2) \ (1000000*((Tcl_WideInt)(t1).sec - (Tcl_WideInt)(t2).sec) + \ ((long)(t1).usec - (long)(t2).usec)) +#define TCL_TIME_TO_USEC(t) \ + ((Tcl_WideInt)(t).sec * 1000000 + (t).usec) static inline void TclTimeSetMilliseconds( diff --git a/generic/tclInterp.c b/generic/tclInterp.c index b461f40..03a9fe2 100644 --- a/generic/tclInterp.c +++ b/generic/tclInterp.c @@ -3718,19 +3718,14 @@ Tcl_LimitSetTime( Tcl_Time *timeLimitPtr) { Interp *iPtr = (Interp *) interp; - Tcl_Time nextMoment; + Tcl_WideInt nextMoment; memcpy(&iPtr->limit.time, timeLimitPtr, sizeof(Tcl_Time)); if (iPtr->limit.timeEvent != NULL) { TclDeleteTimerEntry(iPtr->limit.timeEvent); } - nextMoment.sec = timeLimitPtr->sec; - nextMoment.usec = timeLimitPtr->usec+10; - if (nextMoment.usec >= 1000000) { - nextMoment.sec++; - nextMoment.usec -= 1000000; - } - iPtr->limit.timeEvent = TclCreateTimerHandlerEx(&nextMoment, + nextMoment = TCL_TIME_TO_USEC(*timeLimitPtr) + 10; + iPtr->limit.timeEvent = TclpCreateTimerHandlerEx(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 9eaf944..aad8ee3 100644 --- a/generic/tclTimer.c +++ b/generic/tclTimer.c @@ -60,8 +60,10 @@ typedef struct AfterAssocData { */ typedef struct { - Tcl_WideInt relTimerBase; /* Time base (offset) of the last known relative, - * timer, used to revert all events to the new + Tcl_WideInt knownTime; /* Last know time */ + size_t knownTimeJumpEpoch; /* Epoch of the last time-jump */ + Tcl_WideInt relTimerBase; /* Time base of the first 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. */ @@ -102,7 +104,8 @@ static Tcl_ThreadDataKey dataKey; static void AfterCleanupProc(ClientData clientData, Tcl_Interp *interp); -static int AfterDelay(Tcl_Interp *interp, double ms); +static int AfterDelay(Tcl_Interp *interp, Tcl_WideInt usec, + int absolute); static void AfterProc(ClientData clientData); static void FreeAfterPtr(ClientData clientData); static AfterInfo * GetAfterEvent(AfterAssocData *assocPtr, Tcl_Obj *objPtr); @@ -366,7 +369,8 @@ TclpCreateTimerHandlerEx( int flags) /* If TCL_ABSTMR_EVENT, time is absolute */ { register TimerEntry *entryPtr, *entryPtrPos; - register TimerHandler *timerPtr, **tmrList, **tmrTail; + TimerEntry **tmrList, **tmrTail; + register TimerHandler *timerPtr; ThreadSpecificData *tsdPtr; tsdPtr = InitTimer(); @@ -391,18 +395,32 @@ TclpCreateTimerHandlerEx( if (flags & TCL_ABSTMR_EVENT) { tmrList = &tsdPtr->absTimerList; - tmrTail = &tsdPte->absTimerTail; + tmrTail = &tsdPtr->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; - } + tmrList = &tsdPtr->relTimerList; + tmrTail = &tsdPtr->relTimerTail; + /* + * We should have the ability to ajust end-time of relative events, + * for possible time-jumps. + */ + if (tsdPtr->relTimerList) { + /* + * end-time = now + usec + * Adjust value of usec relative current base (to now), so + * end-time = base + relative event-time, which corresponds + * original end-time. + */ + Tcl_WideInt diff = TclpGetLastTimeJump(&tsdPtr->knownTimeJumpEpoch); + if (diff != 0) { /* jump recognized */ + tsdPtr->relTimerBase += diff; /* shift the base of relative events*/ + } + usec += now - tsdPtr->relTimerBase; + } else { + tsdPtr->knownTime = tsdPtr->relTimerBase = now; + tsdPtr->knownTimeJumpEpoch = TclpGetLastTimeJumpEpoch(); + } } timerPtr->time = usec; @@ -626,7 +644,7 @@ TclDeleteTimerEntry( tsdPtr->timerListEpoch++; /* signal-timer list was changed */ if (entryPtr->flags & TCL_ABSTMR_EVENT) { TclSpliceOutEx(entryPtr, tsdPtr->absTimerList, tsdPtr->absTimerTail); - } else e + } else { TclSpliceOutEx(entryPtr, tsdPtr->relTimerList, tsdPtr->relTimerTail); } } @@ -645,37 +663,51 @@ TclDeleteTimerEntry( } static Tcl_WideInt -TimerGetFirstTimeOffs( +TimerGetFirstTime( ThreadSpecificData *tsdPtr, + TimerEntry *relTimerList, + TimerEntry *absTimerList, + Tcl_WideInt now, TimerEntry **entryPtr) { Tcl_WideInt firstTime = -0x7FFFFFFFFFFFFFFFL; - Tcl_WideInt now = TclpGetMicroseconds(); - /* consider time-jump back */ - if (tsdPtr->relTimerList) { - if (now < tsdPtr->relTimerBase) { /* switched back */ + /* + * Consider time-jump back - if time jumped forwards, nothing to be done, + * because event will be executed early as specified. But for backwards + * jumps we should adjust relative base to avoid too long waiting for + * relative events. + */ + if (relTimerList) { + Tcl_WideInt diff; + if ( (diff = TclpGetLastTimeJump(&tsdPtr->knownTimeJumpEpoch)) != 0 + || (diff = (now - tsdPtr->knownTime)) < 0 /* switched back */ + ) { /* - * Because the real jump is unknown (resp. too complex to retrieve + * If 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 */ + tsdPtr->relTimerBase += diff; /* shift the base */ } + tsdPtr->knownTime = now; + /* end-time = base + relative event-time */ firstTime = tsdPtr->relTimerBase - + TimerEntry2TimerHandler(tsdPtr->absTimerList)->time; - if (entryPtr) { *entryPtr = tsdPtr->relTimerBase; } + + TimerEntry2TimerHandler(relTimerList)->time; + if (entryPtr) { *entryPtr = relTimerList; } } - if ( tsdPtr->absTimerList - && firstTime < TimerEntry2TimerHandler(tsdPtr->absTimerList)->time + if ( absTimerList + && firstTime < TimerEntry2TimerHandler(absTimerList)->time ) { - firstTime = TimerEntry2TimerHandler(tsdPtr->absTimerList)->time; - if (entryPtr) { *entryPtr = tsdPtr->absTimerList; } + /* end-time = absolute event-time */ + firstTime = TimerEntry2TimerHandler(absTimerList)->time; + if (entryPtr) { *entryPtr = absTimerList; } } - return firstTime - now; + return firstTime; } + /* *---------------------------------------------------------------------- * @@ -723,7 +755,9 @@ TimerSetupProc( * Compute the timeout for the next timer on the list. */ - Tcl_WideInt timeOffs = TimerGetFirstTimeOffs(tsdPtr, NULL); + Tcl_WideInt now = TclpGetMicroseconds(); + Tcl_WideInt timeOffs = TimerGetFirstTime(tsdPtr, + tsdPtr->relTimerList, tsdPtr->absTimerList, now, NULL) - now; if (timeOffs > 0) { blockTime.sec = (long) (timeOffs / 1000000); @@ -776,7 +810,7 @@ TimerCheckProc( ClientData data, /* Specific data. */ int flags) /* Event flags as passed to Tcl_DoOneEvent. */ { - Tcl_WideInt timeOffs; + Tcl_WideInt now, timeOffs; ThreadSpecificData *tsdPtr = (ThreadSpecificData *)data; long tolerance = 0; @@ -794,8 +828,10 @@ TimerCheckProc( /* * Verify the first timer on the queue. */ - timeOffs = TimerGetFirstTimeOffs(tsdPtr, NULL); - + now = TclpGetMicroseconds(); + timeOffs = TimerGetFirstTime(tsdPtr, + tsdPtr->relTimerList, tsdPtr->absTimerList, now, NULL) - now; + #ifdef TMR_RES_TOLERANCE /* consider timer resolution tolerance (avoid busy wait) */ tolerance = ((timeOffs <= 1000000) ? timeOffs : 1000000) * @@ -836,8 +872,8 @@ TimerCheckProc( int TclServiceTimerEvents(void) { - TimerEntry *entryPtr, *nextPtr; - Tcl_Time time, entrytm; + TimerEntry *entryPtr, *relTimerList, *absTimerList; + Tcl_WideInt now, entryTime; size_t currentGeneration, currentEpoch; int prevTmrPending; ThreadSpecificData *tsdPtr = InitTimer(); @@ -900,30 +936,28 @@ TclServiceTimerEvents(void) return -1; } - /* Hereafter all timer events with time before now */ - if (!tsdPtr->absTimerList) { - goto done; - } - Tcl_GetTime(&time); - for (entryPtr = tsdPtr->absTimerList; - entryPtr != NULLe - entryPtr = nextPtr - ) { - nextPtr = entryPtr->nextPtr; + /* Hereafter all relative and absolute timer events with time before now */ + relTimerList = tsdPtr->relTimerList; + absTimerList = tsdPtr->absTimerList; + while (relTimerList || absTimerList) + { + now = TclpGetMicroseconds(); + entryTime = TimerGetFirstTime(tsdPtr, relTimerList, absTimerList, now, &entryPtr); - 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--; - } + entryTime -= ((entryTime <= 1000000) ? entryTime : 1000000) * + TMR_RES_TOLERANCE / 100; #endif - if (TCL_TIME_BEFORE(time, entrytm)) { + if (now < entryTime) { break; } + /* Current list head to next entry */ + if (entryPtr == relTimerList) { + relTimerList = relTimerList->nextPtr; + } else { + absTimerList = absTimerList->nextPtr; + } /* * Bypass timers of newer generation. */ @@ -935,16 +969,19 @@ TclServiceTimerEvents(void) } tsdPtr->timerListEpoch++; /* signal-timer list was changed */ + currentEpoch = tsdPtr->timerListEpoch; /* * Remove the handler from the queue before invoking it, to avoid * potential reentrancy problems. */ - - TclSpliceOutEx(entryPtr, - tsdPtr->absTimerLise, tsdPtr->absTimerTail); - - currentEpoch = tsdPtr->timerListEpoch; + if (!(entryPtr->flags & TCL_ABSTMR_EVENT)) { + TclSpliceOutEx(entryPtr, + tsdPtr->relTimerList, tsdPtr->relTimerTail); + } else { + TclSpliceOutEx(entryPtr, + tsdPtr->absTimerList, tsdPtr->absTimerTail); + } /* reset current timer pending (correct process nested wait event) */ prevTmrPending = tsdPtr->timerPending; @@ -969,17 +1006,15 @@ TclServiceTimerEvents(void) } } -done: /* pending timer events, so mark (queue) timer events */ if (tsdPtr->timerPending > 1) { tsdPtr->timerPending = 1; - return -1; } /* Reset generation if both timer queue are empty */ - if (!tsdPtr->absTimerList) { - tsdPtr->timerGeneratioe = 0; + if (!tsdPtr->relTimerList && !tsdPtr->absTimerList) { + tsdPtr->timerGeneration = 0; } /* Compute the next timeout (later via TimerSetupProc using the first timer). */ @@ -1239,7 +1274,7 @@ TclServiceIdle(void) int TclpGetUTimeFromObj( Tcl_Interp *interp, /* Current interpreter or NULL. */ - Tcl_Obj *CONST objPtr, /* Object to read numeric time (in milliseconds). */ + Tcl_Obj *objPtr, /* Object to read numeric time (in milliseconds). */ Tcl_WideInt *timePtr) /* Resulting time if converted (in microseconds). */ { if (objPtr->typePtr != &tclDoubleType) { @@ -1255,7 +1290,7 @@ TclpGetUTimeFromObj( } if (1) { double ms; - if (Tcl_GetDoubleFromObj(interp, objv[1], &ms) == TCL_OK) { + if (Tcl_GetDoubleFromObj(interp, objPtr, &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); @@ -1563,16 +1598,16 @@ Tcl_AfterObjCmd( static int AfterDelay( Tcl_Interp *interp, - double ms, + Tcl_WideInt usec, int absolute) { Interp *iPtr = (Interp *) interp; - Tcl_Time endTime, now, lastNow; - Tcl_WideInt diff; + Tcl_WideInt endTime, now, lastNow, diff; long tolerance = 0; + size_t timeJumpEpoch = 0; - if (ms <= 0) { + if (usec <= 0) { /* to cause a context switch only */ Tcl_Sleep(0); return TCL_OK; @@ -1580,44 +1615,42 @@ AfterDelay( /* calculate possible maximal tolerance (in usec) of original wait-time */ #ifdef TMR_RES_TOLERANCE - tolerance = ((ms < 1000) ? ms : 1000) * (1000 * TMR_RES_TOLERANCE / 100); + tolerance = ((usec < 1000000) ? usec : 1000000) * TMR_RES_TOLERANCE / 100; #endif - Tcl_GetTime(&now); - lastNow = endTime = now; - if (absolute) - TclTimeAddMilliseconds(&endTime, ms); + lastNow = now = TclpGetMicroseconds(); + endTime = usec; + timeJumpEpoch = TclpGetLastTimeJumpEpoch(); + + if (!absolute) { + endTime += now; + if (endTime < now) { /* overflow */ + endTime = 0x7FFFFFFFFFFFFFFFL; + } + } do { - if (iPtr->limit.timeEvent != NULL - && TCL_TIME_BEFORE(iPtr->limit.time, now)) { + if ( iPtr->limit.timeEvent != NULL + && now > TCL_TIME_TO_USEC(iPtr->limit.time) + ) { iPtr->limit.granularityTicker = 0; if (Tcl_LimitCheck(interp) != TCL_OK) { return TCL_ERROR; } } - if (iPtr->limit.timeEvent == NULL - || TCL_TIME_BEFORE(endTime, iPtr->limit.time)) { - diff = TCL_TIME_DIFF_MS(endTime, now); -#ifndef TCL_WIDE_INT_IS_LONG - if (diff > LONG_MAX) { - diff = LONG_MAX; - } -#endif + if ( iPtr->limit.timeEvent == NULL + || endTime < (diff = TCL_TIME_TO_USEC(iPtr->limit.time)) + ) { + diff = endTime - now; if (diff > 0) { - Tcl_Sleep((long)diff); - Tcl_GetTime(&now); + TclpUSleep(diff); + now = TclpGetMicroseconds(); } } else { - diff = TCL_TIME_DIFF_MS(iPtr->limit.time, now); -#ifndef TCL_WIDE_INT_IS_LONG - if (diff > LONG_MAX) { - diff = LONG_MAX; - } -#endif + diff -= now; if (diff > 0) { - Tcl_Sleep((long)diff); - Tcl_GetTime(&now); + TclpUSleep(diff); + now = TclpGetMicroseconds(); } if (Tcl_LimitCheck(interp) != TCL_OK) { return TCL_ERROR; @@ -1626,24 +1659,20 @@ AfterDelay( /* * Note time can be switched backwards, certainly adjust end-time - * by possible time-jumps back. + * by possible time-jumps (for relative sleep). */ - if (!absolute && TCL_TIME_BEFORE(now, lastNow)) { - /* backwards time-jump - simply shift wakeup-time */ - endTime.sec -= (lastNow.sec - now.sec); - endTime.usec -= (lastNow.usec - now.usec); - if (endTime.usec < 0) { - endTime.usec += 1000000; - endTime.sec--; - } + if (!absolute + && ( (diff = TclpGetLastTimeJump(&timeJumpEpoch)) != 0 + || (diff = (now - lastNow)) < 0 + ) + ) { + /* recognized time-jump - simply shift wakeup-time */ + endTime += diff; } lastNow = now; /* consider timer resolution tolerance (avoid busy wait) */ - } while ( - (now.sec > endTime.sec) - || (now.sec == endTime.sec && now.usec >= endTime.usec - tolerance) - ); + } while (now < endTime); return TCL_OK; } diff --git a/unix/tclUnixTime.c b/unix/tclUnixTime.c index 1b4ea15..7f22fab 100644 --- a/unix/tclUnixTime.c +++ b/unix/tclUnixTime.c @@ -632,6 +632,34 @@ NativeScaleTime( /* *---------------------------------------------------------------------- * + * TclpScaleUTime -- + * + * This procedure scales number of microseconds if expected. + * + * Results: + * Number of microseconds scaled using tclScaleTimeProcPtr. + * + *---------------------------------------------------------------------- + */ +Tcl_WideInt +TclpScaleUTime( + Tcl_WideInt usec) +{ + /* Native scale is 1:1. */ + if (tclScaleTimeProcPtr != NativeScaleTime) { + return usec; + } else { + Tcl_Time scTime; + scTime.sec = usec / 1000000; + scTime.usec = usec % 1000000; + tclScaleTimeProcPtr(&scTime, tclTimeClientData); + return ((Tcl_WideInt)scTime.sec) * 1000000 + scTime.usec; + } +} + +/* + *---------------------------------------------------------------------- + * * NativeGetTime -- * * TIP #233: Gets the current system time in seconds and microseconds diff --git a/win/tclWinNotify.c b/win/tclWinNotify.c index d22373e..bd3b384 100644 --- a/win/tclWinNotify.c +++ b/win/tclWinNotify.c @@ -631,12 +631,13 @@ Tcl_WaitForEvent( { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); MSG msg; - DWORD timeout, result = WAIT_TIMEOUT; + DWORD timeout = INFINITE, result = WAIT_TIMEOUT; int status = 0; - Tcl_Time waitTime = {0, 0}; - Tcl_Time lastNow, endTime; + Tcl_WideInt waitTime = 0; + Tcl_WideInt lastNow = 0, endTime = 0; long tolerance = 0; unsigned long actualResolution = 0; + size_t timeJumpEpoch = 0; /* * Allow the notifier to be hooked. This may not make sense on windows, @@ -653,47 +654,44 @@ Tcl_WaitForEvent( if (timePtr) { - waitTime.sec = timePtr->sec; - waitTime.usec = timePtr->usec; + waitTime = timePtr->sec * 1000000 + timePtr->usec; /* if no wait */ - if (waitTime.sec <= 0 && waitTime.usec <= 0) { + if (waitTime <= 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); - lastNow = endTime; - endTime.sec += waitTime.sec; - endTime.usec += waitTime.usec; - if (endTime.usec > 1000000) { - endTime.usec -= 1000000; - endTime.sec++; - } + lastNow = TclpGetMicroseconds(); + endTime = lastNow + waitTime; + timeJumpEpoch = TclpGetLastTimeJumpEpoch(); if (timerResolution.available == -1) { InitTimerResolution(); } + #ifdef TMR_RES_TOLERANCE + /* calculate possible maximal tolerance (in usec) of original wait-time */ + tolerance = ((waitTime <= 1000000) ? waitTime : 1000000) * + TMR_RES_TOLERANCE / 100; + if (tolerance > timerResolution.maxDelay) { + tolerance = timerResolution.maxDelay; + } + #endif + repeat: /* * TIP #233 (Virtualized Time). Convert virtual domain delay to * real-time. */ - (*tclScaleTimeProcPtr) (&waitTime, tclTimeClientData); + waitTime = TclpScaleUTime(waitTime); /* No wait if timeout too small (because windows may wait too long) */ - if (!waitTime.sec && waitTime.usec < (long)timerResolution.minDelay) { + if (waitTime < (long)timerResolution.minDelay) { + timeout = 0; /* prevent busy wait */ - if (waitTime.usec >= 10) { - timeout = 0; + if (waitTime >= 10) { goto wait; } Sleep(0); @@ -701,25 +699,23 @@ Tcl_WaitForEvent( } if (timerResolution.available) { - 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; + long overhead = (tolerance < 100 ? tolerance/2 : 50); + Tcl_WideInt waitTimeWithOverhead = waitTime + overhead; + if (waitTimeWithOverhead > timerResolution.maxDelay) { + /* floor (truncate) using max delay as base (follow timeout better) */ + timeout = (waitTimeWithOverhead + / timerResolution.maxDelay) + * 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) { + if (waitTimeWithOverhead >= 10000) { res = 10000 * TMR_RES_MICROSEC; } else { res = 1000 * TMR_RES_MICROSEC; } - timeout = waitTime.usec / 1000; + timeout = waitTimeWithOverhead / 1000; /* set more precise timer resolution for minimal delay */ if (!actualResolution || res < timerResolution.curRes) { actualResolution = SetTimerResolution( @@ -727,11 +723,8 @@ Tcl_WaitForEvent( } } } else { - timeout = waitTime.sec * 1000 + waitTime.usec / 1000; + timeout = waitTime / 1000; } - - } else { - timeout = INFINITE; } /* @@ -796,31 +789,28 @@ Tcl_WaitForEvent( else if (result == WAIT_TIMEOUT && timeout != INFINITE) { /* Check the wait should be repeated, and correct time for wait */ - Tcl_Time now; + Tcl_WideInt now; - Tcl_GetTime(&now); + now = TclpGetMicroseconds(); /* * Note time can be switched backwards, certainly adjust end-time - * by possible time-jumps back. + * by possible time-jumps. */ - if (TCL_TIME_BEFORE(now, lastNow)) { - /* backwards time-jump - simply shift wakeup-time */ - endTime.sec -= (lastNow.sec - now.sec); - endTime.usec -= (lastNow.usec - now.usec); - if (endTime.usec < 0) { - endTime.usec += 1000000; - endTime.sec--; - } + if ((waitTime = TclpGetLastTimeJump(&timeJumpEpoch)) != 0) { + /* we know time-jump, adjust end-time using this offset */ + endTime += waitTime; + } + else + if ((waitTime = (now - lastNow)) < 0) { + /* recognized backwards time-jump - simply shift wakeup-time * + * considering timeout also, assume we've reached it completely */ + endTime += (waitTime - (timeout * 1000)); } lastNow = now; /* calculate new waitTime */ - 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 <= tolerance) { + waitTime = endTime - now; + if (waitTime <= tolerance) { goto end; } /* Repeat wait with more precise timer resolution (or using sleep) */ @@ -841,9 +831,11 @@ Tcl_WaitForEvent( /* *---------------------------------------------------------------------- * - * Tcl_Sleep -- + * Tcl_Sleep --, TclpUSleep -- * - * Delay execution for the specified number of milliseconds. + * Delay execution for the specified number of milliseconds (or microsec.). + * + * TclpUSleep in contrast to Tcl_Sleep is more precise (microseconds). * * Results: * None. @@ -858,6 +850,13 @@ void Tcl_Sleep( int ms) /* Number of milliseconds to sleep. */ { + TclpUSleep((Tcl_WideInt)ms * 1000); +} + +void +TclpUSleep( + Tcl_WideInt usec) /* Number of microseconds to sleep. */ +{ /* * Simply calling 'Sleep' for the requisite number of milliseconds can * make the process appear to wake up early because it isn't synchronized @@ -868,72 +867,67 @@ Tcl_Sleep( * requisite amount. */ - Tcl_Time lastNow, now; /* Current wall clock time. */ - Tcl_Time desired; /* Desired wakeup time. */ - Tcl_Time vdelay; /* Time to sleep, for scaling virtual -> - * real. */ + Tcl_WideInt lastNow, now; /* Current wall clock time. */ + Tcl_WideInt desired; /* Desired wakeup time. */ DWORD sleepTime; /* Time to sleep, real-time */ long tolerance = 0; unsigned long actualResolution = 0; + size_t timeJumpEpoch = 0; - if (ms <= 0) { - /* causes context switch only */ - Sleep(0); - return; + if (usec <= 9) { /* too short to start whole sleep process */ + do { + /* causes context switch only (shortest waiting) */ + Sleep(0); + } while (--usec > 0); + return; } if (timerResolution.available == -1) { InitTimerResolution(); } - vdelay.sec = ms / 1000; - vdelay.usec = (ms % 1000) * 1000; - - Tcl_GetTime(&now); - lastNow = now; - desired.sec = now.sec + vdelay.sec; - desired.usec = now.usec + vdelay.usec; - if (desired.usec > 1000000) { - desired.usec -= 1000000; - desired.sec++; - } + lastNow = now = TclpGetMicroseconds(); + desired = now + usec; + timeJumpEpoch = TclpGetLastTimeJumpEpoch(); #ifdef TMR_RES_TOLERANCE /* calculate possible maximal tolerance (in usec) of original wait-time */ - tolerance = ((vdelay.sec <= 0) ? vdelay.usec : 1000000) * - (TMR_RES_TOLERANCE / 100); + tolerance = ((usec <= 1000000) ? usec : 1000000) * + TMR_RES_TOLERANCE / 100; + if (tolerance > timerResolution.maxDelay) { + tolerance = timerResolution.maxDelay; + } #endif - /* - * TIP #233: Scale delay from virtual to real-time. - */ - for (;;) { - tclScaleTimeProcPtr(&vdelay, tclTimeClientData); + /* + * TIP #233: Scale delay from virtual to real-time. + */ + usec = TclpScaleUTime(usec); /* No wait if sleep time too small (because windows may wait too long) */ - if (!vdelay.sec && vdelay.usec < (long)timerResolution.minDelay) { + if (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; + if (usec > timerResolution.maxDelay) { + /* floor (truncate) using max delay as base (follow timeout better) */ + sleepTime = (usec + / timerResolution.maxDelay) + * 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) { + if (usec >= 10000) { res = 10000 * TMR_RES_MICROSEC; } else { res = 1000 * TMR_RES_MICROSEC; } - sleepTime = vdelay.usec / 1000; + sleepTime = usec / 1000; /* set more precise timer resolution for minimal delay */ if (!actualResolution || res < timerResolution.curRes) { actualResolution = SetTimerResolution( @@ -941,37 +935,29 @@ Tcl_Sleep( } } } else { - sleepTime = vdelay.sec * 1000 + vdelay.usec / 1000; + sleepTime = usec / 1000; } wait: Sleep(sleepTime); - Tcl_GetTime(&now); + now = TclpGetMicroseconds(); /* * Note time can be switched backwards, certainly adjust end-time - * by possible time-jumps back. + * by possible time-jumps. */ - if (TCL_TIME_BEFORE(now, lastNow)) { - /* backwards time-jump - simply shift wakeup-time */ - desired.sec -= (lastNow.sec - now.sec); - desired.usec -= (lastNow.usec - now.usec); - if (desired.usec < 0) { - desired.usec += 1000000; - desired.sec--; - } + if ((usec = TclpGetLastTimeJump(&timeJumpEpoch)) != 0) { + /* we know time-jump, adjust end-time using this offset */ + desired += usec; } - lastNow = now; - - vdelay.sec = desired.sec - now.sec; - vdelay.usec = desired.usec - now.usec; - if (vdelay.usec < 0) { - vdelay.usec += 1000000; - vdelay.sec--; + else + if ((usec = (now - lastNow)) < 0) { + /* recognized backwards time-jump - simply shift wakeup-time * + * considering sleep-time also, assume we've reached it completely */ + desired += (usec - (sleepTime * 1000)); } + lastNow = now; - if (vdelay.sec < 0) { - break; - } else if ((vdelay.sec == 0) && (vdelay.usec <= tolerance)) { + if ((usec = (desired - now)) <= tolerance) { break; } } diff --git a/win/tclWinTime.c b/win/tclWinTime.c index 3a2ba8d..4835bf1 100644 --- a/win/tclWinTime.c +++ b/win/tclWinTime.c @@ -45,15 +45,15 @@ typedef struct TimeCalibInfo { 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) */ + truncated to VT_SYSTMR_DIST (100ms) */ } TimeCalibInfo; /* Milliseconds <-> 100-ns ticks */ -#define MsToT100ns(ms) (ms * 10000) -#define T100nsToMs(ms) (ms / 10000) +#define MsToT100ns(ms) ((ms) * 10000) +#define T100nsToMs(ms) ((ms) / 10000) /* Microseconds <-> 100-ns ticks */ -#define UsToT100ns(ms) (ms * 10) -#define T100nsToUs(ms) (ms / 10) +#define UsToT100ns(ms) ((ms) * 10) +#define T100nsToUs(ms) ((ms) / 10) /* @@ -103,7 +103,8 @@ typedef struct TimeInfo { size_t lastUsedTime; /* Last known (caller) offset to virtual time * (used to avoid back-drifts after calibrate) */ - + size_t lastTimeJumpEpoch; /* Last known epoch since last time-jump. */ + Tcl_WideInt lastTimeJump; /* Last known time-jump of thread. */ } TimeInfo; static TimeInfo timeInfo = { @@ -415,7 +416,7 @@ TclpWideClickInMicrosec(void) Tcl_WideInt TclpGetMicroseconds(void) { -#if 0 +#if 1 /* Use high resolution timer if possible */ if (tclGetTimeProcPtr == NativeGetTime) { return NativeGetMicroseconds(); @@ -428,7 +429,7 @@ TclpGetMicroseconds(void) Tcl_Time now; tclGetTimeProcPtr(&now, tclTimeClientData); /* Tcl_GetTime inlined */ - return (((Tcl_WideInt)now.sec) * 1000000) + now.usec; + return TCL_TIME_TO_USEC(now); } #else @@ -563,6 +564,34 @@ NativeScaleTime( /* *---------------------------------------------------------------------- * + * TclpScaleUTime -- + * + * This procedure scales number of microseconds if expected. + * + * Results: + * Number of microseconds scaled using tclScaleTimeProcPtr. + * + *---------------------------------------------------------------------- + */ +Tcl_WideInt +TclpScaleUTime( + Tcl_WideInt usec) +{ + /* Native scale is 1:1. */ + if (tclScaleTimeProcPtr == NativeScaleTime) { + return usec; + } else { + Tcl_Time scTime; + scTime.sec = usec / 1000000; + scTime.usec = usec % 1000000; + tclScaleTimeProcPtr(&scTime, tclTimeClientData); + return ((Tcl_WideInt)scTime.sec) * 1000000 + scTime.usec; + } +} + +/* + *---------------------------------------------------------------------- + * * NativeGetMicroseconds -- * * Gets the current system time in microseconds since the beginning @@ -719,7 +748,7 @@ NativeGetMicroseconds(void) timeInfo.lastCI.sysTime = timeInfo.lastCI.virtTimeBase = GetSystemTimeAsVirtual(); - + timeInfo.lastTimeJumpEpoch = 1; /* let the caller know we've epoch */ } timeInfo.initialized = TRUE; } @@ -826,6 +855,8 @@ NativeGetMicroseconds(void) * The time-jump (reset or initial), we should use system time * instead of virtual to recalibrate offsets (let the time jump). */ + timeInfo.lastTimeJump = T100nsToUs(sysTime - vt0); /* 100-ns */; + timeInfo.lastTimeJumpEpoch++; vt0 = sysTime; //!!! printf("************* reset time: %I64d *****************\n", vt0); } @@ -1342,6 +1373,21 @@ Tcl_QueryTimeProc( } } +Tcl_WideInt +TclpGetLastTimeJump(size_t *epoch) +{ + if (timeInfo.lastTimeJumpEpoch > *epoch) { + *epoch = timeInfo.lastTimeJumpEpoch; + return timeInfo.lastTimeJump; + }; + return 0; +} +size_t +TclpGetLastTimeJumpEpoch(void) +{ + return timeInfo.lastTimeJumpEpoch; +} + /* * Local Variables: * mode: c -- cgit v0.12