Bug 586692 - xpcshell: do_timeout does not guarantee desired timeout interval. r=jduell
authorDan Witte <dwitte@mozilla.com>
Fri, 13 Aug 2010 13:28:14 -0700
changeset 50568 fc296a1ba50ec846212043de4c45083bbe474c52
parent 50567 593b16786b35fe0b2fc276c7bcf9ca43e43d1934
child 50569 5eaffc2d635a9fab163cf4db59eda469a89436ba
push idunknown
push userunknown
push dateunknown
reviewersjduell
bugs586692
milestone2.0b4pre
Bug 586692 - xpcshell: do_timeout does not guarantee desired timeout interval. r=jduell
netwerk/test/httpserver/test/head_utils.js
testing/xpcshell/head.js
--- a/netwerk/test/httpserver/test/head_utils.js
+++ b/netwerk/test/httpserver/test/head_utils.js
@@ -36,18 +36,16 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 do_load_httpd_js();
 
 // if these tests fail, we'll want the debug output
 DEBUG = true;
 
-const Timer = CC("@mozilla.org/timer;1", "nsITimer", "initWithCallback");
-
 
 /**
  * Constructs a new nsHttpServer instance.  This function is intended to
  * encapsulate construction of a server so that at some point in the future it
  * is possible to run these tests (with at most slight modifications) against
  * the server when used as an XPCOM component (not as an inline script).
  */
 function createServer()
@@ -206,83 +204,28 @@ function skipHeaders(iter)
  */
 function isException(e, code)
 {
   if (e !== code && e.result !== code)
     do_throw("unexpected error: " + e);
 }
 
 /**
- * Pending timers used by callLater, which must store them to avoid the timer
- * being canceled and destroyed.  Stupid API...
- */
-var __pendingTimers = [];
-
-/**
- * Date.now() is not necessarily monotonically increasing (insert sob story
- * about times not being the right tool to use for measuring intervals of time,
- * robarnold can tell all), so be wary of error by erring by at least
- * __timerFuzz ms.
- */
-const __timerFuzz = 15;
-
-/**
  * Calls the given function at least the specified number of milliseconds later.
  * The callback will not undershoot the given time, but it might overshoot --
  * don't expect precision!
  *
  * @param milliseconds : uint
  *   the number of milliseconds to delay
  * @param callback : function() : void
  *   the function to call
  */
 function callLater(msecs, callback)
 {
-  do_check_true(msecs >= 0);
-
-  var start = Date.now();
-
-  function checkTime()
-  {
-    var index = __pendingTimers.indexOf(timer);
-    do_check_true(index >= 0); // sanity
-    __pendingTimers.splice(index, 1);
-    do_check_eq(__pendingTimers.indexOf(timer), -1);
-
-    // The current nsITimer implementation can undershoot, but even if it
-    // couldn't, paranoia is probably a virtue here given the potential for
-    // random orange on tinderboxen.
-    var end = Date.now();
-    var elapsed = end - start;
-    if (elapsed >= msecs)
-    {
-      dumpn("*** TIMER FIRE " + elapsed + "ms (" + msecs + "ms requested)");
-      try
-      {
-        callback();
-      }
-      catch (e)
-      {
-        do_throw("exception thrown from callLater callback: " + e);
-      }
-      return;
-    }
-
-    // Timer undershot, retry with a little overshoot to try to avoid more
-    // undershoots.
-    var newDelay = msecs - elapsed;
-    dumpn("*** TIMER UNDERSHOOT " + newDelay + "ms " +
-          "(" + msecs + "ms requested, delaying)");
-
-    callLater(newDelay, callback);
-  }
-
-  var timer =
-    new Timer(checkTime, msecs + __timerFuzz, Ci.nsITimer.TYPE_ONE_SHOT);
-  __pendingTimers.push(timer);
+  do_timeout(msecs, callback);
 }
 
 
 /*******************************************************
  * SIMPLE SUPPORT FOR LOADING/TESTING A SERIES OF URLS *
  *******************************************************/
 
 /**
--- a/testing/xpcshell/head.js
+++ b/testing/xpcshell/head.js
@@ -44,17 +44,17 @@
  * for more information.
  */
 
 var _quit = false;
 var _passed = true;
 var _tests_pending = 0;
 var _passedChecks = 0, _falsePassedChecks = 0;
 var _cleanupFunctions = [];
