michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef _INC_NSSShutDown_H michael@0: #define _INC_NSSShutDown_H michael@0: michael@0: #include "nscore.h" michael@0: #include "nspr.h" michael@0: #include "pldhash.h" michael@0: #include "mozilla/CondVar.h" michael@0: #include "mozilla/Mutex.h" michael@0: michael@0: class nsNSSShutDownObject; michael@0: class nsOnPK11LogoutCancelObject; michael@0: michael@0: // Singleton, owner by nsNSSShutDownList michael@0: class nsNSSActivityState michael@0: { michael@0: public: michael@0: nsNSSActivityState(); michael@0: ~nsNSSActivityState(); michael@0: michael@0: // Call enter/leave when PSM enters a scope during which michael@0: // shutting down NSS is prohibited. michael@0: void enter(); michael@0: void leave(); michael@0: michael@0: // Call enter/leave when PSM is about to show a UI michael@0: // while still holding resources. michael@0: void enterBlockingUIState(); michael@0: void leaveBlockingUIState(); michael@0: michael@0: // Is the activity aware of any blocking PSM UI currently shown? michael@0: bool isBlockingUIActive(); michael@0: michael@0: // Is it forbidden to bring up an UI while holding resources? michael@0: bool isUIForbidden(); michael@0: michael@0: // Check whether setting the current thread restriction is possible. michael@0: // If it is possible, and the "do_it_for_real" flag is used, michael@0: // the state tracking will have ensured that we will stay in this state. michael@0: // As of writing, this includes forbidding PSM UI. michael@0: enum RealOrTesting {test_only, do_it_for_real}; michael@0: bool ifPossibleDisallowUI(RealOrTesting rot); michael@0: michael@0: // Notify the state tracking that going to the restricted state is michael@0: // no longer planned. michael@0: // As of writing, this includes clearing the "PSM UI forbidden" flag. michael@0: void allowUI(); michael@0: michael@0: // If currently no UI is shown, wait for all activity to stop, michael@0: // and block any other thread on entering relevant PSM code. michael@0: PRStatus restrictActivityToCurrentThread(); michael@0: michael@0: // Go back to normal state. michael@0: void releaseCurrentThreadActivityRestriction(); michael@0: michael@0: private: michael@0: // The lock protecting all our member variables. michael@0: mozilla::Mutex mNSSActivityStateLock; michael@0: michael@0: // The activity variable, bound to our lock, michael@0: // used either to signal the activity counter reaches zero, michael@0: // or a thread restriction has been released. michael@0: mozilla::CondVar mNSSActivityChanged; michael@0: michael@0: // The number of active scopes holding resources. michael@0: int mNSSActivityCounter; michael@0: michael@0: // The number of scopes holding resources while blocked michael@0: // showing an UI. michael@0: int mBlockingUICounter; michael@0: michael@0: // Whether bringing up UI is currently forbidden michael@0: bool mIsUIForbidden; michael@0: michael@0: // nullptr means "no restriction" michael@0: // if not null, activity is only allowed on that thread michael@0: PRThread* mNSSRestrictedThread; michael@0: }; michael@0: michael@0: // Helper class that automatically enters/leaves the global activity state michael@0: class nsNSSShutDownPreventionLock michael@0: { michael@0: public: michael@0: nsNSSShutDownPreventionLock(); michael@0: ~nsNSSShutDownPreventionLock(); michael@0: }; michael@0: michael@0: // Helper class that automatically enters/leaves the global UI tracking michael@0: class nsPSMUITracker michael@0: { michael@0: public: michael@0: nsPSMUITracker(); michael@0: ~nsPSMUITracker(); michael@0: michael@0: bool isUIForbidden(); michael@0: }; michael@0: michael@0: // Singleton, used by nsNSSComponent to track the list of PSM objects, michael@0: // which hold NSS resources and support the "early cleanup mechanism". michael@0: class nsNSSShutDownList michael@0: { michael@0: public: michael@0: ~nsNSSShutDownList(); michael@0: michael@0: static nsNSSShutDownList *construct(); michael@0: michael@0: // track instances that support early cleanup michael@0: static void remember(nsNSSShutDownObject *o); michael@0: static void forget(nsNSSShutDownObject *o); michael@0: michael@0: // track instances that would like notification when michael@0: // a PK11 logout operation is performed. michael@0: static void remember(nsOnPK11LogoutCancelObject *o); michael@0: static void forget(nsOnPK11LogoutCancelObject *o); michael@0: michael@0: // track the creation and destruction of SSL sockets michael@0: // performed by clients using PSM services michael@0: static void trackSSLSocketCreate(); michael@0: static void trackSSLSocketClose(); michael@0: static bool areSSLSocketsActive(); michael@0: michael@0: // Are we able to do the early cleanup? michael@0: // Returns failure if at the current time "early cleanup" is not possible. michael@0: bool isUIActive(); michael@0: michael@0: // If possible to do "early cleanup" at the current time, remember that we want to michael@0: // do it, and disallow actions that would change the possibility. michael@0: bool ifPossibleDisallowUI(); michael@0: michael@0: // Notify that it is no longer planned to do the "early cleanup". michael@0: void allowUI(); michael@0: michael@0: // Do the "early cleanup", if possible. michael@0: nsresult evaporateAllNSSResources(); michael@0: michael@0: // PSM has been asked to log out of a token. michael@0: // Notify all registered instances that want to react to that event. michael@0: nsresult doPK11Logout(); michael@0: michael@0: static nsNSSActivityState *getActivityState() michael@0: { michael@0: return singleton ? &singleton->mActivityState : nullptr; michael@0: } michael@0: michael@0: private: michael@0: nsNSSShutDownList(); michael@0: static PLDHashOperator michael@0: evaporateAllNSSResourcesHelper(PLDHashTable *table, PLDHashEntryHdr *hdr, michael@0: uint32_t number, void *arg); michael@0: michael@0: static PLDHashOperator michael@0: doPK11LogoutHelper(PLDHashTable *table, PLDHashEntryHdr *hdr, michael@0: uint32_t number, void *arg); michael@0: protected: michael@0: mozilla::Mutex mListLock; michael@0: static nsNSSShutDownList *singleton; michael@0: PLDHashTable mObjects; michael@0: uint32_t mActiveSSLSockets; michael@0: PLDHashTable mPK11LogoutCancelObjects; michael@0: nsNSSActivityState mActivityState; michael@0: }; michael@0: michael@0: /* michael@0: A class deriving from nsNSSShutDownObject will have its instances michael@0: automatically tracked in a list. However, it must follow some rules michael@0: to assure correct behaviour. michael@0: michael@0: The tricky part is that it is not possible to call virtual michael@0: functions from a destructor. michael@0: michael@0: The deriving class must override virtualDestroyNSSReference(). michael@0: Within this function, it should clean up all resources held to NSS. michael@0: The function will be called by the global list, if it is time to michael@0: shut down NSS before all references have been freed. michael@0: michael@0: The same code that goes into virtualDestroyNSSReference must michael@0: also be called from the destructor of the deriving class, michael@0: which is the standard cleanup (not called from the tracking list). michael@0: michael@0: Because of that duplication, it is suggested to implement a michael@0: function destructorSafeDestroyNSSReference() in the deriving michael@0: class, and make the implementation of virtualDestroyNSSReference() michael@0: call destructorSafeDestroyNSSReference(). michael@0: michael@0: The destructor of the derived class must prevent NSS shutdown on michael@0: another thread by acquiring an nsNSSShutDownPreventionLock. It must michael@0: then check to see if NSS has already been shut down by calling michael@0: isAlreadyShutDown(). If NSS has not been shut down, the destructor michael@0: must then call destructorSafeDestroyNSSReference() and then michael@0: shutdown(calledFromObject). The second call will deregister with michael@0: the tracking list, to ensure no additional attempt to free the resources michael@0: will be made. michael@0: michael@0: destructorSafeDestroyNSSReference() does not need to acquire an michael@0: nsNSSShutDownPreventionLock or check isAlreadyShutDown() as long as it michael@0: is only called by the destructor that has already acquired the lock and michael@0: checked for shutdown or by the NSS shutdown code itself (which acquires michael@0: the same lock and checks if objects it cleans up have already cleaned michael@0: up themselves). michael@0: michael@0: destructorSafeDestroyNSSReference() MUST NOT cause any other michael@0: nsNSSShutDownObject to be deconstructed. Doing so can cause michael@0: unsupported concurrent operations on the hash table in the michael@0: nsNSSShutDownList. michael@0: michael@0: class derivedClass : public nsISomeInterface, michael@0: public nsNSSShutDownObject michael@0: { michael@0: virtual void virtualDestroyNSSReference() michael@0: { michael@0: destructorSafeDestroyNSSReference(); michael@0: } michael@0: michael@0: void destructorSafeDestroyNSSReference() michael@0: { michael@0: // clean up all NSS resources here michael@0: } michael@0: michael@0: virtual ~derivedClass() michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: if (isAlreadyShutDown()) { michael@0: return; michael@0: } michael@0: destructorSafeDestroyNSSReference(); michael@0: shutdown(calledFromObject); michael@0: } michael@0: michael@0: NS_IMETHODIMP doSomething() michael@0: { michael@0: if (isAlreadyShutDown()) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: // use the NSS resources and do something michael@0: } michael@0: }; michael@0: */ michael@0: michael@0: class nsNSSShutDownObject michael@0: { michael@0: public: michael@0: michael@0: enum CalledFromType {calledFromList, calledFromObject}; michael@0: michael@0: nsNSSShutDownObject() michael@0: { michael@0: mAlreadyShutDown = false; michael@0: nsNSSShutDownList::remember(this); michael@0: } michael@0: michael@0: virtual ~nsNSSShutDownObject() michael@0: { michael@0: // the derived class must call michael@0: // shutdown(calledFromObject); michael@0: // in its destructor michael@0: } michael@0: michael@0: void shutdown(CalledFromType calledFrom) michael@0: { michael@0: if (!mAlreadyShutDown) { michael@0: if (calledFromObject == calledFrom) { michael@0: nsNSSShutDownList::forget(this); michael@0: } michael@0: if (calledFromList == calledFrom) { michael@0: virtualDestroyNSSReference(); michael@0: } michael@0: mAlreadyShutDown = true; michael@0: } michael@0: } michael@0: michael@0: bool isAlreadyShutDown() { return mAlreadyShutDown; } michael@0: michael@0: protected: michael@0: virtual void virtualDestroyNSSReference() = 0; michael@0: private: michael@0: volatile bool mAlreadyShutDown; michael@0: }; michael@0: michael@0: class nsOnPK11LogoutCancelObject michael@0: { michael@0: public: michael@0: nsOnPK11LogoutCancelObject() michael@0: :mIsLoggedOut(false) michael@0: { michael@0: nsNSSShutDownList::remember(this); michael@0: } michael@0: michael@0: virtual ~nsOnPK11LogoutCancelObject() michael@0: { michael@0: nsNSSShutDownList::forget(this); michael@0: } michael@0: michael@0: void logout() michael@0: { michael@0: // We do not care for a race condition. michael@0: // Once the bool arrived at false, michael@0: // later calls to isPK11LoggedOut() will see it. michael@0: // This is a one-time change from 0 to 1. michael@0: michael@0: mIsLoggedOut = true; michael@0: } michael@0: michael@0: bool isPK11LoggedOut() michael@0: { michael@0: return mIsLoggedOut; michael@0: } michael@0: michael@0: private: michael@0: volatile bool mIsLoggedOut; michael@0: }; michael@0: michael@0: #endif