Bug 608186 - 'IndexedDB: Transactions should expire when we return to the event loop'. r=sicking, a=blocking+
authorBen Turner <bent.mozilla@gmail.com>
Mon, 15 Nov 2010 13:49:49 -0800
changeset 57530 d62eda4e41376046c8f0948fa65faad1151a2a09
parent 57529 ac5a1cd933e3337e6b1c91c6c9a95bf4bebd7f16
child 57544 326225ef1cab3c1ca818020e2666ec84b97dabd9
push idunknown
push userunknown
push dateunknown
reviewerssicking, blocking
bugs608186
milestone2.0b8pre
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 608186 - 'IndexedDB: Transactions should expire when we return to the event loop'. r=sicking, a=blocking+
dom/indexedDB/AsyncConnectionHelper.cpp
dom/indexedDB/AsyncConnectionHelper.h
dom/indexedDB/IDBCursor.cpp
dom/indexedDB/IDBDatabase.cpp
dom/indexedDB/IDBTransaction.cpp
dom/indexedDB/IDBTransaction.h
dom/indexedDB/test/Makefile.in
dom/indexedDB/test/test_transaction_lifetimes.html
dom/indexedDB/test/test_transaction_lifetimes_nested.html
xpcom/threads/nsIThreadInternal.idl
xpcom/threads/nsThread.cpp
xpcom/threads/nsThread.h
--- a/dom/indexedDB/AsyncConnectionHelper.cpp
+++ b/dom/indexedDB/AsyncConnectionHelper.cpp
@@ -140,28 +140,28 @@ NS_IMPL_THREADSAFE_ISUPPORTS2(AsyncConne
 NS_IMETHODIMP
 AsyncConnectionHelper::Run()
 {
   if (NS_IsMainThread()) {
     if (mRequest) {
       mRequest->SetDone();
     }
 
-    NS_ASSERTION(!gCurrentTransaction, "Should be null!");
-    gCurrentTransaction = mTransaction;
+    SetCurrentTransaction(mTransaction);
 
     // Call OnError if the database had an error or if the OnSuccess handler
     // has an error.
     if (NS_FAILED(mResultCode) ||
         NS_FAILED((mResultCode = OnSuccess(mRequest)))) {
       OnError(mRequest, mResultCode);
     }
 
-    NS_ASSERTION(gCurrentTransaction == mTransaction, "Should be unchanged!");
-    gCurrentTransaction = nsnull;
+    NS_ASSERTION(GetCurrentTransaction() == mTransaction,
+                 "Should be unchanged!");
+    SetCurrentTransaction(nsnull);
 
     if (mDispatched && mTransaction) {
       mTransaction->OnRequestFinished();
     }
 
     ReleaseMainThreadObjects();
 
     NS_ASSERTION(!(mDatabase || mTransaction || mRequest), "Subclass didn't "
@@ -314,16 +314,30 @@ AsyncConnectionHelper::DispatchToTransac
 IDBTransaction*
 AsyncConnectionHelper::GetCurrentTransaction()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   return gCurrentTransaction;
 }
 
+// static
+void
+AsyncConnectionHelper::SetCurrentTransaction(IDBTransaction* aTransaction)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  if (aTransaction) {
+    NS_ASSERTION(!gCurrentTransaction, "Overwriting current transaction!");
+  }
+
+  gCurrentTransaction = aTransaction;
+}
+
+
 nsresult
 AsyncConnectionHelper::Init()
 {
   return NS_OK;
 }
 
 nsresult
 AsyncConnectionHelper::OnSuccess(nsIDOMEventTarget* aTarget)
--- a/dom/indexedDB/AsyncConnectionHelper.h
+++ b/dom/indexedDB/AsyncConnectionHelper.h
@@ -82,16 +82,17 @@ public:
 
   void SetError(nsresult aErrorCode)
   {
     NS_ASSERTION(NS_FAILED(aErrorCode), "Not a failure code!");
     mResultCode = aErrorCode;
   }
 
   static IDBTransaction* GetCurrentTransaction();
+  static void SetCurrentTransaction(IDBTransaction* aTransaction);
 
   nsISupports* GetSource()
   {
     return mRequest ? mRequest->Source() : nsnull;
   }
 
 protected:
   AsyncConnectionHelper(IDBDatabase* aDatabase,
--- a/dom/indexedDB/IDBCursor.cpp
+++ b/dom/indexedDB/IDBCursor.cpp
@@ -806,14 +806,20 @@ ContinueRunnable::Run()
 
   nsCOMPtr<nsIDOMEvent> event =
     IDBSuccessEvent::Create(cursor->mRequest, variant, cursor->mTransaction);
   if (!event) {
     NS_ERROR("Failed to create event!");
     return NS_ERROR_FAILURE;
   }
 
+  AsyncConnectionHelper::SetCurrentTransaction(cursor->mTransaction);
+
   PRBool dummy;
   cursor->mRequest->DispatchEvent(event, &dummy);
 
+  NS_ASSERTION(AsyncConnectionHelper::GetCurrentTransaction() ==
+               cursor->mTransaction, "Should be unchanged!");
+  AsyncConnectionHelper::SetCurrentTransaction(nsnull);
+
   cursor->mTransaction->OnRequestFinished();
   return NS_OK;
 }
--- a/dom/indexedDB/IDBDatabase.cpp
+++ b/dom/indexedDB/IDBDatabase.cpp
@@ -631,17 +631,17 @@ IDBDatabase::SetVersion(const nsAString&
   if (!DatabaseInfo::Get(mDatabaseId, &info)) {
     NS_ERROR("This should never fail!");
   }
 
   // Lock the whole database.
   nsTArray<nsString> storesToOpen;
   nsRefPtr<IDBTransaction> transaction =
     IDBTransaction::Create(this, storesToOpen, IDBTransaction::VERSION_CHANGE,
-                           kDefaultDatabaseTimeoutSeconds);
+                           kDefaultDatabaseTimeoutSeconds, true);
   NS_ENSURE_TRUE(transaction, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   nsRefPtr<IDBVersionChangeRequest> request =
     IDBVersionChangeRequest::Create(static_cast<nsPIDOMEventTarget*>(this),
                                     ScriptContext(), Owner(), transaction);
   NS_ENSURE_TRUE(request, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   nsRefPtr<SetVersionHelper> helper =
--- a/dom/indexedDB/IDBTransaction.cpp
+++ b/dom/indexedDB/IDBTransaction.cpp
@@ -43,29 +43,32 @@
 
 #include "mozilla/storage.h"
 #include "nsDOMClassInfo.h"
 #include "nsEventDispatcher.h"
 #include "nsPIDOMWindow.h"
 #include "nsProxyRelease.h"
 #include "nsThreadUtils.h"
 
+#include "AsyncConnectionHelper.h"
 #include "DatabaseInfo.h"
 #include "IDBCursor.h"
 #include "IDBEvents.h"
 #include "IDBFactory.h"
 #include "IDBObjectStore.h"
 #include "TransactionThreadPool.h"
 
 #define SAVEPOINT_NAME "savepoint"
 
 USING_INDEXEDDB_NAMESPACE
 
 namespace {
 
+IDBTransaction::ThreadObserver* gThreadObserver = nsnull;
+
 PLDHashOperator
 DoomCachedStatements(const nsACString& aQuery,
                      nsCOMPtr<mozIStorageStatement>& aStatement,
                      void* aUserArg)
 {
   CommitHelper* helper = static_cast<CommitHelper*>(aUserArg);
   helper->AddDoomedObject(aStatement);
   return PL_DHASH_REMOVE;
@@ -73,17 +76,18 @@ DoomCachedStatements(const nsACString& a
 
 } // anonymous namespace
 
 // static
 already_AddRefed<IDBTransaction>
 IDBTransaction::Create(IDBDatabase* aDatabase,
                        nsTArray<nsString>& aObjectStoreNames,
                        PRUint16 aMode,
-                       PRUint32 aTimeout)
+                       PRUint32 aTimeout,
+                       bool aDispatchDelayed)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   nsRefPtr<IDBTransaction> transaction = new IDBTransaction();
 
   transaction->mScriptContext = aDatabase->ScriptContext();
   transaction->mOwner = aDatabase->Owner();
 
@@ -96,37 +100,45 @@ IDBTransaction::Create(IDBDatabase* aDat
     return nsnull;
   }
 
   if (!transaction->mCachedStatements.Init()) {
     NS_ERROR("Failed to initialize hash!");
     return nsnull;
   }
 
+  if (!aDispatchDelayed) {
+    if (!ThreadObserver::BeginObserving(transaction)) {
+      return nsnull;
+    }
+    transaction->mCreating = true;
+  }
+
   return transaction.forget();
 }
 
 IDBTransaction::IDBTransaction()
 : mReadyState(nsIIDBTransaction::INITIAL),
   mMode(nsIIDBTransaction::READ_ONLY),
   mTimeout(0),
   mPendingRequests(0),
   mSavepointCount(0),
   mAborted(false),
-  mClosed(false)
+  mCreating(false)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 }
 
 IDBTransaction::~IDBTransaction()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(!mPendingRequests, "Should have no pending requests here!");
   NS_ASSERTION(!mSavepointCount, "Should have released them all!");
   NS_ASSERTION(!mConnection, "Should have called CommitOrRollback!");
+  NS_ASSERTION(!mCreating, "Should have been cleared already!");
 
   if (mListenerManager) {
     mListenerManager->Disconnect();
   }
 }
 
 void
 IDBTransaction::OnNewRequest()
@@ -145,20 +157,16 @@ IDBTransaction::OnRequestFinished()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(mPendingRequests, "Mismatched calls!");
   --mPendingRequests;
   if (!mPendingRequests) {
     if (!mAborted) {
       NS_ASSERTION(mReadyState == nsIIDBTransaction::LOADING, "Bad state!");
     }
-
-    NS_ASSERTION(!mClosed, "Shouldn't be closed yet!");
-    mClosed = true;
-
     CommitOrRollback();
   }
 }
 
 nsresult
 IDBTransaction::CommitOrRollback()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
@@ -528,26 +536,45 @@ IDBTransaction::GetCachedStatement(const
     if (!mCachedStatements.Put(aQuery, stmt)) {
       NS_ERROR("Out of memory?!");
     }
   }
 
   return stmt.forget();
 }
 
