Bug 697247: Part 2 - Rework IDB synchronization mechanisms. r=bent
authorKyle Huey <khuey@kylehuey.com>
Wed, 02 Nov 2011 08:53:12 -0400
changeset 79622 572e4723a49653e5095e9319478c727404de5fa4
parent 79621 a64b3abb31c4053aa4e79b3b6cf973f802f13fa7
child 79623 80af665378fd12062aa1438a584ff332f8beaddd
push id286
push userposhannessy@mozilla.com
push dateFri, 04 Nov 2011 01:43:27 +0000
treeherderfx-team@3491b2f021bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbent
bugs697247
milestone10.0a1
Bug 697247: Part 2 - Rework IDB synchronization mechanisms. r=bent
dom/indexedDB/AsyncConnectionHelper.cpp
dom/indexedDB/CheckPermissionsHelper.cpp
dom/indexedDB/CheckPermissionsHelper.h
dom/indexedDB/IDBDatabase.cpp
dom/indexedDB/IDBFactory.cpp
dom/indexedDB/IndexedDatabaseManager.cpp
dom/indexedDB/IndexedDatabaseManager.h
dom/indexedDB/OpenDatabaseHelper.cpp
dom/indexedDB/OpenDatabaseHelper.h
dom/indexedDB/nsIIDBDatabaseException.idl
dom/indexedDB/test/Makefile.in
dom/indexedDB/test/test_setVersion_exclusion.html
dom/indexedDB/test/test_traffic_jam.html
--- a/dom/indexedDB/AsyncConnectionHelper.cpp
+++ b/dom/indexedDB/AsyncConnectionHelper.cpp
@@ -554,19 +554,17 @@ NS_IMPL_QUERY_INTERFACE1(TransactionPool
 
 NS_IMETHODIMP
 TransactionPoolEventTarget::Dispatch(nsIRunnable* aRunnable,
                                      PRUint32 aFlags)
 {
   NS_ASSERTION(aRunnable, "Null pointer!");
   NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL, "Unsupported!");
 
-  TransactionThreadPool* pool = TransactionThreadPool::Get();
-  NS_ASSERTION(pool, "This should never be null!");
-
+  TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
   return pool->Dispatch(mTransaction, aRunnable, false, nsnull);
 }
 
 NS_IMETHODIMP
 TransactionPoolEventTarget::IsOnCurrentThread(bool* aResult)
 {
   *aResult = false;
   return NS_OK;
--- a/dom/indexedDB/CheckPermissionsHelper.cpp
+++ b/dom/indexedDB/CheckPermissionsHelper.cpp
@@ -201,13 +201,13 @@ CheckPermissionsHelper::Observe(nsISuppo
 
   nsresult rv;
   mPromptResult = nsDependentString(aData).ToInteger(&rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
   NS_ASSERTION(mgr, "This should never be null!");
 
-  rv = mgr->WaitForOpenAllowed(mName, mASCIIOrigin, this);
+  rv = NS_DispatchToCurrentThread(this);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
--- a/dom/indexedDB/CheckPermissionsHelper.h
+++ b/dom/indexedDB/CheckPermissionsHelper.h
@@ -59,35 +59,31 @@ class CheckPermissionsHelper : public ns
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIRUNNABLE
   NS_DECL_NSIINTERFACEREQUESTOR
   NS_DECL_NSIOBSERVER
 
   CheckPermissionsHelper(OpenDatabaseHelper* aHelper,
                          nsIDOMWindow* aWindow,
-                         const nsAString& aName,
                          const nsACString& aASCIIOrigin)
   : mHelper(aHelper),
     mWindow(aWindow),
-    mName(aName),
     mASCIIOrigin(aASCIIOrigin),
     mHasPrompted(false),
     mPromptResult(0)
   {
     NS_ASSERTION(aHelper, "Null pointer!");
     NS_ASSERTION(aWindow, "Null pointer!");
-    NS_ASSERTION(!aName.IsEmpty(), "Empty name!");
     NS_ASSERTION(!aASCIIOrigin.IsEmpty(), "Empty origin!");
   }
 
 private:
   nsRefPtr<OpenDatabaseHelper> mHelper;
   nsCOMPtr<nsIDOMWindow> mWindow;
-  nsString mName;
   nsCString mASCIIOrigin;
   bool mHasPrompted;
   PRUint32 mPromptResult;
 };
 
 END_INDEXEDDB_NAMESPACE
 
 #endif // mozilla_dom_indexeddb_checkpermissionshelper_h__
--- a/dom/indexedDB/IDBDatabase.cpp
+++ b/dom/indexedDB/IDBDatabase.cpp
@@ -445,20 +445,16 @@ IDBDatabase::ExitSetVersionTransaction()
 {
   DatabaseInfo* dbInfo;
   if (!DatabaseInfo::Get(mDatabaseId, &dbInfo)) {
     NS_ERROR("This should never fail!");
   }
 
   NS_ASSERTION(dbInfo->runningVersionChange, "How did that happen?");
   dbInfo->runningVersionChange = false;
-
-  IndexedDatabaseManager* manager = IndexedDatabaseManager::Get();
-  NS_ASSERTION(manager, "We should always have a manager here");
-  manager->UnblockSetVersionRunnable(this);
 }
 
 void
 IDBDatabase::OnUnlink()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(!mOwner, "Should have been cleared already!");
 
--- a/dom/indexedDB/IDBFactory.cpp
+++ b/dom/indexedDB/IDBFactory.cpp
@@ -435,19 +435,19 @@ IDBFactory::Open(const nsAString& aName,
 
   nsRefPtr<OpenDatabaseHelper> openHelper =
     new OpenDatabaseHelper(request, aName, origin, aVersion);
 
   rv = openHelper->Init();
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   nsRefPtr<CheckPermissionsHelper> permissionHelper =
-    new CheckPermissionsHelper(openHelper, window, aName, origin);
+    new CheckPermissionsHelper(openHelper, window, origin);
 
   nsRefPtr<IndexedDatabaseManager> mgr = IndexedDatabaseManager::GetOrCreate();
   NS_ENSURE_TRUE(mgr, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
-  rv = mgr->WaitForOpenAllowed(aName, origin, permissionHelper);
+  rv = mgr->WaitForOpenAllowed(origin, openHelper->Id(), permissionHelper);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   request.forget(_retval);
   return NS_OK;
 }
--- a/dom/indexedDB/IndexedDatabaseManager.cpp
+++ b/dom/indexedDB/IndexedDatabaseManager.cpp
@@ -138,144 +138,16 @@ EnumerateToTArray(const nsACString& aKey
   if (!array->AppendElements(*aValue)) {
     NS_WARNING("Out of memory!");
     return PL_DHASH_STOP;
   }
 
   return PL_DHASH_NEXT;
 }
 
-// Responsible for calling IDBDatabase.setVersion after a pending version change
-// transaction has completed.
-class DelayedSetVersion : public nsRunnable
-{
-public:
-  DelayedSetVersion(IDBDatabase* aDatabase,
-                    IDBOpenDBRequest* aRequest,
-                    PRInt64 aOldVersion,
-                    PRInt64 aNewVersion,
-                    AsyncConnectionHelper* aHelper)
-  : mDatabase(aDatabase),
-    mRequest(aRequest),
-    mOldVersion(aOldVersion),
-    mNewVersion(aNewVersion),
-    mHelper(aHelper)
-  {
-    NS_ASSERTION(aDatabase, "Null database!");
-    NS_ASSERTION(aRequest, "Null request!");
-    NS_ASSERTION(aHelper, "Null helper!");
-  }
-
-  NS_IMETHOD Run()
-  {
-    NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-    IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
-    NS_ASSERTION(mgr, "This should never be null!");
-
-    nsresult rv = mgr->SetDatabaseVersion(mDatabase, mRequest,
-                                          mOldVersion, mNewVersion,
-                                          mHelper);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    return NS_OK;
-  }
-
-private:
-  nsRefPtr<IDBDatabase> mDatabase;
-  nsRefPtr<IDBOpenDBRequest> mRequest;
-  PRInt64 mOldVersion;
-  PRInt64 mNewVersion;
-  nsRefPtr<AsyncConnectionHelper> mHelper;
-};
-
-// Responsible for firing "versionchange" events at all live and non-closed
-// databases, and for firing a "blocked" event at the requesting database if any
-// databases fail to close.
-class VersionChangeEventsRunnable : public nsRunnable
-{
-public:
-  VersionChangeEventsRunnable(
-                            IDBDatabase* aRequestingDatabase,
-                            IDBOpenDBRequest* aRequest,
-                            nsTArray<nsRefPtr<IDBDatabase> >& aWaitingDatabases,
-                            PRInt64 aOldVersion,
-                            PRInt64 aNewVersion)
-  : mRequestingDatabase(aRequestingDatabase),
-    mRequest(aRequest),
-    mOldVersion(aOldVersion),
-    mNewVersion(aNewVersion)
-  {
-    NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-    NS_ASSERTION(aRequestingDatabase, "Null pointer!");
-    NS_ASSERTION(aRequest, "Null pointer!");
-
-    if (!mWaitingDatabases.SwapElements(aWaitingDatabases)) {
-      NS_ERROR("This should never fail!");
-    }
-  }
-
-  NS_IMETHOD Run()
-  {
-    NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-    // Fire version change events at all of the databases that are not already
-    // closed. Also kick bfcached documents out of bfcache.
-    for (PRUint32 index = 0; index < mWaitingDatabases.Length(); index++) {
-      nsRefPtr<IDBDatabase>& database = mWaitingDatabases[index];
-
-      if (database->IsClosed()) {
-        continue;
-      }
-
-      // First check if the document the IDBDatabase is part of is bfcached
-      nsCOMPtr<nsIDocument> ownerDoc = database->GetOwnerDocument();
-      nsIBFCacheEntry* bfCacheEntry;
-      if (ownerDoc && (bfCacheEntry = ownerDoc->GetBFCacheEntry())) {
-        bfCacheEntry->RemoveFromBFCacheSync();
-        NS_ASSERTION(database->IsClosed(),
-                     "Kicking doc out of bfcache should have closed database");
-        continue;
-      }
-
-      // Otherwise fire a versionchange event.
-      nsRefPtr<nsDOMEvent> event = 
-        IDBVersionChangeEvent::Create(mOldVersion, mNewVersion);
-      NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
-
-      bool dummy;
-      database->DispatchEvent(event, &dummy);
-    }
-
-    // Now check to see if any didn't close. If there are some running still
-    // then fire the blocked event.
-    for (PRUint32 index = 0; index < mWaitingDatabases.Length(); index++) {
-      if (!mWaitingDatabases[index]->IsClosed()) {
-        nsRefPtr<nsDOMEvent> event =
-          IDBVersionChangeEvent::CreateBlocked(mOldVersion, mNewVersion);
-        NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
-
-        bool dummy;
-        mRequest->DispatchEvent(event, &dummy);
-
-        break;
-      }
-    }
-
-    return NS_OK;
-  }
-
-private:
-  nsRefPtr<IDBDatabase> mRequestingDatabase;
-  nsRefPtr<IDBOpenDBRequest> mRequest;
-  nsTArray<nsRefPtr<IDBDatabase> > mWaitingDatabases;
-  PRInt64 mOldVersion;
-  PRInt64 mNewVersion;
-};
-
 } // anonymous namespace
 
 IndexedDatabaseManager::IndexedDatabaseManager()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(!gInstance, "More than one instance!");
 }
 
@@ -421,210 +293,167 @@ IndexedDatabaseManager::UnregisterDataba
       mLiveDatabases.Remove(aDatabase->Origin());
     }
     return;
   }
   NS_ERROR("Didn't know anything about this database!");
 }
 
 void
