8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-27 06:43:04 +01:00
firebird-mirror/src/jrd/tpc.cpp
2008-12-25 06:09:37 +00:00

468 lines
13 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 <string.h>
#include "../jrd/common.h"
#include "../jrd/jrd.h"
#include "../jrd/tpc.h"
#include "../jrd/ods.h"
#include "../jrd/tra.h"
#include "../jrd/lck.h"
#include "../jrd/pag.h"
#include "gen/iberror.h"
#include "../jrd/cch_proto.h"
#include "../jrd/err_proto.h"
#include "../jrd/gds_proto.h"
#include "../jrd/lck_proto.h"
#include "../jrd/mov_proto.h"
#include "../jrd/tpc_proto.h"
#include "../jrd/tra_proto.h"
#include "../common/classes/auto.h"
#include "../common/utils_proto.h"
using namespace Jrd;
static TxPageCache* allocate_tpc(thread_db*, SLONG);
static SLONG cache_transactions(thread_db*, TxPageCache**, SLONG);
static int extend_cache(thread_db*, SLONG);
int TPC_cache_state(thread_db* tdbb, SLONG 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.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
CHECK_DBB(dbb);
const TxPageCache* tip_cache = dbb->dbb_tip_cache;
if (!tip_cache) {
TPC_initialize_tpc(tdbb, number);
tip_cache = dbb->dbb_tip_cache;
}
if (number && dbb->dbb_pc_transactions) {
if (TRA_precommited(tdbb, number, number))
return tra_precommitted;
}
/* if the transaction is older than the oldest
transaction in our tip cache, it must be committed */
// hvlad: system transaction always committed too
if (number < tip_cache->tpc_base || number == 0)
return tra_committed;
/* locate the specific TIP cache block for the transaction */
for (; tip_cache; tip_cache = tip_cache->tpc_next) {
if (number < tip_cache->tpc_base + dbb->dbb_page_manager.transPerTIP) {
return TRA_state(tip_cache->tpc_transactions, tip_cache->tpc_base, number);
}
}
/* Cover all possibilities by returning active */
return tra_active;
}
void TPC_initialize_tpc(thread_db* tdbb, SLONG 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.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
CHECK_DBB(dbb);
TxPageCache* tip_cache = dbb->dbb_tip_cache;
if (!tip_cache) {
cache_transactions(tdbb, NULL, 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 SLONG trans_per_tip = dbb->dbb_page_manager.transPerTIP;
TxPageCache** tip_cache_ptr;
for (tip_cache_ptr = &dbb->dbb_tip_cache; *tip_cache_ptr;
tip_cache_ptr = &(*tip_cache_ptr)->tpc_next)
{
tip_cache = *tip_cache_ptr;
}
if (number < (SLONG)(tip_cache->tpc_base + trans_per_tip))
return;
cache_transactions(tdbb, tip_cache_ptr, tip_cache->tpc_base + trans_per_tip);
}
void TPC_set_state(thread_db* tdbb, SLONG 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.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
CHECK_DBB(dbb);
const SLONG trans_per_tip = dbb->dbb_page_manager.transPerTIP;
const SLONG byte = TRANS_OFFSET(number % trans_per_tip);
const SSHORT shift = TRANS_SHIFT(number);
for (TxPageCache* tip_cache = dbb->dbb_tip_cache; tip_cache; tip_cache = tip_cache->tpc_next)
{
if (number < (SLONG)(tip_cache->tpc_base + trans_per_tip)) {
UCHAR* address = tip_cache->tpc_transactions + byte;
*address &= ~(TRA_MASK << shift);
*address |= state << shift;
break;
}
}
/* 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 TPC_snapshot_state(thread_db* tdbb, SLONG 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.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
CHECK_DBB(dbb);
const TxPageCache* tip_cache = dbb->dbb_tip_cache;
if (!tip_cache) {
cache_transactions(tdbb, NULL, 0);
tip_cache = dbb->dbb_tip_cache;
}
if (number && dbb->dbb_pc_transactions) {
if (TRA_precommited(tdbb, number, number)) {
return tra_precommitted;
}
}
/* if the transaction is older than the oldest
transaction in our tip cache, it must be committed */
// hvlad: system transaction always committed too
if (number < tip_cache->tpc_base || number == 0) {
return tra_committed;
}
/* locate the specific TIP cache block for the transaction */
for (; tip_cache; tip_cache = tip_cache->tpc_next)
{
if (number < (SLONG) (tip_cache->tpc_base + dbb->dbb_page_manager.transPerTIP ))
{
const USHORT state = TRA_state(tip_cache->tpc_transactions, tip_cache->tpc_base, number);
/* 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;
temp_lock.lck_dbb = dbb;
temp_lock.lck_type = LCK_tra;
temp_lock.lck_owner_handle = LCK_get_owner_handle(tdbb, temp_lock.lck_type);
temp_lock.lck_parent = dbb->dbb_lock;
temp_lock.lck_length = sizeof(SLONG);
temp_lock.lck_key.lck_long = 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 */
return extend_cache(tdbb, number);
}
void TPC_update_cache(thread_db* tdbb, const Ods::tx_inv_page* tip_page, SLONG 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.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
CHECK_DBB(dbb);
const SLONG trans_per_tip = dbb->dbb_page_manager.transPerTIP;
const SLONG first_trans = 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 */
TxPageCache* tip_cache;
while ( (tip_cache = dbb->dbb_tip_cache) ) {
if (dbb->dbb_oldest_transaction >= tip_cache->tpc_base + trans_per_tip)
{
dbb->dbb_tip_cache = tip_cache->tpc_next;
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 */
for (; tip_cache; tip_cache = tip_cache->tpc_next) {
if (first_trans == tip_cache->tpc_base) {
const USHORT l = TRANS_OFFSET(trans_per_tip);
memcpy(tip_cache->tpc_transactions, tip_page->tip_transactions, l);
break;
}
}
/* note that a potential optimization here would be to extend the cache
if the fetched page is not already in cache; it would involve a little
extra effort to make sure the pages remained in order, and since there
is a fetched page passed to us we can't fetch any other pages in this
routine, so I just decided not to do it - djb */
}
static TxPageCache* allocate_tpc(thread_db* tdbb, SLONG 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.
*
**************************************/
Database* dbb = tdbb->getDatabase();
const SLONG trans_per_tip = dbb->dbb_page_manager.transPerTIP;
/* allocate a TIP cache block with enough room for
all desired transactions */
TxPageCache* tip_cache = FB_NEW_RPT(*dbb->dbb_permanent, trans_per_tip / 4) TxPageCache();
tip_cache->tpc_base = base;
return tip_cache;
}
static SLONG cache_transactions(thread_db* tdbb, TxPageCache** tip_cache_ptr, SLONG 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.
*
**************************************/
Database* dbb = tdbb->getDatabase();
CHECK_DBB(dbb);
/* check the header page for the oldest and
newest transaction numbers */
#ifdef SUPERSERVER_V2
const SLONG top = dbb->dbb_next_transaction;
const ULONG hdr_oldest = 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 SLONG top = header->hdr_next_transaction;
const SLONG hdr_oldest = header->hdr_oldest_transaction;
CCH_RELEASE(tdbb, &window);
#endif
oldest = MAX(oldest, hdr_oldest);
/* allocate TxPageCache blocks to hold all transaction states --
assign one TxPageCache block per page to simplify cache maintenance */
const SLONG trans_per_tip = dbb->dbb_page_manager.transPerTIP;
if (!tip_cache_ptr)
tip_cache_ptr = &dbb->dbb_tip_cache;
for (SLONG base = oldest - oldest % trans_per_tip; base <= top; base += trans_per_tip)
{
*tip_cache_ptr = allocate_tpc(tdbb, base);
tip_cache_ptr = &(*tip_cache_ptr)->tpc_next;
}
/* now get the inventory of all transactions, which will
automatically fill in the tip cache pages */
TRA_get_inventory(tdbb, NULL, oldest, top);
// hvlad: cache_transactions returns number of oldest transaction
// just refreshed from header page. No need to cache TIP pages below it
// Moreover out tip cache can now contain an gap between last
// cached tip page and new pages if our process was idle for long time
for (TxPageCache* tip_cache = dbb->dbb_tip_cache;
tip_cache && (tip_cache->tpc_base + trans_per_tip < hdr_oldest);
tip_cache = dbb->dbb_tip_cache)
{
dbb->dbb_tip_cache = tip_cache->tpc_next;
delete tip_cache;
}
return hdr_oldest;
}
static int extend_cache(thread_db* tdbb, SLONG 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.
*
**************************************/
Database* dbb = tdbb->getDatabase();
const SLONG trans_per_tip = 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
TxPageCache* tip_cache = 0;
TxPageCache** tip_cache_ptr;
for (tip_cache_ptr = &dbb->dbb_tip_cache; *tip_cache_ptr;
tip_cache_ptr = &(*tip_cache_ptr)->tpc_next)
{
tip_cache = *tip_cache_ptr;
}
const SLONG oldest = cache_transactions(tdbb, tip_cache_ptr, tip_cache->tpc_base + trans_per_tip);
if (number < oldest)
return tra_committed;
// find the right block for this transaction and return the state
for (tip_cache = dbb->dbb_tip_cache; tip_cache; tip_cache = tip_cache->tpc_next)
{
if (number < (SLONG) (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
return tra_active;
}