-#ifdef DEBUG
 bool
 IDBTransaction::TransactionIsOpen() const
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  return (mReadyState == nsIIDBTransaction::INITIAL ||
-          mReadyState == nsIIDBTransaction::LOADING) &&
-         !mClosed;
+
+  // If we haven't started anything then we're open.
+  if (mReadyState == nsIIDBTransaction::INITIAL) {
+    NS_ASSERTION(AsyncConnectionHelper::GetCurrentTransaction() != this,
+                 "This should be some other transaction (or null)!");
+    return true;
+  }
+
+  // If we've already started then we need to check to see if we still have the
+  // mCreating flag set. If we do (i.e. we haven't returned to the event loop
+  // from the time we were created) then we are open. Otherwise check the
+  // currently running transaction to see if it's the same. We only allow other
+  // requests to be made if this transaction is currently running.
+  if (mReadyState == nsIIDBTransaction::LOADING) {
+    if (mCreating) {
+      return true;
+    }
+
+    if (AsyncConnectionHelper::GetCurrentTransaction() == this) {
+      return true;
+    }
+  }
+
+  return false;
 }
-#endif
 
 already_AddRefed<IDBObjectStore>
 IDBTransaction::GetOrCreateObjectStore(const nsAString& aName,
                                        ObjectStoreInfo* aObjectStoreInfo)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(!aName.IsEmpty(), "Empty name!");
   NS_ASSERTION(aObjectStoreInfo, "Null pointer!");
