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: #include "nsNSSShutDown.h" michael@0: #include "nsCOMPtr.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: #ifdef PR_LOGGING michael@0: extern PRLogModuleInfo* gPIPNSSLog; michael@0: #endif michael@0: michael@0: struct ObjectHashEntry : PLDHashEntryHdr { michael@0: nsNSSShutDownObject *obj; michael@0: }; michael@0: michael@0: static bool michael@0: ObjectSetMatchEntry(PLDHashTable *table, const PLDHashEntryHdr *hdr, michael@0: const void *key) michael@0: { michael@0: const ObjectHashEntry *entry = static_cast(hdr); michael@0: return entry->obj == static_cast(key); michael@0: } michael@0: michael@0: static bool michael@0: ObjectSetInitEntry(PLDHashTable *table, PLDHashEntryHdr *hdr, michael@0: const void *key) michael@0: { michael@0: ObjectHashEntry *entry = static_cast(hdr); michael@0: entry->obj = const_cast(static_cast(key)); michael@0: return true; michael@0: } michael@0: michael@0: static const PLDHashTableOps gSetOps = { michael@0: PL_DHashAllocTable, michael@0: PL_DHashFreeTable, michael@0: PL_DHashVoidPtrKeyStub, michael@0: ObjectSetMatchEntry, michael@0: PL_DHashMoveEntryStub, michael@0: PL_DHashClearEntryStub, michael@0: PL_DHashFinalizeStub, michael@0: ObjectSetInitEntry michael@0: }; michael@0: michael@0: nsNSSShutDownList *nsNSSShutDownList::singleton = nullptr; michael@0: michael@0: nsNSSShutDownList::nsNSSShutDownList() michael@0: :mListLock("nsNSSShutDownList.mListLock") michael@0: { michael@0: mActiveSSLSockets = 0; michael@0: mPK11LogoutCancelObjects.ops = nullptr; michael@0: mObjects.ops = nullptr; michael@0: PL_DHashTableInit(&mObjects, &gSetOps, nullptr, michael@0: sizeof(ObjectHashEntry), 16); michael@0: PL_DHashTableInit(&mPK11LogoutCancelObjects, &gSetOps, nullptr, michael@0: sizeof(ObjectHashEntry), 16); michael@0: } michael@0: michael@0: nsNSSShutDownList::~nsNSSShutDownList() michael@0: { michael@0: if (mObjects.ops) { michael@0: PL_DHashTableFinish(&mObjects); michael@0: mObjects.ops = nullptr; michael@0: } michael@0: if (mPK11LogoutCancelObjects.ops) { michael@0: PL_DHashTableFinish(&mPK11LogoutCancelObjects); michael@0: mPK11LogoutCancelObjects.ops = nullptr; michael@0: } michael@0: PR_ASSERT(this == singleton); michael@0: singleton = nullptr; michael@0: } michael@0: michael@0: void nsNSSShutDownList::remember(nsNSSShutDownObject *o) michael@0: { michael@0: if (!singleton) michael@0: return; michael@0: michael@0: PR_ASSERT(o); michael@0: MutexAutoLock lock(singleton->mListLock); michael@0: PL_DHashTableOperate(&singleton->mObjects, o, PL_DHASH_ADD); michael@0: } michael@0: michael@0: void nsNSSShutDownList::forget(nsNSSShutDownObject *o) michael@0: { michael@0: if (!singleton) michael@0: return; michael@0: michael@0: PR_ASSERT(o); michael@0: MutexAutoLock lock(singleton->mListLock); michael@0: PL_DHashTableOperate(&singleton->mObjects, o, PL_DHASH_REMOVE); michael@0: } michael@0: michael@0: void nsNSSShutDownList::remember(nsOnPK11LogoutCancelObject *o) michael@0: { michael@0: if (!singleton) michael@0: return; michael@0: michael@0: PR_ASSERT(o); michael@0: MutexAutoLock lock(singleton->mListLock); michael@0: PL_DHashTableOperate(&singleton->mPK11LogoutCancelObjects, o, PL_DHASH_ADD); michael@0: } michael@0: michael@0: void nsNSSShutDownList::forget(nsOnPK11LogoutCancelObject *o) michael@0: { michael@0: if (!singleton) michael@0: return; michael@0: michael@0: PR_ASSERT(o); michael@0: MutexAutoLock lock(singleton->mListLock); michael@0: PL_DHashTableOperate(&singleton->mPK11LogoutCancelObjects, o, PL_DHASH_REMOVE); michael@0: } michael@0: michael@0: void nsNSSShutDownList::trackSSLSocketCreate() michael@0: { michael@0: if (!singleton) michael@0: return; michael@0: michael@0: MutexAutoLock lock(singleton->mListLock); michael@0: ++singleton->mActiveSSLSockets; michael@0: } michael@0: michael@0: void nsNSSShutDownList::trackSSLSocketClose() michael@0: { michael@0: if (!singleton) michael@0: return; michael@0: michael@0: MutexAutoLock lock(singleton->mListLock); michael@0: --singleton->mActiveSSLSockets; michael@0: } michael@0: michael@0: bool nsNSSShutDownList::areSSLSocketsActive() michael@0: { michael@0: if (!singleton) { michael@0: // I'd rather prefer to be pessimistic and return true. michael@0: // However, maybe we will get called at a time when the singleton michael@0: // has already been freed, and returning true would bring up an michael@0: // unnecessary warning. michael@0: return false; michael@0: } michael@0: michael@0: MutexAutoLock lock(singleton->mListLock); michael@0: return (singleton->mActiveSSLSockets > 0); michael@0: } michael@0: michael@0: nsresult nsNSSShutDownList::doPK11Logout() michael@0: { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("canceling all open SSL sockets to disallow future IO\n")); michael@0: // During our iteration we will set a bunch of PRBools to true. michael@0: // Nobody else ever modifies that bool, only we do. michael@0: // We only must ensure that our objects do not go away. michael@0: // This is guaranteed by holding the list lock. michael@0: michael@0: MutexAutoLock lock(singleton->mListLock); michael@0: PL_DHashTableEnumerate(&mPK11LogoutCancelObjects, doPK11LogoutHelper, 0); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: nsNSSShutDownList::doPK11LogoutHelper(PLDHashTable *table, michael@0: PLDHashEntryHdr *hdr, uint32_t number, void *arg) michael@0: { michael@0: ObjectHashEntry *entry = static_cast(hdr); michael@0: michael@0: nsOnPK11LogoutCancelObject *pklco = michael@0: reinterpret_cast(entry->obj); michael@0: michael@0: if (pklco) { michael@0: pklco->logout(); michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: bool nsNSSShutDownList::isUIActive() michael@0: { michael@0: bool canDisallow = mActivityState.ifPossibleDisallowUI(nsNSSActivityState::test_only); michael@0: bool bIsUIActive = !canDisallow; michael@0: return bIsUIActive; michael@0: } michael@0: michael@0: bool nsNSSShutDownList::ifPossibleDisallowUI() michael@0: { michael@0: bool isNowDisallowed = mActivityState.ifPossibleDisallowUI(nsNSSActivityState::do_it_for_real); michael@0: return isNowDisallowed; michael@0: } michael@0: michael@0: void nsNSSShutDownList::allowUI() michael@0: { michael@0: mActivityState.allowUI(); michael@0: } michael@0: michael@0: nsresult nsNSSShutDownList::evaporateAllNSSResources() michael@0: { michael@0: if (PR_SUCCESS != mActivityState.restrictActivityToCurrentThread()) { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("failed to restrict activity to current thread\n")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("now evaporating NSS resources\n")); michael@0: int removedCount; michael@0: do { michael@0: MutexAutoLock lock(mListLock); michael@0: removedCount = PL_DHashTableEnumerate(&mObjects, evaporateAllNSSResourcesHelper, 0); michael@0: } while (removedCount > 0); michael@0: michael@0: mActivityState.releaseCurrentThreadActivityRestriction(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: nsNSSShutDownList::evaporateAllNSSResourcesHelper(PLDHashTable *table, michael@0: PLDHashEntryHdr *hdr, uint32_t number, void *arg) michael@0: { michael@0: ObjectHashEntry *entry = static_cast(hdr); michael@0: { michael@0: MutexAutoUnlock unlock(singleton->mListLock); michael@0: entry->obj->shutdown(nsNSSShutDownObject::calledFromList); michael@0: } michael@0: // Never free more than one entry, because other threads might be calling michael@0: // us and remove themselves while we are iterating over the list, michael@0: // and the behaviour of changing the list while iterating is undefined. michael@0: return (PLDHashOperator)(PL_DHASH_STOP | PL_DHASH_REMOVE); michael@0: } michael@0: michael@0: nsNSSShutDownList *nsNSSShutDownList::construct() michael@0: { michael@0: if (singleton) { michael@0: // we should never ever be called twice michael@0: return nullptr; michael@0: } michael@0: michael@0: singleton = new nsNSSShutDownList(); michael@0: return singleton; michael@0: } michael@0: michael@0: nsNSSActivityState::nsNSSActivityState() michael@0: :mNSSActivityStateLock("nsNSSActivityState.mNSSActivityStateLock"), michael@0: mNSSActivityChanged(mNSSActivityStateLock, michael@0: "nsNSSActivityState.mNSSActivityStateLock"), michael@0: mNSSActivityCounter(0), michael@0: mBlockingUICounter(0), michael@0: mIsUIForbidden(false), michael@0: mNSSRestrictedThread(nullptr) michael@0: { michael@0: } michael@0: michael@0: nsNSSActivityState::~nsNSSActivityState() michael@0: { michael@0: } michael@0: michael@0: void nsNSSActivityState::enter() michael@0: { michael@0: MutexAutoLock lock(mNSSActivityStateLock); michael@0: michael@0: while (mNSSRestrictedThread && mNSSRestrictedThread != PR_GetCurrentThread()) { michael@0: mNSSActivityChanged.Wait(); michael@0: } michael@0: michael@0: ++mNSSActivityCounter; michael@0: } michael@0: michael@0: void nsNSSActivityState::leave() michael@0: { michael@0: MutexAutoLock lock(mNSSActivityStateLock); michael@0: michael@0: --mNSSActivityCounter; michael@0: michael@0: mNSSActivityChanged.NotifyAll(); michael@0: } michael@0: michael@0: void nsNSSActivityState::enterBlockingUIState() michael@0: { michael@0: MutexAutoLock lock(mNSSActivityStateLock); michael@0: michael@0: ++mBlockingUICounter; michael@0: } michael@0: michael@0: void nsNSSActivityState::leaveBlockingUIState() michael@0: { michael@0: MutexAutoLock lock(mNSSActivityStateLock); michael@0: michael@0: --mBlockingUICounter; michael@0: } michael@0: michael@0: bool nsNSSActivityState::isBlockingUIActive() michael@0: { michael@0: MutexAutoLock lock(mNSSActivityStateLock); michael@0: return (mBlockingUICounter > 0); michael@0: } michael@0: michael@0: bool nsNSSActivityState::isUIForbidden() michael@0: { michael@0: MutexAutoLock lock(mNSSActivityStateLock); michael@0: return mIsUIForbidden; michael@0: } michael@0: michael@0: bool nsNSSActivityState::ifPossibleDisallowUI(RealOrTesting rot) michael@0: { michael@0: bool retval = false; michael@0: MutexAutoLock lock(mNSSActivityStateLock); michael@0: michael@0: // Checking and disallowing the UI must be done atomically. michael@0: michael@0: if (!mBlockingUICounter) { michael@0: // No UI is currently shown, we are able to evaporate. michael@0: retval = true; michael@0: if (rot == do_it_for_real) { michael@0: // Remember to disallow UI. michael@0: mIsUIForbidden = true; michael@0: michael@0: // to clear the "forbidden" state, michael@0: // one must either call michael@0: // restrictActivityToCurrentThread() + releaseCurrentThreadActivityRestriction() michael@0: // or cancel the operation by calling michael@0: // unprepareCurrentThreadRestriction() michael@0: } michael@0: } michael@0: return retval; michael@0: } michael@0: michael@0: void nsNSSActivityState::allowUI() michael@0: { michael@0: MutexAutoLock lock(mNSSActivityStateLock); michael@0: michael@0: mIsUIForbidden = false; michael@0: } michael@0: michael@0: PRStatus nsNSSActivityState::restrictActivityToCurrentThread() michael@0: { michael@0: PRStatus retval = PR_FAILURE; michael@0: MutexAutoLock lock(mNSSActivityStateLock); michael@0: michael@0: if (!mBlockingUICounter) { michael@0: while (0 < mNSSActivityCounter && !mBlockingUICounter) { michael@0: mNSSActivityChanged.Wait(PR_TicksPerSecond()); michael@0: } michael@0: michael@0: if (mBlockingUICounter) { michael@0: // This should never happen. michael@0: // If we arrive here, our logic is broken. michael@0: PR_ASSERT(0); michael@0: } michael@0: else { michael@0: mNSSRestrictedThread = PR_GetCurrentThread(); michael@0: retval = PR_SUCCESS; michael@0: } michael@0: } michael@0: michael@0: return retval; michael@0: } michael@0: michael@0: void nsNSSActivityState::releaseCurrentThreadActivityRestriction() michael@0: { michael@0: MutexAutoLock lock(mNSSActivityStateLock); michael@0: michael@0: mNSSRestrictedThread = nullptr; michael@0: mIsUIForbidden = false; michael@0: michael@0: mNSSActivityChanged.NotifyAll(); michael@0: } michael@0: michael@0: nsNSSShutDownPreventionLock::nsNSSShutDownPreventionLock() michael@0: { michael@0: nsNSSActivityState *state = nsNSSShutDownList::getActivityState(); michael@0: if (!state) michael@0: return; michael@0: michael@0: state->enter(); michael@0: } michael@0: michael@0: nsNSSShutDownPreventionLock::~nsNSSShutDownPreventionLock() michael@0: { michael@0: nsNSSActivityState *state = nsNSSShutDownList::getActivityState(); michael@0: if (!state) michael@0: return; michael@0: michael@0: state->leave(); michael@0: } michael@0: michael@0: nsPSMUITracker::nsPSMUITracker() michael@0: { michael@0: nsNSSActivityState *state = nsNSSShutDownList::getActivityState(); michael@0: if (!state) michael@0: return; michael@0: michael@0: state->enterBlockingUIState(); michael@0: } michael@0: michael@0: nsPSMUITracker::~nsPSMUITracker() michael@0: { michael@0: nsNSSActivityState *state = nsNSSShutDownList::getActivityState(); michael@0: if (!state) michael@0: return; michael@0: michael@0: state->leaveBlockingUIState(); michael@0: } michael@0: michael@0: bool nsPSMUITracker::isUIForbidden() michael@0: { michael@0: nsNSSActivityState *state = nsNSSShutDownList::getActivityState(); michael@0: if (!state) michael@0: return false; michael@0: michael@0: return state->isUIForbidden(); michael@0: }