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