Bug 488621: PR_Now() resolution on Windows Mobile is 1 second.
authornelson%bolyard.com
Sat, 02 May 2009 23:27:06 +0000
changeset 4093 b7e7342fa9abe97808215f0ee765935db3140ead
parent 4092 304c39806d58bce216826a394de8d553be77de22
child 4094 392647fecd8b87dbe7afe4bf813aab521dd38350
push idunknown
push userunknown
push dateunknown
bugs488621
Bug 488621: PR_Now() resolution on Windows Mobile is 1 second. Patch by Brad Lassey <bugmail@lassey.us>, r=nelson,wtc
pr/include/private/primpl.h
pr/src/md/windows/ntio.c
pr/src/md/windows/ntmisc.c
pr/src/md/windows/w95io.c
pr/src/misc/prtime.c
--- a/pr/include/private/primpl.h
+++ b/pr/include/private/primpl.h
@@ -49,27 +49,29 @@
 #if defined(_PR_PTHREADS)
 #include <pthread.h>
 #endif
 
 #if defined(_PR_BTHREADS)
 #include <kernel/OS.h>
 #endif
 
-#ifdef WINNT
-/* Need to force service-pack 3 extensions to be defined by
-** setting _WIN32_WINNT to NT 4.0 for winsock.h, winbase.h, winnt.h.
-*/
-#ifndef  _WIN32_WINNT
-    #define _WIN32_WINNT 0x0400
-#elif   (_WIN32_WINNT < 0x0400)
-    #undef  _WIN32_WINNT
-    #define _WIN32_WINNT 0x0400
-#endif /* _WIN32_WINNT */
-#endif /* WINNT */
+#ifdef WIN32
+/*
+ * Allow use of functions and symbols first defined in Win2k.
+ */
+#if !defined(WINVER) || (WINVER < 0x0500)
+#undef WINVER
+#define WINVER 0x0500
+#endif
+#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0500)
+#undef _WIN32_WINNT
+#define _WIN32_WINNT 0x0500
+#endif
+#endif /* WIN32 */
 
 #include "nspr.h"
 #include "prpriv.h"
 
 typedef struct PRSegment PRSegment;
 
 #ifdef XP_MAC
 #include "prosdep.h"
