summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog54
-rw-r--r--tests/winTime.test28
-rw-r--r--win/tclWinNotify.c38
-rw-r--r--win/tclWinTest.c79
-rw-r--r--win/tclWinTime.c415
5 files changed, 603 insertions, 11 deletions
diff --git a/ChangeLog b/ChangeLog
index 90cf3b9..0ec1c25 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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 );
+
+}