Bug 825598 - Add a watchdog timer on downloads and automatically restart if timer expires. r=marshall_law
authorDave Hylands <dhylands@gmail.com>
Tue, 15 Jan 2013 00:10:19 +0100
changeset 118862 3eff445e4e91abc27fab7e27e8091033d7e67729
parent 118861 c6f47473693423bb9c265b196af56544bd8ea4e7
child 118863 9eecc4f1a318bc5eddc0147ffdb522aa01b42d9f
push id24180
push useremorley@mozilla.com
push dateTue, 15 Jan 2013 22:58:27 +0000
treeherdermozilla-central@72e34ce7fd92 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmarshall_law
bugs825598
milestone21.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 825598 - Add a watchdog timer on downloads and automatically restart if timer expires. r=marshall_law
b2g/app/b2g.js
b2g/components/UpdatePrompt.js
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -450,16 +450,19 @@ pref("marionette.defaultPrefs.port", 282
 #ifdef MOZ_UPDATER
 // When we're applying updates, we can't let anything hang us on
 // quit+restart.  The user has no recourse.
 pref("shutdown.watchdog.timeoutSecs", 5);
 // Timeout before the update prompt automatically installs the update
 pref("b2g.update.apply-prompt-timeout", 60000); // milliseconds
 // Amount of time to wait after the user is idle before prompting to apply an update
 pref("b2g.update.apply-idle-timeout", 600000); // milliseconds
+// Amount of time after which connection will be restarted if no progress
+pref("b2g.update.download-watchdog-timeout", 120000); // milliseconds
+pref("b2g.update.download-watchdog-max-retries", 5);
 
 pref("app.update.enabled", true);
 pref("app.update.auto", false);
 pref("app.update.silent", false);
 pref("app.update.mode", 0);
 pref("app.update.incompatible.mode", 0);
 pref("app.update.staging.enabled", true);
 pref("app.update.service.enabled", true);
--- a/b2g/components/UpdatePrompt.js
+++ b/b2g/components/UpdatePrompt.js
@@ -13,18 +13,20 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://gre/modules/Services.jsm");
 
 const VERBOSE = 1;
 let log =
   VERBOSE ?
   function log_dump(msg) { dump("UpdatePrompt: "+ msg +"\n"); } :
   function log_noop(msg) { };
 
-const PREF_APPLY_PROMPT_TIMEOUT = "b2g.update.apply-prompt-timeout";
-const PREF_APPLY_IDLE_TIMEOUT   = "b2g.update.apply-idle-timeout";
+const PREF_APPLY_PROMPT_TIMEOUT          = "b2g.update.apply-prompt-timeout";
+const PREF_APPLY_IDLE_TIMEOUT            = "b2g.update.apply-idle-timeout";
+const PREF_DOWNLOAD_WATCHDOG_TIMEOUT     = "b2g.update.download-watchdog-timeout";
+const PREF_DOWNLOAD_WATCHDOG_MAX_RETRIES = "b2g.update.download-watchdog-max-retries";
 
 const NETWORK_ERROR_OFFLINE = 111;
 const FILE_ERROR_TOO_BIG    = 112;
 const HTTP_ERROR_OFFSET     = 1000;
 
 const STATE_DOWNLOADING = 'downloading';
 
 XPCOMUtils.defineLazyServiceGetter(Services, "aus",
@@ -484,35 +486,102 @@ UpdatePrompt.prototype = {
     timer.initWithCallback(this, aTimeoutMs, timer.TYPE_ONE_SHOT);
     return timer;
   },
 
   // nsIRequestObserver
 
   _startedSent: false,
 
+  _watchdogTimer: null,
+  _watchdogTimeout: 0,
+
+  _autoRestartDownload: false,
+  _autoRestartCount: 0,
+
+  watchdogTimerFired: function UP_watchdogTimerFired() {
+    log("Download watchdog fired");
+    this._autoRestartDownload = true;
+    Services.aus.pauseDownload();
+  },
+
+  startWatchdogTimer: function UP_startWatchdogTimer() {
+    if (this._watchdogTimer) {
+      this._watchdogTimer.cancel();
+    } else {
+      this._watchdogTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+      this._watchdogTimeout = Services.prefs.getIntPref(PREF_DOWNLOAD_WATCHDOG_TIMEOUT);
+    }
+    this._watchdogTimer.initWithCallback(this.watchdogTimerFired.bind(this),
+                                         this._watchdogTimeout,
+                                         Ci.nsITimer.TYPE_ONE_SHOT);
+  },
+
+  stopWatchdogTimer: function UP_stopWatchdogTimer() {
+    if (this._watchdogTimer) {
+      this._watchdogTimer.cancel();
+      this._watchdogTimer = null;
+    }
+  },
+
+  touchWatchdogTimer: function UP_touchWatchdogTimer() {
+    this.startWatchdogTimer();
+  },
+
   onStartRequest: function UP_onStartRequest(aRequest, aContext) {
     // Wait until onProgress to send the update-download-started event, in case
     // this request turns out to fail for some reason
     this._startedSent = false;
+    this.startWatchdogTimer();
   },
 
   onStopRequest: function UP_onStopRequest(aRequest, aContext, aStatusCode) {
+    this.stopWatchdogTimer();
+    Services.aus.removeDownloadListener(this);
     let paused = !Components.isSuccessCode(aStatusCode);
+    if (!paused) {
+      // The download was successful, no need to restart
+      this._autoRestartDownload = false;
+    }
+    if (this._autoRestartDownload) {
+      this._autoRestartDownload = false;
+      let watchdogMaxRetries = Services.prefs.getIntPref(PREF_DOWNLOAD_WATCHDOG_MAX_RETRIES);
+      this._autoRestartCount++;
+      if (this._autoRestartCount > watchdogMaxRetries) {
+        log("Download - retry count exceeded - error");
+        // We exceeded the max retries. Treat the download like an error,
+        // which will give the user a chance to restart manually later.
+        this._autoRestartCount = 0;
+        if (Services.um.activeUpdate) {
+          this.showUpdateError(Services.um.activeUpdate);
+        }
+        return;
+      }
+      log("Download - restarting download - attempt " + this._autoRestartCount);
+      this.downloadUpdate(null);
+      return;
+    }
+    this._autoRestartCount = 0;
     this.sendChromeEvent("update-download-stopped", {
-        paused: paused
+      paused: paused
     });
-
-    Services.aus.removeDownloadListener(this);
   },
 
   // nsIProgressEventSink
 
   onProgress: function UP_onProgress(aRequest, aContext, aProgress,
                                      aProgressMax) {
+    if (aProgress == aProgressMax) {
+      // The update.mar validation done by onStopRequest may take
+      // a while before the onStopRequest callback is made, so stop
+      // the timer now.
+      this.stopWatchdogTimer();
+    } else {
+      this.touchWatchdogTimer();
+    }
     if (!this._startedSent) {
       this.sendChromeEvent("update-download-started", {
         total: aProgressMax
       });
       this._startedSent = true;
     }
 
     this.sendChromeEvent("update-download-progress", {