Bug 932162 - dispatch IndexedDB FileInfo releases to the main thread. r=khuey
authorAndrew Sutherland <asutherland@asutherland.org>
Tue, 19 Nov 2013 06:00:20 -0500
changeset 156310 854452f105dc80d47d7b1e34f3fc2a99d5da23a6
parent 156309 e959d0f09a0c35260acb5cb9ca1eec30880cee9b
child 156311 8d1bf5ee0d69042c2f57532c6f33f705c276b9b1
push id25674
push userryanvm@gmail.com
push dateTue, 19 Nov 2013 18:36:40 +0000
treeherdermozilla-central@efaeb105b189 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey
bugs932162
milestone28.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 932162 - dispatch IndexedDB FileInfo releases to the main thread. r=khuey Try runs: Green run prior to b2g mochitest fix and assertion changes: https://tbpl.mozilla.org/?tree=Try&rev=b071f8ef9617 green runs with assertion changes, not b2g disabling: https://tbpl.mozilla.org/?tree=Try&rev=b071f8ef9617 green b2g run: https://tbpl.mozilla.org/?tree=Try&rev=67510897d368
dom/indexedDB/FileInfo.cpp
dom/indexedDB/test/blob_worker_crash_iframe.html
dom/indexedDB/test/mochitest.ini
dom/indexedDB/test/test_blob_worker_crash.html
testing/mochitest/b2g-desktop.json
--- a/dom/indexedDB/FileInfo.cpp
+++ b/dom/indexedDB/FileInfo.cpp
@@ -5,21 +5,38 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "FileInfo.h"
 #include "nsThreadUtils.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 
 USING_INDEXEDDB_NAMESPACE
 
+namespace {
+
+class CleanupFileRunnable MOZ_FINAL : public nsIRunnable
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIRUNNABLE
+
+  CleanupFileRunnable(FileManager* aFileManager, int64_t aFileId);
+
+private:
+  nsRefPtr<FileManager> mFileManager;
+  int64_t mFileId;
+};
+
+} // anonymous namespace
+
 // static
 FileInfo*
 FileInfo::Create(FileManager* aFileManager, int64_t aId)
 {
-  NS_ASSERTION(aId > 0, "Wrong id!");
+  MOZ_ASSERT(aId > 0, "Wrong id!");
 
   if (aId <= INT16_MAX) {
     return new FileInfo16(aFileManager, aId);
   }
 
   if (aId <= INT32_MAX) {
     return new FileInfo32(aFileManager, aId);
   }
@@ -93,21 +110,45 @@ FileInfo::UpdateReferences(mozilla::Thre
   }
 
   delete this;
 }
 
 void
 FileInfo::Cleanup()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  nsRefPtr<CleanupFileRunnable> cleaner =
+    new CleanupFileRunnable(mFileManager, Id());
+
+  // IndexedDatabaseManager is main-thread only.
+  if (!NS_IsMainThread()) {
+    NS_DispatchToMainThread(cleaner);
+    return;
+  }
+
+  cleaner->Run();
+}
 
