Bug 612807 - 'IndexedDB: Transaction thread observer isn't quite safe'. r=jst, a=blocking+.

This commit is contained in:
Ben Turner 2010-11-18 14:19:19 -08:00
parent 5564f047b7
commit 72ea70e7b6
5 changed files with 122 additions and 239 deletions

View File

@ -62,8 +62,6 @@ USING_INDEXEDDB_NAMESPACE
namespace {
IDBTransaction::ThreadObserver* gThreadObserver = nsnull;
PLDHashOperator
DoomCachedStatements(const nsACString& aQuery,
nsCOMPtr<mozIStorageStatement>& aStatement,
@ -106,9 +104,21 @@ IDBTransaction::Create(IDBDatabase* aDatabase,
}
if (!aDispatchDelayed) {
if (!ThreadObserver::BeginObserving(transaction)) {
return nsnull;
}
nsCOMPtr<nsIThreadInternal2> thread =
do_QueryInterface(NS_GetCurrentThread());
NS_ENSURE_TRUE(thread, nsnull);
// We need the current recursion depth first.
PRUint32 depth;
nsresult rv = thread->GetRecursionDepth(&depth);
NS_ENSURE_SUCCESS(rv, nsnull);
NS_ASSERTION(depth, "This should never be 0!");
transaction->mCreatedRecursionDepth = depth - 1;
rv = thread->AddObserver(transaction);
NS_ENSURE_SUCCESS(rv, nsnull);
transaction->mCreating = true;
}
@ -120,6 +130,7 @@ IDBTransaction::IDBTransaction()
mMode(nsIIDBTransaction::READ_ONLY),
mTimeout(0),
mPendingRequests(0),
mCreatedRecursionDepth(0),
mSavepointCount(0),
mAborted(false),
mCreating(false)
@ -633,6 +644,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(IDBTransaction)
NS_INTERFACE_MAP_ENTRY(nsIIDBTransaction)
NS_INTERFACE_MAP_ENTRY(nsIThreadObserver)
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(IDBTransaction)
NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
@ -819,229 +831,52 @@ IDBTransaction::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
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)
// XXX Once nsIThreadObserver gets split this method will disappear.
NS_IMETHODIMP
IDBTransaction::
ThreadObserver::OnDispatchedEvent(nsIThreadInternal* aThread)
IDBTransaction::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_NOTREACHED("Don't call me!");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
IDBTransaction::
ThreadObserver::OnProcessNextEvent(nsIThreadInternal* aThread,
IDBTransaction::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);
}
NS_ASSERTION(aRecursionDepth > mCreatedRecursionDepth,
"Should be impossible!");
NS_ASSERTION(mCreating, "Should be true!");
return NS_OK;
}
NS_IMETHODIMP
IDBTransaction::
ThreadObserver::AfterProcessNextEvent(nsIThreadInternal* aThread,
IDBTransaction::AfterProcessNextEvent(nsIThreadInternal* aThread,
PRUint32 aRecursionDepth)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aThread, "This should never be null!");
NS_ASSERTION(aRecursionDepth >= mCreatedRecursionDepth,
"Should be impossible!");
NS_ASSERTION(mCreating, "Should be true!");
nsRefPtr<ThreadObserver> kungFuDeathGrip;
nsCOMPtr<nsIThreadObserver> observer;
if (aRecursionDepth == mCreatedRecursionDepth) {
// We're back at the event loop, no longer newborn.
mCreating = false;
if (mKungFuDeathGrip) {
NS_ASSERTION(mDone, "Huh?!");
// Maybe set the readyState to DONE if there were no requests generated.
if (mReadyState == nsIIDBTransaction::INITIAL) {
mReadyState = nsIIDBTransaction::DONE;
}
// We can drop the reference to this observer after this call.
kungFuDeathGrip.swap(mKungFuDeathGrip);
// No longer need to observe thread events.
nsCOMPtr<nsIThreadInternal2> thread = do_QueryInterface(aThread);
NS_ASSERTION(thread, "This must never fail!");
// 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);
if(NS_FAILED(thread->RemoveObserver(this))) {
NS_ERROR("Failed to remove observer!");
}
}
return NS_OK;

View File

@ -66,7 +66,8 @@ struct ObjectStoreInfo;
class TransactionThreadPool;
class IDBTransaction : public nsDOMEventTargetHelper,
public nsIIDBTransaction
public nsIIDBTransaction,
public nsIThreadObserver
{
friend class AsyncConnectionHelper;
friend class CommitHelper;
@ -76,6 +77,7 @@ class IDBTransaction : public nsDOMEventTargetHelper,
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSIIDBTRANSACTION
NS_DECL_NSITHREADOBSERVER
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(IDBTransaction,
nsDOMEventTargetHelper)
@ -159,35 +161,6 @@ public:
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();
@ -200,6 +173,7 @@ private:
PRUint16 mMode;
PRUint32 mTimeout;
PRUint32 mPendingRequests;
PRUint32 mCreatedRecursionDepth;
// Only touched on the main thread.
nsRefPtr<nsDOMEventListenerWrapper> mOnErrorListener;

View File

@ -102,6 +102,10 @@ interface nsIThreadInternal : nsIThread
*
* NOTE: It is valid to change the thread's observer during a call to an
* observer method.
*
* NOTE: Will be split into two interfaces soon: one for onProcessNextEvent and
* afterProcessNextEvent, then another that inherits the first and adds
* onDispatchedEvent.
*/
[scriptable, uuid(81D0B509-F198-4417-8020-08EB4271491F)]
interface nsIThreadObserver : nsISupports
@ -166,12 +170,28 @@ interface nsIThreadEventFilter : nsISupports
/**
* Temporary interface, will be merged into nsIThreadInternal.
*/
[scriptable, uuid(718e9346-74cb-4859-8bcc-c9ec37bfb668)]
[scriptable, uuid(4531f101-fddc-4d36-80e7-35260a2f3afe)]
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.
* event is running, and higher when nested events are running. Must only be
* called on the target thread.
*/
readonly attribute unsigned long recursionDepth;
/**
* Add an observer that will *only* receive onProcessNextEvent and
* afterProcessNextEvent callbacks. Always called on the target thread, and
* the implementation does not have to be threadsafe. Order of callbacks is
* not guaranteed (i.e. afterProcessNextEvent may be called first depending on
* whether or not the observer is added in a nested loop). Holds a strong ref.
*/
void addObserver(in nsIThreadObserver observer);
/**
* Remove an observer added via the addObserver call. Once removed the
* observer will never be called again by the thread.
*/
void removeObserver(in nsIThreadObserver observer);
};

View File

@ -560,6 +560,19 @@ void canary_alarm_handler (int signum)
#endif
#define NOTIFY_EVENT_OBSERVERS(func_, params_) \
PR_BEGIN_MACRO \
if (!mEventObservers.IsEmpty()) { \
nsAutoTObserverArray<nsCOMPtr<nsIThreadObserver>, 2>::ForwardIterator \
iter_(mEventObservers); \
nsCOMPtr<nsIThreadObserver> obs_; \
while (iter_.HasMore()) { \
obs_ = iter_.GetNext(); \
obs_ -> func_ params_ ; \
} \
} \
PR_END_MACRO
NS_IMETHODIMP
nsThread::ProcessNextEvent(PRBool mayWait, PRBool *result)
{
@ -576,6 +589,9 @@ nsThread::ProcessNextEvent(PRBool mayWait, PRBool *result)
if (obs)
obs->OnProcessNextEvent(this, mayWait && !ShuttingDown(), mRunningEvent);
NOTIFY_EVENT_OBSERVERS(OnProcessNextEvent,
(this, mayWait && !ShuttingDown(), mRunningEvent));
++mRunningEvent;
#ifdef MOZ_CANARY
@ -616,6 +632,9 @@ nsThread::ProcessNextEvent(PRBool mayWait, PRBool *result)
}
--mRunningEvent;
NOTIFY_EVENT_OBSERVERS(AfterProcessNextEvent, (this, mRunningEvent));
if (obs)
obs->AfterProcessNextEvent(this, mRunningEvent);
@ -745,10 +764,41 @@ NS_IMETHODIMP
nsThread::GetRecursionDepth(PRUint32 *depth)
{
NS_ENSURE_ARG_POINTER(depth);
NS_ENSURE_STATE(PR_GetCurrentThread() == mThread);
*depth = mRunningEvent;
return NS_OK;
}
NS_IMETHODIMP
nsThread::AddObserver(nsIThreadObserver *observer)
{
NS_ENSURE_ARG_POINTER(observer);
NS_ENSURE_STATE(PR_GetCurrentThread() == mThread);
NS_WARN_IF_FALSE(!mEventObservers.Contains(observer),
"Adding an observer twice!");
if (!mEventObservers.AppendElement(observer)) {
NS_WARNING("Out of memory!");
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
NS_IMETHODIMP
nsThread::RemoveObserver(nsIThreadObserver *observer)
{
NS_ENSURE_STATE(PR_GetCurrentThread() == mThread);
if (observer && !mEventObservers.RemoveElement(observer)) {
NS_WARNING("Removing an observer that was never added!");
}
return NS_OK;
}
//-----------------------------------------------------------------------------
NS_IMETHODIMP

View File

@ -46,6 +46,7 @@
#include "nsString.h"
#include "nsAutoLock.h"
#include "nsAutoPtr.h"
#include "nsTObserverArray.h"
// A native thread
class nsThread : public nsIThreadInternal2, public nsISupportsPriority
@ -134,6 +135,9 @@ private:
nsCOMPtr<nsIThreadObserver> mObserver;
// Only accessed on the target thread.
nsAutoTObserverArray<nsCOMPtr<nsIThreadObserver>, 2> mEventObservers;
nsChainedEventQueue *mEvents; // never null
nsChainedEventQueue mEventsRoot;