mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-27 18:03:04 +01:00
f2d0b64ba2
class.
621 lines
16 KiB
C++
621 lines
16 KiB
C++
/*
|
|
* PROGRAM: JRD Access Method
|
|
* MODULE: tpc.cpp
|
|
* DESCRIPTION: TIP Cache for Database
|
|
*
|
|
* The contents of this file are subject to the Interbase Public
|
|
* License Version 1.0 (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.Inprise.com/IPL.html
|
|
*
|
|
* 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 was created by Inprise Corporation
|
|
* and its predecessors. Portions created by Inprise Corporation are
|
|
* Copyright (C) Inprise Corporation.
|
|
*
|
|
* All Rights Reserved.
|
|
* Contributor(s): ______________________________________.
|
|
*/
|
|
|
|
#include "firebird.h"
|
|
#include "../jrd/jrd.h"
|
|
#include "../jrd/ods.h"
|
|
#include "../jrd/tra.h"
|
|
#include "../jrd/pag.h"
|
|
#include "../jrd/cch_proto.h"
|
|
#include "../jrd/lck_proto.h"
|
|
#include "../jrd/ods_proto.h"
|
|
#include "../jrd/tpc_proto.h"
|
|
#include "../jrd/tra_proto.h"
|
|
|
|
|
|
using namespace Firebird;
|
|
|
|
namespace Jrd {
|
|
|
|
TipCache::TipCache(Database* dbb)
|
|
: m_dbb(dbb),
|
|
m_cache(*m_dbb->dbb_permanent)
|
|
{
|
|
}
|
|
|
|
|
|
TipCache::~TipCache()
|
|
{
|
|
SyncLockGuard sync(&m_sync, SYNC_EXCLUSIVE, "TipCache::~TipCache");
|
|
clearCache();
|
|
}
|
|
|
|
|
|
int TipCache::cacheState(thread_db* tdbb, TraNumber number)
|
|
{
|
|
/**************************************
|
|
*
|
|
* T P C _ c a c h e _ s t a t e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Get the current state of a transaction in the cache.
|
|
*
|
|
**************************************/
|
|
|
|
if (number && TRA_precommited(tdbb, number, number))
|
|
return tra_precommitted;
|
|
|
|
SyncLockGuard sync(&m_sync, SYNC_SHARED, "TipCache::cacheState");
|
|
|
|
if (!m_cache.getCount())
|
|
{
|
|
SyncUnlockGuard unlock(sync);
|
|
initializeTpc(tdbb, number);
|
|
}
|
|
|
|
// if the transaction is older than the oldest
|
|
// transaction in our tip cache, it must be committed
|
|
// hvlad: system transaction is always committed too
|
|
|
|
TxPage* tip_cache = m_cache.front();
|
|
if (number < tip_cache->tpc_base || number == 0)
|
|
return tra_committed;
|
|
|
|
// locate the specific TIP cache block for the transaction
|
|
|
|
const ULONG trans_per_tip = m_dbb->dbb_page_manager.transPerTIP;
|
|
const TraNumber base = number - number % trans_per_tip;
|
|
|
|
FB_SIZE_T pos;
|
|
if (m_cache.find(base, pos))
|
|
{
|
|
tip_cache = m_cache[pos];
|
|
|
|
fb_assert(number >= tip_cache->tpc_base);
|
|
fb_assert(tip_cache->tpc_base < MAX_TRA_NUMBER - trans_per_tip);
|
|
fb_assert(number < (tip_cache->tpc_base + trans_per_tip));
|
|
|
|
return TRA_state(tip_cache->tpc_transactions, tip_cache->tpc_base, number);
|
|
}
|
|
|
|
// Cover all possibilities by returning active
|
|
|
|
return tra_active;
|
|
}
|
|
|
|
|
|
static inline bool check_state(int state, ULONG mask)
|
|
{
|
|
return ((1 << state) & mask) != 0;
|
|
}
|
|
|
|
TraNumber TipCache::findStates(thread_db* tdbb, TraNumber minNumber, TraNumber maxNumber,
|
|
ULONG mask, int& state)
|
|
{
|
|
/**************************************
|
|
*
|
|
* T P C _ f i n d _ s t a t e s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Return the oldest transaction in the given state. Lookup in the
|
|
* [min_number, max_number) bounds.
|
|
* If not found, return zero and don't change value of "state".
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* const dbb = tdbb->getDatabase();
|
|
CHECK_DBB(dbb);
|
|
|
|
fb_assert(minNumber <= maxNumber);
|
|
|
|
// Ensure that the TIP cache is extended to fit the requested transactions
|
|
|
|
initializeTpc(tdbb, maxNumber);
|
|
|
|
SyncLockGuard sync(&m_sync, SYNC_SHARED, "TipCache::findStates");
|
|
|
|
const TxPage* tip_cache = m_cache.front();
|
|
|
|
// Check for too old transactions (assumed committed)
|
|
|
|
if (maxNumber < tip_cache->tpc_base)
|
|
return 0;
|
|
|
|
if (minNumber < tip_cache->tpc_base || minNumber == 0)
|
|
{
|
|
if (check_state(tra_committed, mask))
|
|
{
|
|
state = tra_committed;
|
|
return minNumber;
|
|
}
|
|
|
|
minNumber = tip_cache->tpc_base;
|
|
}
|
|
|
|
bool check_precommitted = false;
|
|
if (check_state(tra_precommitted, mask))
|
|
{
|
|
// If we looking for tra_precommitted only and there is no precommitted
|
|
// transactions - return immediately
|
|
|
|
SyncLockGuard syncPC(&dbb->dbb_pc_sync, SYNC_SHARED, "TipCache::findLimbo");
|
|
if (dbb->dbb_pc_transactions != NULL)
|
|
check_precommitted = true;
|
|
else if (mask == (1 << tra_precommitted))
|
|
return 0;
|
|
}
|
|
|
|
const ULONG trans_per_tip = m_dbb->dbb_page_manager.transPerTIP;
|
|
const TraNumber base = minNumber - minNumber % trans_per_tip;
|
|
|
|
// Scan the TIP cache and return the first (i.e. oldest) transaction in
|
|
// state we are looking for
|
|
|
|
FB_SIZE_T pos;
|
|
if (m_cache.find(base, pos))
|
|
{
|
|
for (TraNumber number = minNumber;
|
|
pos < m_cache.getCount() && number < maxNumber;
|
|
pos++)
|
|
{
|
|
tip_cache = m_cache[pos];
|
|
|
|
fb_assert(number >= tip_cache->tpc_base);
|
|
fb_assert(tip_cache->tpc_base < MAX_TRA_NUMBER - trans_per_tip);
|
|
|
|
for (; number < (tip_cache->tpc_base + trans_per_tip) && number < maxNumber;
|
|
number++)
|
|
{
|
|
if (check_precommitted && number && TRA_precommited(tdbb, number, number))
|
|
{
|
|
state = tra_precommitted;
|
|
return number;
|
|
}
|
|
|
|
const int tx_state = TRA_state(tip_cache->tpc_transactions, tip_cache->tpc_base, number);
|
|
if (check_state(tx_state, mask))
|
|
{
|
|
state = tx_state;
|
|
return number;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void TipCache::initializeTpc(thread_db* tdbb, TraNumber number)
|
|
{
|
|
/**************************************
|
|
*
|
|
* T P C _ i n i t i a l i z e _ t p c
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* At transaction startup, intialize the tip cache up to
|
|
* number. This is used at TRA_start () time.
|
|
*
|
|
**************************************/
|
|
|
|
SyncLockGuard sync(&m_sync, SYNC_EXCLUSIVE, "TipCache::initializeTpc");
|
|
|
|
if (m_cache.isEmpty())
|
|
{
|
|
sync.unlock();
|
|
cacheTransactions(tdbb, 0);
|
|
return;
|
|
}
|
|
|
|
// If there is already a cache, extend it if required.
|
|
// find the end of the linked list, and cache
|
|
// all transactions from that point up to the most recent transaction
|
|
|
|
const ULONG trans_per_tip = m_dbb->dbb_page_manager.transPerTIP;
|
|
|
|
const TxPage* tip_cache = m_cache.back();
|
|
|
|
fb_assert(tip_cache->tpc_base < MAX_TRA_NUMBER - trans_per_tip);
|
|
if (number < (tip_cache->tpc_base + trans_per_tip))
|
|
return;
|
|
|
|
if (tip_cache->tpc_base < MAX_TRA_NUMBER - trans_per_tip)
|
|
{
|
|
// ensure last_known calculated *before* unlock !!!
|
|
const TraNumber last_known = tip_cache->tpc_base;
|
|
sync.unlock();
|
|
|
|
cacheTransactions(tdbb, last_known + trans_per_tip);
|
|
}
|
|
}
|
|
|
|
|
|
void TipCache::setState(TraNumber number, SSHORT state)
|
|
{
|
|
/**************************************
|
|
*
|
|
* T P C _ s e t _ s t a t e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Set the state of a particular transaction
|
|
* in the TIP cache.
|
|
*
|
|
**************************************/
|
|
|
|
const ULONG trans_per_tip = m_dbb->dbb_page_manager.transPerTIP;
|
|
const TraNumber base = number - number % trans_per_tip;
|
|
const ULONG byte = TRANS_OFFSET(number % trans_per_tip);
|
|
const USHORT shift = TRANS_SHIFT(number);
|
|
|
|
SyncLockGuard sync(&m_sync, SYNC_EXCLUSIVE, "TipCache::setState");
|
|
|
|
FB_SIZE_T pos;
|
|
if (m_cache.find(base, pos))
|
|
{
|
|
TxPage* tip_cache = m_cache[pos];
|
|
|
|
fb_assert(number >= tip_cache->tpc_base);
|
|
fb_assert(tip_cache->tpc_base < MAX_TRA_NUMBER - trans_per_tip);
|
|
fb_assert(number < (tip_cache->tpc_base + trans_per_tip));
|
|
|
|
UCHAR* address = tip_cache->tpc_transactions + byte;
|
|
*address &= ~(TRA_MASK << shift);
|
|
*address |= state << shift;
|
|
return;
|
|
}
|
|
|
|
// right now we don't set the state of a transaction on a page
|
|
// that has not already been cached -- this should probably be done
|
|
}
|
|
|
|
|
|
int TipCache::snapshotState(thread_db* tdbb, TraNumber number)
|
|
{
|
|
/**************************************
|
|
*
|
|
* T P C _ s n a p s h o t _ s t a t e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Get the current state of a transaction.
|
|
* Look at the TIP cache first, but if it
|
|
* is marked as still alive we must do some
|
|
* further checking to see if it really is.
|
|
*
|
|
**************************************/
|
|
|
|
fb_assert(m_dbb == tdbb->getDatabase());
|
|
|
|
if (number && TRA_precommited(tdbb, number, number))
|
|
return tra_precommitted;
|
|
|
|
SyncLockGuard sync(&m_sync, SYNC_SHARED, "TipCache::snapshotState");
|
|
|
|
if (m_cache.isEmpty())
|
|
{
|
|
sync.unlock();
|
|
cacheTransactions(tdbb, 0);
|
|
sync.lock(SYNC_SHARED, "TipCache::snapshotState");
|
|
}
|
|
|
|
// if the transaction is older than the oldest
|
|
// transaction in our tip cache, it must be committed
|
|
// hvlad: system transaction always committed too
|
|
|
|
TxPage* tip_cache = m_cache.front();
|
|
if (number < tip_cache->tpc_base || number == 0)
|
|
return tra_committed;
|
|
|
|
// locate the specific TIP cache block for the transaction
|
|
|
|
const ULONG trans_per_tip = m_dbb->dbb_page_manager.transPerTIP;
|
|
const TraNumber base = number - number % trans_per_tip;
|
|
|
|
FB_SIZE_T pos;
|
|
if (m_cache.find(base, pos))
|
|
{
|
|
tip_cache = m_cache[pos];
|
|
|
|
fb_assert(number >= tip_cache->tpc_base);
|
|
fb_assert(tip_cache->tpc_base < MAX_TRA_NUMBER - trans_per_tip);
|
|
fb_assert(number < (tip_cache->tpc_base + trans_per_tip));
|
|
|
|
const int state = TRA_state(tip_cache->tpc_transactions, tip_cache->tpc_base, number);
|
|
|
|
sync.unlock();
|
|
|
|
// committed or dead transactions always stay that
|
|
// way, so no need to check their current state
|
|
|
|
if (state == tra_committed || state == tra_dead)
|
|
return state;
|
|
|
|
// see if we can get a lock on the transaction; if we can't
|
|
// then we know it is still active
|
|
Lock temp_lock(tdbb, sizeof(TraNumber), LCK_tra);
|
|
temp_lock.setKey(number);
|
|
|
|
// If we can't get a lock on the transaction, it must be active.
|
|
|
|
if (!LCK_lock(tdbb, &temp_lock, LCK_read, LCK_NO_WAIT))
|
|
{
|
|
fb_utils::init_status(tdbb->tdbb_status_vector);
|
|
return tra_active;
|
|
}
|
|
|
|
fb_utils::init_status(tdbb->tdbb_status_vector);
|
|
LCK_release(tdbb, &temp_lock);
|
|
|
|
// as a last resort we must look at the TIP page to see
|
|
// whether the transaction is committed or dead; to minimize
|
|
// having to do this again we will check the state of all
|
|
// other transactions on that page
|
|
|
|
return TRA_fetch_state(tdbb, number);
|
|
}
|
|
// if the transaction has been started since we last looked, extend the cache upward
|
|
|
|
sync.unlock();
|
|
|
|
return extendCache(tdbb, number);
|
|
}
|
|
|
|
|
|
void TipCache::updateCache(const Ods::tx_inv_page* tip_page, ULONG sequence)
|
|
{
|
|
/**************************************
|
|
*
|
|
* T P C _ u p d a t e _ c a c h e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* A TIP page has been fetched into memory,
|
|
* so we should take the opportunity to update
|
|
* the TIP cache with the state of all transactions
|
|
* on that page.
|
|
*
|
|
**************************************/
|
|
|
|
const ULONG trans_per_tip = m_dbb->dbb_page_manager.transPerTIP;
|
|
const TraNumber first_trans = (TraNumber) sequence * trans_per_tip;
|
|
|
|
// while we're in the area we can check to see if there are
|
|
// any tip cache pages we can release--this is cheaper and
|
|
// easier than finding out when a TIP page is dropped
|
|
|
|
SyncLockGuard sync(&m_sync, SYNC_EXCLUSIVE, "TipCache::updateCache");
|
|
|
|
TxPage* tip_cache = NULL;
|
|
|
|
while (m_cache.hasData())
|
|
{
|
|
tip_cache = m_cache.front();
|
|
|
|
fb_assert(tip_cache->tpc_base < MAX_TRA_NUMBER - trans_per_tip);
|
|
if (m_dbb->dbb_oldest_transaction >= (tip_cache->tpc_base + trans_per_tip))
|
|
{
|
|
m_cache.remove((FB_SIZE_T) 0);
|
|
delete tip_cache;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
// find the appropriate page in the TIP cache and assign all transaction
|
|
// bits -- it's not worth figuring out which ones are actually used
|
|
|
|
FB_SIZE_T pos;
|
|
if (m_cache.find(first_trans, pos))
|
|
tip_cache = m_cache[pos];
|
|
else
|
|
{
|
|
tip_cache = allocTxPage(first_trans);
|
|
m_cache.insert(pos, tip_cache);
|
|
}
|
|
|
|
fb_assert(first_trans == tip_cache->tpc_base);
|
|
|
|
const USHORT len = TRANS_OFFSET(trans_per_tip);
|
|
memcpy(tip_cache->tpc_transactions, tip_page->tip_transactions, len);
|
|
}
|
|
|
|
|
|
TipCache::TxPage* TipCache::allocTxPage(TraNumber base)
|
|
{
|
|
/**************************************
|
|
*
|
|
* a l l o c a t e _ t p c
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Create a tip cache block to hold the state
|
|
* of all transactions on one page.
|
|
*
|
|
**************************************/
|
|
fb_assert(m_sync.ourExclusiveLock());
|
|
|
|
const ULONG trans_per_tip = m_dbb->dbb_page_manager.transPerTIP;
|
|
|
|
// allocate a TIP cache block with enough room for all desired transactions
|
|
|
|
TxPage* tip_cache = FB_NEW_RPT(*m_dbb->dbb_permanent, trans_per_tip / 4) TxPage();
|
|
tip_cache->tpc_base = base;
|
|
|
|
return tip_cache;
|
|
}
|
|
|
|
|
|
TraNumber TipCache::cacheTransactions(thread_db* tdbb, TraNumber oldest)
|
|
{
|
|
/**************************************
|
|
*
|
|
* c a c h e _ t r a n s a c t i o n s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Cache the state of all the transactions since
|
|
* the last time this routine was called, or since
|
|
* the oldest interesting transaction.
|
|
*
|
|
**************************************/
|
|
|
|
// m_sync should be unlocked here !
|
|
|
|
// check the header page for the oldest and newest transaction numbers
|
|
|
|
#ifdef SUPERSERVER_V2
|
|
const TraNumber top = m_dbb->dbb_next_transaction;
|
|
const TraNumber hdr_oldest = m_dbb->dbb_oldest_transaction;
|
|
#else
|
|
WIN window(HEADER_PAGE_NUMBER);
|
|
const Ods::header_page* header = (Ods::header_page*) CCH_FETCH(tdbb, &window, LCK_read, pag_header);
|
|
const TraNumber top = Ods::getNT(header);
|
|
const TraNumber hdr_oldest = Ods::getOIT(header);
|
|
CCH_RELEASE(tdbb, &window);
|
|
#endif
|
|
|
|
// hvlad: No need to cache TIP pages below hdr_oldest just refreshed from
|
|
// header page. Moreover our tip cache can now contain a gap between the last
|
|
// cached tip page and new pages if our process was idle for long time
|
|
|
|
oldest = MAX(oldest, hdr_oldest);
|
|
|
|
// now get the inventory of all transactions, which will automatically
|
|
// fill in the tip cache pages
|
|
// hvlad: note, call below will call updateCache() which will acquire m_sync
|
|
// in exclusive mode. This is the reason why m_sync must be unlocked at the
|
|
// entry of this routine
|
|
|
|
TRA_get_inventory(tdbb, NULL, oldest, top);
|
|
|
|
SyncLockGuard sync(&m_sync, SYNC_EXCLUSIVE, "TipCache::updateCache");
|
|
|
|
const ULONG trans_per_tip = m_dbb->dbb_page_manager.transPerTIP;
|
|
|
|
while (m_cache.hasData())
|
|
{
|
|
TxPage* tip_cache = m_cache.front();
|
|
|
|
fb_assert(tip_cache->tpc_base < MAX_TRA_NUMBER - trans_per_tip);
|
|
if ((tip_cache->tpc_base + trans_per_tip) < hdr_oldest)
|
|
{
|
|
m_cache.remove((FB_SIZE_T) 0);
|
|
delete tip_cache;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
return hdr_oldest;
|
|
}
|
|
|
|
|
|
void TipCache::clearCache()
|
|
{
|
|
fb_assert(m_sync.ourExclusiveLock());
|
|
|
|
while (m_cache.hasData())
|
|
delete m_cache.pop();
|
|
}
|
|
|
|
|
|
int TipCache::extendCache(thread_db* tdbb, TraNumber number)
|
|
{
|
|
/**************************************
|
|
*
|
|
* e x t e n d _ c a c h e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Extend the transaction inventory page
|
|
* cache to include at least all transactions
|
|
* up to the passed transaction, and return
|
|
* the state of the passed transaction.
|
|
*
|
|
**************************************/
|
|
|
|
// m_sync should be unlocked here !
|
|
|
|
const ULONG trans_per_tip = m_dbb->dbb_page_manager.transPerTIP;
|
|
|
|
// find the end of the linked list, and cache
|
|
// all transactions from that point up to the
|
|
// most recent transaction
|
|
|
|
Sync sync(&m_sync, "extendCache");
|
|
sync.lock(SYNC_SHARED);
|
|
|
|
fb_assert(m_cache.hasData());
|
|
TxPage* tip_cache = m_cache.back();
|
|
|
|
if (tip_cache->tpc_base < MAX_TRA_NUMBER - trans_per_tip)
|
|
{
|
|
// ensure last_known calculated *before* unlock !!!
|
|
const TraNumber last_known = tip_cache->tpc_base;
|
|
sync.unlock();
|
|
|
|
const TraNumber oldest = cacheTransactions(tdbb, last_known + trans_per_tip);
|
|
if (number < oldest)
|
|
return tra_committed;
|
|
|
|
sync.lock(SYNC_SHARED);
|
|
}
|
|
|
|
// find the right block for this transaction and return the state
|
|
|
|
const TraNumber base = number - number % trans_per_tip;
|
|
|
|
FB_SIZE_T pos;
|
|
if (m_cache.find(base, pos))
|
|
{
|
|
tip_cache = m_cache[pos];
|
|
|
|
fb_assert(number >= tip_cache->tpc_base);
|
|
fb_assert(tip_cache->tpc_base < MAX_TRA_NUMBER - trans_per_tip);
|
|
fb_assert(number < (tip_cache->tpc_base + trans_per_tip));
|
|
|
|
return TRA_state(tip_cache->tpc_transactions, tip_cache->tpc_base, number);
|
|
}
|
|
|
|
// we should never get to this point, but if we do the
|
|
// safest thing to do is return active
|
|
|
|
fb_assert(false);
|
|
return tra_active;
|
|
}
|
|
|
|
} // namespace Jrd
|