-  if (quota::QuotaManager::IsShuttingDown()) {
-    return;
+CleanupFileRunnable::CleanupFileRunnable(FileManager* aFileManager,
+                                         int64_t aFileId)
+: mFileManager(aFileManager), mFileId(aFileId)
+{
+}
+
+NS_IMPL_ISUPPORTS1(CleanupFileRunnable,
+                   nsIRunnable)
+
+NS_IMETHODIMP
+CleanupFileRunnable::Run()
+{
+  if (mozilla::dom::quota::QuotaManager::IsShuttingDown()) {
+    return NS_OK;
   }
 
   nsRefPtr<IndexedDatabaseManager> mgr = IndexedDatabaseManager::Get();
-  NS_ASSERTION(mgr, "Shouldn't be null!");
+  MOZ_ASSERT(mgr);
 
-  if (NS_FAILED(mgr->AsyncDeleteFile(mFileManager, Id()))) {
+  if (NS_FAILED(mgr->AsyncDeleteFile(mFileManager, mFileId))) {
     NS_WARNING("Failed to delete file asynchronously!");
   }
+
+  return NS_OK;
 }
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/blob_worker_crash_iframe.html
@@ -0,0 +1,98 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Test</title>
+
+  <script type="text/javascript">
+  function report(result) {
+    var message = { source: "iframe" };
+    message.result = result;
+    window.parent.postMessage(message, "*");
+  }
+
+  function runIndexedDBTest() {
+    var db = null;
+
+    // Create the data-store
+    function createDatastore() {
+      try {
+        var request = indexedDB.open(window.location.pathname, 1);
+        request.onupgradeneeded = function(event) {
+          event.target.result.createObjectStore("foo");
+        }
+        request.onsuccess = function(event) {
+          db = event.target.result;
+          createAndStoreBlob();
+        }
+      }
+      catch (e) {
+dump("EXCEPTION IN CREATION: " + e + "\n " + e.stack + "\n");
+        report(false);
+      }
+    }
+
+    function createAndStoreBlob() {
+      const BLOB_DATA = ["fun ", "times ", "all ", "around!"];
+      var blob = new Blob(BLOB_DATA, { type: "text/plain" });
+      var objectStore = db.transaction("foo", "readwrite").objectStore("foo");
+      objectStore.add({ blob: blob }, 42).onsuccess = refetchBlob;
+    }
+
+    function refetchBlob() {
+      var foo = db.transaction("foo").objectStore("foo");
+      foo.get(42).onsuccess = fetchedBlobCreateWorkerAndSendBlob;
+    }
+
+    function fetchedBlobCreateWorkerAndSendBlob(event) {
+      var idbBlob = event.target.result.blob;
+      var compositeBlob = new Blob(['I like the following blob: ', idbBlob],
+                                   { type: "text/fancy" });
+
+      function workerScript() {
+        onmessage = function(event) {
+          // Save the Blob to the worker's global scope.
+          self.holdOntoBlob = event.data;
+          // Send any message so we can serialize and keep our runtime behaviour
+          // consistent.
+          postMessage('kung fu death grip established');
+        }
+      }
+
+      var url =
+        URL.createObjectURL(new Blob(["(", workerScript.toSource(), ")()"]));
+
+      // Keep a reference to the worker on the window.
+      var worker = window.worker = new Worker(url);
+      worker.postMessage(compositeBlob);
+      worker.onmessage = workerLatchedBlobDeleteFromDB;
+    }
+
+    function workerLatchedBlobDeleteFromDB() {
+      // Delete the reference to the Blob from the database leaving the worker
+      // thread reference as the only live reference once a GC has cleaned
+      // out our references that we sent to the worker.  The page that owns
+      // us triggers a GC just for that reason.
+      var objectStore = db.transaction("foo", "readwrite").objectStore("foo");
+      objectStore.delete(42).onsuccess = closeDBTellOwningThread;
+    }
+
+    function closeDBTellOwningThread(event) {
+      // Now that worker has latched the blob, clean up the database.
+      db.close();
+      db = null;
+      report('ready');
+    }
+
+    createDatastore();
+  }
+  </script>
+
+</head>
+
+<body onload="runIndexedDBTest();">
+</body>
+
+</html>
--- a/dom/indexedDB/test/mochitest.ini
+++ b/dom/indexedDB/test/mochitest.ini
@@ -1,12 +1,13 @@
 [DEFAULT]
 support-files =
   bfcache_iframe1.html
   bfcache_iframe2.html
+  blob_worker_crash_iframe.html
   error_events_abort_transactions_iframe.html
   event_propagation_iframe.html
   exceptions_in_events_iframe.html
   file.js
   file_app_isolation.html
   file_app_isolation.js
   helpers.js
   leaving_page_iframe.html
@@ -21,16 +22,17 @@ support-files =
 [test_advance.html]
 [test_app_isolation_inproc.html]
 [test_app_isolation_oop.html]
 [test_autoIncrement.html]
 [test_autoIncrement_indexes.html]
 [test_bfcache.html]
 [test_blob_archive.html]
 [test_blob_simple.html]
+[test_blob_worker_crash.html]
 [test_clear.html]
 [test_complex_keyPaths.html]
 [test_count.html]
 [test_create_index.html]
 [test_create_index_with_integer_keys.html]
 [test_create_objectStore.html]
 [test_cursor_mutation.html]
 [test_cursor_update_updates_indexes.html]
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_blob_worker_crash.html
@@ -0,0 +1,62 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Blob Worker Crash Test</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+  /*
+   * This tests ensures that if the last live reference to a Blob is on the
+   * worker and the database has already been shutdown, that there is no crash
+   * when the owning page gets cleaned up which causes the termination of the
+   * worker which in turn garbage collects during its shutdown.
+   *
+   * We do the IndexedDB stuff in the iframe so we can kill it as part of our
+   * test.  Doing it out here is no good.
+   */
+
+  function testSteps()
+  {
+    info("Open iframe, wait for it to do its IndexedDB stuff.");
+
+    let iframe = document.getElementById("iframe1");
+    window.addEventListener("message", grabEventAndContinueHandler, false);
+    // Put it in a different origin to be safe
+    //allowUnlimitedQuota("http://example.org/");
+    iframe.src = //"http://example.org" +
+                 window.location.pathname.replace(
+                   "test_blob_worker_crash.html",
+                   "blob_worker_crash_iframe.html");
+
+    let event = yield unexpectedSuccessHandler;
+    is(event.data.result, "ready", "worker initialized correctly");
+
+    info("Trigger a GC to clean-up the iframe's main-thread IndexedDB");
+    scheduleGC();
+    yield undefined;
+
+    info("Kill the iframe, forget about it, trigger a GC.");
+    iframe.parentNode.removeChild(iframe);
+    iframe = null;
+    scheduleGC();
+    yield undefined;
+
+    info("If we are still alive, then we win!");
+    ok('Did not crash / trigger an assert!');
+
+    finishTest();
+    yield undefined;
+  }
+  </script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+  <iframe id="iframe1"></iframe>
+</html>
--- a/testing/mochitest/b2g-desktop.json
+++ b/testing/mochitest/b2g-desktop.json
@@ -499,16 +499,17 @@
     "dom/indexedDB/test/test_add_twice_failure.html": "Bug 931116, b2g desktop specific, initial triage",
     "dom/indexedDB/test/test_advance.html": "Bug 931116, b2g desktop specific, initial triage",
     "dom/indexedDB/test/test_app_isolation_inproc.html": "Bug 931116, b2g desktop specific, initial triage",
     "dom/indexedDB/test/test_app_isolation_oop.html": "Bug 931116, b2g desktop specific, initial triage",
     "dom/indexedDB/test/test_autoIncrement.html": "Bug 931116, b2g desktop specific, initial triage",
     "dom/indexedDB/test/test_autoIncrement_indexes.html": "Bug 931116, b2g desktop specific, initial triage",
     "dom/indexedDB/test/test_bfcache.html": "Bug 931116, b2g desktop specific, initial triage",
     "dom/indexedDB/test/test_blob_archive.html": "Bug 931116, b2g desktop specific, initial triage",
+    "dom/indexedDB/test/test_blob_worker_crash.html": "Bug 931116, b2g desktop specific, bug 927889 still present",
     "dom/indexedDB/test/test_blob_simple.html": "Bug 931116, b2g desktop specific, initial triage",
     "dom/indexedDB/test/test_clear.html": "Bug 931116, b2g desktop specific, initial triage",
     "dom/indexedDB/test/test_complex_keyPaths.html": "Bug 931116, b2g desktop specific, initial triage",
     "dom/indexedDB/test/test_count.html": "Bug 931116, b2g desktop specific, initial triage",
     "dom/indexedDB/test/test_create_index.html": "Bug 931116, b2g desktop specific, initial triage",
     "dom/indexedDB/test/test_create_index_with_integer_keys.html": "Bug 931116, b2g desktop specific, initial triage",
     "dom/indexedDB/test/test_create_objectStore.html": "Bug 931116, b2g desktop specific, initial triage",
     "dom/indexedDB/test/test_cursor_mutation.html": "Bug 931116, b2g desktop specific, initial triage",