diff options
-rw-r--r-- | ChangeLog | 54 | ||||
-rw-r--r-- | tests/winTime.test | 28 | ||||
-rw-r--r-- | win/tclWinNotify.c | 38 | ||||
-rw-r--r-- | win/tclWinTest.c | 79 | ||||
-rw-r--r-- | win/tclWinTime.c | 415 |
5 files changed, 603 insertions, 11 deletions
@@ -1,3 +1,57 @@ +2000-11-21 Andreas Kupries <a.kupries@westend.com> + + * All of the changes below are described in TIP #7 ~ Specification + and result from the application of the patch contained + therein. Creator of the patch is Kevin Kenny + <kennykb@crd.ge.com>. The patch used here is actually a bit + different. Two MS specific constant values (format FOOui64) were + replaced with a more portable formatting of the values and an + additional cast to LONGLONG. My cross-compiling gcc was unable to + process the original form. The SF Id of the patch is 102459. + + * tclWinTime.c: Add to the static data a set of variables that + manage the phase-locked techniques, including a + ''CRITICAL_SECTION'' to guard them so that multi-threaded code + is stable. + + * tclWinTime.c: Modify ''TclpGetSeconds'' to call ''TclpGetTime'' + and return the 'seconds' portion of the result. This change is + necessary to make sure that the two times are consistent near + the rollover from one second to another. + + * tclWinTime.c: Modify ''TclpGetClicks'' to use TclpGetTime to + determine the click count as a number of microseconds. + + * tclWinTime.c: Modify ''TclpGetTime'' to return the time as + M*Q+B, where Q is the result of ''QueryPerformanceCounter'', and + M and B are variables maintained by the phase-locked loop to + keep the result as close as possible to the system clock. The + ''TclpGetTime'' call will also launch the phase-lock management + in a separate thread the first time that it is invoked. If the + performance counter is unavailable, or if its frequency is not + one of the two common 8254-compatible rates, then + ''TclpGetTime'' will return the result of ''ftime'' as it does + in Tcl 8.3.2. + + * tclWinTime.c: Add the clock calibration procedure. The + calibration is somewhat complex; to save space, the reader is + referred to the reference implementation for the details of how + the time base and frequency are maintained. + + * tclWinNotify.c: Modify ''Tcl_Sleep'' to test that the process + has, in fact, slept for the requisite time by calling + ''TclpGetTime'' and comparing with the desired time. Otherwise, + roundoff errors may cause the process to awaken early. + + * tclWinTest.c: Add a ''testwinclock'' command. This command + returns a four element list comprising the seconds and + microseconds portions of the system clock and the seconds and + microseconds portions of the Tcl clock. + + * winTime.test: Add to the test suite a test that makes sure that + the Tcl clock stays within 1.1 ms of the system clock over the + duration of the test. + 2000-11-21 Donal K. Fellows <fellowsd@cs.man.ac.uk> * doc/global.n: diff --git a/tests/winTime.test b/tests/winTime.test index 6bcb4b7..a8dec89 100644 --- a/tests/winTime.test +++ b/tests/winTime.test @@ -10,7 +10,7 @@ # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # -# RCS: @(#) $Id: winTime.test,v 1.5 2000/04/10 17:19:06 ericm Exp $ +# RCS: @(#) $Id: winTime.test,v 1.6 2000/11/21 21:33:42 andreas_kupries Exp $ if {[lsearch [namespace children] ::tcltest] == -1} { package require tcltest @@ -33,6 +33,32 @@ test winTime-1.2 {TclpGetDate} {pcOnly} { set result } {1969} +# Next test tries to make sure that the Tcl clock stays in step +# with the Windows clock. 3000 iterations really isn't enough, +# but how many does a tester have patience for? + +test winTime-2.1 {Synchronization of Tcl and Windows clocks} {pcOnly} { + set failed 0 + foreach { sys_sec sys_usec tcl_sec tcl_usec } [testwinclock] {} + set olddiff [expr { abs ( $tcl_sec - $sys_sec + + 1.0e-6 * ( $tcl_usec - $sys_usec ) ) }] + set ok 1 + for { set i 0 } { $i < 3000 } { incr i } { + foreach { sys_sec sys_usec tcl_sec tcl_usec } [testwinclock] {} + set diff [expr { abs ( $tcl_sec - $sys_sec + + 1.0e-6 * ( $tcl_usec - $sys_usec ) ) }] + if { ( $diff > $olddiff + 1000 ) + || ( $diff > 11000 ) } { + set failed 1 + break + } else { + set olddiff $diff + after 1 + } + } + set failed +} {0} + # cleanup ::tcltest::cleanupTests return diff --git a/win/tclWinNotify.c b/win/tclWinNotify.c index d48cae2..fbaaa9b 100644 --- a/win/tclWinNotify.c +++ b/win/tclWinNotify.c @@ -10,7 +10,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclWinNotify.c,v 1.6 2000/06/13 20:30:23 ericm Exp $ + * RCS: @(#) $Id: tclWinNotify.c,v 1.7 2000/11/21 21:33:43 andreas_kupries Exp $ */ #include "tclWinInt.h" @@ -508,5 +508,39 @@ void Tcl_Sleep(ms) int ms; /* Number of milliseconds to sleep. */ { - Sleep((DWORD) ms); + /* + * Simply calling 'Sleep' for the requisite number of milliseconds + * can make the process appear to wake up early because it isn't + * synchronized with the CPU performance counter that is used in + * tclWinTime.c. This behavior is probably benign, but messes + * up some of the corner cases in the test suite. We get around + * this problem by repeating the 'Sleep' call as many times + * as necessary to make the clock advance by the requisite amount. + */ + + Tcl_Time now; /* Current wall clock time */ + Tcl_Time desired; /* Desired wakeup time */ + int sleepTime = ms; /* Time to sleep */ + + TclpGetTime( &now ); + desired.sec = now.sec + ( ms / 1000 ); + desired.usec = now.usec + 1000 * ( ms % 1000 ); + if ( desired.usec > 1000000 ) { + ++desired.sec; + desired.usec -= 1000000; + } + + for ( ; ; ) { + Sleep( sleepTime ); + TclpGetTime( &now ); + if ( now.sec > desired.sec ) { + break; + } else if ( ( now.sec == desired.sec ) + && ( now.usec >= desired.usec ) ) { + break; + } + sleepTime = ( ( 1000 * ( desired.sec - now.sec ) ) + + ( ( desired.usec - now.usec ) / 1000 ) ); + } + } diff --git a/win/tclWinTest.c b/win/tclWinTest.c index 07f198b..8d290a8 100644 --- a/win/tclWinTest.c +++ b/win/tclWinTest.c @@ -8,7 +8,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclWinTest.c,v 1.4 1999/10/29 03:05:13 hobbs Exp $ + * RCS: @(#) $Id: tclWinTest.c,v 1.5 2000/11/21 21:33:43 andreas_kupries Exp $ */ #include "tclWinInt.h" @@ -22,6 +22,10 @@ static int TesteventloopCmd _ANSI_ARGS_((ClientData dummy, static int TestvolumetypeCmd _ANSI_ARGS_((ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +static int TestwinclockCmd _ANSI_ARGS_(( ClientData dummy, + Tcl_Interp* interp, + int objc, + Tcl_Obj *CONST objv[] )); /* *---------------------------------------------------------------------- @@ -52,6 +56,8 @@ TclplatformtestInit(interp) (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); Tcl_CreateObjCommand(interp, "testvolumetype", TestvolumetypeCmd, (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); + Tcl_CreateObjCommand(interp, "testwinclock", TestwinclockCmd, + (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); return TCL_OK; } @@ -188,3 +194,74 @@ TestvolumetypeCmd(clientData, interp, objc, objv) return TCL_OK; #undef VOL_BUF_SIZE } + +/* + *---------------------------------------------------------------------- + * + * TestclockCmd -- + * + * Command that returns the seconds and microseconds portions of + * the system clock and of the Tcl clock so that they can be + * compared to validate that the Tcl clock is staying in sync. + * + * Usage: + * testclock + * + * Parameters: + * None. + * + * Results: + * Returns a standard Tcl result comprising a four-element list: + * the seconds and microseconds portions of the system clock, + * and the seconds and microseconds portions of the Tcl clock. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +TestwinclockCmd( ClientData dummy, + /* Unused */ + Tcl_Interp* interp, + /* Tcl interpreter */ + int objc, + /* Argument count */ + Tcl_Obj *CONST objv[] ) + /* Argument vector */ +{ + CONST static FILETIME posixEpoch = { 0xD53E8000, 0x019DB1DE }; + /* The Posix epoch, expressed as a + * Windows FILETIME */ + Tcl_Time tclTime; /* Tcl clock */ + FILETIME sysTime; /* System clock */ + Tcl_Obj* result; /* Result of the command */ + LARGE_INTEGER t1, t2; + + if ( objc != 1 ) { + Tcl_WrongNumArgs( interp, 1, objv, "" ); + return TCL_ERROR; + } + + TclpGetTime( &tclTime ); + GetSystemTimeAsFileTime( &sysTime ); + t1.LowPart = posixEpoch.dwLowDateTime; + t1.HighPart = posixEpoch.dwHighDateTime; + t2.LowPart = sysTime.dwLowDateTime; + t2.HighPart = sysTime.dwHighDateTime; + t2.QuadPart -= t1.QuadPart; + + result = Tcl_NewObj(); + Tcl_ListObjAppendElement + ( interp, result, Tcl_NewIntObj( (int) (t2.QuadPart / 10000000 ) ) ); + Tcl_ListObjAppendElement + ( interp, result, + Tcl_NewIntObj( (int) ( (t2.QuadPart / 10 ) % 1000000 ) ) ); + Tcl_ListObjAppendElement( interp, result, Tcl_NewIntObj( tclTime.sec ) ); + Tcl_ListObjAppendElement( interp, result, Tcl_NewIntObj( tclTime.usec ) ); + + Tcl_SetObjResult( interp, result ); + + return TCL_OK; +} diff --git a/win/tclWinTime.c b/win/tclWinTime.c index db2affd..5ddbc89 100644 --- a/win/tclWinTime.c +++ b/win/tclWinTime.c @@ -9,7 +9,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclWinTime.c,v 1.5 1999/12/01 00:08:43 hobbs Exp $ + * RCS: @(#) $Id: tclWinTime.c,v 1.6 2000/11/21 21:33:43 andreas_kupries Exp $ */ #include "tclWinInt.h" @@ -38,10 +38,77 @@ typedef struct ThreadSpecificData { static Tcl_ThreadDataKey dataKey; /* + * Calibration interval for the high-resolution timer, in msec + */ + +static CONST unsigned long clockCalibrateWakeupInterval = 10000; + /* FIXME: 10 s -- should be about 10 min! */ + +/* + * Data for managing high-resolution timers. + */ + +typedef struct TimeInfo { + + CRITICAL_SECTION cs; /* Mutex guarding this structure */ + + int initialized; /* Flag == 1 if this structure is + * 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 */ + + /* + * 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. + */ + + ULARGE_INTEGER lastFileTime; + LARGE_INTEGER lastCounter; + LARGE_INTEGER curCounterFreq; + + /* + * The next two values are used only in the calibration thread, to track + * the frequency of the performance counter. + */ + + LONGLONG lastPerfCounter; /* Performance counter the last time + * that UpdateClockEachSecond was called */ + LONGLONG lastSysTime; /* System clock at the last time + * that UpdateClockEachSecond was called */ + LONGLONG estPerfCounterFreq; + /* Current estimate of the counter frequency + * using the system clock as the standard */ + +} TimeInfo; + +static TimeInfo timeInfo = { + NULL, 0, 0, NULL, NULL, 0, 0, 0, 0, 0 +}; + +CONST static FILETIME posixEpoch = { 0xD53E8000, 0x019DB1DE }; + +/* * Declarations for functions defined later in this file. */ static struct tm * ComputeGMT _ANSI_ARGS_((const time_t *tp)); + +static DWORD WINAPI CalibrationThread _ANSI_ARGS_(( LPVOID arg )); + +static void UpdateTimeEachSecond _ANSI_ARGS_(( void )); /* *---------------------------------------------------------------------- @@ -63,7 +130,9 @@ static struct tm * ComputeGMT _ANSI_ARGS_((const time_t *tp)); unsigned long TclpGetSeconds() { - return (unsigned long) time((time_t *) NULL); + Tcl_Time t; + TclpGetTime( &t ); + return t.sec; } /* @@ -89,7 +158,18 @@ TclpGetSeconds() unsigned long TclpGetClicks() { - return GetTickCount(); + /* + * Use the TclpGetTime abstraction to get the time in microseconds, + * as nearly as we can, and return it. + */ + + Tcl_Time now; /* Current Tcl time */ + unsigned long retval; /* Value to return */ + + TclpGetTime( &now ); + retval = ( now.sec * 1000000 ) + now.usec; + return retval; + } /* @@ -134,7 +214,13 @@ TclpGetTimeZone (currentTime) * Returns the current time in timePtr. * * Side effects: - * None. + * On the first call, initializes a set of static variables to + * keep track of the base value of the performance counter, the + * corresponding wall clock (obtained through ftime) and the + * frequency of the performance counter. Also spins a thread + * whose function is to wake up periodically and monitor these + * values, adjusting them as necessary to correct for drift + * in the performance counter's oscillator. * *---------------------------------------------------------------------- */ @@ -143,11 +229,114 @@ void TclpGetTime(timePtr) Tcl_Time *timePtr; /* Location to store time information. */ { + struct timeb t; - ftime(&t); - timePtr->sec = t.time; - timePtr->usec = t.millitm * 1000; + /* Initialize static storage on the first trip through. */ + + /* + * Note: Outer check for 'initialized' is a performance win + * since it avoids an extra mutex lock in the common case. + */ + + if ( !timeInfo.initialized ) { + TclpInitLock(); + if ( !timeInfo.initialized ) { + timeInfo.perfCounterAvailable + = QueryPerformanceFrequency( &timeInfo.curCounterFreq ); + + /* + * Some hardware abstraction layers use the CPU clock + * in place of the real-time clock as a performance counter + * reference. This results in: + * - inconsistent results among the processors on + * multi-processor systems. + * - unpredictable changes in performance counter frequency + * on "gearshift" processors such as Transmeta and + * SpeedStep. + * There seems to be no way to test whether the performance + * counter is reliable, but a useful heuristic is that + * if its frequency is 1.193182 MHz or 3.579545 MHz, it's + * derived from a colorburst crystal and is therefore + * the RTC rather than the TSC. If it's anything else, we + * presume that the performance counter is unreliable. + */ + + if ( timeInfo.perfCounterAvailable + && timeInfo.curCounterFreq.QuadPart != (LONGLONG) 1193182 + && timeInfo.curCounterFreq.QuadPart != (LONGLONG) 3579545 ) { + timeInfo.perfCounterAvailable = FALSE; + } + + /* + * If the performance counter is available, start a thread to + * calibrate it. + */ + + if ( timeInfo.perfCounterAvailable ) { + DWORD id; + InitializeCriticalSection( &timeInfo.cs ); + timeInfo.readyEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + timeInfo.calibrationThread = CreateThread( NULL, + 8192, + CalibrationThread, + (LPVOID) NULL, + 0, + &id ); + SetThreadPriority( timeInfo.calibrationThread, + THREAD_PRIORITY_HIGHEST ); + WaitForSingleObject( timeInfo.readyEvent, INFINITE ); + CloseHandle( timeInfo.readyEvent ); + } + timeInfo.initialized = TRUE; + } + TclpInitUnlock(); + } + + if ( timeInfo.perfCounterAvailable ) { + + /* + * Query the performance counter and use it to calculate the + * current time. + */ + + LARGE_INTEGER curCounter; + /* Current performance counter */ + + LONGLONG curFileTime; + /* Current estimated time, expressed + * as 100-ns ticks since the Windows epoch */ + + static const LARGE_INTEGER posixEpoch = { 0xD53E8000, 0x019DB1DE }; + /* Posix epoch expressed as 100-ns ticks + * since the windows epoch */ + + LONGLONG usecSincePosixEpoch; + /* Current microseconds since Posix epoch */ + + EnterCriticalSection( &timeInfo.cs ); + + QueryPerformanceCounter( &curCounter ); + curFileTime = timeInfo.lastFileTime.QuadPart + + ( ( curCounter.QuadPart - timeInfo.lastCounter.QuadPart ) + * 10000000 / timeInfo.curCounterFreq.QuadPart ); + timeInfo.lastFileTime.QuadPart = curFileTime; + timeInfo.lastCounter.QuadPart = curCounter.QuadPart; + usecSincePosixEpoch = ( curFileTime - posixEpoch.QuadPart ) / 10; + timePtr->sec = (time_t) ( usecSincePosixEpoch / 1000000 ); + timePtr->usec = (unsigned long ) ( usecSincePosixEpoch % 1000000 ); + + LeaveCriticalSection( &timeInfo.cs ); + + + } else { + + /* High resolution timer is not available. Just use ftime */ + + ftime(&t); + timePtr->sec = t.time; + timePtr->usec = t.millitm * 1000; + } } /* @@ -440,3 +629,215 @@ ComputeGMT(tp) return tmPtr; } + +/* + *---------------------------------------------------------------------- + * + * 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 clockCalibrateWakeupInterval ms, 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 ) +{ + FILETIME curFileTime; + + /* Get initial system time and performance counter */ + + GetSystemTimeAsFileTime( &curFileTime ); + QueryPerformanceCounter( &timeInfo.lastCounter ); + QueryPerformanceFrequency( &timeInfo.curCounterFreq ); + timeInfo.lastFileTime.LowPart = curFileTime.dwLowDateTime; + timeInfo.lastFileTime.HighPart = curFileTime.dwHighDateTime; + + /* Initialize the working storage for the calibration callback */ + + timeInfo.lastPerfCounter = timeInfo.lastCounter.QuadPart; + timeInfo.estPerfCounterFreq = timeInfo.curCounterFreq.QuadPart; + + /* + * Wake up the calling thread. When it wakes up, it will release the + * initialization lock. + */ + + SetEvent( timeInfo.readyEvent ); + + /* Run the calibration once a second */ + + for ( ; ; ) { + + Sleep( 1000 ); + UpdateTimeEachSecond(); + + } +} + +/* + *---------------------------------------------------------------------- + * + * 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() +{ + + LARGE_INTEGER curPerfCounter; + /* Current value returned from + * QueryPerformanceCounter */ + + LONGLONG perfCounterDiff; /* Difference between the current value + * and the value of 1 second ago */ + + FILETIME curSysTime; /* Current system time */ + + LARGE_INTEGER curFileTime; /* File time at the time this callback + * was scheduled. */ + + LONGLONG fileTimeDiff; /* Elapsed time on the system clock + * since the last time this procedure + * was called */ + + LONGLONG instantFreq; /* Instantaneous estimate of the + * performance counter frequency */ + + LONGLONG delta; /* Increment to add to the estimated + * performance counter frequency in the + * loop filter */ + + LONGLONG fuzz; /* Tolerance for the perf counter frequency */ + + LONGLONG lowBound; /* Lower bound for the frequency assuming + * 1000 ppm tolerance */ + + LONGLONG hiBound; /* Upper bound for the frequency */ + + /* + * Get current performance counter and system time. + */ + + QueryPerformanceCounter( &curPerfCounter ); + GetSystemTimeAsFileTime( &curSysTime ); + curFileTime.LowPart = curSysTime.dwLowDateTime; + curFileTime.HighPart = curSysTime.dwHighDateTime; + + EnterCriticalSection( &timeInfo.cs ); + + /* + * Find out how many ticks of the performance counter and the + * system clock have elapsed since we got into this procedure. + * Estimate the current frequency. + */ + + perfCounterDiff = curPerfCounter.QuadPart - timeInfo.lastPerfCounter; + timeInfo.lastPerfCounter = curPerfCounter.QuadPart; + fileTimeDiff = curFileTime.QuadPart - timeInfo.lastSysTime; + timeInfo.lastSysTime = curFileTime.QuadPart; + instantFreq = ( 10000000 * perfCounterDiff / fileTimeDiff ); + + /* + * Consider this a timing glitch if instant frequency varies + * significantly from the current estimate. + */ + + fuzz = timeInfo.estPerfCounterFreq >> 10; + lowBound = timeInfo.estPerfCounterFreq - fuzz; + hiBound = timeInfo.estPerfCounterFreq + fuzz; + if ( instantFreq < lowBound || instantFreq > hiBound ) { + LeaveCriticalSection( &timeInfo.cs ); + return; + } + + /* + * Update the current estimate of performance counter frequency. + * This code is equivalent to the loop filter of a phase locked + * loop. + */ + + delta = ( instantFreq - timeInfo.estPerfCounterFreq ) >> 6; + timeInfo.estPerfCounterFreq += delta; + + /* + * Update the current virtual time. + */ + + timeInfo.lastFileTime.QuadPart + += ( ( curPerfCounter.QuadPart - timeInfo.lastCounter.QuadPart ) + * 10000000 / timeInfo.curCounterFreq.QuadPart ); + timeInfo.lastCounter.QuadPart = curPerfCounter.QuadPart; + + delta = curFileTime.QuadPart - timeInfo.lastFileTime.QuadPart; + if ( delta > 10000000 || delta < -10000000 ) { + + /* + * If the virtual time slip exceeds one second, then adjusting + * the counter frequency is hopeless (it'll take over fifteen + * minutes to line up with the system clock). The most likely + * cause of this large a slip is a sudden change to the system + * clock, perhaps because it was being corrected by wristwatch + * and eyeball. Accept the system time, and set the performance + * counter frequency to the current estimate. + */ + + timeInfo.lastFileTime.QuadPart = curFileTime.QuadPart; + timeInfo.curCounterFreq.QuadPart = timeInfo.estPerfCounterFreq; + + } else { + + /* + * Compute a counter frequency that will cause virtual time to line + * up with system time one second from now, assuming that the + * performance counter continues to tick at timeInfo.estPerfCounterFreq. + */ + + timeInfo.curCounterFreq.QuadPart + = 10000000 * timeInfo.estPerfCounterFreq / ( delta + 10000000 ); + + /* + * Limit frequency excursions to 1000 ppm from estimate + */ + + if ( timeInfo.curCounterFreq.QuadPart < lowBound ) { + timeInfo.curCounterFreq.QuadPart = lowBound; + } else if ( timeInfo.curCounterFreq.QuadPart > hiBound ) { + timeInfo.curCounterFreq.QuadPart = hiBound; + } + } + + LeaveCriticalSection( &timeInfo.cs ); + +} |