mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-18 06:45:33 +00:00
Bug 612807 - 'IndexedDB: Transaction thread observer isn't quite safe'. r=jst, a=blocking+.
This commit is contained in:
parent
5564f047b7
commit
72ea70e7b6
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user