-IndexedDatabaseManager::OnOriginClearComplete(OriginClearRunnable* aRunnable)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(aRunnable, "Null pointer!");
-  NS_ASSERTION(!aRunnable->mThread, "Thread should be null!");
-  NS_ASSERTION(aRunnable->mDelayedRunnables.IsEmpty(),
-               "Delayed runnables should have been dispatched already!");
-
-  if (!mOriginClearRunnables.RemoveElement(aRunnable)) {
-    NS_ERROR("Don't know anything about this runnable!");
-  }
-}
-
-void
 IndexedDatabaseManager::OnUsageCheckComplete(AsyncUsageRunnable* aRunnable)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aRunnable, "Null pointer!");
   NS_ASSERTION(!aRunnable->mURI, "Should have been cleared!");
   NS_ASSERTION(!aRunnable->mCallback, "Should have been cleared!");
 
   if (!mUsageRunnables.RemoveElement(aRunnable)) {
     NS_ERROR("Don't know anything about this runnable!");
   }
 }
 
-void
-IndexedDatabaseManager::OnSetVersionRunnableComplete(
-                                                  SetVersionRunnable* aRunnable)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(aRunnable, "Null pointer!");
-  NS_ASSERTION(aRunnable->mDelayedRunnables.IsEmpty(),
-               "Delayed runnables should have been dispatched already!");
-
-  // Remove this runnable from the list. This will allow other databases to
-  // begin to request version changes.
-  if (!mSetVersionRunnables.RemoveElement(aRunnable)) {
-    NS_ERROR("Don't know anything about this runnable!");
-  }
-}
-
 nsresult
-IndexedDatabaseManager::WaitForOpenAllowed(const nsAString& aName,
-                                           const nsACString& aOrigin,
+IndexedDatabaseManager::WaitForOpenAllowed(const nsACString& aOrigin,
+                                           nsIAtom* aId,
                                            nsIRunnable* aRunnable)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!aName.IsEmpty(), "Empty name!");
   NS_ASSERTION(!aOrigin.IsEmpty(), "Empty origin!");
   NS_ASSERTION(aRunnable, "Null pointer!");
 
