Bug 765215 - Firefox 13 hangs on resuming from sleep, introduced wake up adjust for QueryPerformanceCounter, r=ehsan
authorHonza Bambas <honzab.moz@firemni.cz>
Sat, 22 Dec 2012 15:46:14 +0100
changeset 116895 ec5b0408864fe5f741a39676c704f45690b7f721
parent 116894 101f652fe7a5193c28902a15d69d984e9016e00f
child 116896 b9807d681190145ce7a0ee12439d9d909596f31a
push id24076
push userryanvm@gmail.com
push dateSun, 23 Dec 2012 20:50:19 +0000
treeherdermozilla-central@4f74d77d6d8b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs765215
milestone20.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 765215 - Firefox 13 hangs on resuming from sleep, introduced wake up adjust for QueryPerformanceCounter, r=ehsan
xpcom/ds/TimeStamp_windows.cpp
--- a/xpcom/ds/TimeStamp_windows.cpp
+++ b/xpcom/ds/TimeStamp_windows.cpp
@@ -128,16 +128,19 @@ static const ULONGLONG kCalibrationInter
 //
 // Value is number of [ms].
 static const ULONGLONG kOverflowLimit = 100;
 
 // If we are not able to get the value of GTC time increment, use this value
 // which is the most usual increment.
 static const DWORD kDefaultTimeIncrement = 156001;
 
