//------------------------------------------------------------------------- // Desc: Database locking class. // Tabs: 3 // // Copyright (c) 1998-2006 Novell, Inc. All Rights Reserved. // // This program is free software; you can redistribute it and/or // modify it under the terms of version 2 of the GNU General Public // License as published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, contact Novell, Inc. // // To contact Novell about this file by physical or electronic mail, // you may find current contact information at www.novell.com // // $Id: fsrvlock.cpp 12329 2006-01-20 17:49:30 -0700 (Fri, 20 Jan 2006) ahodgkinson $ //------------------------------------------------------------------------- #include "flaimsys.h" #define LOCK_HASH_ENTRIES 256 /**************************************************************************** Desc: ****************************************************************************/ ServerLockManager::~ServerLockManager() { // Signal all pending lock waiters. CheckLockTimeouts( TRUE); // Free everything in the avail lock list. This is where all // of the lock objects should be at this point. while (m_pAvailLockList) { ServerLockObject_p pLockObject = m_pAvailLockList; UnlinkLockObject( pLockObject, FALSE, NULL); pLockObject->Release( NULL); } // Free the hash table. f_free( &m_pHashTbl); return; } /**************************************************************************** Desc: Initializes the lock manager's hash table. ****************************************************************************/ RCODE ServerLockManager::SetupHashTbl() { return( flmAllocHashTbl( LOCK_HASH_ENTRIES, &m_pHashTbl)); } /**************************************************************************** Desc: Finds the lock object for the passed in item identifier. If one is not found, one will be created. ****************************************************************************/ ServerLockObject_p ServerLockManager::GetLockObject( F_ItemId_p pItemId) { F_MutexRef MutexRef( m_phMutex ); FLMUINT uiBucket; ServerLockObject_p pLockObject; ServerLockObject_p pTmpLockObject; // Get the hash bucket. uiBucket = pItemId->GetHashBucket( m_pHashTbl, LOCK_HASH_ENTRIES); // See if the desired file is already in the hash bucket. MutexRef.Lock(); pLockObject = (ServerLockObject_p)m_pHashTbl [uiBucket].pFirstInBucket; // See if any of the objects match. while (pLockObject) { if (pItemId->IsEqual( pLockObject->GetItemIdPtr())) goto Exit; pLockObject = pLockObject->GetNext(); } // If we didn't find a matching object, allocate an object and link it into // the hash bucket. Check to see if we have any in the avail list // first so that we don't have to allocate memory if we can avoid it. if ((pLockObject = m_pAvailLockList) != NULL) { UnlinkLockObject( pLockObject, FALSE, &MutexRef); } else { if ((pLockObject = f_new ServerLockObject) == NULL) goto Exit; } // Setup the new object and put it into the hash bucket. pLockObject->Setup( this, pItemId, uiBucket); pTmpLockObject = (ServerLockObject_p)m_pHashTbl [uiBucket].pFirstInBucket; pLockObject->SetPrev( NULL); pLockObject->SetNext( pTmpLockObject); if (pTmpLockObject) pTmpLockObject->SetPrev( pLockObject); m_pHashTbl [uiBucket].pFirstInBucket = pLockObject; Exit: MutexRef.Unlock(); return( pLockObject); } /**************************************************************************** Desc: Unlinks a lock object from whatever list it is in. ****************************************************************************/ void ServerLockManager::UnlinkLockObject( ServerLockObject * pLockObject, FLMBOOL bPutInAvailList, F_MutexRef * pMutexRef) { ServerLockObject * pTmpLockObject; FLMUINT uiBucket; if (pMutexRef) pMutexRef->Lock(); /* If hash bucket 0xFFFF, unlink from the avail list. Otherwise, unlink from the hash bucket it is in. */ if ((uiBucket = pLockObject->GetHashBucket()) == 0xFFFF) { if ((pTmpLockObject = pLockObject->GetPrev()) == NULL) m_pAvailLockList = pLockObject->GetNext(); else pTmpLockObject->SetNext( pLockObject->GetNext()); m_uiNumAvail--; } else { if ((pTmpLockObject = pLockObject->GetPrev()) == NULL) m_pHashTbl [uiBucket].pFirstInBucket = pLockObject->GetNext(); else pTmpLockObject->SetNext( pLockObject->GetNext()); } if ((pTmpLockObject = pLockObject->GetNext()) != NULL) pTmpLockObject->SetPrev( pLockObject->GetPrev()); if (bPutInAvailList) { if (m_uiNumAvail >= 50) { flmAssert( getRefCount() == 1); pLockObject->Release( NULL); } else { pLockObject->Setup( this, NULL, 0xFFFF); if (m_pAvailLockList) m_pAvailLockList->SetPrev( pLockObject); pLockObject->SetPrev( NULL); pLockObject->SetNext( m_pAvailLockList); m_pAvailLockList = pLockObject; m_uiNumAvail++; } } if (pMutexRef) pMutexRef->Unlock(); } /**************************************************************************** Desc: Checks for any pending lock requests that have timed out. ****************************************************************************/ void ServerLockManager::CheckLockTimeouts( F_MutexRef * pMutexRef, FLMBOOL bTimeoutAll) { FLMUINT uiCurrTime; LOCK_WAITER_p pLockWaiter; pMutexRef->Lock(); uiCurrTime = (FLMUINT)FLM_GET_TIMER(); while ((m_pFirstLockWaiter) && ((bTimeoutAll) || ((m_pFirstLockWaiter->uiWaitTime) && (FLM_ELAPSED_TIME( uiCurrTime, m_pFirstLockWaiter->uiWaitStartTime) >= m_pFirstLockWaiter->uiWaitTime)))) { // Sanity check flmAssert( m_pFirstLockWaiter->pPrevGlobal == NULL); // Lock waiter has timed out. pLockWaiter = m_pFirstLockWaiter; // Remove from global list and lock object's list RemoveWaiter( pLockWaiter); pLockWaiter->pLockObject->RemoveWaiter( pLockWaiter); /* Tell the waiter that it got a lock error. */ *(pLockWaiter->pRc) = RC_SET( FERR_IO_FILE_LOCK_ERR); f_semSignal( pLockWaiter->hESem); } pMutexRef->Unlock(); } /**************************************************************************** Desc: Signal a lock waiter that has a matching thread id. ****************************************************************************/ void ServerLockManager::SignalLockWaiter( FLMUINT uiThreadId) { FLMUINT uiCurrTime; LOCK_WAITER_p pLockWaiter; LOCK_WAITER_p pNextWaiter; F_MutexRef MutexRef( m_phMutex); f_mutexLock( *m_phMutex); uiCurrTime = (FLMUINT)FLM_GET_TIMER(); for( pLockWaiter = m_pFirstLockWaiter; pLockWaiter; pLockWaiter = pNextWaiter) { pNextWaiter = pLockWaiter->pNextGlobal; if( pLockWaiter->uiThreadId == uiThreadId) { // Remove from global list and lock object's list RemoveWaiter( pLockWaiter); pLockWaiter->pLockObject->RemoveWaiter( pLockWaiter); /* Tell the waiter that it got a lock error. */ *(pLockWaiter->pRc) = RC_SET( FERR_IO_FILE_LOCK_ERR); f_semSignal( pLockWaiter->hESem); break; } } f_mutexUnlock( *m_phMutex); return; } /**************************************************************************** Desc: Inserts a waiter into the global list of waiters, sorted by its end wait time. NOTE: This routine assumes that the lock manager's semaphore is already locked. ****************************************************************************/ void ServerLockManager::InsertWaiter( LOCK_WAITER_p pLockWaiter) { LOCK_WAITER_p pPrevLockWaiter; // Determine where in the list this lock waiter should go. if ((pPrevLockWaiter = m_pFirstLockWaiter) != NULL) { FLMUINT uiCurrTime = FLM_GET_TIMER(); FLMUINT uiElapTime; FLMUINT uiTimeLeft; while (pPrevLockWaiter) { // Waiters with zero wait time go to end of list. // They never time out. if (!pPrevLockWaiter->uiWaitTime) { // Should go BEFORE the first zero waiter. pPrevLockWaiter = pPrevLockWaiter->pPrevGlobal; break; } else if (!pLockWaiter->uiWaitTime) { if (!pPrevLockWaiter->pNextGlobal) { break; } pPrevLockWaiter = pPrevLockWaiter->pNextGlobal; } else { // Determine how much time is left on the previous // lock waiter's timer. If it is less than the // new lock waiter's wait time, the new lock waiter // should be inserted AFTER it. Otherwise, the // new lock waiter should be inserted BEFORE it. uiElapTime = FLM_ELAPSED_TIME( uiCurrTime, pPrevLockWaiter->uiWaitStartTime); if (uiElapTime >= pPrevLockWaiter->uiWaitTime) { uiTimeLeft = 0; } else { uiTimeLeft = pPrevLockWaiter->uiWaitTime - uiElapTime; } // New lock waiter will time out before previous lock // waiter - insert it BEFORE the previous lock waiter. if (pLockWaiter->uiWaitTime < uiTimeLeft) { pPrevLockWaiter = pPrevLockWaiter->pPrevGlobal; break; } else { if (!pPrevLockWaiter->pNextGlobal) break; pPrevLockWaiter = pPrevLockWaiter->pNextGlobal; } } } } // Insert into list AFTER pPrevLockWaiter. if ((pLockWaiter->pPrevGlobal = pPrevLockWaiter) != NULL) { if ((pLockWaiter->pNextGlobal = pPrevLockWaiter->pNextGlobal) != NULL) { pLockWaiter->pNextGlobal->pPrevGlobal = pLockWaiter; } pPrevLockWaiter->pNextGlobal = pLockWaiter; } else { if( (pLockWaiter->pNextGlobal = m_pFirstLockWaiter) != NULL) { m_pFirstLockWaiter->pPrevGlobal = pLockWaiter; } m_pFirstLockWaiter = pLockWaiter; } } /**************************************************************************** Desc: See if this item ID is equal to another F_ItemId. ****************************************************************************/ FLMBOOL FFileItemId::IsEqual( F_ItemId_p pItemId) { FFileItemId_p pFFileItemId; RFileItemId_p pRFileItemId; char szName1[ F_FILENAME_SIZE]; char szName2[ F_FILENAME_SIZE]; FLMUINT uiItemType = pItemId->GetItemType(); switch (uiItemType) { case FFILE_ITEM: case FFILE_TRANS_ITEM: { if (m_uiItemType != uiItemType) return( FALSE); pFFileItemId = (FFileItemId_p)pItemId; /* First see if the FFILE pointers are the same. */ if (pFFileItemId->GetFilePtr() == this->GetFilePtr()) return( TRUE); /* Next see if the file names are the same. */ this->GetFileName( szName1); pFFileItemId->GetFileName( szName2); #if !defined( FLM_UNIX) if (f_stricmp( szName1, szName2) == 0) return( TRUE); #else if (f_strcmp( szName1, szName2) == 0) return( TRUE); #endif break; } case RFILE_ITEM: case RFILE_TRANS_ITEM: { if ((uiItemType == RFILE_ITEM && m_uiItemType != FFILE_ITEM) || (uiItemType == RFILE_TRANS_ITEM && m_uiItemType != FFILE_TRANS_ITEM)) { return( FALSE); } pRFileItemId = (RFileItemId_p)pItemId; /* See if the file names are the same. */ this->GetFileName( szName1); pRFileItemId->GetFileName( szName2); #if !defined( FLM_UNIX) if (f_stricmp( szName1, szName2) == 0) return( TRUE); #else if (f_strcmp( szName1, szName2) == 0) return( TRUE); #endif break; } default: { break; } } return( FALSE); } /**************************************************************************** Desc: Get file name for this file item. ****************************************************************************/ void FFileItemId::GetFileName( char * pszFileName) { char szTmpPath[ F_PATH_MAX_SIZE]; f_pathReduce( m_pFile->pszDbPath, szTmpPath, pszFileName); #if !defined( FLM_UNIX) while( *pszFileName) { *pszFileName = f_toupper( *pszFileName); pszFileName++; } #endif } /**************************************************************************** Desc: ****************************************************************************/ RFileItemId::RFileItemId( FLMBYTE * pszFileName, FLMBOOL bTrans) { char * pszTmp = &m_szFileName [0]; while( *pszFileName) { #if !defined( FLM_UNIX) *pszFileName = f_toupper( *pszFileName); #else *pszTmp++ = *pszFileName; #endif pszFileName++; } *pszTmp = 0; m_uiItemType = (FLMUINT)((bTrans) ? (FLMUINT)RFILE_TRANS_ITEM : (FLMUINT)RFILE_ITEM); } /**************************************************************************** Desc: See if this item ID is equal to another F_ItemId. ****************************************************************************/ FLMBOOL RFileItemId::IsEqual( F_ItemId * pItemId) { FFileItemId * pFFileItemId; RFileItemId * pRFileItemId; char szName1 [F_FILENAME_SIZE]; char szName2 [F_FILENAME_SIZE]; FLMUINT uiItemType = pItemId->GetItemType(); switch (uiItemType) { case FFILE_ITEM: case FFILE_TRANS_ITEM: if ((uiItemType == FFILE_ITEM && m_uiItemType != RFILE_ITEM) || (uiItemType == FFILE_TRANS_ITEM && m_uiItemType != RFILE_TRANS_ITEM)) { return( FALSE); } pFFileItemId = (FFileItemId_p)pItemId; /* See if the file names are the same. */ this->GetFileName( szName1); pFFileItemId->GetFileName( szName2); #if !defined( FLM_UNIX) if (f_stricmp( szName1, szName2) == 0) return( TRUE); #else if (f_strcmp( szName1, szName2) == 0) return( TRUE); #endif break; case RFILE_ITEM: case RFILE_TRANS_ITEM: if (m_uiItemType != uiItemType) return( FALSE); pRFileItemId = (RFileItemId_p)pItemId; /* See if the file names are the same. */ this->GetFileName( szName1); pRFileItemId->GetFileName( szName2); #if !defined( FLM_UNIX) if (f_stricmp( szName1, szName2) == 0) return( TRUE); #else if (f_strcmp( szName1, szName2) == 0) return( TRUE); #endif break; default: break; } return( FALSE); } /**************************************************************************** Desc: ****************************************************************************/ ServerLockObject::ServerLockObject() { m_pServerLockMgr = NULL; m_pItemId = NULL; m_uiLockThreadId = 0; m_uiLockTime = 0; m_uiLockCnt = 0; m_pFirstLockWaiter = m_pLastLockWaiter = NULL; m_uiNumWaiters = 0; m_pNext = m_pPrev = NULL; m_uiSharedLockCnt = 0; m_bExclLock = FALSE; m_uiBucket = 0xFFFF; m_bStartTimeSet = FALSE; } FLMUINT ServerLockObject::Release( F_MutexRef * pMutexRef) { FLMUINT uiRefCnt = --m_ui32RefCnt; if( !uiRefCnt) { delete this; goto Exit; } // When it is no longer pointed to from anything but the server lock // manager, put it into the avail list. if (uiRefCnt == 1) { LOCK_WAITER * pLockWaiter; F_MutexRef TmpMutexRef( m_pServerLockMgr->GetSemPtr()); if( !pMutexRef) { pMutexRef = &TmpMutexRef; } // Signal all waiters that they cannot get the lock. pMutexRef->Lock(); while (m_pFirstLockWaiter) { pLockWaiter = m_pFirstLockWaiter; // Remove from global list and lock object's list RemoveWaiter( pLockWaiter); m_pServerLockMgr->RemoveWaiter( pLockWaiter); // Tell the waiter that it got a lock error and signal // the thread to wake it up. *(pLockWaiter->pRc) = RC_SET( FERR_IO_FILE_LOCK_ERR); f_semSignal( pLockWaiter->hESem); } m_pServerLockMgr->UnlinkLockObject( this, TRUE, NULL); pMutexRef->Unlock(); } Exit: return uiRefCnt; } /**************************************************************************** Desc: Initialize some data for the lock object. ****************************************************************************/ void ServerLockObject::Setup( ServerLockManager_p pServerLockMgr, F_ItemId_p pItemId, FLMUINT uiBucket) { m_pServerLockMgr = pServerLockMgr; if (m_pItemId) { m_pItemId->Release(); m_pItemId = NULL; } if ((m_pItemId = pItemId) != NULL) { m_pItemId->AddRef(); } m_uiBucket = uiBucket; } /**************************************************************************** Desc: Removes a waiter from the list of waiters on this object. NOTE: This routine assumes that the lock manager's semaphore is already locked. ****************************************************************************/ void ServerLockObject::RemoveWaiter( LOCK_WAITER_p pLockWaiter) { if (pLockWaiter->pNext) pLockWaiter->pNext->pPrev = pLockWaiter->pPrev; else m_pLastLockWaiter = pLockWaiter->pPrev; if (pLockWaiter->pPrev) pLockWaiter->pPrev->pNext = pLockWaiter->pNext; else m_pFirstLockWaiter = pLockWaiter->pNext; flmAssert( m_uiNumWaiters > 0); m_uiNumWaiters--; } /**************************************************************************** Desc: Lock this object. If object is locked, wait the specified number of seconds. ****************************************************************************/ RCODE ServerLockObject::Lock( FLMBOOL bLogEvent, FDB * pDb, // used for event callbacks. FLMBOOL bSendSuspendEvent, // Send suspend event, as opposed to // waiting event, when waiting. FLMBOOL bExclReq, // Exclusive or shared lock? FLMUINT uiMaxWaitSecs, // Maximum wait time in seconds. FLMINT iPriority, // Lock priority DB_STATS * pDbStats // Place to collect stats. ) { RCODE rc = FERR_OK; RCODE TempRc; LOCK_WAITER LockWait; FLMBOOL bSemLocked = FALSE; F_MutexRef MutexRef( m_pServerLockMgr->GetSemPtr()); MutexRef.Lock(); bSemLocked = TRUE; if ((m_pFirstLockWaiter) || (m_bExclLock) || ((bExclReq) && (m_uiSharedLockCnt))) { // Object is locked, wait to get lock. if (!uiMaxWaitSecs) { rc = RC_SET( FERR_IO_FILE_LOCK_ERR); goto Exit; } // Set up to wait for the lock. f_memset( &LockWait, 0, sizeof( LockWait)); LockWait.pLockObject = this; LockWait.hESem = F_SEM_NULL; if (RC_BAD( f_semCreate( &LockWait.hESem))) { rc = RC_SET( FERR_IO_FILE_LOCK_ERR); goto Exit; } // Link into list of waiters on this object. if ((LockWait.pPrev = m_pLastLockWaiter) != NULL) { LockWait.pPrev->pNext = &LockWait; } else { m_pFirstLockWaiter = &LockWait; } m_pLastLockWaiter = &LockWait; m_uiNumWaiters++; LockWait.uiThreadId = f_threadId(); LockWait.pRc = &rc; // Note: rc better be changed to success or lock error // by the process that signals us. rc = RC_SET( FERR_ACCESS_DENIED); LockWait.bExclReq = bExclReq; LockWait.iPriority = iPriority; LockWait.uiWaitStartTime = (FLMUINT)FLM_GET_TIMER(); if (bExclReq && pDbStats) { f_timeGetTimeStamp( &LockWait.StartTime); LockWait.pDbStats = pDbStats; } if (uiMaxWaitSecs == FLM_NO_TIMEOUT) { LockWait.uiWaitTime = 0; } else { FLM_SECS_TO_TIMER_UNITS( uiMaxWaitSecs, LockWait.uiWaitTime); } // Link to list of global waiters - ordered by end time. m_pServerLockMgr->InsertWaiter( &LockWait); MutexRef.Unlock(); bSemLocked = FALSE; // Do the event callback, if any registered. if (bLogEvent && gv_FlmSysData.EventHdrs [F_EVENT_LOCKS].pEventCBList) { flmDoEventCallback( F_EVENT_LOCKS, (FEventType)((bSendSuspendEvent) ? F_EVENT_LOCK_SUSPENDED : F_EVENT_LOCK_WAITING), (void *)pDb, (void *)LockWait.uiThreadId); } // Now just wait to be signaled. if (RC_BAD( TempRc = f_semWait( LockWait.hESem, F_SEM_WAITFOREVER))) { #ifdef FLM_NLM EnterDebugger(); #else flmAssert( 0); #endif rc = TempRc; } else { // Process that signaled us better set the rc to something // besides FERR_ACCESS_DENIED. if (rc == FERR_ACCESS_DENIED) { #ifdef FLM_NLM EnterDebugger(); #else flmAssert( 0); #endif } } // Do the event callback, if any registered. if (bLogEvent && gv_FlmSysData.EventHdrs [F_EVENT_LOCKS].pEventCBList) { if (RC_BAD( rc)) { flmDoEventCallback( F_EVENT_LOCKS, F_EVENT_LOCK_TIMEOUT, (void *)pDb, (void *)LockWait.uiThreadId); } else { flmDoEventCallback( F_EVENT_LOCKS, (FEventType)((bSendSuspendEvent) ? F_EVENT_LOCK_RESUMED : F_EVENT_LOCK_GRANTED), (void *)pDb, (void *)LockWait.uiThreadId); } } /* Free the semaphore */ f_semDestroy( &LockWait.hESem); } else { // Object is NOT locked in a conflicting mode. Grant the // lock immediately. m_uiLockThreadId = f_threadId(); m_bExclLock = bExclReq; if (!bExclReq) { m_uiSharedLockCnt++; } else { m_uiLockTime = (FLMUINT)FLM_GET_TIMER(); flmAssert( m_uiSharedLockCnt == 0); // Take care of statistics gathering. if (pDbStats) { // If m_bStartTimeSet is TRUE, we started the // clock the last time nobody had the exclusive // lock, so we need to sum up idle time now. if (m_bStartTimeSet) { flmAddElapTime( &m_StartTime, &pDbStats->NoLocks.ui64ElapMilli); pDbStats->NoLocks.ui64Count++; } // Restart the clock for this locker. f_timeGetTimeStamp( &m_StartTime); m_bStartTimeSet = TRUE; } else { m_bStartTimeSet = FALSE; } } // Do the event callback, if any registered. if (bLogEvent && !bSendSuspendEvent && gv_FlmSysData.EventHdrs [F_EVENT_LOCKS].pEventCBList) { MutexRef.Unlock(); bSemLocked = FALSE; flmDoEventCallback( F_EVENT_LOCKS, F_EVENT_LOCK_GRANTED, (void *)pDb, (void *)m_uiLockThreadId); } } Exit: if (RC_OK( rc)) { m_uiLockCnt++; } if (bSemLocked) { MutexRef.Unlock(); } return( rc); } /**************************************************************************** Desc: Unlock this object. If there is a pending lock request, give the lock to the next waiter. ****************************************************************************/ RCODE ServerLockObject::Unlock( FLMBOOL bLogEvent, FDB_p pDb, FLMBOOL bRelease, DB_STATS * pDbStats ) { RCODE rc = FERR_OK; F_SEM hESem; LOCK_WAITER_p pLockWaiter; F_MutexRef MutexRef( m_pServerLockMgr->GetSemPtr()); MutexRef.Lock(); if (m_bExclLock) { flmAssert( m_uiSharedLockCnt == 0); m_bExclLock = FALSE; // Record how long the lock was held, if we were tracking // it. if (pDbStats && m_bStartTimeSet) { flmAddElapTime( &m_StartTime, &pDbStats->HeldLock.ui64ElapMilli); pDbStats->HeldLock.ui64Count++; } m_bStartTimeSet = FALSE; } else { flmAssert( m_uiSharedLockCnt > 0); m_uiSharedLockCnt--; } // Do the event callback, if any registered. // NOTE: flmDoEventCallback locks the event semaphore. // Since we are inside the lock manager's semaphore lock, // the callback should not do ANYTHING that would cause // us to end up in here again! if (bLogEvent && gv_FlmSysData.EventHdrs [F_EVENT_LOCKS].pEventCBList) { flmDoEventCallback( F_EVENT_LOCKS, F_EVENT_LOCK_RELEASED, (void *)pDb, (void *)m_uiLockThreadId); } m_uiLockThreadId = 0; /* See if we need to signal the next set of waiters. */ if (m_pFirstLockWaiter && !m_uiSharedLockCnt) { m_bExclLock = m_pFirstLockWaiter->bExclReq; while (m_pFirstLockWaiter) { if (!m_bExclLock) { m_uiSharedLockCnt++; } pLockWaiter = m_pFirstLockWaiter; hESem = pLockWaiter->hESem; // Unlink the waiter from the list of waiters on this lock // object and then from the global list of waiters. // IMPORTANT NOTE: Do NOT signal the semaphore until AFTER // doing this unlinking. This is because LOCK_WAITER // structures exist only on the stack of the thread // being signaled. If we tried to assign m_pFirstLockWaiter after // signaling the semaphore, the LOCK_WAITER structure could // disappear and m_pFirstLockWaiter would get garbage. RemoveWaiter( pLockWaiter); m_pServerLockMgr->RemoveWaiter( pLockWaiter); // Update statistics for the waiter. if (pLockWaiter->pDbStats) { flmAddElapTime( &pLockWaiter->StartTime, &pLockWaiter->pDbStats->WaitingForLock.ui64ElapMilli); pLockWaiter->pDbStats->WaitingForLock.ui64Count++; } // Grant the lock to this waiter and signal the thread // to wake it up. m_uiLockThreadId = pLockWaiter->uiThreadId; if (m_bExclLock) { m_uiLockTime = (FLMUINT)FLM_GET_TIMER(); // Restart the stats timer if (pDbStats) { m_bStartTimeSet = TRUE; f_timeGetTimeStamp( &m_StartTime); } } *(pLockWaiter->pRc) = FERR_OK; f_semSignal( hESem); // If the next waiter is not a shared lock request or // the lock that was granted was exclusive, we stop // here. if (m_bExclLock || (m_pFirstLockWaiter && m_pFirstLockWaiter->bExclReq)) { break; } } } else if (bRelease && !m_pFirstLockWaiter && // No one is wating for the object !m_uiSharedLockCnt) // No one has the object locked { // Release the object. If the reference count drops to 1, // the object will be put in the avail list. The caller // should have performed an AddRef() on the object at // some point prior to calling this method. Once this routine // returns the caller should not attempt further access of the object. Release( &MutexRef); bRelease = FALSE; } // Start timer, if not already running. If the timer is not set at // this point, it will be because nobody has been granted the exclusive // lock. If someone was granted the exclusive lock, the timer would // have been started above. We start it here so we can track idle // time. if (pDbStats && !m_bStartTimeSet && !bRelease) { flmAssert( !m_bExclLock); m_bStartTimeSet = TRUE; f_timeGetTimeStamp( &m_StartTime); } // If we get to this point and bRelease is still TRUE, someone was // waiting to acquire the lock or there is still a shared lock // count. if( bRelease) { // All lock waiters should have done an AddRef on the lock object. // At this point we should still have our reference to the object, // the lock manager's reference, and a reference from at least one // waiter (that may have been granted above). flmAssert( m_ui32RefCnt >= 3); m_ui32RefCnt--; } MutexRef.Unlock(); return( rc); } /**************************************************************************** Desc: Returns information about the pending lock requests. ****************************************************************************/ void ServerLockObject::GetLockInfo( FLMINT iPriority, FLOCK_INFO * pLockInfo ) { LOCK_WAITER_p pLockWaiter; F_MutexRef MutexRef( m_pServerLockMgr->GetSemPtr()); f_memset( pLockInfo, 0, sizeof( FLOCK_INFO)); MutexRef.Lock(); // Get the type of lock, if any. if (m_bExclLock) { pLockInfo->eCurrLockType = FLM_LOCK_EXCLUSIVE; pLockInfo->uiThreadId = m_uiLockThreadId; } else if (m_uiSharedLockCnt) { pLockInfo->eCurrLockType = FLM_LOCK_SHARED; pLockInfo->uiThreadId = 0; } else { pLockInfo->eCurrLockType = FLM_LOCK_NONE; pLockInfo->uiThreadId = 0; } // Get information on pending lock requests. pLockWaiter = m_pFirstLockWaiter; for ( ; pLockWaiter; pLockWaiter = pLockWaiter->pNext ) { // Count the number of exclusive and shared waiters. if (pLockWaiter->bExclReq) { pLockInfo->uiNumExclQueued++; } else { pLockInfo->uiNumSharedQueued++; } // Count the number of waiters at or above input priority. if (pLockWaiter->iPriority >= iPriority) { pLockInfo->uiPriorityCount++; } } MutexRef.Unlock(); } /**************************************************************************** Desc: Return the lock waiters for this object. ****************************************************************************/ RCODE ServerLockObject::GetLockInfo( FLMBOOL bGetWaiters, void * pvLockUsers ) { RCODE rc = FERR_OK; F_MutexRef MutexRef( m_pServerLockMgr->GetSemPtr()); LOCK_USER * pLockUser; LOCK_WAITER * pLockWaiter; FLMUINT uiCnt; FLMUINT uiElapTime; FLMUINT uiCurrTime; MutexRef.Lock(); uiCurrTime = (FLMUINT)FLM_GET_TIMER(); if (!bGetWaiters) { pLockUser = (LOCK_USER *)pvLockUsers; pLockUser->uiThreadId = m_uiLockThreadId; uiElapTime = FLM_ELAPSED_TIME( uiCurrTime, m_uiLockTime); FLM_TIMER_UNITS_TO_MILLI( uiElapTime, pLockUser->uiTime); } else { if (!m_uiNumWaiters && !m_uiLockThreadId) { *((LOCK_USER **)pvLockUsers) = NULL; goto Exit; } uiCnt = m_uiNumWaiters + 1; // Add one for lock holder. if( RC_BAD( rc = f_alloc( sizeof( LOCK_USER) * (uiCnt + 1), &pLockUser))) { goto Exit; } *((LOCK_USER **)pvLockUsers) = pLockUser; // Output the lock holder first. pLockUser->uiThreadId = m_uiLockThreadId; uiElapTime = FLM_ELAPSED_TIME( uiCurrTime, m_uiLockTime); FLM_TIMER_UNITS_TO_MILLI( uiElapTime, pLockUser->uiTime); pLockUser++; uiCnt--; // Output the lock waiters. pLockWaiter = m_pFirstLockWaiter; while (pLockWaiter && uiCnt) { pLockUser->uiThreadId = pLockWaiter->uiThreadId; uiElapTime = FLM_ELAPSED_TIME( uiCurrTime, pLockWaiter->uiWaitStartTime); FLM_TIMER_UNITS_TO_MILLI( uiElapTime, pLockUser->uiTime); pLockWaiter = pLockWaiter->pNext; pLockUser++; uiCnt--; } flmAssert( pLockWaiter == NULL && uiCnt == 0); // zero out the last one. f_memset( pLockUser, 0, sizeof( LOCK_USER)); } Exit: MutexRef.Unlock(); return( rc); } /**************************************************************************** Desc: Return the lock waiters for this object. ****************************************************************************/ RCODE ServerLockObject::GetLockInfo( FlmLockInfo * pLockInfo) { RCODE rc = FERR_OK; F_MutexRef MutexRef( m_pServerLockMgr->GetSemPtr()); LOCK_WAITER * pLockWaiter; FLMUINT uiCnt; FLMUINT uiElapTime; FLMUINT uiCurrTime; FLMUINT uiMilli; MutexRef.Lock(); uiCurrTime = (FLMUINT)FLM_GET_TIMER(); if (!m_uiNumWaiters && !m_uiLockThreadId) { pLockInfo->setLockCount( 0); goto Exit; } uiCnt = m_uiNumWaiters + 1; // Add one for lock holder. if( pLockInfo->setLockCount( uiCnt) == FALSE) { goto Exit; } // Output the lock holder first. uiElapTime = FLM_ELAPSED_TIME( uiCurrTime, m_uiLockTime); FLM_TIMER_UNITS_TO_MILLI( uiElapTime, uiMilli); if( pLockInfo->addLockInfo( 0, m_uiLockThreadId, uiMilli) == FALSE) { goto Exit; } uiCnt--; // Output the lock waiters. pLockWaiter = m_pFirstLockWaiter; while( pLockWaiter && uiCnt) { uiElapTime = FLM_ELAPSED_TIME( uiCurrTime, pLockWaiter->uiWaitStartTime); FLM_TIMER_UNITS_TO_MILLI( uiElapTime, uiMilli); if( pLockInfo->addLockInfo( (m_uiNumWaiters - uiCnt) + 1, pLockWaiter->uiThreadId, uiMilli) == FALSE) { goto Exit; } pLockWaiter = pLockWaiter->pNext; uiCnt--; } flmAssert( pLockWaiter == NULL && uiCnt == 0); Exit: MutexRef.Unlock(); return( rc); } /**************************************************************************** Desc: Returns TRUE if there are lock waiters with a priority > iPriority ****************************************************************************/ FLMBOOL ServerLockObject::haveHigherPriorityWaiter( FLMINT iPriority) { LOCK_WAITER_p pLockWaiter; F_MutexRef MutexRef( m_pServerLockMgr->GetSemPtr()); FLMBOOL bWaiters = FALSE; MutexRef.Lock(); pLockWaiter = m_pFirstLockWaiter; for ( ; pLockWaiter; pLockWaiter = pLockWaiter->pNext ) { // If we find a waiter with a priority > the specified // priority, we're done. if (pLockWaiter->iPriority > iPriority) { bWaiters = TRUE; break; } } MutexRef.Unlock(); return( bWaiters); }