-  // See if we're currently clearing database files for this origin. If so then
-  // queue the runnable for later dispatch after we're done clearing.
-  PRUint32 count = mOriginClearRunnables.Length();
+  nsAutoPtr<SynchronizedOp> op(new SynchronizedOp(aOrigin, aId));
+
+  // See if this runnable needs to wait.
+  bool delayed = false;
+  for (PRUint32 index = mSynchronizedOps.Length(); index > 0; index--) {
+    nsAutoPtr<SynchronizedOp>& existingOp = mSynchronizedOps[index - 1];
+    if (op->MustWaitFor(*existingOp)) {
+      existingOp->DelayRunnable(aRunnable);
+      delayed = true;
+      break;
+    }
+  }
+
+  // Otherwise, dispatch it immediately.
+  if (!delayed) {
+    nsresult rv = NS_DispatchToCurrentThread(aRunnable);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  // Adding this to the synchronized ops list will block any additional
+  // ops from proceeding until this one is done.
+  mSynchronizedOps.AppendElement(op.forget());
+
+  return NS_OK;
+}
+
+void
+IndexedDatabaseManager::AllowNextSynchronizedOp(const nsACString& aOrigin,
+                                                nsIAtom* aId)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(!aOrigin.IsEmpty(), "Empty origin!");
+
+  PRUint32 count = mSynchronizedOps.Length();
   for (PRUint32 index = 0; index < count; index++) {
-    nsRefPtr<OriginClearRunnable>& data = mOriginClearRunnables[index];
-    if (data->mOrigin == aOrigin) {
-      nsCOMPtr<nsIRunnable>* newPtr =
-        data->mDelayedRunnables.AppendElement(aRunnable);
-      NS_ENSURE_TRUE(newPtr, NS_ERROR_OUT_OF_MEMORY);
+    nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
+    if (op->mOrigin.Equals(aOrigin)) {
+      if (op->mId == aId) {
+        NS_ASSERTION(op->mDatabases.IsEmpty(), "How did this happen?");
+
+        op->DispatchDelayedRunnables();
 
-      return NS_OK;
+        mSynchronizedOps.RemoveElementAt(index);
+        return;
+      }
+
+      // If one or the other is for an origin clear, we should have matched
+      // solely on origin.
+      NS_ASSERTION(op->mId && aId, "Why didn't we match earlier?");
     }
   }
 
-  // Check to see if we're currently doing a SetVersion transaction for this
-  // database. If so then we delay this runnable for later.
-  for (PRUint32 index = 0; index < mSetVersionRunnables.Length(); index++) {
-    nsRefPtr<SetVersionRunnable>& runnable = mSetVersionRunnables[index];
-    if (runnable->mRequestingDatabase->Name() == aName &&
-        runnable->mRequestingDatabase->Origin() == aOrigin) {
-      nsCOMPtr<nsIRunnable>* newPtr =
-        runnable->mDelayedRunnables.AppendElement(aRunnable);
-      NS_ENSURE_TRUE(newPtr, NS_ERROR_OUT_OF_MEMORY);
+  NS_NOTREACHED("Why didn't we find a SynchronizedOp?");
+}
+
+nsresult
+IndexedDatabaseManager::AcquireExclusiveAccess(const nsACString& aOrigin, 
+                                               IDBDatabase* aDatabase,
+                                               AsyncConnectionHelper* aHelper,
+                                               WaitingOnDatabasesCallback aCallback,
+                                               void* aClosure)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(aHelper, "Why are you talking to me?");
 
-      return NS_OK;
+  // Find the right SynchronizedOp.
+  SynchronizedOp* op = nsnull;
+  PRUint32 count = mSynchronizedOps.Length();
+  for (PRUint32 index = 0; index < count; index++) {
+    SynchronizedOp* currentop = mSynchronizedOps[index].get();
+    if (currentop->mOrigin.Equals(aOrigin)) {
+      if (!currentop->mId ||
+          (aDatabase && currentop->mId == aDatabase->Id())) {
+        // We've found the right one.
+        NS_ASSERTION(!currentop->mHelper,
+                     "SynchronizedOp already has a helper?!?");
+        op = currentop;
+        break;
+      }
     }
   }
 
-  // We aren't currently clearing databases for this origin and we're not
-  // running a SetVersion transaction for this database so dispatch the runnable
-  // immediately.
-  return NS_DispatchToCurrentThread(aRunnable);
+  NS_ASSERTION(op, "We didn't find a SynchronizedOp?");
+
+  nsTArray<IDBDatabase*>* array;
+  mLiveDatabases.Get(aOrigin, &array);
+
+  // We need to wait for the databases to go away.
+  // Hold on to all database objects that represent the same database file
+  // (except the one that is requesting this version change).
+  nsTArray<nsRefPtr<IDBDatabase> > liveDatabases;
+
+  if (array) {
+    PRUint32 count = array->Length();
+    for (PRUint32 index = 0; index < count; index++) {
+      IDBDatabase*& database = array->ElementAt(index);
+      if (!database->IsClosed() &&
+          (!aDatabase ||
+           (aDatabase &&
+            database != aDatabase &&
+            database->Id() == aDatabase->Id()))) {
+        liveDatabases.AppendElement(database);
+      }
+    }
+  }
+
+  if (liveDatabases.IsEmpty()) {
+    IndexedDatabaseManager::DispatchHelper(aHelper);
+    return NS_OK;
+  }
+
+  NS_ASSERTION(op->mDatabases.IsEmpty(), "How do we already have databases here?");
+  op->mDatabases.AppendElements(liveDatabases);
+  op->mHelper = aHelper;
+
+  // Give our callback the databases so it can decide what to do with them.
+  aCallback(liveDatabases, aClosure);
+
+  NS_ASSERTION(liveDatabases.IsEmpty(),
+               "Should have done something with the array!");
+  return NS_OK;
 }
 
 // static
 bool
 IndexedDatabaseManager::IsShuttingDown()
 {
   return !!gShutdown;
 }
 
-nsresult
-IndexedDatabaseManager::SetDatabaseVersion(IDBDatabase* aDatabase,
-                                           IDBOpenDBRequest* aRequest,
-                                           PRInt64 aOldVersion,
-                                           PRInt64 aNewVersion,
-                                           AsyncConnectionHelper* aHelper)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(aDatabase, "Null pointer!");
-  NS_ASSERTION(aHelper, "Null pointer!");
-
-  nsresult rv;
-
-  // See if another database has already asked to change the version.
-  for (PRUint32 index = 0; index < mSetVersionRunnables.Length(); index++) {
-    nsRefPtr<SetVersionRunnable>& runnable = mSetVersionRunnables[index];
-    if (runnable->mRequestingDatabase->Id() == aDatabase->Id()) {
-      if (runnable->mRequestingDatabase == aDatabase) {
-        // Same database, just queue this call to run after the current
-        // SetVersion transaction completes.
-        nsRefPtr<DelayedSetVersion> delayed =
-          new DelayedSetVersion(aDatabase, aRequest, aOldVersion, aNewVersion,
-                                aHelper);
-        if (!runnable->mDelayedRunnables.AppendElement(delayed)) {
-          NS_WARNING("Out of memory!");
-          return NS_ERROR_OUT_OF_MEMORY;
-        }
-        return NS_OK;
-      }
-
-      // Different database, we can't let this one succeed.
-      aHelper->SetError(NS_ERROR_DOM_INDEXEDDB_DEADLOCK_ERR);
-
-      rv = NS_DispatchToCurrentThread(aHelper);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      return NS_OK;
-    }
-  }
-
-  // Grab all live databases for the same origin.
-  nsTArray<IDBDatabase*>* array;
-  if (!mLiveDatabases.Get(aDatabase->Origin(), &array)) {
-    NS_ERROR("Must have some alive if we've got a live argument!");
-  }
-
-  // Hold on to all database objects that represent the same database file
-  // (except the one that is requesting this version change).
-  nsTArray<nsRefPtr<IDBDatabase> > liveDatabases;
-
-  for (PRUint32 index = 0; index < array->Length(); index++) {
-    IDBDatabase*& database = array->ElementAt(index);
-    if (database != aDatabase &&
-        database->Id() == aDatabase->Id() &&
-        !database->IsClosed() &&
-        !liveDatabases.AppendElement(database)) {
-      NS_WARNING("Out of memory?");
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-  }
-
-  // Adding an element to this array here will keep other databases from
-  // requesting a version change.
-  nsRefPtr<SetVersionRunnable> runnable =
-    new SetVersionRunnable(aDatabase, liveDatabases);
-  if (!mSetVersionRunnables.AppendElement(runnable)) {
-    NS_WARNING("Out of memory!");
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  NS_ASSERTION(liveDatabases.IsEmpty(), "Should have swapped!");
-
-  // When all databases are closed we want to dispatch the SetVersion
-  // transaction to the transaction pool.
-  runnable->mHelper = aHelper;
-
-  if (runnable->mDatabases.IsEmpty()) {
-    // There are no other databases that need to be closed. Go ahead and run
-    // the transaction now.
-    RunSetVersionTransaction(aDatabase);
-  }
-  else {
-    // Otherwise we need to wait for all the other databases to complete.
-    // Schedule a version change events runnable .
-    nsTArray<nsRefPtr<IDBDatabase> > waitingDatabases;
-    if (!waitingDatabases.AppendElements(runnable->mDatabases)) {
-      NS_WARNING("Out of memory!");
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    nsRefPtr<VersionChangeEventsRunnable> eventsRunnable =
-      new VersionChangeEventsRunnable(aDatabase, aRequest, waitingDatabases,
-                                      aOldVersion, aNewVersion);
-
-    rv = NS_DispatchToCurrentThread(eventsRunnable);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  return NS_OK;
-}
-
 void
 IndexedDatabaseManager::AbortCloseDatabasesForWindow(nsPIDOMWindow* aWindow)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aWindow, "Null pointer!");
 
   nsAutoTArray<IDBDatabase*, 50> liveDatabases;
   mLiveDatabases.EnumerateRead(EnumerateToTArray, &liveDatabases);
@@ -671,81 +500,59 @@ IndexedDatabaseManager::HasOpenTransacti
 }
 
 void
 IndexedDatabaseManager::OnDatabaseClosed(IDBDatabase* aDatabase)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aDatabase, "Null pointer!");
 
-  // Check through the list of SetVersionRunnables we have amassed to see if
-  // this database is part of a SetVersion callback.
-  for (PRUint32 index = 0; index < mSetVersionRunnables.Length(); index++) {
-    nsRefPtr<SetVersionRunnable>& runnable = mSetVersionRunnables[index];
+  // Check through the list of SynchronizedOps to see if any are waiting for
+  // this database to close before proceeding.
+  PRUint32 count = mSynchronizedOps.Length();
+  for (PRUint32 index = 0; index < count; index++) {
+    nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
 
-    if (runnable->mRequestingDatabase->Id() == aDatabase->Id()) {
-      // This is the SetVersionRunnable for the given database file. Remove the
-      // database from the list of databases that need to be closed. Since we
-      // use this hook for SetVersion requests that don't actually need to wait
-      // for other databases the mDatabases array may be empty.
-      if (!runnable->mDatabases.IsEmpty() &&
-          !runnable->mDatabases.RemoveElement(aDatabase)) {
-        NS_ERROR("Didn't have this database in our list!");
-      }
+    if (op->mOrigin == aDatabase->Origin() &&
+        (op->mId == aDatabase->Id() || !op->mId)) {
+      // This database is in the scope of this SynchronizedOp.  Remove it
+      // from the list if necessary.
+      if (op->mDatabases.RemoveElement(aDatabase)) {
+        // Now set up the helper if there are no more live databases.
+        NS_ASSERTION(op->mHelper, "How did we get rid of the helper before "
+                     "removing the last database?");
+        if (op->mDatabases.IsEmpty()) {
+          // At this point, all databases are closed, so no new transactions
+          // can be started.  There may, however, still be outstanding
+          // transactions that have not completed.  We need to wait for those
+          // before we dispatch the helper.
 
-      // Now run the helper if there are no more live databases.
-      if (runnable->mHelper && runnable->mDatabases.IsEmpty()) {
-        // At this point, all databases are closed, so no new transactions can
-        // be started.  There may, however, still be outstanding transactions
-        // that have not completed.  We need to wait for those before we
-        // dispatch the helper.
+          TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
+          if (!pool) {
+            NS_ERROR("IndexedDB is totally broken.");
+            return;
+          }
 
-        TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
-
-        nsRefPtr<WaitForTransactionsToFinishRunnable> waitRunnable =
-          new WaitForTransactionsToFinishRunnable(runnable);
+          nsRefPtr<WaitForTransactionsToFinishRunnable> waitRunnable =
+            new WaitForTransactionsToFinishRunnable(op);
 
-        // All other databases should be closed, so we only need to wait on this
-        // one.
-        nsAutoTArray<nsRefPtr<IDBDatabase>, 1> array;
-        if (!array.AppendElement(aDatabase)) {
-          NS_ERROR("This should never fail!");
-        }
+          nsAutoTArray<nsRefPtr<IDBDatabase>, 1> array;
+          array.AppendElement(aDatabase);
 
-        // Use the WaitForTransactionsToFinishRunnable as the callback.
-        if (!pool->WaitForAllDatabasesToComplete(array, waitRunnable)) {
-          NS_WARNING("Failed to wait for transaction to complete!");
+          // Use the WaitForTransactionsToFinishRunnable as the callback.
+          if (!pool->WaitForAllDatabasesToComplete(array, waitRunnable)) {
+            NS_WARNING("Failed to wait for transaction to complete!");
+          }
         }
+        break;
       }
-      break;
     }
   }
 }
 
-void
-IndexedDatabaseManager::UnblockSetVersionRunnable(IDBDatabase* aDatabase)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(aDatabase, "Null pointer!");
-
-  // Check through the list of SetVersionRunnables to find the one we're seeking.
-  for (PRUint32 index = 0; index < mSetVersionRunnables.Length(); index++) {
-    nsRefPtr<SetVersionRunnable>& runnable = mSetVersionRunnables[index];
-
-    if (runnable->mRequestingDatabase->Id() == aDatabase->Id()) {
-      NS_ASSERTION(!runnable->mHelper,
-                 "Why are we unblocking a runnable if the helper didn't run?");
-      NS_DispatchToCurrentThread(runnable);
-      return;
-    }
-  }
-
-  NS_NOTREACHED("How did we get here!");
-}
-
 // static
 bool
 IndexedDatabaseManager::SetCurrentDatabase(IDBDatabase* aDatabase)
 {
   NS_ASSERTION(gCurrentDatabaseIndex != BAD_TLS_INDEX,
                "This should have been set already!");
 
 #ifdef DEBUG
@@ -850,16 +657,55 @@ IndexedDatabaseManager::EnsureQuotaManag
   }
 
   NS_ASSERTION(!mTrackedQuotaPaths.Contains(path), "What?!");
 
   mTrackedQuotaPaths.AppendElement(path);
   return rv;
 }
 
+// static
+nsresult
+IndexedDatabaseManager::DispatchHelper(AsyncConnectionHelper* aHelper)
+{
+  nsresult rv = NS_OK;
+
+  // If the helper has a transaction, dispatch it to the transaction
+  // threadpool.
+  if (aHelper->HasTransaction()) {
+    rv = aHelper->DispatchToTransactionPool();
+  }
+  else {
+    // Otherwise, dispatch it to the IO thread.
+    IndexedDatabaseManager* manager = IndexedDatabaseManager::Get();
+    NS_ASSERTION(manager, "We should definitely have a manager here");
+
+    rv = aHelper->Dispatch(manager->IOThread());
+  }
+
+  NS_ENSURE_SUCCESS(rv, rv);
+  return rv;
+}
+
+bool
+IndexedDatabaseManager::IsClearOriginPending(const nsACString& origin)
+{
+  // Iterate through our SynchronizedOps to see if we have an entry that matches
+  // this origin and has no id.
+  PRUint32 count = mSynchronizedOps.Length();
+  for (PRUint32 index = 0; index < count; index++) {
+    nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
+    if (op->mOrigin.Equals(origin) && !op->mId) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 NS_IMPL_ISUPPORTS2(IndexedDatabaseManager, nsIIndexedDatabaseManager,
                                            nsIObserver)
 
 NS_IMETHODIMP
 IndexedDatabaseManager::GetUsageForURI(
                                      nsIURI* aURI,
                                      nsIIndexedDatabaseUsageCallback* aCallback)
 {
@@ -885,22 +731,20 @@ IndexedDatabaseManager::GetUsageForURI(
   if (origin.EqualsLiteral("null")) {
     rv = NS_DispatchToCurrentThread(runnable);
     NS_ENSURE_SUCCESS(rv, rv);
     return NS_OK;
   }
 
   // See if we're currently clearing the databases for this origin. If so then
   // we pretend that we've already deleted everything.
-  for (PRUint32 index = 0; index < mOriginClearRunnables.Length(); index++) {
-    if (mOriginClearRunnables[index]->mOrigin == origin) {
-      rv = NS_DispatchToCurrentThread(runnable);
-      NS_ENSURE_SUCCESS(rv, rv);
-      return NS_OK;
-    }
+  if (IsClearOriginPending(origin)) {
+    rv = NS_DispatchToCurrentThread(runnable);
+    NS_ENSURE_SUCCESS(rv, rv);
+    return NS_OK;
   }
 
   // Otherwise dispatch to the IO thread to actually compute the usage.
   rv = mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
@@ -944,68 +788,47 @@ IndexedDatabaseManager::ClearDatabasesFo
   nsresult rv = nsContentUtils::GetASCIIOrigin(aURI, origin);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Non-standard URIs can't create databases anyway, so return immediately.
   if (origin.EqualsLiteral("null")) {
     return NS_OK;
   }
 
-  // If we're already clearing out files for this origin then return
+  // If there is a pending or running clear operation for this origin, return
   // immediately.
-  PRUint32 clearDataCount = mOriginClearRunnables.Length();
-  for (PRUint32 index = 0; index < clearDataCount; index++) {
-    if (mOriginClearRunnables[index]->mOrigin == origin) {
-      return NS_OK;
-    }
+  if (IsClearOriginPending(origin)) {
+    return NS_OK;
   }
 
+  // Queue up the origin clear runnable.
+  nsRefPtr<OriginClearRunnable> runnable =
+    new OriginClearRunnable(origin, mIOThread);
+
+  rv = WaitForOpenAllowed(origin, nsnull, runnable);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Give the runnable some help by invalidating any databases in the way.
   // We need to grab references to any live databases here to prevent them from
   // dying while we invalidate them.
   nsTArray<nsRefPtr<IDBDatabase> > liveDatabases;
 
   // Grab all live databases for this origin.
   nsTArray<IDBDatabase*>* array;
   if (mLiveDatabases.Get(origin, &array)) {
-    if (!liveDatabases.AppendElements(*array)) {
-      NS_WARNING("Out of memory?");
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-  }
-
-  nsRefPtr<OriginClearRunnable> runnable =
-    new OriginClearRunnable(origin, mIOThread);
-
-  // Make a new entry for this origin in mOriginClearRunnables.
-  nsRefPtr<OriginClearRunnable>* newRunnable =
-    mOriginClearRunnables.AppendElement(runnable);
-  NS_ENSURE_TRUE(newRunnable, NS_ERROR_OUT_OF_MEMORY);
-
-  if (liveDatabases.IsEmpty()) {
-    rv = runnable->Run();
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    return NS_OK;
+    liveDatabases.AppendElements(*array);
   }
 
   // Invalidate all the live databases first.
   for (PRUint32 index = 0; index < liveDatabases.Length(); index++) {
     liveDatabases[index]->Invalidate();
   }
 
-  // Now set up our callback so that we know when they have finished.
-  TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
-  NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE);
-
-  if (!pool->WaitForAllDatabasesToComplete(liveDatabases, runnable)) {
-    NS_WARNING("Can't wait on databases!");
-    return NS_ERROR_FAILURE;
-  }
-
-  NS_ASSERTION(liveDatabases.IsEmpty(), "Should have swapped!");
+  // After everything has been invalidated the helper should be dispatched to
+  // the end of the event queue.
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 IndexedDatabaseManager::Observe(nsISupports* aSubject,
                                 const char* aTopic,
                                 const PRUnichar* aData)
@@ -1072,17 +895,17 @@ IndexedDatabaseManager::Observe(nsISuppo
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::OriginClearRunnable,
                               nsIRunnable)
 
 NS_IMETHODIMP
 IndexedDatabaseManager::OriginClearRunnable::Run()
 {
   if (NS_IsMainThread()) {
-    // On the first time on the main thread we simply dispatch to the IO thread.
+    // On the first time on the main thread we dispatch to the IO thread.
     if (mFirstCallback) {
       NS_ASSERTION(mThread, "Should have a thread here!");
 
       mFirstCallback = false;
 
       nsCOMPtr<nsIThread> thread;
       mThread.swap(thread);
 
@@ -1092,29 +915,21 @@ IndexedDatabaseManager::OriginClearRunna
         return NS_ERROR_FAILURE;
       }
 
       return NS_OK;
     }
 
     NS_ASSERTION(!mThread, "Should have been cleared already!");
 
-    // Dispatch any queued runnables that we collected while we were waiting.
-    for (PRUint32 index = 0; index < mDelayedRunnables.Length(); index++) {
-      if (NS_FAILED(NS_DispatchToCurrentThread(mDelayedRunnables[index]))) {
-        NS_WARNING("Failed to dispatch delayed runnable!");
-      }
-    }
-    mDelayedRunnables.Clear();
-
     // Tell the IndexedDatabaseManager that we're done.
     IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
-    if (mgr) {
-      mgr->OnOriginClearComplete(this);
-    }
+    NS_ASSERTION(mgr, "This should never fail!");
+
+    mgr->AllowNextSynchronizedOp(mOrigin, nsnull);
 
     return NS_OK;
   }
 
   NS_ASSERTION(!mThread, "Should have been cleared already!");
 
   // Remove the directory that contains all our databases.
   nsCOMPtr<nsIFile> directory;
@@ -1252,87 +1067,96 @@ IndexedDatabaseManager::AsyncUsageRunnab
       NS_WARNING("Failed to dispatch to main thread!");
     }
   }
 
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
-IndexedDatabaseManager::SetVersionRunnable::SetVersionRunnable(
-                                   IDBDatabase* aDatabase,
-                                   nsTArray<nsRefPtr<IDBDatabase> >& aDatabases)
-: mRequestingDatabase(aDatabase)
-{
-  NS_ASSERTION(aDatabase, "Null database!");
-  if (!mDatabases.SwapElements(aDatabases)) {
-    NS_ERROR("This should never fail!");
-  }
-}
-
-IndexedDatabaseManager::SetVersionRunnable::~SetVersionRunnable()
-{
-}
-
-NS_IMPL_ISUPPORTS1(IndexedDatabaseManager::SetVersionRunnable, nsIRunnable)
-
-NS_IMETHODIMP
-IndexedDatabaseManager::SetVersionRunnable::Run()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!mHelper, "Should have been cleared already!");
-
-  // Dispatch any queued runnables that we picked up while waiting for the
-  // SetVersion transaction to complete.
-  for (PRUint32 index = 0; index < mDelayedRunnables.Length(); index++) {
-    if (NS_FAILED(NS_DispatchToCurrentThread(mDelayedRunnables[index]))) {
-      NS_WARNING("Failed to dispatch delayed runnable!");
-    }
-  }
-
-  // No need to hold these alive any longer.
-  mDelayedRunnables.Clear();
-
-  IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
-  NS_ASSERTION(mgr, "This should never be null!");
-
-  // Let the IndexedDatabaseManager know that the SetVersion transaction has
-  // completed.
-  mgr->OnSetVersionRunnableComplete(this);
-
-  return NS_OK;
-}
-
 NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::WaitForTransactionsToFinishRunnable,
                               nsIRunnable)
 
 NS_IMETHODIMP
 IndexedDatabaseManager::WaitForTransactionsToFinishRunnable::Run()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(mOp && mOp->mHelper, "What?");
 
   // Don't hold the callback alive longer than necessary.
   nsRefPtr<AsyncConnectionHelper> helper;
-  helper.swap(mRunnable->mHelper);
-
-  nsRefPtr<SetVersionRunnable> runnable;
-  runnable.swap(mRunnable);
+  helper.swap(mOp->mHelper);
 
-  // If the helper has a transaction, dispatch it to the transaction
-  // threadpool.
-  if (helper->HasTransaction()) {
-    if (NS_FAILED(helper->DispatchToTransactionPool())) {
-      NS_WARNING("Failed to dispatch to thread pool!");
-    }
-  }
-  // Otherwise, dispatch it to the IO thread.
-  else {
-    IndexedDatabaseManager* manager = IndexedDatabaseManager::Get();
-    NS_ASSERTION(manager, "We should definitely have a manager here");
+  mOp = nsnull;
 
-    helper->Dispatch(manager->IOThread());
-  }
+  IndexedDatabaseManager::DispatchHelper(helper);
 
   // The helper is responsible for calling
-  // IndexedDatabaseManager::UnblockSetVersionRunnable.
+  // IndexedDatabaseManager::AllowNextSynchronizedOp.
 
   return NS_OK;
 }
+
+
+IndexedDatabaseManager::SynchronizedOp::SynchronizedOp(const nsACString& aOrigin,
+                                                       nsIAtom* aId)
+: mOrigin(aOrigin), mId(aId)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  MOZ_COUNT_CTOR(IndexedDatabaseManager::SynchronizedOp);
+}
+
+IndexedDatabaseManager::SynchronizedOp::~SynchronizedOp()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  MOZ_COUNT_DTOR(IndexedDatabaseManager::SynchronizedOp);
+}
+
+bool
+IndexedDatabaseManager::SynchronizedOp::MustWaitFor(const SynchronizedOp& aRhs)
+  const
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  // If the origins don't match, the second can proceed.
+  if (!aRhs.mOrigin.Equals(mOrigin)) {
+    return false;
+  }
+
+  // If the origins and the ids match, the second must wait.
+  if (aRhs.mId == mId) {
+    return true;
+  }
+
+  // Waiting is required if either one corresponds to an origin clearing
+  // (a null Id).
+  if (!aRhs.mId || !mId) {
+    return true;
+  }
+
+  // Otherwise, things for the same origin but different databases can proceed
+  // independently.
+  return false;
+}
+
+void
+IndexedDatabaseManager::SynchronizedOp::DelayRunnable(nsIRunnable* aRunnable)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(mDelayedRunnables.IsEmpty() || !mId,
+               "Only ClearOrigin operations can delay multiple runnables!");
+
+  mDelayedRunnables.AppendElement(aRunnable);
+}
+
+void
+IndexedDatabaseManager::SynchronizedOp::DispatchDelayedRunnables()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(!mHelper, "Any helper should be gone by now!");
+
+  PRUint32 count = mDelayedRunnables.Length();
+  for (PRUint32 index = 0; index < count; index++) {
+    NS_DispatchToCurrentThread(mDelayedRunnables[index]);
+  }
+
+  mDelayedRunnables.Clear();
+}
--- a/dom/indexedDB/IndexedDatabaseManager.h
+++ b/dom/indexedDB/IndexedDatabaseManager.h
@@ -76,36 +76,55 @@ public:
   // Returns an owning reference! No one should call this but the factory.
   static IndexedDatabaseManager* FactoryCreate();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIINDEXEDDATABASEMANAGER
   NS_DECL_NSIOBSERVER
 
   // Waits for databases to be cleared and for version change transactions to
