/*************************************************************************** Desc: *****************************************************************************/ RCODE F_Database::startMaintThread( void) { RCODE rc = NE_SFLM_OK; char szThreadName[ F_PATH_MAX_SIZE]; char szBaseName[ 32]; flmAssert( !m_pMaintThrd); flmAssert( m_hMaintSem == F_SEM_NULL); // Generate the thread name if( RC_BAD( rc = gv_SFlmSysData.pFileSystem->pathReduce( m_pszDbPath, szThreadName, szBaseName))) { goto Exit; } f_sprintf( (char *)szThreadName, "Maintenance (%s)", (char *)szBaseName); // Create the maintenance semaphore if( RC_BAD( rc = f_semCreate( &m_hMaintSem))) { goto Exit; } // Start the thread. if( RC_BAD( rc = gv_SFlmSysData.pThreadMgr->createThread( &m_pMaintThrd, F_Database::maintenanceThread, szThreadName, 0, 0, this, NULL, 32000))) { goto Exit; } // Signal the thread to check for any queued work f_semSignal( m_hMaintSem); Exit: if( RC_BAD( rc)) { if( m_hMaintSem != F_SEM_NULL) { f_semDestroy( &m_hMaintSem); } } return( rc); }
/**************************************************************************** Desc: This routine notifies threads waiting for a pending read or write to complete. This routine assumes that the notify list mutex is already locked. ****************************************************************************/ void FLMAPI f_notifySignal( F_NOTIFY_LIST_ITEM * pNotifyList, RCODE notifyRc) { while( pNotifyList) { F_SEM hSem; *(pNotifyList->pRc) = notifyRc; hSem = pNotifyList->hSem; pNotifyList = pNotifyList->pNext; f_semSignal( hSem); } }
/**************************************************************************** Desc: ****************************************************************************/ FSTATIC void f_rwlockNotify( F_RWLOCK_IMP * pReadWriteLock) { F_NOTIFY_LIST_ITEM * pNotify = pReadWriteLock->pNotifyList; FLMBOOL bFoundWriter = FALSE; f_assertMutexLocked( pReadWriteLock->hMutex); while( pNotify && !bFoundWriter) { F_SEM hSem; *(pNotify->pRc) = NE_FLM_OK; hSem = pNotify->hSem; bFoundWriter = (FLMBOOL)((FLMINT)pNotify->pvData); pNotify = pNotify->pNext; f_semSignal( hSem); } pReadWriteLock->pNotifyList = pNotify; }
/**************************************************************************** Desc: This routine finishes up after creating a new F_Database object. It will notify any other threads waiting for the operation to complete of the status of the operation. ****************************************************************************/ void F_Database::newDatabaseFinish( RCODE OpenRc) // Return code to send to other threads that are // waiting for the open to complete. { F_NOTIFY_LIST_ITEM * pNotify; F_SEM hSem; // Notify anyone waiting on the operation what its status is. pNotify = m_pOpenNotifies; while (pNotify) { *(pNotify->pRc) = OpenRc; hSem = pNotify->hSem; pNotify = pNotify->pNext; f_semSignal( hSem); } m_pOpenNotifies = NULL; m_uiFlags &= (~(DBF_BEING_OPENED)); }
/**************************************************************************** Desc: Thread that will delete block chains from deleted indexes and tables in the background. ****************************************************************************/ RCODE SQFAPI F_Database::maintenanceThread( IF_Thread * pThread) { RCODE rc = NE_SFLM_OK; F_Database * pDatabase = (F_Database *)pThread->getParm1(); F_Db * pDb; F_Row * pRow; FLMUINT64 ui64MaintRowId; FLMBOOL bStartedTrans; FLMBOOL bShutdown; F_DbSystem * pDbSystem; FSTableCursor * pTableCursor; FLMUINT uiBlkAddress; FLMBOOL bIsNull; FLMUINT uiBlocksToFree; FLMUINT uiBlocksFreed; Retry: rc = NE_SFLM_OK; pDb = NULL; pRow = NULL; bStartedTrans = FALSE; bShutdown = FALSE; pDbSystem = NULL; pTableCursor = NULL; if( (pDbSystem = f_new F_DbSystem) == NULL) { rc = RC_SET( NE_SFLM_MEM); goto Exit; } pThread->setThreadStatus( FLM_THREAD_STATUS_INITIALIZING); if( RC_BAD( rc = pDbSystem->internalDbOpen( pDatabase, &pDb))) { // If the file is being closed, this is not an error. if( pDatabase->getFlags() & DBF_BEING_CLOSED) { rc = NE_SFLM_OK; bShutdown = TRUE; } goto Exit; } pDbSystem->Release(); pDbSystem = NULL; if ((pTableCursor = f_new FSTableCursor) == NULL) { rc = RC_SET( NE_SFLM_MEM); goto Exit; } for( ;;) { pThread->setThreadStatus( FLM_THREAD_STATUS_RUNNING); if( RC_BAD( rc = pDb->beginBackgroundTrans( pThread))) { goto Exit; } bStartedTrans = TRUE; pTableCursor->resetCursor(); if (RC_BAD( rc = pTableCursor->setupRange( pDb, SFLM_TBLNUM_BLOCK_CHAINS, 1, FLM_MAX_UINT64, FALSE))) { goto Exit; } // Free up to 25 blocks per transaction. uiBlocksToFree = 25; while (uiBlocksToFree) { if (RC_BAD( rc = pTableCursor->nextRow( pDb, &pRow, &ui64MaintRowId))) { if (rc != NE_SFLM_EOF_HIT) { RC_UNEXPECTED_ASSERT( rc); goto Exit; } rc = NE_SFLM_OK; break; } if (RC_BAD( rc = pRow->getUINT( pDb, SFLM_COLNUM_BLOCK_CHAINS_BLOCK_ADDRESS, &uiBlkAddress, &bIsNull))) { goto Exit; } if (bIsNull) { rc = RC_SET_AND_ASSERT( NE_SFLM_DATA_ERROR); goto Exit; } if( RC_BAD( rc = pDb->maintBlockChainFree( ui64MaintRowId, uiBlkAddress, uiBlocksToFree, 0, &uiBlocksFreed))) { goto Exit; } uiBlocksToFree -= uiBlocksFreed; } bStartedTrans = FALSE; if( RC_BAD( rc = pDb->commitTrans( 0, FALSE))) { goto Exit; } pThread->setThreadStatus( FLM_THREAD_STATUS_SLEEPING); f_semWait( pDatabase->m_hMaintSem, F_WAITFOREVER); if (pThread->getShutdownFlag()) { bShutdown = TRUE; goto Exit; } } Exit: pThread->setThreadStatus( FLM_THREAD_STATUS_TERMINATING); if (pDbSystem) { pDbSystem->Release(); } if (pRow) { pRow->ReleaseRow(); } if( bStartedTrans) { pDb->abortTrans(); } if (pDb) { pDb->Release(); pDb = NULL; } if (!bShutdown) { flmAssert( RC_BAD( rc)); f_sleep( 250); f_semSignal( pDatabase->m_hMaintSem); goto Retry; } return( rc); }
/**************************************************************************** Desc: This routine obtains exclusive access to a database by creating a .lck file. FLAIM holds the .lck file open as long as the database is open. When the database is finally closed, it deletes the .lck file. This is only used for 3.x databases. ****************************************************************************/ RCODE F_Database::getExclAccess( const char * pszFilePath) { RCODE rc = NE_XFLM_OK; FLMBOOL bNotifyWaiters = FALSE; FLMBOOL bMutexLocked = FALSE; F_SEM hWaitSem = F_SEM_NULL; // If m_pLockFileHdl is non-NULL, it means that we currently // have the database locked with a lock file. There is no need to make // this test inside a mutex lock, because the lock file handle can only // be set to NULL when the use count goes to zero, meaning that the thread // that sets it to NULL will be the only thread accessing it. // However, it is possible that two or more threads will simultaneously // test m_pLockFileHdl and discover that it is NULL. In that case, // we allow one thread to proceed and attempt to get a lock on the database // while the other threads wait to be notified of the results of the // attempt to lock the database. if (m_pLockFileHdl) { goto Exit; } lockMutex(); bMutexLocked = TRUE; if (m_bBeingLocked) { // If the database is in the process of being locked by another // thread, wait for the lock to complete. NOTE: f_notifyWait will // re-lock the mutex before returning. if( RC_BAD( rc = f_semCreate( &hWaitSem))) { goto Exit; } rc = f_notifyWait( m_hMutex, hWaitSem, NULL, &m_pLockNotifies); goto Exit; } // No other thread was attempting to lock the database, so // set this thread up to make the attempt. Other threads // coming in at this point will be required to wait and // be notified of the results. m_bBeingLocked = TRUE; bNotifyWaiters = TRUE; unlockMutex(); bMutexLocked = FALSE; if (RC_BAD( rc = flmCreateLckFile( pszFilePath, &m_pLockFileHdl))) { goto Exit; } Exit: if (bNotifyWaiters) { F_NOTIFY_LIST_ITEM * pNotify; F_SEM hSem; // Notify any thread waiting on the lock what its status is. if( !bMutexLocked) { lockMutex(); bMutexLocked = TRUE; } pNotify = m_pLockNotifies; while (pNotify) { *(pNotify->pRc) = rc; hSem = pNotify->hSem; pNotify = pNotify->pNext; f_semSignal( hSem); } m_bBeingLocked = FALSE; m_pLockNotifies = NULL; unlockMutex(); bMutexLocked = FALSE; } if( bMutexLocked) { unlockMutex(); } if( hWaitSem != F_SEM_NULL) { f_semDestroy( &hWaitSem); } return( rc); }
/**************************************************************************** Desc: This destructor frees all of the structures associated with an F_Database object. Whoever called this routine has already determined that it is safe to do so. Notes: The global mutex is assumed to be locked when entering the routine. It may be unlocked and re-locked before the routine exits, however. ****************************************************************************/ F_Database::~F_Database() { F_NOTIFY_LIST_ITEM * pCloseNotifies; F_Dict * pDict; F_Dict * pTmpDict; // At this point, the use count better be zero flmAssert( !m_uiOpenIFDbCount); // Shut down all background threads before shutting down the CP thread. shutdownDatabaseThreads(); if (m_pRfl) { m_pRfl->closeFile(); } // Shouldn't have any pending input at this point flmAssert( !m_pPendingInput); // At this point, the use count better be zero flmAssert( !m_uiOpenIFDbCount); // Unlock the mutex f_mutexUnlock( gv_XFlmSysData.hShareMutex); // Shut down the checkpoint thread if( m_pCPThrd) { m_pCPThrd->stopThread(); m_pCPThrd->Release(); m_pCPThrd = NULL; } // Unlink all of the F_Dict objects that are connected to the // database. lockMutex(); while (m_pDictList) { m_pDictList->unlinkFromDatabase(); } unlockMutex(); // Take the file out of its name hash bucket, if any. if (m_uiBucket != 0xFFFF) { f_mutexLock( gv_XFlmSysData.hShareMutex); if (m_pPrev) { m_pPrev->m_pNext = m_pNext; } else { gv_XFlmSysData.pDatabaseHashTbl[ m_uiBucket].pFirstInBucket = m_pNext; } if (m_pNext) { m_pNext->m_pPrev = m_pPrev; } m_uiBucket = 0xFFFF; // After this point, we should not need to keep the global mutex locked // because the F_Database is no longer visible to any thread to find in // the hash table. f_mutexUnlock( gv_XFlmSysData.hShareMutex); } // Shouldn't have any queries at this point. But we will be nice in case // we do and will unlink the queries from the list flmAssert( !m_pFirstQuery); while (m_pFirstQuery) { F_Query * pQuery = m_pFirstQuery; m_pFirstQuery = m_pFirstQuery->m_pNext; pQuery->m_pPrev = NULL; pQuery->m_pNext = NULL; pQuery->m_pDatabase = NULL; } // Free the RFL data, if any. if (m_pRfl) { m_pRfl->Release(); m_pRfl = NULL; } flmAssert( m_pOpenNotifies == NULL); m_pOpenNotifies = NULL; // Save pCloseNotifies -- we will notify any waiters once the // F_Database has been freed. pCloseNotifies = m_pCloseNotifies; // Free any dictionary usage structures associated with the database. pDict = m_pDictList; while (pDict) { pTmpDict = pDict; pDict = pDict->getNext(); pTmpDict->Release(); } m_pDictList = NULL; // Free any shared cache associated with the database. // IMPORTANT NOTE: // Cannot have the global mutex locked when these are called because // these routines lock the block cache mutex and the node cache mutex. // If both the global mutex and the block or node cache mutexes are to be // locked, the rule is that the block or node cache mutex must be locked // before locking the global mutex. This is because neededByReadTrans // will end up doing it in this order - when neededByReadTrans is called // either the block or node cache mutex is already locked, and it will // additionally lock the global mutex. Since that order is already // required, we cannot have anyone else attempting to lock the mutexes // in a different order. freeBlockCache(); freeNodeCache(); // Release the lock objects. if (m_pWriteLockObj) { m_pWriteLockObj->Release(); m_pWriteLockObj = NULL; } if (m_pDatabaseLockObj) { m_pDatabaseLockObj->Release(); m_pDatabaseLockObj = NULL; } // Close and delete the lock file. if (m_pLockFileHdl) { (void)m_pLockFileHdl->closeFile(); m_pLockFileHdl->Release(); m_pLockFileHdl = NULL; } // Free the write buffer managers. if (m_pBufferMgr) { m_pBufferMgr->Release(); m_pBufferMgr = NULL; } // Free the log header write buffer if (m_pDbHdrWriteBuf) { f_freeAlignedBuffer( (void **)&m_pDbHdrWriteBuf); } // Free the update buffer if (m_pucUpdBuffer) { f_free( &m_pucUpdBuffer); m_uiUpdBufferSize = 0; } m_krefPool.poolFree(); if (m_ppBlocksDone) { f_free( &m_ppBlocksDone); m_uiBlocksDoneArraySize = 0; } // Notify waiters that the F_Database is gone while (pCloseNotifies) { F_SEM hSem; *(pCloseNotifies->pRc) = NE_XFLM_OK; hSem = pCloseNotifies->hSem; pCloseNotifies = pCloseNotifies->pNext; f_semSignal( hSem); } f_free( &m_pszDbPath); // Encryption stuff if (m_pszDbPasswd) { f_free( &m_pszDbPasswd); } if (m_pWrappingKey) { m_pWrappingKey->Release(); m_pWrappingKey = NULL; } flmAssert( !m_pFirstNode && !m_pLastNode && !m_pLastDirtyNode); if (m_hMutex != F_MUTEX_NULL) { f_mutexDestroy( &m_hMutex); } // Global mutex is still expected to be locked at this point f_mutexLock( gv_XFlmSysData.hShareMutex); }