Bug 1573564 - Make DNS lookup completely asynchronous. r=BenB,pmorris
authorNeil Rashbrook <neil@parkwaycc.co.uk>
Wed, 14 Aug 2019 10:12:37 +0200
changeset 27319 6c065f72218ce826ec61371a65dc944903d204ed
parent 27318 aa7d58ca2f4bb006d8b18c0a50a59368d8596a99
child 27320 5fcdaaf9a166114e58db8a55d7b6f083907fa1df
push id16281
push usermozilla@jorgk.com
push dateWed, 14 Aug 2019 08:25:43 +0000
treeherdercomm-central@089cade441b7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersBenB, pmorris
bugs1573564
Bug 1573564 - Make DNS lookup completely asynchronous. r=BenB,pmorris
mail/components/accountcreation/content/emailWizard.js
mail/components/accountcreation/content/fetchConfig.js
mail/components/accountcreation/content/util.js
--- a/mail/components/accountcreation/content/emailWizard.js
+++ b/mail/components/accountcreation/content/emailWizard.js
@@ -549,17 +549,17 @@ EmailConfigWizard.prototype = {
 
   // --------------
   // Detection step
 
   /**
    * Try to find an account configuration for this email address.
    * This is the function which runs the autoconfig.
    */
-  async findConfig(domain, emailAddress) {
+  findConfig(domain, emailAddress) {
     gEmailWizardLogger.info("findConfig()");
     if (this._abortable) {
       this.onStop();
     }
     this.switchToMode("find-config");
     this.startSpinner("looking_up_settings");
 
     var self = this;
@@ -608,17 +608,17 @@ EmailConfigWizard.prototype = {
       fetch = fetchConfigFromDB(domain,
         call.successCallback(), call.errorCallback());
       call.setAbortable(fetch);
 
       call = priority.addCall();
       this.addStatusLine("looking_up_settings_mx", call);
       // "found_settings_db" is correct. We display the same message for both db and mx cases.
       call.foundMsg = "found_settings_db";
-      fetch = await fetchConfigForMX(domain,
+      fetch = fetchConfigForMX(domain,
         call.successCallback(), call.errorCallback());
       call.setAbortable(fetch);
 
       call = priority.addCall();
       this.addStatusLine("looking_up_settings_exchange", call);
       call.foundMsg = "found_settings_exchange";
       fetch = fetchConfigFromExchange(domain,
         emailAddress, this._exchangeUsername, this._password,
--- a/mail/components/accountcreation/content/fetchConfig.js
+++ b/mail/components/accountcreation/content/fetchConfig.js
@@ -144,22 +144,22 @@ function fetchConfigFromDB(domain, succe
  *   conclusional jump we make here.) and alternative domains
  *   (e.g. yahoo.de -> yahoo.com).
  * - We make a look up for the base domain. E.g. if MX is
  *   mx1.incoming.servers.hoster.com, we look up hoster.com.
  *   Thanks to Services.eTLD, we also get bbc.co.uk right.
  *
  * Params @see fetchConfigFromISP()
  */
-async function fetchConfigForMX(domain, successCallback, errorCallback) {
+function fetchConfigForMX(domain, successCallback, errorCallback) {
   const sanitizedDomain = sanitize.hostname(domain);
   const sucAbortable = new SuccessiveAbortable();
   const time = Date.now();
 
-  await getMX(sanitizedDomain,
+  sucAbortable.current = getMX(sanitizedDomain,
     function(mxHostname) { // success
       ddump("getmx took " + (Date.now() - time) + "ms");
       let sld = Services.eTLD.getBaseDomainFromHost(mxHostname);
       ddump("base domain " + sld + " for " + mxHostname);
       if (sld == sanitizedDomain) {
         errorCallback(new Exception("MX lookup would be no different from domain"));
         return;
       }
@@ -183,25 +183,22 @@ async function fetchConfigForMX(domain, 
  * abort the initial MX query.
  *
  * @param {string}  sanitizedDomain @see fetchConfigFromISP()
  * @param {function(hostname {string})}  successCallback
  *   Called when we found an MX for the domain.
  *   For |hostname|, see description above.
  * @param {function({Exception|string})}  errorCallback @see fetchConfigFromISP()
  */
-async function getMX(sanitizedDomain, successCallback, errorCallback) {
-  try {
-    const records = await DNS.mx(sanitizedDomain);
+function getMX(sanitizedDomain, successCallback, errorCallback) {
+  return new PromiseAbortable(DNS.mx(sanitizedDomain), function(records) {
     const filteredRecs = records.filter(record => record.host);
 
     if (filteredRecs.length > 0) {
       const sortedRecs = filteredRecs.sort((a, b) => a.prio > b.prio);
       const firstHost = sortedRecs[0].host;
       successCallback(firstHost);
     } else {
       errorCallback(new Exception(
         "No hostname found in MX records for sanitizedDomain=" + sanitizedDomain));
     }
-  } catch (error) {
-    errorCallback(error);
-  }
+  }, errorCallback);
 }
--- a/mail/components/accountcreation/content/util.js
+++ b/mail/components/accountcreation/content/util.js
@@ -154,16 +154,44 @@ Abortable.prototype = {
 
 function CancelledException(msg) {
   Exception.call(this, msg);
 }
 CancelledException.prototype = Object.create(Exception.prototype);
 CancelledException.prototype.constructor = CancelledException;
 
 /**
+ * Utility implementation, for waiting for a promise to resolve,
+ * but allowing its result to be cancelled.
+ */
+function PromiseAbortable(promise, successCallback, errorCallback) {
+  Abortable.call(this); // call super constructor
+  let complete = false;
+  this.cancel = function(e) {
+    if (!complete) {
+      complete = true;
+      errorCallback(e || new CancelledException());
+    }
+  };
+  promise.then(function(result) {
+    if (!complete) {
+      successCallback(result);
+      complete = true;
+    }
+  }).catch(function(e) {
+    if (!complete) {
+      complete = true;
+      errorCallback(e);
+    }
+  });
+}
+PromiseAbortable.prototype = Object.create(Abortable.prototype);
+PromiseAbortable.prototype.constructor = PromiseAbortable;
+
+/**
  * Utility implementation, for allowing to abort a setTimeout.
  * Use like: return new TimeoutAbortable(setTimeout(function(){ ... }, 0));
  * @param setTimeoutID {Integer}  Return value of setTimeout()
  */
 function TimeoutAbortable(setTimeoutID) {
   Abortable.call(this); // call super constructor
   this._id = setTimeoutID;
 }
@@ -379,17 +407,17 @@ function PriorityOrderAbortable(successC
   this._successfulCall = null;
 
   this.addOneFinishedObserver(finishedCall => {
     for (let call of this._calls) {
       if (!call.finished) {
         if (this._successfulCall) {
           // abort
           if (call.callerAbortable) {
-            call.callerAbortable.cancel(NoLongerNeededException("Another higher call succeeded"));
+            call.callerAbortable.cancel(new NoLongerNeededException("Another higher call succeeded"));
           }
           continue;
         }
         // It's pending. do nothing and wait for it.
         return;
       }
       if (!call.succeeded) {
         // it failed. ignore it.