-  // complete before dispatching the give runnable.
-  nsresult WaitForOpenAllowed(const nsAString& aName,
-                              const nsACString& aOrigin,
+  // complete before dispatching the given runnable.
+  nsresult WaitForOpenAllowed(const nsACString& aOrigin,
+                              nsIAtom* aId,
                               nsIRunnable* aRunnable);
 
+  void AllowNextSynchronizedOp(const nsACString& aOrigin,
+                               nsIAtom* aId);
+
   nsIThread* IOThread()
   {
     NS_ASSERTION(mIOThread, "This should never be null!");
     return mIOThread;
   }
 
   // Returns true if we've begun the shutdown process.
   static bool IsShuttingDown();
 
-  // Begins the process of setting a database version.
-  nsresult SetDatabaseVersion(IDBDatabase* aDatabase,
-                              IDBOpenDBRequest* aRequest,
-                              PRInt64 aOldVersion,
-                              PRInt64 aNewVersion,
-                              AsyncConnectionHelper* aHelper);
+  typedef void (*WaitingOnDatabasesCallback)(nsTArray<nsRefPtr<IDBDatabase> >&, void*);
+
+  // Acquire exclusive access to the database given (waits for all others to
+  // close).  If databases need to close first, the callback will be invoked
+  // with an array of said databases.
+  nsresult AcquireExclusiveAccess(IDBDatabase* aDatabase,
+                                  AsyncConnectionHelper* aHelper,
+                                  WaitingOnDatabasesCallback aCallback,
+                                  void* aClosure)
+  {
+    NS_ASSERTION(aDatabase, "Need a DB here!");
+    return AcquireExclusiveAccess(aDatabase->Origin(), aDatabase, aHelper,
+                                  aCallback, aClosure);
+  }
+  nsresult AcquireExclusiveAccess(const nsACString& aOrigin, 
+                                  AsyncConnectionHelper* aHelper,
+                                  WaitingOnDatabasesCallback aCallback,
+                                  void* aClosure)
+  {
+    return AcquireExclusiveAccess(aOrigin, nsnull, aHelper, aCallback,
+                                  aClosure);
+  }
 
   // Called when a window is being purged from the bfcache or the user leaves
   // a page which isn't going into the bfcache. Forces any live database
   // objects to close themselves and aborts any running transactions.
   void AbortCloseDatabasesForWindow(nsPIDOMWindow* aWindow);
 
   // Used to check if there are running transactions in a given window.
   bool HasOpenTransactions(nsPIDOMWindow* aWindow);
@@ -117,61 +136,58 @@ public:
   GetIndexedDBQuotaMB();
 
   nsresult EnsureQuotaManagementForDirectory(nsIFile* aDirectory);
 
 private:
   IndexedDatabaseManager();
   ~IndexedDatabaseManager();
 
+  nsresult AcquireExclusiveAccess(const nsACString& aOrigin, 
+                                  IDBDatabase* aDatabase,
+                                  AsyncConnectionHelper* aHelper,
+                                  WaitingOnDatabasesCallback aCallback,
+                                  void* aClosure);
+
   // Called when a database is created.
   bool RegisterDatabase(IDBDatabase* aDatabase);
 
   // Called when a database is being unlinked or destroyed.
   void UnregisterDatabase(IDBDatabase* aDatabase);
 
   // Called when a database has been closed.
   void OnDatabaseClosed(IDBDatabase* aDatabase);
 
-  // Called when a version change transaction can run immediately.
-  void RunSetVersionTransaction(IDBDatabase* aDatabase)
-  {
-    OnDatabaseClosed(aDatabase);
-  }
-
   // Responsible for clearing the database files for a particular origin on the
   // IO thread. Created when nsIIDBIndexedDatabaseManager::ClearDatabasesForURI
   // is called. Runs three times, first on the main thread, next on the IO
   // thread, and then finally again on the main thread. While on the IO thread
   // the runnable will actually remove the origin's database files and the
   // directory that contains them before dispatching itself back to the main
-  // thread. When on the main thread the runnable will dispatch any queued
-  // runnables and then notify the IndexedDatabaseManager that the job has been
-  // completed.
+  // thread. When back on the main thread the runnable will notify the
+  // IndexedDatabaseManager that the job has been completed.
   class OriginClearRunnable : public nsIRunnable
   {
   public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIRUNNABLE
 
     OriginClearRunnable(const nsACString& aOrigin,
                         nsIThread* aThread)
     : mOrigin(aOrigin),
       mThread(aThread),
       mFirstCallback(true)
     { }
 
     nsCString mOrigin;
     nsCOMPtr<nsIThread> mThread;
-    nsTArray<nsCOMPtr<nsIRunnable> > mDelayedRunnables;
     bool mFirstCallback;
   };
 
-  // Called when OriginClearRunnable has finished its Run() method.
-  inline void OnOriginClearComplete(OriginClearRunnable* aRunnable);
+  bool IsClearOriginPending(const nsACString& origin);
 
   // Responsible for calculating the amount of space taken up by databases of a
   // certain origin. Created when nsIIDBIndexedDatabaseManager::GetUsageForURI
   // is called. May be canceled with
   // nsIIDBIndexedDatabaseManager::CancelGetUsageForURI. Runs twice, first on
   // the IO thread, then again on the main thread. While on the IO thread the
   // runnable will calculate the size of all files in the origin's directory
   // before dispatching itself back to the main thread. When on the main thread
@@ -199,73 +215,69 @@ private:
     nsCOMPtr<nsIIndexedDatabaseUsageCallback> mCallback;
     PRUint64 mUsage;
     PRInt32 mCanceled;
   };
 
   // Called when AsyncUsageRunnable has finished its Run() method.
   inline void OnUsageCheckComplete(AsyncUsageRunnable* aRunnable);
 