@@ -1210,16 +1212,23 @@ extern PROsfd _PR_MD_FAST_ACCEPT(PRFileD
 extern PRInt32 _PR_MD_FAST_ACCEPT_READ(PRFileDesc *sd, PROsfd *newSock, 
                                 PRNetAddr **raddr, void *buf, PRInt32 amount,
                                 PRIntervalTime timeout, PRBool fast,
                                 _PR_AcceptTimeoutCallback callback,
                                 void *callbackArg);
 
 extern void _PR_MD_UPDATE_ACCEPT_CONTEXT(PROsfd s, PROsfd ls);
 #define _PR_MD_UPDATE_ACCEPT_CONTEXT _MD_UPDATE_ACCEPT_CONTEXT
+/*
+ * The NSPR epoch (00:00:00 1 Jan 1970 UTC) in FILETIME.
+ * We store the value in a PRTime variable for convenience.
+ * This constant is used by _PR_FileTimeToPRTime().
+ * This is defined in ntmisc.c
+ */
+extern const PRTime _pr_filetime_offset;
 #endif /* WIN32 */
 
 extern PRInt32 _PR_MD_SENDFILE(
     PRFileDesc *sock, PRSendFileData *sfd, 
 	PRInt32 flags, PRIntervalTime timeout);
 #define _PR_MD_SENDFILE _MD_SENDFILE
 
 extern PRStatus _PR_MD_GETSOCKNAME(
--- a/pr/src/md/windows/ntio.c
+++ b/pr/src/md/windows/ntio.c
@@ -95,27 +95,16 @@ static DWORD fileAccessTable[] = {
  * NSPR-to-NT access right mapping table for directories.
  */
 static DWORD dirAccessTable[] = {
     FILE_GENERIC_READ,
     FILE_GENERIC_WRITE|FILE_DELETE_CHILD,
     FILE_GENERIC_EXECUTE
 };
 
-/*
- * The NSPR epoch (00:00:00 1 Jan 1970 UTC) in FILETIME.
- * We store the value in a PRTime variable for convenience.
- * This constant is used by _PR_FileTimeToPRTime().
- */
-#ifdef __GNUC__
-static const PRTime _pr_filetime_offset = 116444736000000000LL;
-#else
-static const PRTime _pr_filetime_offset = 116444736000000000i64;
-#endif
-
 static PRBool IsPrevCharSlash(const char *str, const char *current);
 
 #define _NEED_351_FILE_LOCKING_HACK
 #ifdef _NEED_351_FILE_LOCKING_HACK
 #define _PR_LOCAL_FILE 1
 #define _PR_REMOTE_FILE 2
 PRBool IsFileLocalInit();
 PRInt32 IsFileLocal(HANDLE hFile);
--- a/pr/src/md/windows/ntmisc.c
+++ b/pr/src/md/windows/ntmisc.c
@@ -36,16 +36,18 @@
  * ***** END LICENSE BLOCK ***** */
 
 /*
  * ntmisc.c
  *
  */
 
 #include "primpl.h"
+#include <math.h>     /* for fabs() */
+#include <windows.h>
 
 char *_PR_MD_GET_ENV(const char *name)
 {
     return getenv(name);
 }
 
 /*
 ** _PR_MD_PUT_ENV() -- add or change environment variable
@@ -64,42 +66,278 @@ PRIntn _PR_MD_PUT_ENV(const char *name)
  **
  **     Date and time routines
  **
  **************************************************************************
  **************************************************************************
  */
 
 /*
+ * The NSPR epoch (00:00:00 1 Jan 1970 UTC) in FILETIME.
+ * We store the value in a PRTime variable for convenience.
+ */
+#ifdef __GNUC__
+const PRTime _pr_filetime_offset = 116444736000000000LL;
+#else
+const PRTime _pr_filetime_offset = 116444736000000000i64;
+#endif
+
+#ifdef WINCE
+
+#define FILETIME2INT64(ft) \
+  (((PRInt64)ft.dwHighDateTime) << 32 | (PRInt64)ft.dwLowDateTime)
+
+static void
+LowResTime(LPFILETIME lpft)
+{
+    GetCurrentFT(lpft);
+}
+
+typedef struct CalibrationData {
+    long double freq;         /* The performance counter frequency */
+    long double offset;       /* The low res 'epoch' */
+    long double timer_offset; /* The high res 'epoch' */
+
+    /* The last high res time that we returned since recalibrating */
+    PRInt64 last;
+
+    PRBool calibrated;
+
+    CRITICAL_SECTION data_lock;
+    CRITICAL_SECTION calibration_lock;
+    PRInt64 granularity;
+} CalibrationData;
+
+static CalibrationData calibration;
+
+static void
+NowCalibrate(void)
+{
+    FILETIME ft, ftStart;
+    LARGE_INTEGER liFreq, now;
+
+    if (calibration.freq == 0.0) {
+	if(!QueryPerformanceFrequency(&liFreq)) {
+	    /* High-performance timer is unavailable */
+	    calibration.freq = -1.0;
+	} else {
+	    calibration.freq = (long double) liFreq.QuadPart;
+	}
+    }
+    if (calibration.freq > 0.0) {
+	PRInt64 calibrationDelta = 0;
+	/*
+	 * By wrapping a timeBegin/EndPeriod pair of calls around this loop,
+	 * the loop seems to take much less time (1 ms vs 15ms) on Vista. 
+	 */
+	timeBeginPeriod(1);
+	LowResTime(&ftStart);
+	do {
+	    LowResTime(&ft);
+	} while (memcmp(&ftStart,&ft, sizeof(ft)) == 0);
+	timeEndPeriod(1);
+
+	calibration.granularity = 
+	    (FILETIME2INT64(ft) - FILETIME2INT64(ftStart))/10;
+
+	QueryPerformanceCounter(&now);
+
+	calibration.offset = (long double) FILETIME2INT64(ft);
+	calibration.timer_offset = (long double) now.QuadPart;
+	/*
+	 * The windows epoch is around 1600. The unix epoch is around 1970. 
+	 * _pr_filetime_offset is the difference (in windows time units which
+	 * are 10 times more highres than the JS time unit) 
+	 */
+	calibration.offset -= _pr_filetime_offset;
+	calibration.offset *= 0.1;
+	calibration.last = 0;
+
+	calibration.calibrated = PR_TRUE;
+    }
+}
+
+#define CALIBRATIONLOCK_SPINCOUNT 0
+#define DATALOCK_SPINCOUNT 4096
+#define LASTLOCK_SPINCOUNT 4096
+
+static PRStatus
+_MD_InitTime(void)
+{
+    memset(&calibration, 0, sizeof(calibration));
+    NowCalibrate();
+    InitializeCriticalSection(&calibration.calibration_lock);
+    InitializeCriticalSection(&calibration.data_lock);
+    return PR_SUCCESS;
+}
+
+void
+_MD_CleanupTime(void)
+{
+    DeleteCriticalSection(&calibration.calibration_lock);
+    DeleteCriticalSection(&calibration.data_lock);
+}
+
+#define MUTEX_SETSPINCOUNT(m, c)
+
+static PRCallOnceType calibrationOnce;
+
+/*
  *-----------------------------------------------------------------------
  *
  * PR_Now --
  *
  *     Returns the current time in microseconds since the epoch.
  *     The epoch is midnight January 1, 1970 GMT.
  *     The implementation is machine dependent.  This is the
  *     implementation for Windows.
  *     Cf. time_t time(time_t *tp)
  *
  *-----------------------------------------------------------------------
  */
 
 PR_IMPLEMENT(PRTime)
 PR_Now(void)
 {
+    long double lowresTime, highresTimerValue;
+    FILETIME ft;
+    LARGE_INTEGER now;
+    PRBool calibrated = PR_FALSE;
+    PRBool needsCalibration = PR_FALSE;
+    PRInt64 returnedTime;
+    long double cachedOffset = 0.0;
+
+    /* For non threadsafe platforms, _MD_InitTime is not necessary */
+    PR_CallOnce(&calibrationOnce, _MD_InitTime);
+    do {
+	if (!calibration.calibrated || needsCalibration) {
+	    EnterCriticalSection(&calibration.calibration_lock);
+	    EnterCriticalSection(&calibration.data_lock);
+
+	    /* Recalibrate only if no one else did before us */
+	    if (calibration.offset == cachedOffset) {
+		/*
+		 * Since calibration can take a while, make any other
+		 * threads immediately wait 
+		 */
+		MUTEX_SETSPINCOUNT(&calibration.data_lock, 0);
+
+		NowCalibrate();
+
+		calibrated = PR_TRUE;
+
+		/* Restore spin count */
+		MUTEX_SETSPINCOUNT(&calibration.data_lock, DATALOCK_SPINCOUNT);
+	    }
+	    LeaveCriticalSection(&calibration.data_lock);
+	    LeaveCriticalSection(&calibration.calibration_lock);
+	}
+
+	/* Calculate a low resolution time */
+	LowResTime(&ft);
+	lowresTime = ((long double)(FILETIME2INT64(ft) - _pr_filetime_offset))
+		     * 0.1;
+
+	if (calibration.freq > 0.0) {
+	    long double highresTime, diff;
+	    DWORD timeAdjustment, timeIncrement;
+	    BOOL timeAdjustmentDisabled;
+
+	    /* Default to 15.625 ms if the syscall fails */
+	    long double skewThreshold = 15625.25;
+
+	    /* Grab high resolution time */
+	    QueryPerformanceCounter(&now);
+	    highresTimerValue = (long double)now.QuadPart;
+
+	    EnterCriticalSection(&calibration.data_lock);
+	    highresTime = calibration.offset + 1000000L *
+		(highresTimerValue-calibration.timer_offset)/calibration.freq;
+	    cachedOffset = calibration.offset;
+
+	    /* 
+	     * On some dual processor/core systems, we might get an earlier 
+	     * time so we cache the last time that we returned.
+	     */
+	    calibration.last = PR_MAX(calibration.last,(PRInt64)highresTime);
+	    returnedTime = calibration.last;
+	    LeaveCriticalSection(&calibration.data_lock);
+
+	    /* Get an estimate of clock ticks per second from our own test */
+	    skewThreshold = calibration.granularity;
+	    /* Check for clock skew */
+	    diff = lowresTime - highresTime;
+
+	    /* 
+	     * For some reason that I have not determined, the skew can be
+	     * up to twice a kernel tick. This does not seem to happen by
+	     * itself, but I have only seen it triggered by another program
+	     * doing some kind of file I/O. The symptoms are a negative diff
+	     * followed by an equally large positive diff. 
+	     */
+	    if (fabs(diff) > 2*skewThreshold) {
+		if (calibrated) {
+		    /*
+		     * If we already calibrated once this instance, and the
+		     * clock is still skewed, then either the processor(s) are
+		     * wildly changing clockspeed or the system is so busy that
+		     * we get switched out for long periods of time. In either
+		     * case, it would be infeasible to make use of high
+		     * resolution results for anything, so let's resort to old
+		     * behavior for this call. It's possible that in the
+		     * future, the user will want the high resolution timer, so
+		     * we don't disable it entirely. 
+		     */
+		    returnedTime = (PRInt64)lowresTime;
+		    needsCalibration = PR_FALSE;
+		} else {
+		    /*
+		     * It is possible that when we recalibrate, we will return 
+		     * a value less than what we have returned before; this is
+		     * unavoidable. We cannot tell the different between a
+		     * faulty QueryPerformanceCounter implementation and user
+		     * changes to the operating system time. Since we must
+		     * respect user changes to the operating system time, we
+		     * cannot maintain the invariant that Date.now() never
+		     * decreases; the old implementation has this behavior as
+		     * well. 
+		     */
+		    needsCalibration = PR_TRUE;
+		}
+	    } else {
+		/* No detectable clock skew */
+		returnedTime = (PRInt64)highresTime;
+		needsCalibration = PR_FALSE;
+	    }
+	} else {
+	    /* No high resolution timer is available, so fall back */
+	    returnedTime = (PRInt64)lowresTime;
+	}
+    } while (needsCalibration);
+
+    return returnedTime;
+}
+
+#else
+
+PR_IMPLEMENT(PRTime)
+PR_Now(void)
+{
     PRTime prt;
     FILETIME ft;
     SYSTEMTIME st;
 
     GetSystemTime(&st);
     SystemTimeToFileTime(&st, &ft);
     _PR_FileTimeToPRTime(&ft, &prt);
     return prt;       
 }
 
+#endif
+
 /*
  ***********************************************************************
  ***********************************************************************
  *
  * Process creation routines
  *
  ***********************************************************************
  ***********************************************************************
--- a/pr/src/md/windows/w95io.c
+++ b/pr/src/md/windows/w95io.c
@@ -255,27 +255,16 @@ static DWORD fileAccessTable[] = {
  * NSPR-to-NT access right mapping table for directories.
  */
 static DWORD dirAccessTable[] = {
     FILE_GENERIC_READ,
     FILE_GENERIC_WRITE|FILE_DELETE_CHILD,
     FILE_GENERIC_EXECUTE
 };
 
-/*
- * The NSPR epoch (00:00:00 1 Jan 1970 UTC) in FILETIME.
- * We store the value in a PRTime variable for convenience.
- * This constant is used by _PR_FileTimeToPRTime().
- */
-#if defined(__MINGW32__)
-static const PRTime _pr_filetime_offset = 116444736000000000LL;
-#else
-static const PRTime _pr_filetime_offset = 116444736000000000i64;
-#endif
-
 /* Windows CE has GetFileAttributesEx. */
 #ifndef WINCE
 typedef BOOL (WINAPI *GetFileAttributesExFn)(LPCTSTR,
                                              GET_FILEEX_INFO_LEVELS,
                                              LPVOID); 
 static GetFileAttributesExFn getFileAttributesEx;
 static void InitGetFileInfo(void);
 #endif
--- a/pr/src/misc/prtime.c
+++ b/pr/src/misc/prtime.c
@@ -599,16 +599,19 @@ void _PR_InitTime(void)
 void _PR_CleanupTime(void)
 {
 #ifdef HAVE_LOCALTIME_MONITOR
     if (monitor) {
         PR_DestroyLock(monitor);
         monitor = NULL;
     }
 #endif
+#ifdef WINCE
+    _MD_CleanupTime();
+#endif
 }
 
 #if defined(XP_UNIX) || defined(XP_PC) || defined(XP_BEOS)
 
 PR_IMPLEMENT(PRTimeParameters)
 PR_LocalTimeParameters(const PRExplodedTime *gmt)
 {