/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: NPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Netscape Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * ActiveState Tool Corp.. * Portions created by the Initial Developer are Copyright (C) 2001 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Contributor(s): Mark Hammond * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the NPL, indicate your * decision by deleting the provisions above and replace them with the notice * 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 NPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsISupports.h" #include "nsExceptionService.h" #include "nsIServiceManager.h" #include "nsCOMPtr.h" #include "prthread.h" #include "prlock.h" static const PRUintn BAD_TLS_INDEX = (PRUintn) -1; #define CHECK_SERVICE_USE_OK() if (!lock) return NS_ERROR_NOT_INITIALIZED #define CHECK_MANAGER_USE_OK() if (!mService || !nsExceptionService::lock) return NS_ERROR_NOT_INITIALIZED // A key for our registered module providers hashtable class nsProviderKey : public nsHashKey { protected: PRUint32 mKey; public: nsProviderKey(PRUint32 key) : mKey(key) {} PRUint32 HashCode(void) const { return mKey; } PRBool Equals(const nsHashKey *aKey) const { return mKey == ((const nsProviderKey *) aKey)->mKey; } nsHashKey *Clone() const { return new nsProviderKey(mKey); } PRUint32 GetValue() { return mKey; } }; /** Exception Manager definition **/ class nsExceptionManager : public nsIExceptionManager { public: NS_DECL_ISUPPORTS NS_DECL_NSIEXCEPTIONMANAGER nsExceptionManager(nsExceptionService *svc); virtual ~nsExceptionManager(); /* additional members */ nsCOMPtr mCurrentException; nsExceptionManager *mNextThread; // not ref-counted. nsExceptionService *mService; // not ref-counted #ifdef NS_DEBUG static PRInt32 totalInstances; #endif }; #ifdef NS_DEBUG PRInt32 nsExceptionManager::totalInstances = 0; #endif // Note this object is single threaded - the service itself ensures // one per thread. // An exception if the destructor, which may be called on // the thread shutting down xpcom NS_IMPL_ISUPPORTS1(nsExceptionManager, nsIExceptionManager) nsExceptionManager::nsExceptionManager(nsExceptionService *svc) : mNextThread(nsnull), mService(svc) { /* member initializers and constructor code */ NS_INIT_ISUPPORTS(); #ifdef NS_DEBUG PR_AtomicIncrement(&totalInstances); #endif } nsExceptionManager::~nsExceptionManager() { /* destructor code */ #ifdef NS_DEBUG PR_AtomicDecrement(&totalInstances); #endif // NS_DEBUG } /* void setCurrentException (in nsIException error); */ NS_IMETHODIMP nsExceptionManager::SetCurrentException(nsIException *error) { CHECK_MANAGER_USE_OK(); mCurrentException = error; return NS_OK; } /* nsIException getCurrentException (); */ NS_IMETHODIMP nsExceptionManager::GetCurrentException(nsIException **_retval) { CHECK_MANAGER_USE_OK(); *_retval = mCurrentException; NS_IF_ADDREF(*_retval); return NS_OK; } /* nsIException getExceptionFromProvider( in nsresult rc, in nsIException defaultException); */ NS_IMETHODIMP nsExceptionManager::GetExceptionFromProvider(nsresult rc, nsIException * defaultException, nsIException **_retval) { CHECK_MANAGER_USE_OK(); // Just delegate back to the service with the provider map. return mService->GetExceptionFromProvider(rc, defaultException, _retval); } /* The Exception Service */ PRUintn nsExceptionService::tlsIndex = BAD_TLS_INDEX; PRLock *nsExceptionService::lock = PR_FALSE; nsExceptionManager *nsExceptionService::firstThread = nsnull; #ifdef NS_DEBUG PRInt32 nsExceptionService::totalInstances = 0; #endif NS_IMPL_THREADSAFE_ISUPPORTS2(nsExceptionService, nsIExceptionService, nsIObserver) nsExceptionService::nsExceptionService() : mProviders(4, PR_TRUE) /* small, thread-safe hashtable */ { #ifdef NS_DEBUG if (PR_AtomicIncrement(&totalInstances)!=1) { NS_ERROR("The nsExceptionService is a singleton!"); } #endif NS_INIT_ISUPPORTS(); /* member initializers and constructor code */ if (tlsIndex == BAD_TLS_INDEX) { PRStatus status; status = PR_NewThreadPrivateIndex( &tlsIndex, ThreadDestruct ); NS_WARN_IF_FALSE(status==0, "ScriptErrorService could not allocate TLS storage."); } lock = PR_NewLock(); NS_WARN_IF_FALSE(lock, "Error allocating ExceptionService lock"); // observe XPCOM shutdown. nsCOMPtr observerService = do_GetService("@mozilla.org/observer-service;1"); NS_WARN_IF_FALSE(observerService, "Could not get observer service!"); if (observerService) observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_FALSE); } nsExceptionService::~nsExceptionService() { Shutdown(); /* destructor code */ #ifdef NS_DEBUG PR_AtomicDecrement(&totalInstances); #endif } /*static*/ void nsExceptionService::ThreadDestruct( void *data ) { if (!lock) { NS_WARNING("nsExceptionService ignoring thread destruction after shutdown"); return; } DropThread( (nsExceptionManager *)data ); } void nsExceptionService::Shutdown() { mProviders.Reset(); if (lock) { DropAllThreads(); PR_DestroyLock(lock); lock = nsnull; } } /* void setCurrentException (in nsIException error); */ NS_IMETHODIMP nsExceptionService::SetCurrentException(nsIException *error) { CHECK_SERVICE_USE_OK(); nsCOMPtr sm; nsresult nr = GetCurrentExceptionManager(getter_AddRefs(sm)); if (NS_FAILED(nr)) return nr; return sm->SetCurrentException(error); } /* nsIException getCurrentException (); */ NS_IMETHODIMP nsExceptionService::GetCurrentException(nsIException **_retval) { CHECK_SERVICE_USE_OK(); nsCOMPtr sm; nsresult nr = GetCurrentExceptionManager(getter_AddRefs(sm)); if (NS_FAILED(nr)) return nr; return sm->GetCurrentException(_retval); } /* nsIException getExceptionFromProvider( in nsresult rc, in nsIException defaultException); */ NS_IMETHODIMP nsExceptionService::GetExceptionFromProvider(nsresult rc, nsIException * defaultException, nsIException **_retval) { CHECK_SERVICE_USE_OK(); return DoGetExceptionFromProvider(rc, defaultException, _retval); } /* readonly attribute nsIExceptionManager currentExceptionManager; */ NS_IMETHODIMP nsExceptionService::GetCurrentExceptionManager(nsIExceptionManager * *aCurrentScriptManager) { CHECK_SERVICE_USE_OK(); nsExceptionManager *mgr = (nsExceptionManager *)PR_GetThreadPrivate(tlsIndex); if (mgr == nsnull) { // Stick the new exception object in with no reference count. mgr = new nsExceptionManager(this); if (mgr == nsnull) return NS_ERROR_OUT_OF_MEMORY; PR_SetThreadPrivate(tlsIndex, mgr); // The reference count is held in the thread-list AddThread(mgr); } *aCurrentScriptManager = mgr; NS_ADDREF(*aCurrentScriptManager); return NS_OK; } /* void registerErrorProvider (in nsIExceptionProvider provider, in PRUint32 moduleCode); */ NS_IMETHODIMP nsExceptionService::RegisterExceptionProvider(nsIExceptionProvider *provider, PRUint32 errorModule) { CHECK_SERVICE_USE_OK(); nsProviderKey key(errorModule); if (mProviders.Put(&key, provider)) { NS_WARNING("Registration of exception provider overwrote another provider with the same module code!"); } return NS_OK; } /* void unregisterErrorProvider (in nsIExceptionProvider provider, in PRUint32 errorModule); */ NS_IMETHODIMP nsExceptionService::UnregisterExceptionProvider(nsIExceptionProvider *provider, PRUint32 errorModule) { CHECK_SERVICE_USE_OK(); nsProviderKey key(errorModule); if (!mProviders.Remove(&key)) { NS_WARNING("Attempt to unregister an unregistered exception provider!"); return NS_ERROR_UNEXPECTED; } return NS_OK; } // nsIObserver NS_IMETHODIMP nsExceptionService::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *someData) { Shutdown(); return NS_OK; } nsresult nsExceptionService::DoGetExceptionFromProvider(nsresult errCode, nsIException * defaultException, nsIException **_exc) { nsProviderKey key(NS_ERROR_GET_MODULE(errCode)); nsCOMPtr provider = dont_AddRef((nsIExceptionProvider *)mProviders.Get(&key)); // No provider so we'll return the default exception if (!provider) { *_exc = defaultException; NS_IF_ADDREF(*_exc); return NS_OK; } return provider->GetException(errCode, defaultException, _exc); } // thread management /*static*/ void nsExceptionService::AddThread(nsExceptionManager *thread) { PR_Lock(lock); thread->mNextThread = firstThread; firstThread = thread; NS_ADDREF(thread); PR_Unlock(lock); } /*static*/ void nsExceptionService::DoDropThread(nsExceptionManager *thread) { nsExceptionManager **emp = &firstThread; while (*emp != thread) { NS_ABORT_IF_FALSE(*emp, "Could not find the thread to drop!"); emp = &(*emp)->mNextThread; } *emp = thread->mNextThread; NS_RELEASE(thread); } /*static*/ void nsExceptionService::DropThread(nsExceptionManager *thread) { PR_Lock(lock); DoDropThread(thread); PR_Unlock(lock); } /*static*/ void nsExceptionService::DropAllThreads() { PR_Lock(lock); while (firstThread) DoDropThread(firstThread); PR_Unlock(lock); }