-  void UnblockSetVersionRunnable(IDBDatabase* aDatabase);
-
-  // Responsible for waiting until all databases have been closed before running
-  // the version change transaction. Created when
-  // IndexedDatabaseManager::SetDatabaseVersion is called. Runs only once on the
-  // main thread when the version change transaction has completed.
-  class SetVersionRunnable : public nsIRunnable
+  // A struct that contains the information corresponding to a pending or
+  // running operation that requires synchronization (e.g. opening a db,
+  // clearing dbs for an origin, etc).
+  struct SynchronizedOp
   {
-  public:
-    NS_DECL_ISUPPORTS
-    NS_DECL_NSIRUNNABLE
+    SynchronizedOp(const nsACString& aOrigin, nsIAtom* aId);
+    ~SynchronizedOp();
+
+    // Test whether the second SynchronizedOp needs to get behind this one.
+    bool MustWaitFor(const SynchronizedOp& aRhs) const;
 
-    SetVersionRunnable(IDBDatabase* aDatabase,
-                       nsTArray<nsRefPtr<IDBDatabase> >& aDatabases);
-    ~SetVersionRunnable();
+    void DelayRunnable(nsIRunnable* aRunnable);
+    void DispatchDelayedRunnables();
 
-    nsRefPtr<IDBDatabase> mRequestingDatabase;
-    nsTArray<nsRefPtr<IDBDatabase> > mDatabases;
+    const nsCString mOrigin;
+    nsCOMPtr<nsIAtom> mId;
     nsRefPtr<AsyncConnectionHelper> mHelper;
     nsTArray<nsCOMPtr<nsIRunnable> > mDelayedRunnables;
+    nsTArray<nsRefPtr<IDBDatabase> > mDatabases;
   };
 
