Backed out 2 changesets (bug 1305836) for causing the failure rate of browser_bug880101.js to explode
authorPhil Ringnalda <philringnalda@gmail.com>
Thu, 29 Sep 2016 21:19:55 -0700
changeset 315813 6f31b67533799bd84fe3b207681bcef2c78dac47
parent 315812 8305ce9fcda4d74ecc4dfab1dfd13129e3fea629
child 315814 59db3b0781908d9e2145477ff8dc5a85729e25bf
child 315974 4ea6813874d8644200306b2553cf2fcfefb28080
push id20634
push usercbook@mozilla.com
push dateFri, 30 Sep 2016 10:10:13 +0000
treeherderfx-team@afe79b010d13 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1305836, 880101
milestone52.0a1
backs out578a4588fb7cb7c6140edeb268c9f0c3cc13edc3
89a22098629e7e18383d794b78ee0700a7b28d96
Backed out 2 changesets (bug 1305836) for causing the failure rate of browser_bug880101.js to explode Backed out changeset 578a4588fb7c (bug 1305836) Backed out changeset 89a22098629e (bug 1305836)
testing/mochitest/browser-test-overlay.xul
testing/mochitest/browser-test.js
testing/mochitest/cc-analyzer.js
testing/mochitest/jar.mn
testing/mochitest/moz.build
--- a/testing/mochitest/browser-test-overlay.xul
+++ b/testing/mochitest/browser-test-overlay.xul
@@ -4,9 +4,10 @@
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <overlay id="browserTestOverlay"
          xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"/>
   <script type="application/javascript" src="chrome://mochikit/content/mochitest-e10s-utils.js"/>
   <script type="application/javascript" src="chrome://mochikit/content/browser-test.js"/>
+  <script type="application/javascript" src="chrome://mochikit/content/cc-analyzer.js"/>
 </overlay>