@@ -787,16 +814,244 @@ IDBTransaction::SetOntimeout(nsIDOMEvent
 nsresult
 IDBTransaction::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
 {
   aVisitor.mCanHandle = PR_TRUE;
   aVisitor.mParentTarget = mDatabase;
   return NS_OK;
 }
 
+IDBTransaction::
+ThreadObserver::ThreadObserver()
+: mBaseRecursionDepth(0),
+  mDone(false)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(!gThreadObserver, "Multiple observers?!");
+}
+
+IDBTransaction::
+ThreadObserver::~ThreadObserver()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(gThreadObserver == this, "Multiple observers?!");
+
+#ifdef DEBUG
+  for (PRUint32 i = 0; i < mTransactions.Length(); i++) {
+    NS_ASSERTION(mTransactions[i].transactions.IsEmpty(),
+                 "Unprocessed transactions!");
+  }
+#endif
+
+  // Clear the global.
+  gThreadObserver = nsnull;
+}
+
+void
+IDBTransaction::
+ThreadObserver::UpdateNewlyCreatedTransactions(PRUint32 aRecursionDepth)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  for (PRUint32 i = 0; i < mTransactions.Length(); i++) {
+    TransactionInfo& info = mTransactions[i];
+
+    if (info.recursionDepth == aRecursionDepth) {
+      for (PRUint32 j = 0; j < info.transactions.Length(); j++) {
+        nsRefPtr<IDBTransaction>& transaction = info.transactions[j];
+
+        // Clear the mCreating flag now.
+        transaction->mCreating = false;
+
+        // And maybe set the readyState to DONE if there were no requests
+        // generated.
+        if (transaction->mReadyState == nsIIDBTransaction::INITIAL) {
+          transaction->mReadyState = nsIIDBTransaction::DONE;
+        }
+      }
+
+      // Don't hang on to transactions any longer than we have to.
+      info.transactions.Clear();
+
+      break;
+    }
+  }
+}
+
+// static
+bool
+IDBTransaction::
+ThreadObserver::BeginObserving(IDBTransaction* aTransaction)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(aTransaction, "Null pointer!");
+
+  nsCOMPtr<nsIThreadInternal2> thread(do_QueryInterface(NS_GetCurrentThread()));
+  NS_ENSURE_TRUE(thread, false);
+
+  // We need the current recursion depth first.
+  PRUint32 depth;
+  nsresult rv = thread->GetRecursionDepth(&depth);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  NS_ASSERTION(depth, "This should never be 0!");
+  depth--;
+
+  // If we've already got an observer created then simply append this
+  // transaction to its list.
+  if (gThreadObserver) {
+    for (PRUint32 i = 0; i < gThreadObserver->mTransactions.Length(); i++) {
+      TransactionInfo& info = gThreadObserver->mTransactions[i];
+      if (info.recursionDepth == depth) {
+        if (!info.transactions.AppendElement(aTransaction)) {
+          NS_WARNING("Out of memory!");
+          return false;
+        }
+        return true;
+      }
+    }
+
+    // No transactions at this depth yet, make a new entry
+    TransactionInfo* newInfo = gThreadObserver->mTransactions.AppendElement();
+    if (!newInfo || !newInfo->transactions.AppendElement(aTransaction)) {
+      NS_WARNING("Out of memory!");
+      return false;
+    }
+    newInfo->recursionDepth = depth;
+
+    return true;
+  }
+
+  // Make a new thread observer and install it.
+  nsRefPtr<ThreadObserver> observer(new ThreadObserver());
+
+  TransactionInfo* info = observer->mTransactions.AppendElement();
+  NS_ASSERTION(info, "This should never fail!");
+
+  info->recursionDepth = observer->mBaseRecursionDepth = depth;
+
+  if (!info->transactions.AppendElement(aTransaction)) {
+    NS_WARNING("Out of memory!");
+    return false;
+  }
+
+  // We need to keep the thread observer chain intact so grab the previous
+  // observer.
+  rv = thread->GetObserver(getter_AddRefs(observer->mPreviousObserver));
+  NS_ENSURE_SUCCESS(rv, false);
+
+  // Now set our new observer.
+  rv = thread->SetObserver(observer);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  // And set the global so that we don't recreate it later.
+  gThreadObserver = observer;
+  return true;
+}
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(IDBTransaction::ThreadObserver, nsIThreadObserver)
+
+NS_IMETHODIMP
+IDBTransaction::
+ThreadObserver::OnDispatchedEvent(nsIThreadInternal* aThread)
+{
+  // This may be called on any thread!
+
+  // Nothing special is needed here, just call the previous observer.
+  if (mPreviousObserver) {
+    return mPreviousObserver->OnDispatchedEvent(aThread);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+IDBTransaction::
+ThreadObserver::OnProcessNextEvent(nsIThreadInternal* aThread,
+                                   PRBool aMayWait,
+                                   PRUint32 aRecursionDepth)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(aThread, "This should never be null!");
+  NS_ASSERTION(!mKungFuDeathGrip, "Shouldn't have a self-ref here!");
+
+  // If we're at the base recursion depth here then we're ready to unset
+  // ourselves as the thread observer.
+  if (aRecursionDepth == mBaseRecursionDepth || mDone) {
+    // From here on we'll continue to try to unset ourselves.
+    mDone = true;
+
+    nsCOMPtr<nsIThreadObserver> currentObserver;
+    if (NS_FAILED(aThread->GetObserver(getter_AddRefs(currentObserver)))) {
+      NS_WARNING("Can't get current observer?!");
+    }
+
+    // We can only set the previous observer if this is the current observer.
+    // Otherwise someone else has installed themselves into the chain and we
+    // have to hang around until they unset themselves.
+    if (currentObserver == this) {
+      // Setting a different thread observer could delete us. Maintain a
+      // reference until AfterProcessNextEvent is called.
+      mKungFuDeathGrip = this;
+
+      // Set our previous observer back on the thread.
+      if (NS_FAILED(aThread->SetObserver(mPreviousObserver))) {
+        NS_ERROR("This should never fail!");
+      }
+    }
+  }
+
+  // Take care of any transactions that were created at this recursion depth.
+  UpdateNewlyCreatedTransactions(aRecursionDepth);
+
+  // And call the previous observer.
+  if (mPreviousObserver) {
+    return mPreviousObserver->OnProcessNextEvent(aThread, aMayWait,
+                                                 aRecursionDepth);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+IDBTransaction::
+ThreadObserver::AfterProcessNextEvent(nsIThreadInternal* aThread,
+                                      PRUint32 aRecursionDepth)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(aThread, "This should never be null!");
+
+  nsRefPtr<ThreadObserver> kungFuDeathGrip;
+  nsCOMPtr<nsIThreadObserver> observer;
+
+  if (mKungFuDeathGrip) {
+    NS_ASSERTION(mDone, "Huh?!");
+
+    // We can drop the reference to this observer after this call.
+    kungFuDeathGrip.swap(mKungFuDeathGrip);
+
+    // And we don't need the previous observer after this call either.
+    observer.swap(mPreviousObserver);
+  }
+  else {
+    // Still call the previous observer.
+    observer = mPreviousObserver;
+  }
+
+  // We may have collected more transactions while the event was processed.
+  // Update them now.
+  UpdateNewlyCreatedTransactions(aRecursionDepth);
+
+  if (observer) {
+    return observer->AfterProcessNextEvent(aThread, aRecursionDepth);
+  }
+
+  return NS_OK;
+}
+
 CommitHelper::CommitHelper(IDBTransaction* aTransaction)
 : mTransaction(aTransaction),
   mAborted(!!aTransaction->mAborted),
   mHaveMetadata(false)
 {
   mConnection.swap(aTransaction->mConnection);
 }
 
@@ -804,18 +1059,16 @@ CommitHelper::~CommitHelper()
 {
 }
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(CommitHelper, nsIRunnable)
 
 NS_IMETHODIMP
 CommitHelper::Run()
 {
-  NS_ASSERTION(mTransaction->mClosed, "Should be closed!");
-
   if (NS_IsMainThread()) {
     NS_ASSERTION(mDoomedObjects.IsEmpty(), "Didn't release doomed objects!");
 
     mTransaction->mReadyState = nsIIDBTransaction::DONE;
 
     nsCOMPtr<nsIDOMEvent> event;
     if (mAborted) {
       if (mHaveMetadata) {
--- a/dom/indexedDB/IDBTransaction.h
+++ b/dom/indexedDB/IDBTransaction.h
@@ -40,16 +40,17 @@
 #ifndef mozilla_dom_indexeddb_idbtransaction_h__
 #define mozilla_dom_indexeddb_idbtransaction_h__
 
 #include "mozilla/dom/indexedDB/IndexedDatabase.h"
 #include "mozilla/dom/indexedDB/IDBDatabase.h"
 
 #include "nsIIDBTransaction.h"
 #include "nsIRunnable.h"
+#include "nsIThreadInternal.h"
 
 #include "nsDOMEventTargetHelper.h"
 #include "nsCycleCollectionParticipant.h"
 
 #include "nsAutoPtr.h"
 #include "nsHashKeys.h"
 #include "nsInterfaceHashtable.h"
 
@@ -64,30 +65,32 @@ class CommitHelper;
 struct ObjectStoreInfo;
 class TransactionThreadPool;
 
 class IDBTransaction : public nsDOMEventTargetHelper,
                        public nsIIDBTransaction
 {
   friend class AsyncConnectionHelper;
   friend class CommitHelper;
+  friend class ThreadObserver;
   friend class TransactionThreadPool;
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIIDBTRANSACTION
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(IDBTransaction,
                                            nsDOMEventTargetHelper)
 
   static already_AddRefed<IDBTransaction>
   Create(IDBDatabase* aDatabase,
          nsTArray<nsString>& aObjectStoreNames,
          PRUint16 aMode,
-         PRUint32 aTimeout);
+         PRUint32 aTimeout,
+         bool aDispatchDelayed = false);
 
   // nsPIDOMEventTarget
   virtual nsresult PreHandleEvent(nsEventChainPreVisitor& aVisitor);
 
   void OnNewRequest();
   void OnRequestFinished();
 
   bool StartSavepoint();
@@ -128,26 +131,17 @@ public:
   already_AddRefed<mozIStorageStatement>
   GetCachedStatement(const char (&aQuery)[N])
   {
     nsCString query;
     query.AssignLiteral(aQuery);
     return GetCachedStatement(query);
   }
 
-#ifdef DEBUG
   bool TransactionIsOpen() const;
-#else
-  bool TransactionIsOpen() const
-  {
-    return (mReadyState == nsIIDBTransaction::INITIAL ||
-            mReadyState == nsIIDBTransaction::LOADING) &&
-           !mClosed;
-  }
-#endif
 
   bool IsWriteAllowed() const
   {
     return mMode == nsIIDBTransaction::READ_WRITE ||
            mMode == nsIIDBTransaction::VERSION_CHANGE;
   }
 
   PRUint16 Mode()
@@ -160,16 +154,45 @@ public:
     NS_ASSERTION(mDatabase, "This should never be null!");
     return mDatabase;
   }
 
   already_AddRefed<IDBObjectStore>
   GetOrCreateObjectStore(const nsAString& aName,
                          ObjectStoreInfo* aObjectStoreInfo);
 
+  class ThreadObserver : public nsIThreadObserver
+  {
+  public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSITHREADOBSERVER
+
+    static bool BeginObserving(IDBTransaction* aTransaction);
+
+  private:
+    ThreadObserver();
+    ~ThreadObserver();
+
+    void UpdateNewlyCreatedTransactions(PRUint32 aRecursionDepth);
+
+    struct TransactionInfo
+    {
+      PRUint32 recursionDepth;
+      nsTArray<nsRefPtr<IDBTransaction> > transactions;
+    };
+
+    nsAutoTArray<TransactionInfo, 1> mTransactions;
+
+    nsCOMPtr<nsIThreadObserver> mPreviousObserver;
+    nsRefPtr<ThreadObserver> mKungFuDeathGrip;
+
+    PRUint32 mBaseRecursionDepth;
+    bool mDone;
+  };
+
 private:
   IDBTransaction();
   ~IDBTransaction();
 
   nsresult CommitOrRollback();
 
   nsRefPtr<IDBDatabase> mDatabase;
   nsTArray<nsString> mObjectStoreNames;
@@ -191,17 +214,17 @@ private:
   nsCOMPtr<mozIStorageConnection> mConnection;
 
   // Only touched on the database thread.
   PRUint32 mSavepointCount;
 
   nsTArray<nsRefPtr<IDBObjectStore> > mCreatedObjectStores;
 
   bool mAborted;
-  bool mClosed;
+  bool mCreating;
 };
 
 class CommitHelper : public nsIRunnable
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIRUNNABLE
 
--- a/dom/indexedDB/test/Makefile.in
+++ b/dom/indexedDB/test/Makefile.in
@@ -73,16 +73,18 @@ TEST_FILES = \
   test_overlapping_transactions.html \
   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_transaction_abort.html \
+  test_transaction_lifetimes.html \
+  test_transaction_lifetimes_nested.html \
   test_setVersion.html \
   test_setVersion_abort.html \
   test_setVersion_events.html \
   test_writer_starvation.html \
   $(NULL)
 
 ifeq (browser,$(MOZ_BUILD_APP))
 BROWSER_TEST_FILES = \
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_transaction_lifetimes.html
@@ -0,0 +1,55 @@
+<!--
+  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="/MochiKit/packed.js"></script>
+  <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()
+    {
+      let request = moz_indexedDB.open(window.location.pathname);
+      request.onerror = errorHandler;
+      request.onsuccess = grabEventAndContinueHandler;
+      let event = yield;
+
+      let db = event.result;
+      db.onerror = errorHandler;
+
+      db.setVersion("1").onsuccess = grabEventAndContinueHandler;
+      let event = yield;
+
+      event.transaction.oncomplete = continueToNextStep;
+
+      db.createObjectStore("foo", "", true);
+      yield;
+
+      let transaction = db.transaction("foo");
+      continueToNextStep();
+      yield;
+
+      try {
+        transaction.objectStore("foo");
+        ok(false, "Should have thrown!");
+      }
+      catch (e) {
+        ok(e instanceof IDBDatabaseException, "Got database exception.");
+        is(e.code, IDBDatabaseException.NOT_ALLOWED_ERR, "Good error code.");
+      }
+
+      finishTest();
+      yield;
+    }
+  </script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_transaction_lifetimes_nested.html
@@ -0,0 +1,79 @@
+<!--
+  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="/MochiKit/packed.js"></script>
+  <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()
+    {
+      let request = moz_indexedDB.open(window.location.pathname);
+      request.onerror = errorHandler;
+      request.onsuccess = grabEventAndContinueHandler;
+      let event = yield;
+
+      let db = event.result;
+      db.onerror = errorHandler;
+
+      db.setVersion("1").onsuccess = grabEventAndContinueHandler;
+      event = yield;
+
+      event.transaction.oncomplete = continueToNextStep;
+      db.createObjectStore("foo", "");
+      yield;
+
+      let transaction1 = db.transaction("foo");
+      is(transaction1.readyState, IDBTransaction.INITIAL, "Correct readyState");
+
+      let transaction2;
+
+      netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+      let thread = Components.classes["@mozilla.org/thread-manager;1"]
+                             .getService()
+                             .currentThread;
+
+      let eventHasRun;
+
+      thread.dispatch(function() {
+        eventHasRun = true;
+
+        is(transaction1.readyState, IDBTransaction.INITIAL,
+           "Correct readyState");
+
+        transaction2 = db.transaction("foo");
+        is(transaction2.readyState, IDBTransaction.INITIAL,
+           "Correct readyState");
+
+      }, Components.interfaces.nsIThread.DISPATCH_NORMAL);
+
+      while (!eventHasRun) {
+        thread.processNextEvent(false);
+      }
+
+      is(transaction1.readyState, IDBTransaction.INITIAL, "Correct readyState");
+
+      ok(transaction2, "Non-null transaction2");
+      is(transaction2.readyState, IDBTransaction.DONE, "Correct readyState");
+
+      continueToNextStep();
+      yield;
+
+      is(transaction1.readyState, IDBTransaction.DONE, "Correct readyState");
+
+      finishTest();
+      yield;
+    }
+  </script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
--- a/xpcom/threads/nsIThreadInternal.idl
+++ b/xpcom/threads/nsIThreadInternal.idl
@@ -157,8 +157,21 @@ interface nsIThreadEventFilter : nsISupp
    *
    * @param event
    *   The event being dispatched.
    *
    * WARNING: This method must not make any calls on the thread object.
    */
   [notxpcom] boolean acceptEvent(in nsIRunnable event);
 };
+
+/**
+ * Temporary interface, will be merged into nsIThreadInternal.
+ */
+[scriptable, uuid(718e9346-74cb-4859-8bcc-c9ec37bfb668)]
+interface nsIThreadInternal2 : nsIThreadInternal
+{
+  /**
+   * The current recursion depth, 0 when no events are running, 1 when a single
+   * event is running, and higher when nested events are running.
+   */
+  readonly attribute unsigned long recursionDepth;
+};
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -152,16 +152,17 @@ nsThreadClassInfo::GetClassIDNoAlloc(nsC
 
 //-----------------------------------------------------------------------------
 
 NS_IMPL_THREADSAFE_ADDREF(nsThread)
 NS_IMPL_THREADSAFE_RELEASE(nsThread)
 NS_INTERFACE_MAP_BEGIN(nsThread)
   NS_INTERFACE_MAP_ENTRY(nsIThread)
   NS_INTERFACE_MAP_ENTRY(nsIThreadInternal)
+  NS_INTERFACE_MAP_ENTRY(nsIThreadInternal2)
   NS_INTERFACE_MAP_ENTRY(nsIEventTarget)
   NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIThread)
   if (aIID.Equals(NS_GET_IID(nsIClassInfo))) {
     foundInterface = static_cast<nsIClassInfo*>(&sThreadClassInfo);
   } else
 NS_INTERFACE_MAP_END
 NS_IMPL_CI_INTERFACE_GETTER4(nsThread, nsIThread, nsIThreadInternal,
@@ -733,16 +734,27 @@ nsThread::nsChainedEventQueue::PutEvent(
     val = mQueue.PutEvent(event);
   } else {
     val = mNext->PutEvent(event);
   }
   return val;
 }
 
 //-----------------------------------------------------------------------------
+// nsIThreadInternal2
+
+NS_IMETHODIMP
+nsThread::GetRecursionDepth(PRUint32 *depth)
+{
+  NS_ENSURE_ARG_POINTER(depth);
+  *depth = mRunningEvent;
+  return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsThreadSyncDispatch::Run()
 {
   if (mSyncTask) {
     mSyncTask->Run();
     mSyncTask = nsnull;
     // unblock the origin thread
--- a/xpcom/threads/nsThread.h
+++ b/xpcom/threads/nsThread.h
@@ -43,23 +43,24 @@
 #include "nsISupportsPriority.h"
 #include "nsEventQueue.h"
 #include "nsThreadUtils.h"
 #include "nsString.h"
 #include "nsAutoLock.h"
 #include "nsAutoPtr.h"
 
 // A native thread
-class nsThread : public nsIThreadInternal, public nsISupportsPriority
+class nsThread : public nsIThreadInternal2, public nsISupportsPriority
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIEVENTTARGET
   NS_DECL_NSITHREAD
   NS_DECL_NSITHREADINTERNAL
+  NS_DECL_NSITHREADINTERNAL2
   NS_DECL_NSISUPPORTSPRIORITY
 
   nsThread();
 
   // Initialize this as a wrapper for a new PRThread.
   nsresult Init();
 
   // Initialize this as a wrapper for the current PRThread.