-  // Called when SetVersionRunnable has finished its Run() method.
-  inline void OnSetVersionRunnableComplete(SetVersionRunnable* aRunnable);
-
-
   // A callback runnable used by the TransactionPool when it's safe to proceed
   // with a SetVersion/DeleteDatabase/etc.
   class WaitForTransactionsToFinishRunnable : public nsIRunnable
   {
   public:
-    WaitForTransactionsToFinishRunnable(SetVersionRunnable* aRunnable)
-    : mRunnable(aRunnable)
+    WaitForTransactionsToFinishRunnable(SynchronizedOp* aOp)
+    : mOp(aOp)
     {
-      NS_ASSERTION(mRunnable, "Why don't we have a runnable?");
-      NS_ASSERTION(mRunnable->mDatabases.IsEmpty(), "We're here too early!");
+      NS_ASSERTION(mOp, "Why don't we have a runnable?");
+      NS_ASSERTION(mOp->mDatabases.IsEmpty(), "We're here too early!");
+      NS_ASSERTION(mOp->mHelper, "What are we supposed to do when we're done?");
     }
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSIRUNNABLE
 
   private:
-    nsRefPtr<SetVersionRunnable> mRunnable;
+    // The IndexedDatabaseManager holds this alive.
+    SynchronizedOp* mOp;
   };
 