+// Time since GTC fallback after we forbid recalibration on wake up [ms]
+static const DWORD kForbidRecalibrationTime = 2000;
+
 // ----------------------------------------------------------------------------
 // Global variables, not changing at runtime
 // ----------------------------------------------------------------------------
 
 /**
  * The [mt] unit:
  *
  * Many values are kept in ticks of the Performance Coutner x 1000,
@@ -163,16 +166,22 @@ static LONGLONG sFrequencyPerSec = 0;
 // Schematically, QPC works correctly if ((QPC_now - GTC_now) -
 // (QPC_calib - GTC_calib)) is in  [sUnderrunThreshold, sOverrunThreshold]
 // interval every time we access them.
 //
 // Kept in [mt]
 static LONGLONG sUnderrunThreshold;
 static LONGLONG sOverrunThreshold;
 
+// QPC may be reset after wake up.  But because we may return GTC + sSkew
+// for a short time before we reclibrate after wakeup, result of 
+// CalibratedPerformanceCounter may go radically backwrads.  We have
+// to compensate this jump.
+static LONGLONG sWakeupAdjust = 0;
+
 // ----------------------------------------------------------------------------
 // Global lock
 // ----------------------------------------------------------------------------
 
 // Thread spin count before entering the full wait state for sTimeStampLock.
 // Inspired by Rob Arnold's work on PRMJ_Now().
 static const DWORD kLockSpinCount = 4096;
 
@@ -202,16 +211,21 @@ static ULONGLONG sLastGTCResult = 0;
 // Kept in [mt]
 static ULONGLONG sLastResult = 0;
 
 // Time of the last performed calibration.
 //
 // Kept in [ms]
 static ULONGLONG sLastCalibrated;
 
+// Time of fallback to GTC
+//
+// Kept in [ms] and filled only with value of GTC
+static ULONGLONG sFallbackTime = 0;
+
 // The following variable stores two booleans, both initialized to false.
 //
 // The lower word is fallbackToGTC:
 // After we have detected a run out of bounderies set this to true.  This
 // then disallows use of QPC result for the hi-res timer.
 //
 // The higher word is forceRecalibrate:
 // Set to true to force recalibration on QPC read.  This is generally set after
@@ -322,21 +336,29 @@ StandbyObserver::sInitialized = false;
 
 NS_IMETHODIMP
 StandbyObserver::Observe(nsISupports *subject,
                          const char *topic,
                          const PRUnichar *data)
 {
   AutoCriticalSection lock(&sTimeStampLock);
 
+  CalibrationFlags value;
+  value.dwordValue = sCalibrationFlags.dwordValue;
+
+  if (value.flags.fallBackToGTC &&
+      ((sGetTickCount64() - sFallbackTime) > kForbidRecalibrationTime)) {
+    LOG(("Disallowing recalibration since the time from fallback is too long"));
+    return NS_OK;
+  }
+
   // Clear the potentiall fallback flag now and try using
   // QPC again after wake up.
-  CalibrationFlags value;
+  value.flags.forceRecalibrate = value.flags.fallBackToGTC;
   value.flags.fallBackToGTC = false;
-  value.flags.forceRecalibrate = true;
   sCalibrationFlags.dwordValue = value.dwordValue; // aligned 32-bit writes are atomic
 
   LOG(("TimeStamp: system has woken up, reset GTC fallback"));
 
   return NS_OK;
 }
 
 
@@ -460,21 +482,22 @@ PerformanceCounter()
 {
   LARGE_INTEGER pc;
   ::QueryPerformanceCounter(&pc);
   return pc.QuadPart * 1000ULL;
 }
 
 // Called when we detect a larger deviation of QPC to disable it.
 static inline void
-RecordFlaw()
+RecordFlaw(ULONGLONG gtc)
 {
   sCalibrationFlags.flags.fallBackToGTC = true;
+  sFallbackTime = gtc;
 
-  LOG(("TimeStamp: falling back to GTC :("));
+  LOG(("TimeStamp: falling back to GTC at %llu :(", gtc));
 
 #if 0
   // This code has been disabled, because we:
   // 0. InitResolution must not be called under the lock (would reenter) while
   //    we shouldn't release it here just to allow it
   // 1. may return back to using QPC after system wake up
   // 2. InitResolution for GTC will probably return 0 anyway (increments
   //    only every 15 or 16 ms.)
@@ -521,28 +544,37 @@ CheckCalibration(LONGLONG overflow, ULON
          sinceLastCalibration / 1000,
          mt2ms_d(overflow),
          mt2ms_d(trend)));
 
     if (trend > ms2mt(kOverflowLimit)) {
       // This sets fallBackToGTC, we have detected
       // an unreliability of QPC, stop using it.
       AutoCriticalSection lock(&sTimeStampLock);
-      RecordFlaw();
+      RecordFlaw(gtc);
       return false;
     }
   }
 
   if (sinceLastCalibration > kCalibrationInterval || value.flags.forceRecalibrate) {
     // Recalculate the skew now
     AutoCriticalSection lock(&sTimeStampLock);
+
+    // If this is forced recalibration after wakeup, we have to take care of any large
+    // QPC jumps from GTC + current skew.  It can happen that QPC after waking up is
+    // reset or jumps a lot to the past.  When we would start using QPC again
+    // the result of CalibratedPerformanceCounter would go radically back - actually
+    // stop increasing since there is a simple MAX(last, now) protection.
+    if (value.flags.forceRecalibrate)
+      sWakeupAdjust += sSkew - (qpc - ms2mt(gtc));
+
     sSkew = qpc - ms2mt(gtc);
     sLastCalibrated = gtc;
-    LOG(("TimeStamp: new skew is %1.2fms (force:%d)",
-      mt2ms_d(sSkew), value.flags.forceRecalibrate));
+    LOG(("TimeStamp: new skew is %1.2fms, wakeup adjust is %1.2fms (force:%d)",
+      mt2ms_d(sSkew), mt2ms_d(sWakeupAdjust), value.flags.forceRecalibrate));
 
     sCalibrationFlags.flags.forceRecalibrate = false;
   }
 
   return true;
 }
 
 // AtomicStoreIfGreaterThan tries to store the maximum of two values in one of them
@@ -583,17 +615,17 @@ CalibratedPerformanceCounter()
   // XXX This is using ObserverService, cannot instantiate in the static
   // startup, really needs a better initation code here.
   StandbyObserver::Ensure();
 
   // Don't hold the lock over call to QueryPerformanceCounter, since it is
   // the largest bottleneck, let threads read the value concurently to have
   // possibly a better performance.
 
-  ULONGLONG qpc = PerformanceCounter();
+  ULONGLONG qpc = PerformanceCounter() + sWakeupAdjust;
 
   // Rollover protection
   ULONGLONG gtc = sGetTickCount64();
 
   LONGLONG diff = qpc - ms2mt(gtc) - sSkew;
   LONGLONG overflow = 0;
 
   if (diff < sUnderrunThreshold) {
@@ -702,17 +734,17 @@ TimeStamp::Startup()
   sLastCalibrated = sGetTickCount64();
   sSkew = qpc - ms2mt(sLastCalibrated);
 
   InitThresholds();
   InitResolution();
 
   sHasStableTSC = HasStableTSC();
 
-  LOG(("TimeStamp: initial skew is %1.2fms", mt2ms_d(sSkew)));
+  LOG(("TimeStamp: initial skew is %1.2fms, sHasStableTSC=%d", mt2ms_d(sSkew), sHasStableTSC));
 
   return NS_OK;
 }
 
 void
 TimeStamp::Shutdown()
 {
   DeleteCriticalSection(&sTimeStampLock);