-var _pendingCallbacks = [];
+var _pendingTimers = [];
 
 function _dump(str) {
   if (typeof _XPCSHELL_PROCESS == "undefined") {
     dump(str);
   } else {
     dump(_XPCSHELL_PROCESS + ": " + str);
   }
 }
@@ -84,37 +84,73 @@ try { // nsIXULRuntime is not available 
           .getService(Components.interfaces.nsICrashReporter)) {
       crashReporter.enabled = true;
       crashReporter.minidumpPath = do_get_cwd();
     }
   }
 }
 catch (e) { }
 
+/**
+ * Date.now() is not necessarily monotonically increasing (insert sob story
+ * about times not being the right tool to use for measuring intervals of time,
+ * robarnold can tell all), so be wary of error by erring by at least
+ * _timerFuzz ms.
+ */
+const _timerFuzz = 15;
 
-function _TimerCallback(func, timer) {
+function _Timer(func, delay) {
+  delay = Number(delay);
+  if (delay < 0)
+    do_throw("do_timeout() delay must be nonnegative");
+
   if (typeof func !== "function")
-    throw new Error("string callbacks no longer accepted; use a function!");
+    do_throw("string callbacks no longer accepted; use a function!");
 
   this._func = func;
+  this._start = Date.now();
+  this._delay = delay;
+
+  var timer = Components.classes["@mozilla.org/timer;1"]
+                        .createInstance(Components.interfaces.nsITimer);
+  timer.initWithCallback(this, delay + _timerFuzz, timer.TYPE_ONE_SHOT);
+
   // Keep timer alive until it fires
-  _pendingCallbacks.push(timer);
+  _pendingTimers.push(timer);
 }
-_TimerCallback.prototype = {
+_Timer.prototype = {
   QueryInterface: function(iid) {
     if (iid.Equals(Components.interfaces.nsITimerCallback) ||
         iid.Equals(Components.interfaces.nsISupports))
       return this;
 
     throw Components.results.NS_ERROR_NO_INTERFACE;
   },
 
   notify: function(timer) {
-    _pendingCallbacks.splice(_pendingCallbacks.indexOf(timer), 1);
-    this._func.call(null);
+    _pendingTimers.splice(_pendingTimers.indexOf(timer), 1);
+
+    // The current nsITimer implementation can undershoot, but even if it
+    // couldn't, paranoia is probably a virtue here given the potential for
+    // random orange on tinderboxen.
+    var end = Date.now();
+    var elapsed = end - this._start;
+    if (elapsed >= this._delay) {
+      try {
+        this._func.call(null);
+      } catch (e) {
+        do_throw("exception thrown from callLater callback: " + e);
+      }
+      return;
+    }
+
+    // Timer undershot, retry with a little overshoot to try to avoid more
+    // undershoots.
+    var newDelay = this._delay - elapsed;
+    do_timeout(newDelay, this._func);
   }
 };
 
 function _do_main() {
   if (_quit)
     return;
 
   _dump("TEST-INFO | (xpcshell/head.js) | running event loop\n");
@@ -218,21 +254,28 @@ function _load_files(aFiles) {
   }
 
   aFiles.forEach(loadTailFile);
 }
 
 
 /************** Functions to be used from the tests **************/
 
-
+/**
+ * Calls the given function at least the specified number of milliseconds later.
+ * The callback will not undershoot the given time, but it might overshoot --
+ * don't expect precision!
+ *
+ * @param delay : uint
+ *   the number of milliseconds to delay
+ * @param callback : function() : void
+ *   the function to call
+ */
 function do_timeout(delay, func) {
-  var timer = Components.classes["@mozilla.org/timer;1"]
-                        .createInstance(Components.interfaces.nsITimer);
-  timer.initWithCallback(new _TimerCallback(func, timer), delay, timer.TYPE_ONE_SHOT);
+  new _Timer(func, Number(delay));
 }
 
 function do_execute_soon(callback) {
   do_test_pending();
   var tm = Components.classes["@mozilla.org/thread-manager;1"]
                      .getService(Components.interfaces.nsIThreadManager);
 
   tm.mainThread.dispatch({