+  static nsresult DispatchHelper(AsyncConnectionHelper* aHelper);
+
   // Maintains a list of live databases per origin.
   nsClassHashtable<nsCStringHashKey, nsTArray<IDBDatabase*> > mLiveDatabases;
 
-  // Maintains a list of origins that are currently being cleared.
-  nsAutoTArray<nsRefPtr<OriginClearRunnable>, 1> mOriginClearRunnables;
-
   // Maintains a list of origins that we're currently enumerating to gather
   // usage statistics.
   nsAutoTArray<nsRefPtr<AsyncUsageRunnable>, 1> mUsageRunnables;
 
-  // Maintains a list of SetVersion calls that are in progress.
-  nsAutoTArray<nsRefPtr<SetVersionRunnable>, 1> mSetVersionRunnables;
+  // Maintains a list of synchronized operatons that are in progress or queued.
+  nsAutoTArray<nsAutoPtr<SynchronizedOp>, 5> mSynchronizedOps;
 
   // Thread on which IO is performed.
   nsCOMPtr<nsIThread> mIOThread;
 
   // A timer that gets activated at shutdown to ensure we close all databases.
   nsCOMPtr<nsITimer> mShutdownTimer;
 
   // A single threadsafe instance of our quota callback. Created on the main
--- a/dom/indexedDB/OpenDatabaseHelper.cpp
+++ b/dom/indexedDB/OpenDatabaseHelper.cpp
@@ -493,16 +493,19 @@ public:
     mTransaction->SetTransactionListener(this);
   }
 
   NS_DECL_ISUPPORTS_INHERITED
 
   nsresult GetSuccessResult(JSContext* aCx,
                             jsval* aVal);
 
+  static
+  void QueueVersionChange(nsTArray<nsRefPtr<IDBDatabase> >& aDatabases,
+                          void* aClosure);
 protected:
   nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
   nsresult Init();
 
   // SetVersionHelper never fires an error event at the request.  It hands that
   // responsibility back to the OpenDatabaseHelper
   void OnError() { }
 
@@ -514,16 +517,101 @@ protected:
 private:
   // In-params
   nsRefPtr<OpenDatabaseHelper> mOpenHelper;
   nsRefPtr<IDBOpenDBRequest> mOpenRequest;
   PRUint64 mRequestedVersion;
   PRUint64 mCurrentVersion;
 };
 
+// Responsible for firing "versionchange" events at all live and non-closed
+// databases, and for firing a "blocked" event at the requesting database if any
+// databases fail to close.
+class VersionChangeEventsRunnable : public nsRunnable
+{
+public:
+  VersionChangeEventsRunnable(
+                            IDBDatabase* aRequestingDatabase,
+                            IDBOpenDBRequest* aRequest,
+                            nsTArray<nsRefPtr<IDBDatabase> >& aWaitingDatabases,
+                            PRInt64 aOldVersion,
+                            PRInt64 aNewVersion)
+  : mRequestingDatabase(aRequestingDatabase),
+    mRequest(aRequest),
+    mOldVersion(aOldVersion),
+    mNewVersion(aNewVersion)
+  {
+    NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+    NS_ASSERTION(aRequestingDatabase, "Null pointer!");
+    NS_ASSERTION(aRequest, "Null pointer!");
+
+    if (!mWaitingDatabases.SwapElements(aWaitingDatabases)) {
+      NS_ERROR("This should never fail!");
+    }
+  }
+
+  NS_IMETHOD Run()
+  {
+    NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+    // Fire version change events at all of the databases that are not already
+    // closed. Also kick bfcached documents out of bfcache.
+    PRUint32 count = mWaitingDatabases.Length();
+    for (PRUint32 index = 0; index < count; index++) {
+      nsRefPtr<IDBDatabase>& database = mWaitingDatabases[index];
+
+      if (database->IsClosed()) {
+        continue;
+      }
+
+      // First check if the document the IDBDatabase is part of is bfcached.
+      nsCOMPtr<nsIDocument> ownerDoc = database->GetOwnerDocument();
+      nsIBFCacheEntry* bfCacheEntry;
+      if (ownerDoc && (bfCacheEntry = ownerDoc->GetBFCacheEntry())) {
+        bfCacheEntry->RemoveFromBFCacheSync();
+        NS_ASSERTION(database->IsClosed(),
+                     "Kicking doc out of bfcache should have closed database");
+        continue;
+      }
+
+      // Otherwise fire a versionchange event.
+      nsRefPtr<nsDOMEvent> event = 
+        IDBVersionChangeEvent::Create(mOldVersion, mNewVersion);
+      NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
+
+      bool dummy;
+      database->DispatchEvent(event, &dummy);
+    }
+
+    // Now check to see if any didn't close. If there are some running still
+    // then fire the blocked event.
+    for (PRUint32 index = 0; index < count; index++) {
+      if (!mWaitingDatabases[index]->IsClosed()) {
+        nsRefPtr<nsDOMEvent> event =
+          IDBVersionChangeEvent::CreateBlocked(mOldVersion, mNewVersion);
+        NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
+
+        bool dummy;
+        mRequest->DispatchEvent(event, &dummy);
+
+        break;
+      }
+    }
+
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<IDBDatabase> mRequestingDatabase;
+  nsRefPtr<IDBOpenDBRequest> mRequest;
+  nsTArray<nsRefPtr<IDBDatabase> > mWaitingDatabases;
+  PRInt64 mOldVersion;
+  PRInt64 mNewVersion;
+};
+
 } // anonymous namespace
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(OpenDatabaseHelper, nsIRunnable);
 
 nsresult
 OpenDatabaseHelper::Init()
 {
   nsCString str(mASCIIOrigin);
@@ -689,18 +777,19 @@ OpenDatabaseHelper::StartSetVersion()
 
   nsRefPtr<SetVersionHelper> helper =
     new SetVersionHelper(transaction, mOpenDBRequest, this, mRequestedVersion,
                          mCurrentVersion);
 
   IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
   NS_ASSERTION(mgr, "This should never be null!");
 
-  rv = mgr->SetDatabaseVersion(mDatabase, mOpenDBRequest, mCurrentVersion,
-                               mRequestedVersion, helper);
+  rv = mgr->AcquireExclusiveAccess(mDatabase, helper,
+                                   &SetVersionHelper::QueueVersionChange,
+                                   helper);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   // The SetVersionHelper is responsible for dispatching us back to the
   // main thread again and changing the state to eSetVersionCompleted.
   mState = eSetVersionPending;
 
   return NS_OK;
 }
@@ -751,16 +840,21 @@ OpenDatabaseHelper::Run()
     NS_ASSERTION(mState == eFiringEvents, "Why are we here?");
 
     if (NS_FAILED(mResultCode)) {
       DispatchErrorEvent();
     } else {
       DispatchSuccessEvent();
     }
 
+    IndexedDatabaseManager* manager = IndexedDatabaseManager::Get();
+    NS_ASSERTION(manager, "This should never be null!");
+
+    manager->AllowNextSynchronizedOp(mASCIIOrigin, mDatabaseId);
+
     ReleaseMainThreadObjects();
 
     return NS_OK;
   }
 
   // If we're on the DB thread, do that
   NS_ASSERTION(mState == eDBWork, "Why are we here?");
   mResultCode = DoDatabaseWork();
@@ -995,16 +1089,37 @@ SetVersionHelper::GetSuccessResult(JSCon
   NS_ASSERTION(mTransaction, "Better have a transaction!");
 
   mOpenRequest->SetTransaction(mTransaction);
 
   return WrapNative(aCx, NS_ISUPPORTS_CAST(nsIDOMEventTarget*, mDatabase),
                     aVal);
 }
 
