From 307bdb22675b8e06a5efff9b83cbdbfa4ec6f471 Mon Sep 17 00:00:00 2001 From: "bsmedberg%covad.net" Date: Tue, 17 Jan 2006 15:41:40 +0000 Subject: [PATCH] Bug 319999 - XPCOM Shutdown: xpcom-shutdown-threads notification and cleanup of event queues before component manager shutdown, r=darin git-svn-id: svn://10.0.0.236/trunk@187710 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/netwerk/base/src/nsIOThreadPool.cpp | 8 +- mozilla/xpcom/base/nsError.h | 5 + mozilla/xpcom/build/nsXPComInit.cpp | 27 +-- mozilla/xpcom/threads/nsEventQueue.cpp | 10 +- mozilla/xpcom/threads/nsEventQueue.h | 3 +- mozilla/xpcom/threads/nsEventQueueService.cpp | 167 +++++++++--------- mozilla/xpcom/threads/nsEventQueueService.h | 17 +- mozilla/xpcom/threads/nsIEventQueue.idl | 5 +- mozilla/xpcom/threads/nsPIEventQueueChain.h | 6 +- 9 files changed, 137 insertions(+), 111 deletions(-) diff --git a/mozilla/netwerk/base/src/nsIOThreadPool.cpp b/mozilla/netwerk/base/src/nsIOThreadPool.cpp index 36d20423997..9abfca17311 100644 --- a/mozilla/netwerk/base/src/nsIOThreadPool.cpp +++ b/mozilla/netwerk/base/src/nsIOThreadPool.cpp @@ -68,7 +68,7 @@ static PRLogModuleInfo *gIOThreadPoolLog = nsnull; // the thread pool are dispatched on one of the threads. a variable number // of threads are maintained. the threads die off if they remain idle for // more than THREAD_IDLE_TIMEOUT. the thread pool shuts down when it receives -// the "xpcom-shutdown" event. +// the "xpcom-shutdown-threads" event. //----------------------------------------------------------------------------- class nsIOThreadPool : public nsIEventTarget @@ -125,10 +125,10 @@ nsIOThreadPool::Init() PR_INIT_CLIST(&mEventQ); - // we want to shutdown the i/o thread pool at xpcom-shutdown time... + // We want to shutdown the i/o thread pool at xpcom-shutdown-threads time. nsCOMPtr os = do_GetService("@mozilla.org/observer-service;1"); if (os) - os->AddObserver(this, "xpcom-shutdown", PR_FALSE); + os->AddObserver(this, "xpcom-shutdown-threads", PR_FALSE); return NS_OK; } @@ -224,7 +224,7 @@ nsIOThreadPool::IsOnCurrentThread(PRBool *result) NS_IMETHODIMP nsIOThreadPool::Observe(nsISupports *, const char *topic, const PRUnichar *) { - NS_ASSERTION(strcmp(topic, "xpcom-shutdown") == 0, "unexpected topic"); + NS_ASSERTION(strcmp(topic, "xpcom-shutdown-threads") == 0, "unexpected topic"); Shutdown(); return NS_OK; } diff --git a/mozilla/xpcom/base/nsError.h b/mozilla/xpcom/base/nsError.h index f957bdc4b6b..4b05ad663a9 100644 --- a/mozilla/xpcom/base/nsError.h +++ b/mozilla/xpcom/base/nsError.h @@ -295,6 +295,11 @@ #define NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_XPCOM, 1) +/** + * Various operations are not permitted during XPCOM shutdown and will fail + * with this exception. + */ +#define NS_ERROR_ILLEGAL_DURING_SHUTDOWN NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_XPCOM, 30) /* * This will return the nsresult corresponding to the most recent NSPR failure diff --git a/mozilla/xpcom/build/nsXPComInit.cpp b/mozilla/xpcom/build/nsXPComInit.cpp index bc62482dd47..86690df7b98 100644 --- a/mozilla/xpcom/build/nsXPComInit.cpp +++ b/mozilla/xpcom/build/nsXPComInit.cpp @@ -700,7 +700,7 @@ NS_UnregisterXPCOMExitRoutine(XPCOMExitRoutine exitRoutine) // // - Notify "xpcom-shutdown" for modules to release primary (root) references // - Notify "xpcom-shutdown-threads" for thread joins -// - Shutdown the main event queue (TODO) +// - Shutdown the event queues // - Release the Global Service Manager // - Release all service instances held by the global service manager // - Release the Global Service Manager itself @@ -718,16 +718,22 @@ NS_ShutdownXPCOM(nsIServiceManager* servMgr) { nsresult rv; - // grab the event queue so that we can process events before exiting. - nsCOMPtr currentQ; - NS_GetCurrentEventQ(getter_AddRefs(currentQ)); - nsCOMPtr moduleLoaders; // Notify observers of xpcom shutting down { // Block it so that the COMPtr will get deleted before we hit // servicemanager shutdown + + // grab the event queue service so that we can process events and + // manage event queues + nsRefPtr eqs; + CallGetService(NS_EVENTQUEUESERVICE_CONTRACTID, + (nsEventQueueServiceImpl**) getter_AddRefs(eqs)); + + nsCOMPtr currentQ; + NS_GetCurrentEventQ(getter_AddRefs(currentQ), eqs); + nsCOMPtr observerService = do_GetService("@mozilla.org/observer-service;1"); @@ -754,6 +760,12 @@ NS_ShutdownXPCOM(nsIServiceManager* servMgr) if (currentQ) currentQ->ProcessPendingEvents(); + if (eqs) + eqs->Shutdown(); + + if (currentQ) + currentQ->ProcessPendingEvents(); + // We save the "xpcom-shutdown-loaders" observers to notify after // the observerservice is gone. if (observerService) @@ -774,11 +786,6 @@ NS_ShutdownXPCOM(nsIServiceManager* servMgr) // here again: NS_IF_RELEASE(servMgr); - if (currentQ) { - currentQ->ProcessPendingEvents(); - currentQ = 0; - } - // Shutdown global servicemanager if (nsComponentManagerImpl::gComponentManager) { nsComponentManagerImpl::gComponentManager->FreeServices(); diff --git a/mozilla/xpcom/threads/nsEventQueue.cpp b/mozilla/xpcom/threads/nsEventQueue.cpp index 6d375e7eef8..66e0bcd28f9 100644 --- a/mozilla/xpcom/threads/nsEventQueue.cpp +++ b/mozilla/xpcom/threads/nsEventQueue.cpp @@ -206,11 +206,13 @@ NS_IMPL_THREADSAFE_ISUPPORTS3(nsEventQueueImpl, NS_IMETHODIMP nsEventQueueImpl::StopAcceptingEvents() { - // this assertion is bogus. I should be able to shut down the eldest queue, - // as long as there are no younger children + if (mYoungerQueue) { + NS_ERROR("Should not shut down eventq while child queues are active"); + mYoungerQueue->StopAcceptingEvents(); + } - - NS_ASSERTION(mElderQueue || !mYoungerQueue, "attempted to disable eldest queue in chain"); + // XXXThis method can be called from any thread: do we need to protect this + // with a lock? mAcceptingEvents = PR_FALSE; CheckForDeactivation(); #if defined(PR_LOGGING) && defined(DEBUG_danm) diff --git a/mozilla/xpcom/threads/nsEventQueue.h b/mozilla/xpcom/threads/nsEventQueue.h index 446b30fa321..8cffe436667 100644 --- a/mozilla/xpcom/threads/nsEventQueue.h +++ b/mozilla/xpcom/threads/nsEventQueue.h @@ -40,8 +40,7 @@ #include "nsIEventQueue.h" #include "nsPIEventQueueChain.h" -class nsEventQueueImpl : public nsIEventQueue, - public nsPIEventQueueChain +class nsEventQueueImpl : public nsPIEventQueueChain { public: nsEventQueueImpl(); diff --git a/mozilla/xpcom/threads/nsEventQueueService.cpp b/mozilla/xpcom/threads/nsEventQueueService.cpp index 6b16002e28e..afb9b34dd1c 100644 --- a/mozilla/xpcom/threads/nsEventQueueService.cpp +++ b/mozilla/xpcom/threads/nsEventQueueService.cpp @@ -35,6 +35,7 @@ * Stuart Parmenter * Mike Kaply * Dan Mosedale + * Benjamin Smedberg * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), @@ -55,6 +56,7 @@ #include "nsIComponentManager.h" #include "nsIThread.h" #include "nsPIEventQueueChain.h" +#include "nsAutoLock.h" #include "prlog.h" @@ -66,8 +68,9 @@ extern PRUint32 gEventQueueLogCount; static NS_DEFINE_CID(kEventQueueCID, NS_EVENTQUEUE_CID); nsEventQueueServiceImpl::nsEventQueueServiceImpl() + : mEventQMonitor(PR_NewMonitor()) + , mIsShuttingDown(PR_FALSE) { - mEventQMonitor = PR_NewMonitor(); #if defined(PR_LOGGING) && defined(DEBUG_danm) if (!gEventQueueLog) gEventQueueLog = PR_NewLogModule("nseventqueue"); @@ -76,31 +79,32 @@ nsEventQueueServiceImpl::nsEventQueueServiceImpl() PR_STATIC_CALLBACK(PLDHashOperator) hash_enum_remove_queues(const void *aThread_ptr, - nsCOMPtr& aEldestQueue, + nsCOMPtr& aEldestQueue, void* closure) { - // 'aQueue' should be the eldest queue. - nsCOMPtr pie(do_QueryInterface(aEldestQueue)); - nsCOMPtr q; - - // stop accepting events for youngest to oldest - pie->GetYoungest(getter_AddRefs(q)); - while (q) { - q->StopAcceptingEvents(); - - nsCOMPtr pq(do_QueryInterface(q)); - pq->GetElder(getter_AddRefs(q)); - } - + aEldestQueue->StopAcceptingEvents(); return PL_DHASH_REMOVE; } +void +nsEventQueueServiceImpl::Shutdown() +{ + NS_ASSERTION(mEventQTable.Count() <= 1, + "Event queues for secondary threads exist during shutdown."); + + nsAutoMonitor mon(mEventQMonitor); + mIsShuttingDown = PR_TRUE; + + // Call StopAcceptingEvents on any queues that are left: this should be only + // the main-thread event queue at this point, since xpcom-shutdown-threads has + // already been notified. + mEventQTable.Enumerate(hash_enum_remove_queues, nsnull); +} + nsEventQueueServiceImpl::~nsEventQueueServiceImpl() { - // XXX make it so we only enum over this once - mEventQTable.Enumerate(hash_enum_remove_queues, nsnull); // call StopAcceptingEvents on everything and clear out the hashtable - - PR_DestroyMonitor(mEventQMonitor); + if (mEventQMonitor) + PR_DestroyMonitor(mEventQMonitor); } nsresult @@ -128,7 +132,10 @@ nsEventQueueServiceImpl::Init() } /* nsISupports interface implementation... */ -NS_IMPL_THREADSAFE_ISUPPORTS1(nsEventQueueServiceImpl, nsIEventQueueService) +// QI to our CID so that NS_ShutdownXPCOM can call our internal methods +NS_IMPL_THREADSAFE_ISUPPORTS2(nsEventQueueServiceImpl, + nsIEventQueueService, + nsEventQueueServiceImpl) /* nsIEventQueueService interface implementation... */ @@ -164,10 +171,10 @@ nsEventQueueServiceImpl::CreateFromIThread(nsIThread *aThread, PRBool aNative, NS_IMETHODIMP nsEventQueueServiceImpl::MakeNewQueue(PRThread* thread, PRBool aNative, - nsIEventQueue **aQueue) + nsPIEventQueueChain **aQueue) { nsresult rv; - nsCOMPtr queue = do_CreateInstance(kEventQueueCID, &rv); + nsCOMPtr queue(do_CreateInstance(kEventQueueCID, &rv)); if (NS_SUCCEEDED(rv)) { rv = queue->InitFromPRThread(thread, aNative); @@ -183,22 +190,24 @@ nsEventQueueServiceImpl::MakeNewQueue(PRThread* thread, NS_IMETHODIMP nsEventQueueServiceImpl::CreateEventQueue(PRThread *aThread, PRBool aNative) { - nsresult rv = NS_OK; - /* Enter the lock that protects the EventQ hashtable... */ - PR_EnterMonitor(mEventQMonitor); + nsresult rv; + + nsAutoMonitor mon(mEventQMonitor); + + if (mIsShuttingDown) + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; /* create only one event queue chain per thread... */ - if (!mEventQTable.GetWeak(aThread)) { - nsCOMPtr queue; + if (mEventQTable.GetWeak(aThread)) + return NS_OK; - // we don't have one in the table, create new queue - rv = MakeNewQueue(aThread, aNative, getter_AddRefs(queue)); - if (NS_SUCCEEDED(rv)) - mEventQTable.Put(aThread, queue); // add to the table (initial addref) - } + nsCOMPtr queue; + + // we don't have one in the table, create new queue + rv = MakeNewQueue(aThread, aNative, getter_AddRefs(queue)); + if (NS_SUCCEEDED(rv)) + mEventQTable.Put(aThread, queue); // add to the table (initial addref) - // Release the EventQ lock... - PR_ExitMonitor(mEventQMonitor); return rv; } @@ -206,26 +215,23 @@ nsEventQueueServiceImpl::CreateEventQueue(PRThread *aThread, PRBool aNative) NS_IMETHODIMP nsEventQueueServiceImpl::DestroyThreadEventQueue(void) { - nsresult rv = NS_OK; - /* Enter the lock that protects the EventQ hashtable... */ - PR_EnterMonitor(mEventQMonitor); + nsAutoMonitor mon(mEventQMonitor); PRThread* currentThread = PR_GetCurrentThread(); - nsIEventQueue* queue = mEventQTable.GetWeak(currentThread); + nsPIEventQueueChain* queue = mEventQTable.GetWeak(currentThread); if (queue) { queue->StopAcceptingEvents(); // tell the queue to stop accepting events queue = nsnull; // Queue may die on the next line mEventQTable.Remove(currentThread); // remove nsIEventQueue from hash table (releases) } - // Release the EventQ lock... - PR_ExitMonitor(mEventQMonitor); - return rv; + return NS_OK; } NS_IMETHODIMP -nsEventQueueServiceImpl::CreateFromPLEventQueue(PLEventQueue* aPLEventQueue, nsIEventQueue** aResult) +nsEventQueueServiceImpl::CreateFromPLEventQueue(PLEventQueue* aPLEventQueue, + nsIEventQueue** aResult) { // Create our thread queue using the component manager nsresult rv; @@ -266,17 +272,17 @@ nsresult nsEventQueueServiceImpl::GetYoungestEventQueue(nsIEventQueue *queue, ns NS_IMETHODIMP nsEventQueueServiceImpl::PushThreadEventQueue(nsIEventQueue **aNewQueue) { - nsresult rv = NS_OK; + nsresult rv; + PRThread* currentThread = PR_GetCurrentThread(); PRBool native = PR_TRUE; // native by default as per old comment - NS_ASSERTION(aNewQueue, "PushThreadEventQueue called with null param"); /* Enter the lock that protects the EventQ hashtable... */ - PR_EnterMonitor(mEventQMonitor); + nsAutoMonitor mon(mEventQMonitor); - nsIEventQueue* queue = mEventQTable.GetWeak(currentThread); + nsPIEventQueueChain* queue = mEventQTable.GetWeak(currentThread); NS_ASSERTION(queue, "pushed event queue on top of nothing"); @@ -288,33 +294,29 @@ nsEventQueueServiceImpl::PushThreadEventQueue(nsIEventQueue **aNewQueue) } } - nsIEventQueue* newQueue = nsnull; + nsPIEventQueueChain* newQueue = nsnull; rv = MakeNewQueue(currentThread, native, &newQueue); // AddRefs on success - if (NS_SUCCEEDED(rv)) { - if (!queue) { - // shouldn't happen. as a fallback, we guess you wanted a native queue - mEventQTable.Put(currentThread, newQueue); - } + NS_ENSURE_SUCCESS(rv, rv); - // append to the event queue chain -- QI the queue in the hash table - nsCOMPtr ourChain(do_QueryInterface(queue)); - if (ourChain) - ourChain->AppendQueue(newQueue); // append new queue to it - - *aNewQueue = newQueue; - -#if defined(PR_LOGGING) && defined(DEBUG_danm) - PLEventQueue *equeue; - (*aNewQueue)->GetPLEventQueue(&equeue); - PR_LOG(gEventQueueLog, PR_LOG_DEBUG, - ("EventQueue: Service push queue [queue=%lx]",(long)equeue)); - ++gEventQueueLogCount; -#endif + if (!queue) { + // shouldn't happen. as a fallback, we guess you wanted a native queue + mEventQTable.Put(currentThread, newQueue); } - // Release the EventQ lock... - PR_ExitMonitor(mEventQMonitor); - return rv; + // append the new queue to the event queue chain + queue->AppendQueue(newQueue); + + *aNewQueue = newQueue; + +#if defined(PR_LOGGING) && defined(DEBUG_danm) + PLEventQueue *equeue; + (*aNewQueue)->GetPLEventQueue(&equeue); + PR_LOG(gEventQueueLog, PR_LOG_DEBUG, + ("EventQueue: Service push queue [queue=%lx]",(long)equeue)); + ++gEventQueueLogCount; +#endif + + return NS_OK; } // disable and release the given queue (though the last one won't be released) @@ -323,20 +325,20 @@ nsEventQueueServiceImpl::PopThreadEventQueue(nsIEventQueue *aQueue) { PRThread* currentThread = PR_GetCurrentThread(); - /* Enter the lock that protects the EventQ hashtable... */ - PR_EnterMonitor(mEventQMonitor); + nsCOMPtr eldestQueue; - nsCOMPtr eldestQueue; - mEventQTable.Get(currentThread, getter_AddRefs(eldestQueue)); + { + nsAutoMonitor mon(mEventQMonitor); + mEventQTable.Get(currentThread, getter_AddRefs(eldestQueue)); - // If we are popping the eldest queue, remove its mEventQTable entry. - if (aQueue == eldestQueue) - mEventQTable.Remove(currentThread); + // If we are popping the eldest queue, remove its mEventQTable entry. + if (aQueue == eldestQueue) + mEventQTable.Remove(currentThread); + } // Exit the monitor before processing pending events to avoid deadlock. // Our reference from the eldestQueue nsCOMPtr will keep that object alive. // Since it is thread-private, no one else can race with us here. - PR_ExitMonitor(mEventQMonitor); if (!eldestQueue) return NS_ERROR_FAILURE; @@ -377,13 +379,12 @@ nsEventQueueServiceImpl::GetThreadEventQueue(PRThread* aThread, nsIEventQueue** if (NS_FAILED(rv)) return rv; } - /* Enter the lock that protects the EventQ hashtable... */ - PR_EnterMonitor(mEventQMonitor); + nsCOMPtr queue; - nsCOMPtr queue; - mEventQTable.Get(keyThread, getter_AddRefs(queue)); - - PR_ExitMonitor(mEventQMonitor); + { + nsAutoMonitor mon(mEventQMonitor); + mEventQTable.Get(keyThread, getter_AddRefs(queue)); + } if (queue) { GetYoungestEventQueue(queue, aResult); // get the youngest active queue diff --git a/mozilla/xpcom/threads/nsEventQueueService.h b/mozilla/xpcom/threads/nsEventQueueService.h index 6fddb941607..91422fd8a1e 100644 --- a/mozilla/xpcom/threads/nsEventQueueService.h +++ b/mozilla/xpcom/threads/nsEventQueueService.h @@ -42,16 +42,22 @@ #include "nsInterfaceHashtable.h" #include "nsHashKeys.h" #include "nsIEventQueue.h" +#include "nsPIEventQueueChain.h" //////////////////////////////////////////////////////////////////////////////// class nsEventQueueServiceImpl : public nsIEventQueueService { public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_EVENTQUEUESERVICE_CID) + nsEventQueueServiceImpl(); nsresult Init(); + // During XPCOM shutdown: stop accepting events on all event queues + void Shutdown(); + // nsISupports interface... NS_DECL_ISUPPORTS @@ -65,13 +71,18 @@ private: Addref the descriptor in any case. parameter aNative is ignored if the queue already exists. */ NS_IMETHOD CreateEventQueue(PRThread *aThread, PRBool aNative); - NS_IMETHOD MakeNewQueue(PRThread* thread, PRBool aNative, nsIEventQueue **aQueue); - inline nsresult GetYoungestEventQueue(nsIEventQueue *queue, nsIEventQueue **aResult); + NS_IMETHOD MakeNewQueue(PRThread* thread, PRBool aNative, + nsPIEventQueueChain **aQueue); + inline nsresult GetYoungestEventQueue(nsIEventQueue *queue, + nsIEventQueue **aResult); - nsInterfaceHashtable mEventQTable; + nsInterfaceHashtable mEventQTable; PRMonitor *mEventQMonitor; + PRBool mIsShuttingDown; }; +NS_DEFINE_STATIC_IID_ACCESSOR(nsEventQueueServiceImpl, NS_EVENTQUEUESERVICE_CID) + //////////////////////////////////////////////////////////////////////////////// #endif // nsEventQueueService_h__ diff --git a/mozilla/xpcom/threads/nsIEventQueue.idl b/mozilla/xpcom/threads/nsIEventQueue.idl index 0e1a10e3047..5761c629546 100644 --- a/mozilla/xpcom/threads/nsIEventQueue.idl +++ b/mozilla/xpcom/threads/nsIEventQueue.idl @@ -104,8 +104,9 @@ interface nsIEventQueue : nsIEventTarget boolean isQueueNative(); - // effectively kill the queue. warning: the queue is allowed to delete - // itself any time after this. + /** + * Stop accepting events on this queue. + */ void stopAcceptingEvents(); }; diff --git a/mozilla/xpcom/threads/nsPIEventQueueChain.h b/mozilla/xpcom/threads/nsPIEventQueueChain.h index 2da9003b164..7f84e3cc1ee 100644 --- a/mozilla/xpcom/threads/nsPIEventQueueChain.h +++ b/mozilla/xpcom/threads/nsPIEventQueueChain.h @@ -40,13 +40,13 @@ #include "nsISupports.h" -// {8f310040-82a7-11d3-95bc-0060083a0bcf} +// {6a418ae8-db08-495d-843a-0822ebdc9d10} #define NS_IEVENTQUEUECHAIN_IID \ -{ 0x8f310040, 0x82a7, 0x11d3, { 0x95, 0xbc, 0x0, 0x60, 0x8, 0x3a, 0xb, 0xcf } } +{ 0x6a418ae8, 0xdb08, 0x495d, { 0x84, 0x3a, 0x08, 0x22, 0xeb, 0xdc, 0x9d, 0x10 } } class nsIEventQueue; -class nsPIEventQueueChain : public nsISupports +class nsPIEventQueueChain : public nsIEventQueue { public: NS_DECLARE_STATIC_IID_ACCESSOR(NS_IEVENTQUEUECHAIN_IID)