--- a/testing/mochitest/browser-test.js
+++ b/testing/mochitest/browser-test.js
@@ -124,16 +124,18 @@ function testInit() {
   let gmm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
   gmm.loadFrameScript("chrome://mochikit/content/tests/SimpleTest/AsyncUtilsContent.js", true);
 }
 
 function Tester(aTests, structuredLogger, aCallback) {
   this.structuredLogger = structuredLogger;
   this.tests = aTests;
   this.callback = aCallback;
+  this.openedWindows = {};
+  this.openedURLs = {};
 
   this._scriptLoader = Services.scriptloader;
   this.EventUtils = {};
   this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", this.EventUtils);
   var simpleTestScope = {};
   this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/specialpowersAPI.js", simpleTestScope);
   this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/SpecialPowersObserverAPI.js", simpleTestScope);
   this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/ChromePowers.js", simpleTestScope);
@@ -208,16 +210,17 @@ Tester.prototype = {
   ExtensionTestUtils: null,
   Assert: null,
 
   repeat: 0,
   runUntilFailure: false,
   checker: null,
   currentTestIndex: -1,
   lastStartTime: null,
+  openedWindows: null,
   lastAssertionCount: 0,
   failuresFromInitialWindowState: 0,
 
   get currentTest() {
     return this.tests[this.currentTestIndex];
   },
   get done() {
     return this.currentTestIndex == this.tests.length - 1;
@@ -240,16 +243,18 @@ Tester.prototype = {
       let coveragePath = gConfig.jscovDirPrefix;
       let {CoverageCollector} = Cu.import("resource://testing-common/CoverageUtils.jsm",
                                           {});
       this._coverageCollector = new CoverageCollector(coveragePath);
     }
 
     this.structuredLogger.info("*** Start BrowserChrome Test Results ***");
     Services.console.registerListener(this);
+    Services.obs.addObserver(this, "chrome-document-global-created", false);
+    Services.obs.addObserver(this, "content-document-global-created", false);
     this._globalProperties = Object.keys(window);
     this._globalPropertyWhitelist = [
       "navigator", "constructor", "top",
       "Application",
       "__SS_tabsToRestore", "__SSi",
       "webConsoleCommandController",
     ];
 
@@ -360,16 +365,18 @@ Tester.prototype = {
     if (this.repeat > 0) {
       --this.repeat;
       this.currentTestIndex = -1;
       this.nextTest();
     }
     else{
       TabDestroyObserver.destroy();
       Services.console.unregisterListener(this);
+      Services.obs.removeObserver(this, "chrome-document-global-created");
+      Services.obs.removeObserver(this, "content-document-global-created");
       this.Promise.Debugging.clearUncaughtErrorObservers();
       this._treatUncaughtRejectionsAsFailures = false;
 
       // In the main process, we print the ShutdownLeaksCollector message here.
       let pid = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).processID;
       dump("Completed ShutdownLeaks collections in process " + pid + "\n");
 
       this.structuredLogger.info("TEST-START | Shutdown");
@@ -388,26 +395,50 @@ Tester.prototype = {
                                       "No tests to run. Did you pass invalid test_paths?");
       }
       this.structuredLogger.info("*** End BrowserChrome Test Results ***");
 
       // Tests complete, notify the callback and return
       this.callback(this.tests);
       this.callback = null;
       this.tests = null;
+      this.openedWindows = null;
     }
   },
 
   haltTests: function Tester_haltTests() {
     // Do not run any further tests
     this.currentTestIndex = this.tests.length - 1;
     this.repeat = 0;
   },
 
-  observe: function Tester_onConsoleMessage(aConsoleMessage) {
+  observe: function Tester_observe(aSubject, aTopic, aData) {
+    if (!aTopic) {
+      this.onConsoleMessage(aSubject);
+    } else if (this.currentTest) {
+      this.onDocumentCreated(aSubject);
+    }
+  },
+
+  onDocumentCreated: function Tester_onDocumentCreated(aWindow) {
+    let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                       .getInterface(Ci.nsIDOMWindowUtils);
+    let outerID = utils.outerWindowID;
+    let innerID = utils.currentInnerWindowID;
+
+    if (!(outerID in this.openedWindows)) {
+      this.openedWindows[outerID] = this.currentTest;
+    }
+    this.openedWindows[innerID] = this.currentTest;
+
+    let url = aWindow.location.href || "about:blank";
+    this.openedURLs[outerID] = this.openedURLs[innerID] = url;
+  },
+
+  onConsoleMessage: function Tester_onConsoleMessage(aConsoleMessage) {
     // Ignore empty messages.
     if (!aConsoleMessage.message)
       return;
 
     try {
       var msg = "Console message: " + aConsoleMessage.message;
       if (this.currentTest)
         this.currentTest.addResult(new testMessage(msg));
@@ -600,29 +631,39 @@ Tester.prototype = {
           }
         }
 
         // Schedule GC and CC runs before finishing in order to detect
         // DOM windows leaked by our tests or the tested code. Note that we
         // use a shrinking GC so that the JS engine will discard JIT code and
         // JIT caches more aggressively.
 
-        let shutdownCleanup = aCallback => {
+        let checkForLeakedGlobalWindows = aCallback => {
           Cu.schedulePreciseShrinkingGC(() => {
-            // Run the GC and CC a few times to make sure that as much
-            // as possible is freed.
-            let numCycles = 3;
-            for (i = 0; i < numCycles; i++) {
-              Cu.forceGC();
-              Cu.forceCC();
-            }
-            aCallback();
+            let analyzer = new CCAnalyzer();
+            analyzer.run(() => {
+              let results = [];
+              for (let obj of analyzer.find("nsGlobalWindow ")) {
+                let m = obj.name.match(/^nsGlobalWindow #(\d+)/);
+                if (m && m[1] in this.openedWindows)
+                  results.push({ name: obj.name, url: m[1] });
+              }
+              aCallback(results);
+            });
           });
         };
 
+        let reportLeaks = aResults => {
+          for (let result of aResults) {
+            let test = this.openedWindows[result.url];
+            let msg = "leaked until shutdown [" + result.name +
+                      " " + (this.openedURLs[result.url] || "NULL") + "]";
+            test.addResult(new testResult(false, msg, "", false));
+          }
+        };
 
         let {AsyncShutdown} =
           Cu.import("resource://gre/modules/AsyncShutdown.jsm", {});
 
         let barrier = new AsyncShutdown.Barrier(
           "ShutdownLeaks: Wait for cleanup to be finished before checking for leaks");
         Services.obs.notifyObservers({wrappedJSObject: barrier},
           "shutdown-leaks-before-check", null);
@@ -634,19 +675,27 @@ Tester.prototype = {
           // Simulate memory pressure so that we're forced to free more resources
           // and thus get rid of more false leaks like already terminated workers.
           Services.obs.notifyObservers(null, "memory-pressure", "heap-minimize");
 
           let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
                        .getService(Ci.nsIMessageBroadcaster);
           ppmm.broadcastAsyncMessage("browser-test:collect-request");
 
-          shutdownCleanup(() => {
+          checkForLeakedGlobalWindows(aResults => {
+            if (aResults.length == 0) {
+              this.finish();
+              return;
+            }
+            // After the first check, if there are reported leaked windows, sleep
+            // for a while, to allow off-main-thread work to complete and free up
+            // main-thread objects.  Then check again.
             setTimeout(() => {
-              shutdownCleanup(() => {
+              checkForLeakedGlobalWindows(aResults => {
+                reportLeaks(aResults);
                 this.finish();
               });
             }, 1000);
           });
         });
 
         return;
       }
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/cc-analyzer.js
@@ -0,0 +1,127 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function CCAnalyzer() {
+}
+
+CCAnalyzer.prototype = {
+  clear: function () {
+    this.callback = null;
+    this.processingCount = 0;
+    this.graph = {};
+    this.roots = [];
+    this.garbage = [];
+    this.edges = [];
+    this.listener = null;
+  },
+
+  run: function (aCallback) {
+    this.clear();
+    this.callback = aCallback;
+
+    this.listener = Cc["@mozilla.org/cycle-collector-logger;1"].
+      createInstance(Ci.nsICycleCollectorListener);
+
+    this.listener.disableLog = true;
+    this.listener.wantAfterProcessing = true;
+
+    this.runCC(3);
+  },
+
+  runCC: function (aCounter) {
+    let utils = window.QueryInterface(Ci.nsIInterfaceRequestor).
+        getInterface(Ci.nsIDOMWindowUtils);
+
+    if (aCounter > 1) {
+      utils.garbageCollect();
+      setTimeout(this.runCC.bind(this, aCounter - 1), 0);
+    } else {
+      utils.garbageCollect(this.listener);
+      this.processLog();
+    }
+  },
+
+  processLog: function () {
+    // Process entire heap step by step in 5K chunks
+    for (let i = 0; i < 5000; i++) {
+      if (!this.listener.processNext(this)) {
+        this.callback();
+        this.clear();
+        return;
+      }
+    }
+
+    // Next chunk on timeout.
+    setTimeout(this.processLog.bind(this), 0);
+  },
+
+  noteRefCountedObject: function (aAddress, aRefCount, aObjectDescription) {
+    let o = this.ensureObject(aAddress);
+    o.address = aAddress;
+    o.refcount = aRefCount;
+    o.name = aObjectDescription;
+  },
+
+  noteGCedObject: function (aAddress, aMarked, aObjectDescription) {
+    let o = this.ensureObject(aAddress);
+    o.address = aAddress;
+    o.gcmarked = aMarked;
+    o.name = aObjectDescription;
+  },
+
+  noteEdge: function (aFromAddress, aToAddress, aEdgeName) {
+    let fromObject = this.ensureObject(aFromAddress);
+    let toObject = this.ensureObject(aToAddress);
+    fromObject.edges.push({name: aEdgeName, to: toObject});
+    toObject.owners.push({name: aEdgeName, from: fromObject});
+
+    this.edges.push({
+      name: aEdgeName,
+      from: fromObject,
+      to: toObject
+    });
+  },
+
+  describeRoot: function (aAddress, aKnownEdges) {
+    let o = this.ensureObject(aAddress);
+    o.root = true;
+    o.knownEdges = aKnownEdges;
+    this.roots.push(o);
+  },
+
+  describeGarbage: function (aAddress) {
+    let o = this.ensureObject(aAddress);
+    o.garbage = true;
+    this.garbage.push(o);
+  },
+
+  ensureObject: function (aAddress) {
+    if (!this.graph[aAddress])
+      this.graph[aAddress] = new CCObject();
+
+    return this.graph[aAddress];
+  },
+
+  find: function (aText) {
+    let result = [];
+    for (let address in this.graph) {
+      let o = this.graph[address];
+      if (!o.garbage && o.name.indexOf(aText) >= 0)
+        result.push(o);
+    }
+    return result;
+  }
+};
+
+function CCObject() {
+  this.name = "";
+  this.address = null;
+  this.refcount = 0;
+  this.gcmarked = false;
+  this.root = false;
+  this.garbage = false;
+  this.knownEdges = 0;
+  this.edges = [];
+  this.owners = [];
+}
--- a/testing/mochitest/jar.mn
+++ b/testing/mochitest/jar.mn
@@ -2,16 +2,17 @@ mochikit.jar:
 % content mochikit %content/
   content/browser-harness.xul (browser-harness.xul)
   content/browser-test.js (browser-test.js)
   content/browser-test-overlay.xul (browser-test-overlay.xul)
   content/jetpack-package-harness.js (jetpack-package-harness.js)
   content/jetpack-package-overlay.xul (jetpack-package-overlay.xul)
   content/jetpack-addon-harness.js (jetpack-addon-harness.js)
   content/jetpack-addon-overlay.xul (jetpack-addon-overlay.xul)
+  content/cc-analyzer.js (cc-analyzer.js)
   content/chrome-harness.js (chrome-harness.js)
   content/mochitest-e10s-utils.js (mochitest-e10s-utils.js)
   content/shutdown-leaks-collector.js (shutdown-leaks-collector.js)
   content/ShutdownLeaksCollector.jsm (ShutdownLeaksCollector.jsm)
   content/harness.xul (harness.xul)
   content/redirect.html (redirect.html)
   content/server.js (server.js)
   content/chunkifyTests.js (chunkifyTests.js)
--- a/testing/mochitest/moz.build
+++ b/testing/mochitest/moz.build
@@ -40,16 +40,17 @@ TEST_HARNESS_FILES.testing.mochitest += 
     '/build/valgrind/cross-architecture.sup',
     '/build/valgrind/i386-redhat-linux-gnu.sup',
     '/build/valgrind/x86_64-redhat-linux-gnu.sup',
     '/netwerk/test/httpserver/httpd.js',
     'bisection.py',
     'browser-harness.xul',
     'browser-test-overlay.xul',
     'browser-test.js',
+    'cc-analyzer.js',
     'chrome-harness.js',
     'chunkifyTests.js',
     'gen_template.pl',
     'harness.xul',
     'jetpack-addon-harness.js',
     'jetpack-addon-overlay.xul',
     'jetpack-package-harness.js',
     'jetpack-package-overlay.xul',