+// static
+void
+SetVersionHelper::QueueVersionChange(nsTArray<nsRefPtr<IDBDatabase> >& aDatabases,
+                                     void* aClosure)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(!aDatabases.IsEmpty(), "Why are we here?");
+
+  SetVersionHelper* helper = static_cast<SetVersionHelper*>(aClosure);
+  NS_ASSERTION(helper, "Why don't we have a helper?");
+
+  nsRefPtr<VersionChangeEventsRunnable> eventsRunnable =
+    new VersionChangeEventsRunnable(helper->mOpenHelper->Database(),
+                                    helper->mOpenRequest,
+                                    aDatabases,
+                                    helper->mCurrentVersion,
+                                    helper->mRequestedVersion);
+
+  NS_DispatchToCurrentThread(eventsRunnable);
+}
+
 already_AddRefed<nsDOMEvent>
 SetVersionHelper::CreateSuccessEvent()
 {
   NS_ASSERTION(mCurrentVersion < mRequestedVersion, "Huh?");
 
   return IDBVersionChangeEvent::CreateUpgradeNeeded(mCurrentVersion,
                                                     mRequestedVersion);
 }
--- a/dom/indexedDB/OpenDatabaseHelper.h
+++ b/dom/indexedDB/OpenDatabaseHelper.h
@@ -81,16 +81,27 @@ public:
   nsresult GetResultCode()
   {
     return mResultCode;
   }
 
   nsresult NotifySetVersionFinished();
   void BlockDatabase();
 
+  nsIAtom* Id() const
+  {
+    return mDatabaseId.get();
+  }
+
+  IDBDatabase* Database() const
+  {
+    NS_ASSERTION(mDatabase, "Calling at the wrong time!");
+    return mDatabase;
+  }
+
 protected:
   // Methods only called on the main thread
   nsresult EnsureSuccessResult();
   nsresult StartSetVersion();
   nsresult GetSuccessResult(JSContext* aCx,
                           jsval* aVal);
   void DispatchSuccessEvent();
   void DispatchErrorEvent();
--- a/dom/indexedDB/nsIIDBDatabaseException.idl
+++ b/dom/indexedDB/nsIIDBDatabaseException.idl
@@ -34,28 +34,27 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
-[scriptable, uuid(2f182bf1-1542-47fe-b2f7-4b1741b5283c)]
+[scriptable, uuid(7aad2542-a5cb-4a57-b20c-c7d16b8582ab)]
 interface nsIIDBDatabaseException : nsISupports
 {
   // const unsigned short NO_ERR = 0;
   const unsigned short UNKNOWN_ERR = 1;
   const unsigned short NON_TRANSIENT_ERR = 2;
   const unsigned short NOT_FOUND_ERR = 3;
   const unsigned short CONSTRAINT_ERR = 4;
   const unsigned short DATA_ERR = 5;
   const unsigned short NOT_ALLOWED_ERR = 6;
   const unsigned short TRANSACTION_INACTIVE_ERR = 7;
   const unsigned short ABORT_ERR = 8;
   const unsigned short READ_ONLY_ERR = 9;
-  const unsigned short RECOVERABLE_ERR = 10;
-  const unsigned short TRANSIENT_ERR = 11;
-  const unsigned short TIMEOUT_ERR = 12;
-  const unsigned short DEADLOCK_ERR = 13;
+  const unsigned short TIMEOUT_ERR = 10;
+  const unsigned short QUOTA_ERR = 11;
+  const unsigned short VERSION_ERR = 12;
 
   readonly attribute unsigned short code;
 };
--- a/dom/indexedDB/test/Makefile.in
+++ b/dom/indexedDB/test/Makefile.in
@@ -87,16 +87,17 @@ TEST_FILES = \
   test_put_get_values.html \
   test_put_get_values_autoIncrement.html \
   test_readonly_transactions.html \
   test_remove_index.html \
   test_remove_objectStore.html \
   test_request_readyState.html \
   test_success_events_after_abort.html \
   test_third_party.html \
+  test_traffic_jam.html \
   test_transaction_abort.html \
   test_transaction_lifetimes.html \
   test_transaction_lifetimes_nested.html \
   test_setVersion.html \
   test_setVersion_abort.html \
   test_setVersion_events.html \
   test_setVersion_exclusion.html \
   test_writer_starvation.html \
--- a/dom/indexedDB/test/test_setVersion_exclusion.html
+++ b/dom/indexedDB/test/test_setVersion_exclusion.html
@@ -7,20 +7,16 @@
   <title>Indexed Database Property 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">
     function testSteps()
     {
-      todo(false, "Reenable me!");
-      finishTest();
-      yield;
-
       const name = window.location.pathname;
 
       let request = mozIndexedDB.open(name, 1);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
       request.onsuccess = unexpectedSuccessHandler;
 
       let request2 = mozIndexedDB.open(name, 2);
@@ -74,16 +70,19 @@
       try {
         db.transaction("foo");
         ok(true, "Transactions should be allowed now!");
       } catch (e) {
         ok(false, "Transactions should be allowed now!");
       }
 
       request2.onupgradeneeded = null;
+      request2.onsuccess = grabEventAndContinueHandler;
+
+      yield;
 
       finishTest();
       yield;
     }
 
   </script>
   <script type="text/javascript;version=1.7" src="helpers.js"></script>
 
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_traffic_jam.html
@@ -0,0 +1,99 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Property 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">
+    function testSteps()
+    {
+      const name = window.location.pathname;
+
+      let requests = [];
+      function doOpen(version, errorCallback, upgradeNeededCallback, successCallback) {
+        let request = mozIndexedDB.open(name, version);
+        request.onerror = errorCallback;
+        request.onupgradeneeded = upgradeNeededCallback;
+        request.onsuccess = successCallback;
+        requests.push(request);
+      }
+
+      doOpen(1, errorHandler, grabEventAndContinueHandler, grabEventAndContinueHandler);
+      doOpen(2, errorHandler, unexpectedSuccessHandler, unexpectedSuccessHandler);
+
+      let event = yield;
+      is(event.type, "upgradeneeded", "expect an upgradeneeded event");
+      is(event.target, requests[0], "fired at the right request");
+
+      let db = event.target.result;
+      db.createObjectStore("foo");
+
+      doOpen(3, errorHandler, unexpectedSuccessHandler, unexpectedSuccessHandler);
+      doOpen(2, errorHandler, unexpectedSuccessHandler, unexpectedSuccessHandler);
+      doOpen(3, errorHandler, unexpectedSuccessHandler, unexpectedSuccessHandler);
+
+      event.target.transaction.oncomplete = grabEventAndContinueHandler;
+
+      event = yield;
+      is(event.type, "complete", "expect a complete event");
+      is(event.target, requests[0].transaction, "expect it to be fired at the transaction");
+
+      event = yield;
+      is(event.type, "success", "expect a success event");
+      is(event.target, requests[0], "fired at the right request");
+      event.target.result.close();
+
+      requests[1].onupgradeneeded = grabEventAndContinueHandler;
+
+      event = yield;
+      is(event.type, "upgradeneeded", "expect an upgradeneeded event");
+      is(event.target, requests[1], "fired at the right request");
+
+      requests[1].onsuccess = grabEventAndContinueHandler;
+
+      event = yield;
+      is(event.type, "success", "expect a success event");
+      is(event.target, requests[1], "fired at the right request");
+      event.target.result.close();
+
+      requests[2].onupgradeneeded = grabEventAndContinueHandler;
+ 
+      event = yield;
+      is(event.type, "upgradeneeded", "expect an upgradeneeded event");
+      is(event.target, requests[2], "fired at the right request");
+
+      requests[2].onsuccess = grabEventAndContinueHandler;
+
+      event = yield;
+      is(event.type, "success", "expect a success event");
+      is(event.target, requests[2], "fired at the right request");
+      event.target.result.close();
+
+      requests[3].onerror = new ExpectError(IDBDatabaseException.VERSION_ERR);
+
+      event = yield;
+
+      requests[4].onsuccess = grabEventAndContinueHandler;
+
+      event = yield;
+      is(event.type, "success", "expect a success event");
+      is(event.target, requests[4], "fired at the right request");
+      event.target.result.close();
+
+      finishTest();
+      yield;
+    }
+
+  </script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>