8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-24 20:43:04 +01:00
firebird-mirror/src/jrd/tra.cpp

3709 lines
100 KiB
C++
Raw Normal View History

2001-05-23 15:26:42 +02:00
/*
* PROGRAM: JRD Access Method
* MODULE: tra.cpp
2001-05-23 15:26:42 +02:00
* DESCRIPTION: Transaction manager
*
* 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): ______________________________________.
* 2001.07.06 Sean Leyne - Code Cleanup, removed "#ifdef READONLY_DATABASE"
* conditionals, as the engine now fully supports
* readonly databases.
* 2002.10.29 Nickolay Samofatov: Added support for savepoints
2001-05-23 15:26:42 +02:00
*/
#include "firebird.h"
2001-05-23 15:26:42 +02:00
#include <string.h>
2004-03-22 12:38:23 +01:00
#include "../jrd/common.h"
2001-05-23 15:26:42 +02:00
#include "../jrd/jrd.h"
#include "../jrd/tra.h"
#include "../jrd/ods.h"
#include "../jrd/pag.h"
#include "../jrd/lck.h"
2003-11-08 17:40:17 +01:00
#include "../jrd/ibase.h"
2001-05-23 15:26:42 +02:00
#include "../jrd/lls.h"
2003-11-30 22:04:18 +01:00
#include "../jrd/btr.h"
2001-05-23 15:26:42 +02:00
#include "../jrd/req.h"
#include "../jrd/exe.h"
#include "../jrd/extds/ExtDS.h"
2001-05-23 15:26:42 +02:00
#include "../jrd/rse.h"
#include "../jrd/intl_classes.h"
#include "../jrd/jrd_pwd.h"
2008-01-16 13:22:11 +01:00
#include "../jrd/ThreadStart.h"
#include "../jrd/UserManagement.h"
2001-05-23 15:26:42 +02:00
#include "../jrd/blb_proto.h"
#include "../jrd/cch_proto.h"
#include "../jrd/cmp_proto.h"
#include "../jrd/dfw_proto.h"
#include "../jrd/dpm_proto.h"
#include "../jrd/err_proto.h"
#include "../jrd/exe_proto.h"
#include "../jrd/ext_proto.h"
#include "../jrd/gds_proto.h"
#include "../jrd/isc_proto.h"
#include "../jrd/lck_proto.h"
#include "../jrd/met_proto.h"
#include "../jrd/mov_proto.h"
#include "../jrd/pag_proto.h"
2001-05-23 15:26:42 +02:00
#include "../jrd/rlck_proto.h"
#include "../jrd/thread_proto.h"
2001-05-23 15:26:42 +02:00
#include "../jrd/tpc_proto.h"
#include "../jrd/tra_proto.h"
#include "../jrd/vio_proto.h"
#include "../jrd/enc_proto.h"
#include "../jrd/jrd_proto.h"
#include "../common/classes/ClumpletWriter.h"
2007-09-04 10:22:48 +02:00
#include "../common/classes/TriState.h"
2008-02-14 12:52:59 +01:00
#include "../common/utils_proto.h"
2001-05-23 15:26:42 +02:00
#include "../lock/lock_proto.h"
2008-02-28 14:48:16 +01:00
#include "../dsql/dsql.h"
#include "../dsql/dsql_proto.h"
#include "../common/StatusArg.h"
2009-02-01 23:10:12 +01:00
#include "../jrd/trace/TraceManager.h"
#include "../jrd/trace/TraceJrdHelpers.h"
2001-05-23 15:26:42 +02:00
2004-05-03 23:43:56 +02:00
const int DYN_MSG_FAC = 8;
2001-05-23 15:26:42 +02:00
using namespace Jrd;
using namespace Ods;
using namespace Firebird;
#ifdef GARBAGE_THREAD
#include "../jrd/isc_s_proto.h"
#endif
2008-03-12 08:33:12 +01:00
typedef Firebird::GenericMap<Firebird::Pair<Firebird::NonPooled<USHORT, UCHAR> > > RelationLockTypeMap;
2001-05-23 15:26:42 +02:00
#ifdef SUPERSERVER_V2
static SLONG bump_transaction_id(thread_db*, WIN *);
2001-05-23 15:26:42 +02:00
#else
static header_page* bump_transaction_id(thread_db*, WIN *);
2001-05-23 15:26:42 +02:00
#endif
static Lock* create_transaction_lock(thread_db* tdbb, void* object);
static void retain_context(thread_db*, jrd_tra*, bool, SSHORT);
2001-05-23 15:26:42 +02:00
#ifdef VMS
static void compute_oldest_retaining(thread_db*, jrd_tra*, bool);
2001-05-23 15:26:42 +02:00
#endif
2008-03-12 08:33:12 +01:00
static void expand_view_lock(thread_db* tdbb, jrd_tra*, jrd_rel*, UCHAR lock_type,
const char* option_name, RelationLockTypeMap& lockmap, const int level);
static tx_inv_page* fetch_inventory_page(thread_db*, WIN *, SLONG, USHORT);
2008-03-12 08:33:12 +01:00
static const char* get_lockname_v3(const UCHAR lock);
static SLONG inventory_page(thread_db*, SLONG);
static SSHORT limbo_transaction(thread_db*, SLONG);
static void link_transaction(thread_db*, jrd_tra*);
static void restart_requests(thread_db*, jrd_tra*);
static void start_sweeper(thread_db*);
static THREAD_ENTRY_DECLARE sweep_database(THREAD_ENTRY_PARAM);
static void transaction_options(thread_db*, jrd_tra*, const UCHAR*, USHORT);
2008-01-16 13:22:11 +01:00
static jrd_tra* transaction_start(thread_db* tdbb, jrd_tra* temp);
2001-05-23 15:26:42 +02:00
static const UCHAR sweep_tpb[] =
{
isc_tpb_version1, isc_tpb_read,
2001-05-23 15:26:42 +02:00
isc_tpb_read_committed, isc_tpb_rec_version
};
2006-10-08 18:03:37 +02:00
void TRA_attach_request(Jrd::jrd_tra* transaction, Jrd::jrd_req* request)
{
// When request finishes normally transaction reference is not cleared.
// Then if afterwards request is restarted TRA_attach_request is called again.
2009-08-23 11:49:58 +02:00
if (request->req_transaction)
{
if (request->req_transaction == transaction)
return;
TRA_detach_request(request);
}
fb_assert(request->req_transaction == NULL);
fb_assert(request->req_tra_next == NULL);
fb_assert(request->req_tra_prev == NULL);
// Assign transaction reference
request->req_transaction = transaction;
// Add request to the doubly linked list
2009-08-23 11:49:58 +02:00
if (transaction->tra_requests)
{
fb_assert(transaction->tra_requests->req_tra_prev == NULL);
transaction->tra_requests->req_tra_prev = request;
request->req_tra_next = transaction->tra_requests;
}
transaction->tra_requests = request;
}
2006-10-08 18:03:37 +02:00
void TRA_detach_request(Jrd::jrd_req* request)
{
if (!request->req_transaction)
return;
// Remove request from the doubly linked list
2009-08-23 11:49:58 +02:00
if (request->req_tra_next)
{
fb_assert(request->req_tra_next->req_tra_prev == request);
request->req_tra_next->req_tra_prev = request->req_tra_prev;
}
2009-08-23 11:49:58 +02:00
if (request->req_tra_prev)
{
fb_assert(request->req_tra_prev->req_tra_next == request);
request->req_tra_prev->req_tra_next = request->req_tra_next;
}
2009-08-23 11:49:58 +02:00
else
{
fb_assert(request->req_transaction->tra_requests == request);
request->req_transaction->tra_requests = request->req_tra_next;
}
// Clear references
request->req_transaction = NULL;
request->req_tra_next = NULL;
request->req_tra_prev = NULL;
}
bool TRA_active_transactions(thread_db* tdbb, Database* dbb)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* T R A _ a c t i v e _ t r a n s a c t i o n s
*
**************************************
*
* Functional description
* Determine if any transactions are active.
* Return true is active transactions; otherwise
* return false if no active transactions.
2001-05-23 15:26:42 +02:00
*
**************************************/
SET_TDBB(tdbb);
2001-05-23 15:26:42 +02:00
#ifndef VMS
return ((LCK_query_data(tdbb, dbb->dbb_lock, LCK_tra, LCK_ANY)) ? true : false);
2001-05-23 15:26:42 +02:00
#else
2009-08-21 11:45:08 +02:00
// Read header page and allocate transaction number.
2001-05-23 15:26:42 +02:00
2003-12-31 06:36:12 +01:00
ULONG number, oldest, active;
2001-05-23 15:26:42 +02:00
#ifdef SUPERSERVER_V2
number = dbb->dbb_next_transaction;
oldest = dbb->dbb_oldest_transaction;
active = MAX(dbb->dbb_oldest_active, dbb->dbb_oldest_transaction);
#else
2009-08-23 11:49:58 +02:00
if (dbb->dbb_flags & DBB_read_only)
{
2001-05-23 15:26:42 +02:00
number = dbb->dbb_next_transaction;
oldest = dbb->dbb_oldest_transaction;
active = MAX(dbb->dbb_oldest_active, dbb->dbb_oldest_transaction);
}
2009-08-23 11:49:58 +02:00
else
{
2007-03-09 09:56:31 +01:00
WIN window(HEADER_PAGE_NUMBER);
const header_page* header = (header_page*) CCH_FETCH(tdbb, &window, LCK_read, pag_header);
2001-05-23 15:26:42 +02:00
number = header->hdr_next_transaction;
oldest = header->hdr_oldest_transaction;
2008-12-25 07:09:37 +01:00
active = MAX(header->hdr_oldest_active, header->hdr_oldest_transaction);
2001-05-23 15:26:42 +02:00
CCH_RELEASE(tdbb, &window);
}
2009-08-21 11:45:08 +02:00
#endif // SUPERSERVER_V2
2001-05-23 15:26:42 +02:00
2003-12-31 06:36:12 +01:00
const ULONG base = oldest & ~TRA_MASK;
const size_t length = (number - base + TRA_MASK) / 4;
2001-05-23 15:26:42 +02:00
MemoryPool* const pool = dbb->dbb_permanent;
Firebird::AutoPtr<jrd_tra> trans =
FB_NEW(*pool) jrd_tra(pool, &dbb->dbb_memory_stats, NULL, NULL, length);
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// Build transaction bitmap to scan for active transactions.
2001-05-23 15:26:42 +02:00
TRA_get_inventory(tdbb, trans->tra_transactions, base, number);
Lock temp_lock;
2001-05-23 15:26:42 +02:00
temp_lock.lck_dbb = dbb;
temp_lock.lck_object = trans;
2001-05-23 15:26:42 +02:00
temp_lock.lck_type = LCK_tra;
temp_lock.lck_owner_handle = LCK_get_owner_handle(tdbb, temp_lock.lck_type);
2001-05-23 15:26:42 +02:00
temp_lock.lck_parent = dbb->dbb_lock;
temp_lock.lck_length = sizeof(SLONG);
2009-08-23 11:49:58 +02:00
for (; active <= number; active++)
{
2003-12-31 06:36:12 +01:00
const ULONG byte = TRANS_OFFSET(active - base);
const USHORT shift = TRANS_SHIFT(active);
const USHORT state = (trans->tra_transactions[byte] >> shift) & TRA_MASK;
2009-08-23 11:49:58 +02:00
if (state == tra_active)
{
2001-05-23 15:26:42 +02:00
temp_lock.lck_key.lck_long = active;
if (!LCK_lock(tdbb, &temp_lock, LCK_read, LCK_NO_WAIT)) {
return true;
2001-05-23 15:26:42 +02:00
}
LCK_release(tdbb, &temp_lock);
}
}
return false;
2001-05-23 15:26:42 +02:00
#endif
}
void TRA_cleanup(thread_db* tdbb)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* T R A _ c l e a n u p
*
**************************************
*
* Functional description
* TRA_cleanup is called at startup while an exclusive lock is
* held on the database. Because we haven't started a transaction,
* and we have an exclusive lock on the db, any transactions marked
* as active on the transaction inventory pages are indeed dead.
* Mark them so.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
2001-05-23 15:26:42 +02:00
CHECK_DBB(dbb);
2009-08-21 11:45:08 +02:00
// Return without cleaning up the TIP's for a ReadOnly database
2001-05-23 15:26:42 +02:00
if (dbb->dbb_flags & DBB_read_only)
return;
2009-08-21 11:45:08 +02:00
// First, make damn sure there are no outstanding transactions
2001-05-23 15:26:42 +02:00
2008-12-25 07:09:37 +01:00
for (Attachment* attachment = dbb->dbb_attachments; attachment; attachment = attachment->att_next)
2003-09-13 14:03:11 +02:00
{
if (attachment->att_transactions)
2001-05-23 15:26:42 +02:00
return;
2003-09-13 14:03:11 +02:00
}
2001-05-23 15:26:42 +02:00
2006-05-22 00:07:35 +02:00
const SLONG trans_per_tip = dbb->dbb_page_manager.transPerTIP;
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// Read header page and allocate transaction number. Since
// the transaction inventory page was initialized to zero, it
// transaction is automatically marked active.
2001-05-23 15:26:42 +02:00
2006-05-22 00:07:35 +02:00
WIN window(HEADER_PAGE_NUMBER);
const header_page* header = (header_page*) CCH_FETCH(tdbb, &window, LCK_read, pag_header);
2003-12-31 06:36:12 +01:00
const SLONG ceiling = header->hdr_next_transaction;
const SLONG active = header->hdr_oldest_active;
2001-05-23 15:26:42 +02:00
CCH_RELEASE(tdbb, &window);
if (ceiling == 0)
return;
2009-08-21 11:45:08 +02:00
// Zip thru transactions from the "oldest active" to the next looking for
// active transactions. When one is found, declare it dead.
2001-05-23 15:26:42 +02:00
2003-12-31 06:36:12 +01:00
const SLONG last = ceiling / trans_per_tip;
SLONG number = active % trans_per_tip;
SLONG limbo = 0;
2001-05-23 15:26:42 +02:00
2008-12-25 07:09:37 +01:00
for (SLONG sequence = active / trans_per_tip; sequence <= last; sequence++, number = 0)
2003-12-31 06:36:12 +01:00
{
2001-05-23 15:26:42 +02:00
window.win_page = inventory_page(tdbb, sequence);
tx_inv_page* tip = (tx_inv_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_transactions);
2003-12-31 06:36:12 +01:00
SLONG max = ceiling - (sequence * trans_per_tip);
2001-05-23 15:26:42 +02:00
if (max > trans_per_tip)
max = trans_per_tip - 1;
2008-12-25 07:09:37 +01:00
for (; number <= max; number++)
{
2003-12-31 06:36:12 +01:00
const SLONG trans_offset = TRANS_OFFSET(number);
UCHAR* byte = tip->tip_transactions + trans_offset;
const SSHORT shift = TRANS_SHIFT(number);
const SSHORT state = (*byte >> shift) & TRA_MASK;
2001-05-23 15:26:42 +02:00
if (state == tra_limbo && limbo == 0)
limbo = sequence * trans_per_tip + number;
2008-12-25 07:09:37 +01:00
else if (state == tra_active)
{
2001-05-23 15:26:42 +02:00
CCH_MARK(tdbb, &window);
*byte &= ~(TRA_MASK << shift);
// hvlad: mark system transaction as committed
if (sequence == 0 && number == 0) {
*byte |= tra_committed << shift;
}
else {
*byte |= tra_dead << shift;
}
2001-05-23 15:26:42 +02:00
}
}
#ifdef SUPERSERVER_V2
2008-12-25 07:09:37 +01:00
if (sequence == last)
{
2001-05-23 15:26:42 +02:00
CCH_MARK(tdbb, &window);
2008-12-25 07:09:37 +01:00
for (; number < trans_per_tip; number++)
{
2003-12-31 06:36:12 +01:00
const SLONG trans_offset = TRANS_OFFSET(number);
UCHAR* byte = tip->tip_transactions + trans_offset;
const SSHORT shift = TRANS_SHIFT(number);
2001-05-23 15:26:42 +02:00
*byte &= ~(TRA_MASK << shift);
if (tip->tip_next)
*byte |= tra_committed << shift;
else
2002-04-29 17:05:11 +02:00
*byte |= tra_active << shift;
2001-05-23 15:26:42 +02:00
}
}
#endif
CCH_RELEASE(tdbb, &window);
}
#ifdef SUPERSERVER_V2
window.win_page = inventory_page(tdbb, last);
2008-12-25 07:09:37 +01:00
tx_inv_page* tip = (tx_inv_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_transactions);
2001-05-23 15:26:42 +02:00
2008-12-25 07:09:37 +01:00
while (tip->tip_next)
{
2001-05-23 15:26:42 +02:00
CCH_RELEASE(tdbb, &window);
window.win_page = inventory_page(tdbb, ++last);
tip = (tx_inv_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_transactions);
2001-05-23 15:26:42 +02:00
CCH_MARK(tdbb, &window);
2008-12-25 07:09:37 +01:00
for (number = 0; number < trans_per_tip; number++)
{
const SLONG trans_offset = TRANS_OFFSET(number);
UCHAR* byte = tip->tip_transactions + trans_offset;
const USHORT shift = TRANS_SHIFT(number);
2001-05-23 15:26:42 +02:00
*byte &= ~(TRA_MASK << shift);
if (tip->tip_next || !number)
*byte |= tra_committed << shift;
else
*byte |= tra_active << shift;
}
if (!tip->tip_next)
dbb->dbb_next_transaction = last * trans_per_tip;
}
CCH_RELEASE(tdbb, &window);
#endif
}
void TRA_commit(thread_db* tdbb, jrd_tra* transaction, const bool retaining_flag)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* T R A _ c o m m i t
*
**************************************
*
* Functional description
* Commit a transaction.
*
**************************************/
SET_TDBB(tdbb);
2009-02-01 23:10:12 +01:00
TraceTransactionEnd trace(transaction, true, retaining_flag);
EDS::Transaction::jrdTransactionEnd(tdbb, transaction, true, retaining_flag, false);
2008-05-18 04:02:50 +02:00
// If this is a commit retaining, and no updates have been performed,
// and no events have been posted (via stored procedures etc)
// no-op the operation.
2001-05-23 15:26:42 +02:00
if (retaining_flag && !(transaction->tra_flags & TRA_write || transaction->tra_deferred_job))
{
2001-05-23 15:26:42 +02:00
transaction->tra_flags &= ~TRA_prepared;
// Get rid of all user savepoints
while (transaction->tra_save_point && transaction->tra_save_point->sav_flags & SAV_user)
{
Savepoint* const next = transaction->tra_save_point->sav_next;
transaction->tra_save_point->sav_next = NULL;
VIO_verb_cleanup(tdbb, transaction);
transaction->tra_save_point = next;
}
2009-02-01 23:10:12 +01:00
trace.finish(res_successful);
2001-05-23 15:26:42 +02:00
return;
}
if (transaction->tra_flags & TRA_invalidated)
ERR_post(Arg::Gds(isc_trans_invalid));
2001-05-23 15:26:42 +02:00
Jrd::ContextPoolHolder context(tdbb, transaction->tra_pool);
2001-05-23 15:26:42 +02:00
2008-05-18 04:02:50 +02:00
// Perform any meta data work deferred
2001-05-23 15:26:42 +02:00
if (!(transaction->tra_flags & TRA_prepared))
2008-03-18 14:04:05 +01:00
DFW_perform_work(tdbb, transaction);
2001-05-23 15:26:42 +02:00
if (transaction->tra_flags & (TRA_prepare2 | TRA_reconnected))
2003-12-22 11:00:59 +01:00
MET_update_transaction(tdbb, transaction, true);
2001-05-23 15:26:42 +02:00
2008-05-18 04:02:50 +02:00
// Check in with external file system
2001-05-23 15:26:42 +02:00
EXT_trans_commit(transaction);
#ifdef GARBAGE_THREAD
2009-08-21 11:45:08 +02:00
// Flush pages if transaction logically modified data
2001-05-23 15:26:42 +02:00
if (transaction->tra_flags & TRA_write)
#endif
CCH_flush(tdbb, FLUSH_TRAN, transaction->tra_number);
2001-05-23 15:26:42 +02:00
#ifdef GARBAGE_THREAD
2009-08-23 11:49:58 +02:00
else if (transaction->tra_flags & (TRA_prepare2 | TRA_reconnected))
{
2009-08-21 11:45:08 +02:00
// If the transaction only read data but is a member of a
// multi-database transaction with a transaction description
// message then flush RDB$TRANSACTIONS.
2001-05-23 15:26:42 +02:00
CCH_flush(tdbb, FLUSH_SYSTEM, 0);
2001-05-23 15:26:42 +02:00
}
#endif
2009-04-04 18:39:31 +02:00
if (retaining_flag)
2009-02-01 23:10:12 +01:00
{
trace.finish(res_successful);
retain_context(tdbb, transaction, true, tra_committed);
2001-05-23 15:26:42 +02:00
return;
}
2009-08-21 11:45:08 +02:00
// Set the state on the inventory page to be committed
2001-05-23 15:26:42 +02:00
TRA_set_state(tdbb, transaction, transaction->tra_number, tra_committed);
2009-08-21 11:45:08 +02:00
// Perform any post commit work
2001-05-23 15:26:42 +02:00
DFW_perform_post_commit_work(transaction);
2009-08-21 11:45:08 +02:00
// notify any waiting locks that this transaction is committing;
// there could be no lock if this transaction is being reconnected
2001-05-23 15:26:42 +02:00
++transaction->tra_use_count;
Lock* lock = transaction->tra_lock;
2003-12-31 06:36:12 +01:00
if (lock && (lock->lck_logical < LCK_write))
LCK_convert(tdbb, lock, LCK_write, LCK_WAIT);
2001-05-23 15:26:42 +02:00
--transaction->tra_use_count;
TRA_release_transaction(tdbb, transaction, &trace);
2001-05-23 15:26:42 +02:00
}
2009-04-26 12:24:44 +02:00
void TRA_extend_tip(thread_db* tdbb, ULONG sequence) //, WIN* precedence_window)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* T R A _ e x t e n d _ t i p
*
**************************************
*
* Functional description
* Allocate and link in new TIP (transaction inventory page).
* This is called from TRA_start and from validate/repair.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
2001-05-23 15:26:42 +02:00
CHECK_DBB(dbb);
2009-08-21 11:45:08 +02:00
// Start by fetching prior transaction page, if any
tx_inv_page* prior_tip = NULL;
2006-05-22 00:07:35 +02:00
WIN prior_window(DB_PAGE_SPACE, -1);
if (sequence) {
prior_tip = fetch_inventory_page(tdbb, &prior_window, (SLONG) (sequence - 1), LCK_write);
}
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// Allocate and format new page
2006-05-22 00:07:35 +02:00
WIN window(DB_PAGE_SPACE, -1);
tx_inv_page* tip = (tx_inv_page*) DPM_allocate(tdbb, &window);
tip->tip_header.pag_type = pag_transactions;
2001-05-23 15:26:42 +02:00
CCH_must_write(&window);
CCH_RELEASE(tdbb, &window);
2009-08-21 11:45:08 +02:00
// Release prior page
2001-05-23 15:26:42 +02:00
2009-08-23 11:49:58 +02:00
if (sequence)
{
2001-05-23 15:26:42 +02:00
CCH_MARK_MUST_WRITE(tdbb, &prior_window);
2006-05-22 00:07:35 +02:00
prior_tip->tip_next = window.win_page.getPageNum();
2001-05-23 15:26:42 +02:00
CCH_RELEASE(tdbb, &prior_window);
}
2009-08-21 11:45:08 +02:00
// Link into internal data structures
2001-05-23 15:26:42 +02:00
2003-12-31 06:36:12 +01:00
vcl* vector = dbb->dbb_t_pages =
vcl::newVector(*dbb->dbb_permanent, dbb->dbb_t_pages, sequence + 1);
2006-05-22 00:07:35 +02:00
(*vector)[sequence] = window.win_page.getPageNum();
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// Write into pages relation
2001-05-23 15:26:42 +02:00
2006-05-22 00:07:35 +02:00
DPM_pages(tdbb, 0, pag_transactions, sequence, window.win_page.getPageNum());
2001-05-23 15:26:42 +02:00
}
int TRA_fetch_state(thread_db* tdbb, SLONG number)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* T R A _ f e t c h _ s t a t e
*
**************************************
*
* Functional description
* Physically fetch the state of a given
* transaction on the transaction inventory
* page.
2001-05-23 15:26:42 +02:00
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
2001-05-23 15:26:42 +02:00
CHECK_DBB(dbb);
2009-08-21 11:45:08 +02:00
// locate and fetch the proper TIP page
2001-05-23 15:26:42 +02:00
2003-12-31 06:36:12 +01:00
const ULONG tip_number = (ULONG) number;
2006-05-22 00:07:35 +02:00
const SLONG trans_per_tip = dbb->dbb_page_manager.transPerTIP;
2003-12-31 06:36:12 +01:00
const ULONG tip_seq = tip_number / trans_per_tip;
2006-05-22 00:07:35 +02:00
WIN window(DB_PAGE_SPACE, -1);
const tx_inv_page* tip = fetch_inventory_page(tdbb, &window, tip_seq, LCK_read);
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// calculate the state of the desired transaction
2001-05-23 15:26:42 +02:00
2003-12-31 06:36:12 +01:00
const ULONG byte = TRANS_OFFSET(tip_number % trans_per_tip);
const USHORT shift = TRANS_SHIFT(tip_number);
const USHORT state = (tip->tip_transactions[byte] >> shift) & TRA_MASK;
2001-05-23 15:26:42 +02:00
CCH_RELEASE(tdbb, &window);
return state;
}
void TRA_get_inventory(thread_db* tdbb, UCHAR* bit_vector, ULONG base, ULONG top)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* T R A _ g e t _ i n v e n t o r y
*
**************************************
*
* Functional description
* Get an inventory of the state of all transactions
* between the base and top transactions passed.
* To get a consistent view of the transaction
* inventory (in case we ever implement sub-transactions),
2001-05-23 15:26:42 +02:00
* do handoffs to read the pages in order.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
2001-05-23 15:26:42 +02:00
CHECK_DBB(dbb);
2006-05-22 00:07:35 +02:00
const ULONG trans_per_tip = dbb->dbb_page_manager.transPerTIP;
2003-12-31 06:36:12 +01:00
ULONG sequence = base / trans_per_tip;
const ULONG last = top / trans_per_tip;
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// fetch the first inventory page
2001-05-23 15:26:42 +02:00
2006-05-22 00:07:35 +02:00
WIN window(DB_PAGE_SPACE, -1);
const tx_inv_page* tip = fetch_inventory_page(tdbb, &window, (SLONG) sequence++, LCK_read);
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// move the first page into the bit vector
2001-05-23 15:26:42 +02:00
2003-12-31 06:36:12 +01:00
UCHAR* p = bit_vector;
2009-08-23 11:49:58 +02:00
if (p)
{
2003-12-31 06:36:12 +01:00
ULONG l = base % trans_per_tip;
const UCHAR* q = tip->tip_transactions + TRANS_OFFSET(l);
2001-05-23 15:26:42 +02:00
l = TRANS_OFFSET(MIN((top + TRA_MASK - base), trans_per_tip - l));
2008-02-03 11:41:44 +01:00
memcpy(p, q, l);
2001-05-23 15:26:42 +02:00
p += l;
}
2009-08-21 11:45:08 +02:00
// move successive pages into the bit vector
2001-05-23 15:26:42 +02:00
2009-08-23 11:49:58 +02:00
while (sequence <= last)
{
2001-05-23 15:26:42 +02:00
base = sequence * trans_per_tip;
2009-08-21 11:45:08 +02:00
// release the read lock as we go, so that some one else can
// commit without having to signal all other transactions.
2001-05-23 15:26:42 +02:00
tip = (tx_inv_page*) CCH_HANDOFF(tdbb, &window, inventory_page(tdbb, sequence++),
2001-05-23 15:26:42 +02:00
LCK_read, pag_transactions);
TPC_update_cache(tdbb, tip, sequence - 1);
2009-08-23 11:49:58 +02:00
if (p)
{
2003-12-31 06:36:12 +01:00
const ULONG l = TRANS_OFFSET(MIN((top + TRA_MASK - base), trans_per_tip));
2008-02-03 11:41:44 +01:00
memcpy(p, tip->tip_transactions, l);
2001-05-23 15:26:42 +02:00
p += l;
}
}
CCH_RELEASE(tdbb, &window);
}
int TRA_get_state(thread_db* tdbb, SLONG number)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* T R A _ g e t _ s t a t e
*
**************************************
*
* Functional description
* Get the state of a given transaction on the
2001-05-23 15:26:42 +02:00
* transaction inventory page.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
2001-05-23 15:26:42 +02:00
CHECK_DBB(dbb);
if (dbb->dbb_tip_cache)
return TPC_snapshot_state(tdbb, number);
if (number && dbb->dbb_pc_transactions)
2008-02-24 04:23:40 +01:00
{
2001-05-23 15:26:42 +02:00
if (TRA_precommited(tdbb, number, number))
return tra_precommitted;
2008-02-24 04:23:40 +01:00
}
2001-05-23 15:26:42 +02:00
return TRA_fetch_state(tdbb, number);
}
#ifdef SUPERSERVER_V2
void TRA_header_write(thread_db* tdbb, Database* dbb, SLONG number)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* T R A _ h e a d e r _ w r i t e
*
**************************************
*
* Functional description
* Force transaction ID on header to disk.
* Do post fetch check of the transaction
* ID header write as a concurrent thread
* might have written the header page
* while blocked on the latch.
*
* The idea is to amortize the cost of
* header page I/O across multiple transactions.
*
**************************************/
SET_TDBB(tdbb);
2009-08-21 11:45:08 +02:00
// If transaction number is already on disk just return.
2001-05-23 15:26:42 +02:00
2009-08-23 11:49:58 +02:00
if (!number || dbb->dbb_last_header_write < number)
{
2007-03-09 09:56:31 +01:00
WIN window(HEADER_PAGE_NUMBER);
header_page* header = (header_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_header);
2001-05-23 15:26:42 +02:00
2009-08-23 11:49:58 +02:00
if (header->hdr_next_transaction)
{
2001-05-23 15:26:42 +02:00
if (header->hdr_oldest_active > header->hdr_next_transaction)
2009-08-21 11:45:08 +02:00
BUGCHECK(266); //next transaction older than oldest active
2001-05-23 15:26:42 +02:00
if (header->hdr_oldest_transaction > header->hdr_next_transaction)
2009-08-21 11:45:08 +02:00
BUGCHECK(267); // next transaction older than oldest transaction
2001-05-23 15:26:42 +02:00
}
2009-08-21 11:45:08 +02:00
// The header page might have been written while waiting
// for the latch; perform a post fetch check and optimize
// this case by not writing the page again.
2001-05-23 15:26:42 +02:00
2009-08-23 11:49:58 +02:00
if (!number || dbb->dbb_last_header_write < number)
{
2001-05-23 15:26:42 +02:00
CCH_MARK_MUST_WRITE(tdbb, &window);
if (dbb->dbb_next_transaction > header->hdr_next_transaction)
header->hdr_next_transaction = dbb->dbb_next_transaction;
if (dbb->dbb_oldest_active > header->hdr_oldest_active)
header->hdr_oldest_active = dbb->dbb_oldest_active;
if (dbb->dbb_oldest_transaction > header->hdr_oldest_transaction)
header->hdr_oldest_transaction = dbb->dbb_oldest_transaction;
if (dbb->dbb_oldest_snapshot > header->hdr_oldest_snapshot)
header->hdr_oldest_snapshot = dbb->dbb_oldest_snapshot;
}
CCH_RELEASE(tdbb, &window);
}
}
#endif
void TRA_init(Database* dbb)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* T R A _ i n i t
*
**************************************
*
* Functional description
* "Start" the system transaction.
*
**************************************/
CHECK_DBB(dbb);
MemoryPool* const pool = dbb->dbb_permanent;
jrd_tra* const trans = FB_NEW(*pool) jrd_tra(pool, &dbb->dbb_memory_stats, NULL, NULL);
2003-12-31 06:36:12 +01:00
dbb->dbb_sys_trans = trans;
2001-05-23 15:26:42 +02:00
trans->tra_flags |= TRA_system | TRA_ignore_limbo;
}
2004-03-07 08:58:55 +01:00
void TRA_invalidate(Database* database, ULONG mask)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* T R A _ i n v a l i d a t e
*
**************************************
*
* Functional description
* Invalidate any active transactions that may have
* modified a page that couldn't be written.
*
**************************************/
for (Attachment* attachment = database->dbb_attachments; attachment;
2008-12-25 07:09:37 +01:00
attachment = attachment->att_next)
2003-12-31 06:36:12 +01:00
{
for (jrd_tra* transaction = attachment->att_transactions; transaction;
transaction = transaction->tra_next)
2003-12-31 06:36:12 +01:00
{
2008-12-25 07:09:37 +01:00
const ULONG transaction_mask = 1L << (transaction->tra_number & (BITS_PER_LONG - 1));
2001-05-23 15:26:42 +02:00
if (transaction_mask & mask && transaction->tra_flags & TRA_write)
transaction->tra_flags |= TRA_invalidated;
}
2003-12-31 06:36:12 +01:00
}
2001-05-23 15:26:42 +02:00
}
2008-02-28 14:48:16 +01:00
void TRA_link_cursor(jrd_tra* transaction, dsql_req* cursor)
{
/**************************************
*
* T R A _ l i n k _ c u r s o r
*
**************************************
*
* Functional description
* Add cursor to the list of open cursors belonging to this transaction.
*
**************************************/
fb_assert(!transaction->tra_open_cursors.exist(cursor));
transaction->tra_open_cursors.add(cursor);
}
void TRA_unlink_cursor(jrd_tra* transaction, dsql_req* cursor)
{
/**************************************
*
* T R A _ u n l i n k _ c u r s o r
*
**************************************
*
* Functional description
* Remove cursor from the list of open cursors.
*
**************************************/
size_t pos;
if (transaction->tra_open_cursors.find(cursor, pos))
{
transaction->tra_open_cursors.remove(pos);
}
}
void TRA_post_resources(thread_db* tdbb, jrd_tra* transaction, ResourceList& resources)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* T R A _ p o s t _ r e s o u r c e s
*
**************************************
*
* Functional description
2006-08-08 04:42:10 +02:00
* Post interest in relation/procedure/collation existence to transaction.
* This guarantees that the relation/procedure/collation won't be dropped
2001-05-23 15:26:42 +02:00
* out from under the transaction.
*
**************************************/
SET_TDBB(tdbb);
Jrd::ContextPoolHolder context(tdbb, transaction->tra_pool);
2001-05-23 15:26:42 +02:00
for (Resource* rsc = resources.begin(); rsc < resources.end(); rsc++)
{
if (rsc->rsc_type == Resource::rsc_relation ||
rsc->rsc_type == Resource::rsc_procedure ||
rsc->rsc_type == Resource::rsc_collation)
{
size_t i;
if (!transaction->tra_resources.find(*rsc, i))
2003-09-13 14:03:11 +02:00
{
transaction->tra_resources.insert(i, *rsc);
switch (rsc->rsc_type)
{
case Resource::rsc_relation:
MET_post_existence(tdbb, rsc->rsc_rel);
if (rsc->rsc_rel->rel_file) {
EXT_tra_attach(rsc->rsc_rel->rel_file, transaction);
}
2001-05-23 15:26:42 +02:00
break;
case Resource::rsc_procedure:
rsc->rsc_prc->prc_use_count++;
2001-05-23 15:26:42 +02:00
#ifdef DEBUG_PROCS
{
char buffer[256];
sprintf(buffer,
"Called from TRA_post_resources():\n\t Incrementing use count of %s\n",
2004-09-13 23:06:41 +02:00
rsc->rsc_prc->prc_name->c_str());
2001-05-23 15:26:42 +02:00
JRD_print_procedure_info(tdbb, buffer);
}
#endif
break;
case Resource::rsc_collation:
rsc->rsc_coll->incUseCount(tdbb);
break;
2003-11-03 17:21:37 +01:00
default: // shut up compiler warning
break;
2001-05-23 15:26:42 +02:00
}
}
}
2003-12-31 06:36:12 +01:00
}
2001-05-23 15:26:42 +02:00
}
bool TRA_pc_active(thread_db* tdbb, SLONG number)
{
/**************************************
*
* T R A _ p c _ a c t i v e
*
**************************************
*
* Functional description
* Returns whether a given precommitted transaction
* owned by some other guy active or not.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
CHECK_DBB(dbb);
Lock temp_lock;
temp_lock.lck_dbb = dbb;
temp_lock.lck_type = LCK_tra_pc;
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 true;
}
LCK_release(tdbb, &temp_lock);
return false;
}
bool TRA_precommited(thread_db* tdbb, SLONG old_number, SLONG new_number)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* T R A _ p r e c o m m i t e d (s i c)
*
**************************************
*
* Functional description
* Maintain a vector of active precommitted
* transactions. If old_number <> new_number
2001-05-23 15:26:42 +02:00
* then swap old_number with new_number in
* the vector. If old_number equals new_number
* then test for that number's presence in
* the vector.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
2001-05-23 15:26:42 +02:00
CHECK_DBB(dbb);
2003-12-31 06:36:12 +01:00
vcl* vector = dbb->dbb_pc_transactions;
2009-08-23 11:49:58 +02:00
if (!vector)
{
2001-05-23 15:26:42 +02:00
if (old_number == new_number)
return false;
2001-12-24 03:51:06 +01:00
vector = dbb->dbb_pc_transactions = vcl::newVector(*dbb->dbb_permanent, 1);
2001-05-23 15:26:42 +02:00
}
2003-12-31 06:36:12 +01:00
SLONG* zp = 0;
2009-08-23 11:49:58 +02:00
for (vcl::iterator p = vector->begin(), end = vector->end(); p < end; ++p)
{
2001-05-23 15:26:42 +02:00
if (*p == old_number)
return (*p = new_number) ? true : false;
2001-05-23 15:26:42 +02:00
if (!zp && !*p)
zp = &*p;
2001-05-23 15:26:42 +02:00
}
if (old_number == new_number || new_number == 0)
return false;
2009-08-23 11:49:58 +02:00
2001-05-23 15:26:42 +02:00
if (zp)
*zp = new_number;
2009-08-23 11:49:58 +02:00
else
{
2001-12-24 03:51:06 +01:00
vector->resize(vector->count() + 1);
(*vector)[vector->count() - 1] = new_number;
2001-05-23 15:26:42 +02:00
}
return true;
2001-05-23 15:26:42 +02:00
}
2009-08-23 11:49:58 +02:00
void TRA_prepare(thread_db* tdbb, jrd_tra* transaction, USHORT length, const UCHAR* msg)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* T R A _ p r e p a r e
*
**************************************
*
* Functional description
* Put a transaction into limbo.
*
**************************************/
SET_TDBB(tdbb);
if (transaction->tra_flags & TRA_prepared)
return;
if (transaction->tra_flags & TRA_invalidated)
ERR_post(Arg::Gds(isc_trans_invalid));
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
/* If there's a transaction description message, log it to RDB$TRANSACTION
We should only log a message to RDB$TRANSACTION if there is a message
to log (if the length = 0, we won't log the transaction in RDB$TRANSACTION)
These messages are used to recover transactions in limbo. The message indicates
the action that is to be performed (hence, if nothing is getting logged, don't
bother).
*/
/* Make sure that if msg is NULL there is no length. The two
should go hand in hand
msg == NULL || *msg == NULL
*/
2003-11-04 00:59:24 +01:00
fb_assert(!(!msg && length) || (msg && (!*msg && length)));
2001-05-23 15:26:42 +02:00
2009-08-23 11:49:58 +02:00
if (msg && length)
{
2001-05-23 15:26:42 +02:00
MET_prepare(tdbb, transaction, length, msg);
transaction->tra_flags |= TRA_prepare2;
}
2009-08-21 11:45:08 +02:00
// Check in with external file system
2001-05-23 15:26:42 +02:00
EXT_trans_prepare(transaction);
2009-08-21 11:45:08 +02:00
// Perform any meta data work deferred
2001-05-23 15:26:42 +02:00
2008-03-18 14:04:05 +01:00
DFW_perform_work(tdbb, transaction);
2001-05-23 15:26:42 +02:00
#ifdef GARBAGE_THREAD
2009-08-21 11:45:08 +02:00
// Flush pages if transaction logically modified data
2001-05-23 15:26:42 +02:00
if (transaction->tra_flags & TRA_write)
#endif
CCH_flush(tdbb, FLUSH_TRAN, transaction->tra_number);
2001-05-23 15:26:42 +02:00
#ifdef GARBAGE_THREAD
2009-08-23 11:49:58 +02:00
else if (transaction->tra_flags & TRA_prepare2)
{
2009-08-21 11:45:08 +02:00
// If the transaction only read data but is a member of a
// multi-database transaction with a transaction description
// message then flush RDB$TRANSACTIONS.
2001-05-23 15:26:42 +02:00
CCH_flush(tdbb, FLUSH_SYSTEM, 0);
2001-05-23 15:26:42 +02:00
}
#endif
2009-08-21 11:45:08 +02:00
// Set the state on the inventory page to be limbo
2001-05-23 15:26:42 +02:00
transaction->tra_flags |= TRA_prepared;
TRA_set_state(tdbb, transaction, transaction->tra_number, tra_limbo);
}
jrd_tra* TRA_reconnect(thread_db* tdbb, const UCHAR* id, USHORT length)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* T R A _ r e c o n n e c t
*
**************************************
*
* Functional description
* Reconnect to a transaction in limbo.
*
**************************************/
SET_TDBB(tdbb);
Database* const dbb = tdbb->getDatabase();
2001-05-23 15:26:42 +02:00
CHECK_DBB(dbb);
Attachment* const attachment = tdbb->getAttachment();
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// Cannot work on limbo transactions for ReadOnly database
2001-05-23 15:26:42 +02:00
if (dbb->dbb_flags & DBB_read_only)
ERR_post(Arg::Gds(isc_read_only_database));
2001-05-23 15:26:42 +02:00
const SLONG number = gds__vax_integer(id, length);
if (number > dbb->dbb_next_transaction)
PAG_header(tdbb, true);
2009-06-25 04:29:13 +02:00
const UCHAR state = (number > dbb->dbb_next_transaction) ?
255 : limbo_transaction(tdbb, number);
2001-05-23 15:26:42 +02:00
2009-08-23 11:49:58 +02:00
if (state != tra_limbo)
{
USHORT message;
2009-01-20 09:33:59 +01:00
switch (state)
{
2001-05-23 15:26:42 +02:00
case tra_active:
2009-08-21 11:45:08 +02:00
message = 262; // ACTIVE
2001-05-23 15:26:42 +02:00
break;
case tra_dead:
2009-08-21 11:45:08 +02:00
message = 264; // ROLLED BACK
2001-05-23 15:26:42 +02:00
break;
case tra_committed:
2009-08-21 11:45:08 +02:00
message = 263; // COMMITTED
2001-05-23 15:26:42 +02:00
break;
default:
2009-08-21 11:45:08 +02:00
message = 265; // ILL DEFINED
2001-05-23 15:26:42 +02:00
break;
}
TEXT text[128];
2003-12-31 06:36:12 +01:00
USHORT flags = 0;
2008-03-12 08:33:12 +01:00
gds__msg_lookup(NULL, JRD_BUGCHK, message, sizeof(text), text, &flags);
2001-05-23 15:26:42 +02:00
ERR_post(Arg::Gds(isc_no_recon) <<
2008-12-25 07:09:37 +01:00
Arg::Gds(isc_tra_state) << Arg::Num(number) << Arg::Str(text));
2001-05-23 15:26:42 +02:00
}
MemoryPool* const pool = dbb->createPool();
Jrd::ContextPoolHolder context(tdbb, pool);
jrd_tra* const trans = jrd_tra::create(pool, attachment, NULL);
trans->tra_number = number;
trans->tra_flags |= TRA_prepared | TRA_reconnected | TRA_write;
link_transaction(tdbb, trans);
2001-05-23 15:26:42 +02:00
return trans;
}
void TRA_release_transaction(thread_db* tdbb, jrd_tra* transaction, TraceTransactionEnd* trace)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* T R A _ r e l e a s e _ t r a n s a c t i o n
*
**************************************
*
* Functional description
* Cleanup a transaction. This is called by both COMMIT and
* ROLLBACK as well as code in JRD to get rid of remote
* transactions.
*
**************************************/
SET_TDBB(tdbb);
2008-01-29 11:11:52 +01:00
Database* dbb = tdbb->getDatabase();
Attachment* attachment = tdbb->getAttachment();
2001-05-23 15:26:42 +02:00
if (!transaction->tra_outer)
{
if (transaction->tra_blobs->getFirst())
2006-04-06 10:18:53 +02:00
{
while (true)
{
BlobIndex *current = &transaction->tra_blobs->current();
2009-08-23 11:49:58 +02:00
if (current->bli_materialized)
{
if (!transaction->tra_blobs->getNext())
break;
}
2009-08-23 11:49:58 +02:00
else
{
ULONG temp_id = current->bli_temp_id;
BLB_cancel(tdbb, current->bli_blob_object);
if (!transaction->tra_blobs->locate(Firebird::locGreat, temp_id))
break;
}
2006-04-06 10:18:53 +02:00
}
}
2001-05-23 15:26:42 +02:00
while (transaction->tra_arrays)
BLB_release_array(transaction->tra_arrays);
}
2001-05-23 15:26:42 +02:00
2009-08-23 11:49:58 +02:00
if (transaction->tra_pool)
{
// Iterate the doubly linked list of requests for transaction and null out the transaction references
while (transaction->tra_requests)
TRA_detach_request(transaction->tra_requests);
}
2001-05-23 15:26:42 +02:00
2006-07-18 04:45:35 +02:00
// Release interest in relation/procedure existence for transaction
2001-05-23 15:26:42 +02:00
2004-05-09 07:48:33 +02:00
for (Resource* rsc = transaction->tra_resources.begin();
rsc < transaction->tra_resources.end(); rsc++)
{
2009-01-20 09:33:59 +01:00
switch (rsc->rsc_type)
{
2009-03-31 06:14:31 +02:00
case Resource::rsc_relation:
MET_release_existence(tdbb, rsc->rsc_rel);
if (rsc->rsc_rel->rel_file) {
EXT_tra_detach(rsc->rsc_rel->rel_file, transaction);
}
2009-03-31 06:14:31 +02:00
break;
case Resource::rsc_procedure:
2001-05-23 15:26:42 +02:00
CMP_decrement_prc_use_count(tdbb, rsc->rsc_prc);
break;
case Resource::rsc_collation:
rsc->rsc_coll->decUseCount(tdbb);
break;
2001-05-23 15:26:42 +02:00
default:
2009-03-31 06:14:31 +02:00
fb_assert(false);
2001-05-23 15:26:42 +02:00
}
2003-12-31 06:36:12 +01:00
}
2001-05-23 15:26:42 +02:00
2008-02-24 04:23:40 +01:00
{ // scope
2008-01-29 11:11:52 +01:00
vec<jrd_rel*>& rels = *dbb->dbb_relations;
2006-05-25 10:40:23 +02:00
for (size_t i = 0; i < rels.count(); i++)
2006-05-22 00:07:35 +02:00
{
2006-05-25 10:40:23 +02:00
jrd_rel* relation = rels[i];
2006-05-22 00:07:35 +02:00
if (relation && (relation->rel_flags & REL_temp_tran))
{
relation->delPages(tdbb, transaction->tra_number);
}
}
2008-02-24 04:23:40 +01:00
} // end scope
2006-05-22 00:07:35 +02:00
2006-07-18 04:45:35 +02:00
// Release the locks associated with the transaction
2001-05-23 15:26:42 +02:00
vec<Lock*>* vector = transaction->tra_relation_locks;
2009-08-23 11:49:58 +02:00
if (vector)
{
vec<Lock*>::iterator lock = vector->begin();
for (ULONG i = 0; i < vector->count(); ++i, ++lock)
2003-12-31 06:36:12 +01:00
{
2001-05-23 15:26:42 +02:00
if (*lock)
LCK_release(tdbb, *lock);
2003-12-31 06:36:12 +01:00
}
}
2001-05-23 15:26:42 +02:00
++transaction->tra_use_count;
if (transaction->tra_lock)
LCK_release(tdbb, transaction->tra_lock);
--transaction->tra_use_count;
2006-07-18 04:45:35 +02:00
// release the sparse bit map used for commit retain transaction
2001-05-23 15:26:42 +02:00
delete transaction->tra_commit_sub_trans;
2001-05-23 15:26:42 +02:00
if (transaction->tra_flags & TRA_precommitted)
TRA_precommited(tdbb, transaction->tra_number, 0);
2001-05-23 15:26:42 +02:00
if (trace)
trace->finish(res_successful);
2006-07-18 04:45:35 +02:00
// Unlink the transaction from the database block
2001-05-23 15:26:42 +02:00
2008-03-08 22:20:26 +01:00
for (jrd_tra** ptr = &attachment->att_transactions; *ptr; ptr = &(*ptr)->tra_next)
{
2009-08-23 11:49:58 +02:00
if (*ptr == transaction)
{
2001-05-23 15:26:42 +02:00
*ptr = transaction->tra_next;
break;
}
}
2008-02-28 14:48:16 +01:00
// Release transaction's under-modification-rpb list
delete transaction->tra_rpblist;
2001-05-23 15:26:42 +02:00
2006-07-18 04:45:35 +02:00
// Release the database snapshot, if any
delete transaction->tra_db_snapshot;
2008-02-28 14:48:16 +01:00
// Close all open DSQL cursors
while (transaction->tra_open_cursors.getCount())
{
DSQL_free_statement(tdbb, transaction->tra_open_cursors.pop(), DSQL_close);
}
// Release the transaction and its pool
2001-05-23 15:26:42 +02:00
jrd_tra::destroy(dbb, transaction);
2001-05-23 15:26:42 +02:00
}
void TRA_rollback(thread_db* tdbb, jrd_tra* transaction, const bool retaining_flag,
const bool force_flag)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* T R A _ r o l l b a c k
*
**************************************
*
* Functional description
* Rollback a transaction.
*
**************************************/
SET_TDBB(tdbb);
2009-02-01 23:10:12 +01:00
TraceTransactionEnd trace(transaction, false, retaining_flag);
EDS::Transaction::jrdTransactionEnd(tdbb, transaction, false, retaining_flag, false /*force_flag ?*/);
Jrd::ContextPoolHolder context(tdbb, transaction->tra_pool);
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// Check in with external file system
2001-05-23 15:26:42 +02:00
EXT_trans_rollback(transaction);
if (transaction->tra_flags & (TRA_prepare2 | TRA_reconnected))
2003-12-22 11:00:59 +01:00
MET_update_transaction(tdbb, transaction, false);
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// If force flag is true, get rid of all savepoints to mark the transaction as dead
if (force_flag || transaction->tra_flags & TRA_invalidated)
2009-02-02 04:35:52 +01:00
{
// Free all savepoint data
// We can do it in reverse order because nothing except simple deallocation
// of memory is really done in VIO_verb_cleanup when we pass NULL as sav_next
while (transaction->tra_save_point)
{
Savepoint* const next = transaction->tra_save_point->sav_next;
transaction->tra_save_point->sav_next = NULL;
VIO_verb_cleanup(tdbb, transaction);
transaction->tra_save_point = next;
}
}
else
{
VIO_temp_cleanup(tdbb, transaction);
}
2009-08-21 11:45:08 +02:00
// Find out if there is a transaction savepoint we can use to rollback our transaction
2003-12-31 06:36:12 +01:00
bool tran_sav = false;
for (const Savepoint* temp = transaction->tra_save_point; temp; temp = temp->sav_next)
{
2009-08-23 11:49:58 +02:00
if (temp->sav_flags & SAV_trans_level)
{
2003-12-31 06:36:12 +01:00
tran_sav = true;
2002-11-03 18:29:51 +01:00
break;
}
}
2009-08-21 11:45:08 +02:00
// Measure transaction savepoint size if there is one. We'll use it for undo
// only if it is small enough
2004-09-28 23:50:10 +02:00
IPTR count = SAV_LARGE;
2009-08-23 11:49:58 +02:00
if (tran_sav)
{
for (const Savepoint* temp = transaction->tra_save_point; temp; temp = temp->sav_next)
{
2002-11-03 18:29:51 +01:00
count = VIO_savepoint_large(temp, count);
if (count < 0)
break;
}
}
2002-11-03 18:29:51 +01:00
// We are going to use savepoint to undo transaction
2009-08-23 11:49:58 +02:00
if (tran_sav && count > 0)
{
2002-11-03 18:29:51 +01:00
// Undo all user savepoints work
2009-08-23 11:49:58 +02:00
while (transaction->tra_save_point->sav_flags & SAV_user)
{
2009-08-21 11:45:08 +02:00
++transaction->tra_save_point->sav_verb_count; // cause undo
VIO_verb_cleanup(tdbb, transaction);
}
2003-12-31 06:36:12 +01:00
}
2009-08-23 11:49:58 +02:00
else
{
2002-11-03 18:29:51 +01:00
// Free all savepoint data
// We can do it in reverse order because nothing except simple deallocation
// of memory is really done in VIO_verb_cleanup when we pass NULL as sav_next
while (transaction->tra_save_point && transaction->tra_save_point->sav_flags & SAV_user)
{
Savepoint* const next = transaction->tra_save_point->sav_next;
2002-11-03 18:29:51 +01:00
transaction->tra_save_point->sav_next = NULL;
VIO_verb_cleanup(tdbb, transaction);
transaction->tra_save_point = next;
}
2009-08-23 11:49:58 +02:00
if (transaction->tra_save_point)
{
2002-11-03 18:29:51 +01:00
if (!(transaction->tra_save_point->sav_flags & SAV_trans_level))
2009-08-21 11:45:08 +02:00
BUGCHECK(287); // Too many savepoints
// This transaction savepoint contains wrong data now. Clean it up
VIO_verb_cleanup(tdbb, transaction); // get rid of transaction savepoint
2002-11-03 18:29:51 +01:00
}
}
SSHORT state = tra_dead;
2002-11-03 18:29:51 +01:00
// Only transaction savepoint could be there
if (transaction->tra_save_point)
{
2001-05-23 15:26:42 +02:00
if (!(transaction->tra_save_point->sav_flags & SAV_trans_level))
2009-08-21 11:45:08 +02:00
BUGCHECK(287); // Too many savepoints
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// Make sure that any error during savepoint undo is handled by marking
// the transaction as dead.
2001-05-23 15:26:42 +02:00
2001-12-24 03:51:06 +01:00
try {
2009-08-21 11:45:08 +02:00
// In an attempt to avoid deadlocks, clear the precedence by writing
// all dirty buffers for this transaction.
2001-05-23 15:26:42 +02:00
2009-08-23 11:49:58 +02:00
if (transaction->tra_flags & TRA_write)
{
CCH_flush(tdbb, FLUSH_TRAN, transaction->tra_number);
2009-08-21 11:45:08 +02:00
++transaction->tra_save_point->sav_verb_count; // cause undo
2001-05-23 15:26:42 +02:00
VIO_verb_cleanup(tdbb, transaction);
CCH_flush(tdbb, FLUSH_TRAN, transaction->tra_number);
2001-05-23 15:26:42 +02:00
}
else
VIO_verb_cleanup(tdbb, transaction);
// All changes are undone, so we may mark the transaction
// as committed
state = tra_committed;
2001-05-23 15:26:42 +02:00
}
2009-08-23 11:49:58 +02:00
catch (const Firebird::Exception&)
{
2009-08-21 11:45:08 +02:00
// Prevent a bugcheck in TRA_set_state to cause a loop
// Clear the error because the rollback will succeed.
fb_utils::init_status(tdbb->tdbb_status_vector);
2001-12-24 03:51:06 +01:00
}
2001-05-23 15:26:42 +02:00
}
2009-08-23 11:49:58 +02:00
else if (!(transaction->tra_flags & TRA_write))
{
// There were no changes within the transaction, so we may mark it
// as committed
state = tra_committed;
2001-05-23 15:26:42 +02:00
}
// If this is a rollback retain abort this transaction and start a new one.
2001-05-23 15:26:42 +02:00
2009-04-04 18:39:31 +02:00
if (retaining_flag)
2009-02-01 23:10:12 +01:00
{
trace.finish(res_successful);
retain_context(tdbb, transaction, false, state);
2001-05-23 15:26:42 +02:00
return;
}
2008-02-24 04:23:40 +01:00
TRA_set_state(tdbb, transaction, transaction->tra_number, state);
2009-02-01 23:10:12 +01:00
TRA_release_transaction(tdbb, transaction, &trace);
2001-05-23 15:26:42 +02:00
}
void TRA_set_state(thread_db* tdbb, jrd_tra* transaction, SLONG number, SSHORT state)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* T R A _ s e t _ s t a t e
*
**************************************
*
* Functional description
* Set the state of a transaction in the inventory page.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
2001-05-23 15:26:42 +02:00
CHECK_DBB(dbb);
2009-08-21 11:45:08 +02:00
// If we're terminating ourselves and we've been precommitted then just return.
2001-05-23 15:26:42 +02:00
2008-12-25 07:09:37 +01:00
if (transaction && transaction->tra_number == number && transaction->tra_flags & TRA_precommitted)
2003-12-31 06:36:12 +01:00
{
return;
}
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// If it is a ReadOnly DB, set the new state in the TIP cache and return
2009-08-23 11:49:58 +02:00
if ((dbb->dbb_flags & DBB_read_only) && dbb->dbb_tip_cache)
{
2001-05-23 15:26:42 +02:00
TPC_set_state(tdbb, number, state);
return;
}
2006-05-25 10:40:23 +02:00
const ULONG trans_per_tip = dbb->dbb_page_manager.transPerTIP;
2003-12-31 06:36:12 +01:00
const SLONG sequence = number / trans_per_tip;
2006-05-22 00:07:35 +02:00
//trans_per_tip = dbb->dbb_page_manager.transPerTIP;
2003-12-31 06:36:12 +01:00
const ULONG byte = TRANS_OFFSET(number % trans_per_tip);
const SSHORT shift = TRANS_SHIFT(number);
2001-05-23 15:26:42 +02:00
2006-05-22 00:07:35 +02:00
WIN window(DB_PAGE_SPACE, -1);
tx_inv_page* tip = fetch_inventory_page(tdbb, &window, (SLONG) sequence, LCK_write);
2001-05-23 15:26:42 +02:00
#ifdef SUPERSERVER_V2
CCH_MARK(tdbb, &window);
const ULONG generation = tip->pag_generation;
2001-05-23 15:26:42 +02:00
#else
CCH_MARK_MUST_WRITE(tdbb, &window);
#endif
2009-08-21 11:45:08 +02:00
// set the state on the TIP page
2001-05-23 15:26:42 +02:00
2003-12-31 06:36:12 +01:00
UCHAR* address = tip->tip_transactions + byte;
2001-05-23 15:26:42 +02:00
*address &= ~(TRA_MASK << shift);
*address |= state << shift;
2009-08-21 11:45:08 +02:00
// set the new state in the TIP cache as well
2001-05-23 15:26:42 +02:00
if (dbb->dbb_tip_cache)
TPC_set_state(tdbb, number, state);
CCH_RELEASE(tdbb, &window);
#ifdef SUPERSERVER_V2
2009-08-21 11:45:08 +02:00
// Let the TIP be lazily updated for read-only queries.
// To amortize write of TIP page for update transactions,
// exit engine to allow other transactions to update the TIP
// and use page generation to determine if page was written.
2001-05-23 15:26:42 +02:00
if (transaction && !(transaction->tra_flags & TRA_write))
return;
2008-02-24 04:23:40 +01:00
{ //scope
Database::Checkout dcoHolder(dbb);
THREAD_YIELD();
2001-05-23 15:26:42 +02:00
}
2008-02-24 04:23:40 +01:00
tip = reinterpret_cast<tx_inv_page*>(CCH_FETCH(tdbb, &window, LCK_write, pag_transactions));
if (generation == tip->pag_generation)
CCH_MARK_MUST_WRITE(tdbb, &window);
CCH_RELEASE(tdbb, &window);
2001-05-23 15:26:42 +02:00
#endif
}
int TRA_snapshot_state(thread_db* tdbb, const jrd_tra* trans, SLONG number)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* T R A _ s n a p s h o t _ s t a t e
*
**************************************
*
* Functional description
* Get the state of a numbered transaction when a
* transaction started.
*
**************************************/
SET_TDBB(tdbb);
if (number && TRA_precommited(tdbb, number, number))
return tra_precommitted;
if (number == trans->tra_number)
return tra_us;
2005-11-06 04:20:18 +01:00
// If the transaction is older than the oldest
// interesting transaction, it must be committed.
2001-05-23 15:26:42 +02:00
if (number < trans->tra_oldest)
return tra_committed;
2005-11-06 04:20:18 +01:00
// If the transaction is the system transaction, it is considered committed.
2001-05-23 15:26:42 +02:00
2005-11-06 04:20:18 +01:00
if (number == TRA_system_transaction)
2001-05-23 15:26:42 +02:00
return tra_committed;
2005-11-06 04:20:18 +01:00
// Look in the transaction cache for read committed transactions
// fast, and the system transaction. The system transaction can read
// data from active transactions.
2001-05-23 15:26:42 +02:00
if (trans->tra_flags & TRA_read_committed)
return TPC_snapshot_state(tdbb, number);
2005-11-06 04:20:18 +01:00
if (trans->tra_flags & TRA_system)
{
int state = TPC_snapshot_state(tdbb, number);
if (state == tra_active)
return tra_committed;
2008-02-24 04:23:40 +01:00
return state;
2005-11-06 04:20:18 +01:00
}
// If the transaction is a commited sub-transction - do the easy lookup.
if (trans->tra_commit_sub_trans && UInt32Bitmap::test(trans->tra_commit_sub_trans, number))
{
return tra_committed;
}
2001-05-23 15:26:42 +02:00
2005-11-06 04:20:18 +01:00
// If the transaction is younger than we are and we are not read committed
// or the system transaction, the transaction must be considered active.
2001-05-23 15:26:42 +02:00
if (number > trans->tra_top)
return tra_active;
return TRA_state(trans->tra_transactions.begin(), trans->tra_oldest, number);
2001-05-23 15:26:42 +02:00
}
jrd_tra* TRA_start(thread_db* tdbb, ULONG flags, SSHORT lock_timeout, Jrd::jrd_tra* outer)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* T R A _ s t a r t
*
**************************************
*
* Functional description
* Start a user transaction.
*
**************************************/
SET_TDBB(tdbb);
Database* const dbb = tdbb->getDatabase();
Attachment* const attachment = tdbb->getAttachment();
2001-05-23 15:26:42 +02:00
2008-01-16 13:22:11 +01:00
if (dbb->dbb_ast_flags & DBB_shut_tran)
{
ERR_post(Arg::Gds(isc_shutinprog) << Arg::Str(attachment->att_filename));
2003-12-31 06:36:12 +01:00
}
2001-05-23 15:26:42 +02:00
2008-01-16 13:22:11 +01:00
// To handle the problems of relation locks, allocate a temporary
// transaction block first, seize relation locks, then go ahead and
// make up the real transaction block.
MemoryPool* const pool = outer ? outer->getAutonomousPool() : dbb->createPool();
Jrd::ContextPoolHolder context(tdbb, pool);
jrd_tra* const temp = jrd_tra::create(pool, attachment, outer);
2001-05-23 15:26:42 +02:00
temp->tra_flags = flags & TRA_OPTIONS_MASK;
2008-01-16 13:22:11 +01:00
temp->tra_lock_timeout = lock_timeout;
jrd_tra* transaction = NULL;
2009-04-04 18:39:31 +02:00
try
{
transaction = transaction_start(tdbb, temp);
delete temp;
}
catch (const Exception&)
{
jrd_tra::destroy(dbb, temp);
throw;
}
2009-02-01 23:10:12 +01:00
if (attachment->att_trace_manager->needs().event_transaction_start)
{
TraceConnectionImpl conn(attachment);
TraceTransactionImpl tran(transaction);
attachment->att_trace_manager->event_transaction_start(&conn,
&tran, 0, NULL, res_successful);
}
2009-02-02 04:35:52 +01:00
2009-02-01 23:10:12 +01:00
return transaction;
2008-01-16 13:22:11 +01:00
}
2001-05-23 15:26:42 +02:00
jrd_tra* TRA_start(thread_db* tdbb, int tpb_length, const UCHAR* tpb, Jrd::jrd_tra* outer)
2008-01-16 13:22:11 +01:00
{
/**************************************
*
* T R A _ s t a r t
*
**************************************
*
* Functional description
* Start a user transaction.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
Attachment* attachment = tdbb->getAttachment();
2001-05-23 15:26:42 +02:00
2008-01-16 13:22:11 +01:00
if (dbb->dbb_ast_flags & DBB_shut_tran)
{
ERR_post(Arg::Gds(isc_shutinprog) << Arg::Str(attachment->att_filename));
2001-05-23 15:26:42 +02:00
}
2008-01-16 13:22:11 +01:00
// To handle the problems of relation locks, allocate a temporary
// transaction block first, seize relation locks, then go ahead and
// make up the real transaction block.
MemoryPool* const pool = outer ? outer->getAutonomousPool() : dbb->createPool();
Jrd::ContextPoolHolder context(tdbb, pool);
jrd_tra* const temp = jrd_tra::create(pool, attachment, outer);
2001-05-23 15:26:42 +02:00
jrd_tra* transaction = NULL;
try
{
transaction_options(tdbb, temp, tpb, tpb_length);
transaction = transaction_start(tdbb, temp);
delete temp;
}
catch (const Exception&)
{
jrd_tra::destroy(dbb, temp);
throw;
}
2009-02-01 23:10:12 +01:00
if (attachment->att_trace_manager->needs().event_transaction_start)
{
TraceConnectionImpl conn(attachment);
TraceTransactionImpl tran(transaction);
attachment->att_trace_manager->event_transaction_start(&conn,
&tran, tpb_length, tpb, res_successful);
}
2009-02-02 04:35:52 +01:00
2009-02-01 23:10:12 +01:00
return transaction;
2008-01-16 13:22:11 +01:00
}
2001-05-23 15:26:42 +02:00
2008-01-16 13:22:11 +01:00
int TRA_state(const UCHAR* bit_vector, ULONG oldest, ULONG number)
{
/**************************************
*
* T R A _ s t a t e
*
**************************************
*
* Functional description
* Get the state of a transaction from a cached
* bit vector.
* NOTE: This code is reproduced elsewhere in
* this module for speed. If changes are made
* to this code make them in the replicated code also.
*
**************************************/
const ULONG base = oldest & ~TRA_MASK;
const ULONG byte = TRANS_OFFSET(number - base);
const USHORT shift = TRANS_SHIFT(number);
2001-05-23 15:26:42 +02:00
2008-01-16 13:22:11 +01:00
return (bit_vector[byte] >> shift) & TRA_MASK;
}
2001-05-23 15:26:42 +02:00
void TRA_sweep(thread_db* tdbb)
2008-01-16 13:22:11 +01:00
{
/**************************************
*
* T R A _ s w e e p
*
**************************************
*
* Functional description
* Make a garbage collection pass thru database.
*
**************************************/
SET_TDBB(tdbb);
Database* const dbb = tdbb->getDatabase();
2008-01-16 13:22:11 +01:00
CHECK_DBB(dbb);
2001-05-23 15:26:42 +02:00
if (!dbb->allowSweepRun(tdbb))
2008-01-16 13:22:11 +01:00
{
dbb->clearSweepFlags(tdbb);
return;
2008-01-16 13:22:11 +01:00
}
2001-05-23 15:26:42 +02:00
fb_assert(dbb->dbb_flags & DBB_sweep_in_progress);
Attachment* const attachment = tdbb->getAttachment();
2001-05-23 15:26:42 +02:00
2008-01-16 13:22:11 +01:00
jrd_tra* const tdbb_old_trans = tdbb->getTransaction();
2008-02-13 17:46:11 +01:00
jrd_tra* transaction = NULL;
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// Clean up the temporary locks we've gotten in case anything goes wrong
2001-05-23 15:26:42 +02:00
2008-01-16 13:22:11 +01:00
try {
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// Identify ourselves as a sweeper thread. This accomplishes two goals:
// 1) Sweep transaction is started "precommitted" and
// 2) Execution is throttled in JRD_reschedule() by
// yielding the processor when our quantum expires.
2001-05-23 15:26:42 +02:00
2008-01-16 13:22:11 +01:00
tdbb->tdbb_flags |= TDBB_sweeper;
2001-05-23 15:26:42 +02:00
TraceSweepEvent traceSweep(tdbb);
2012-08-28 20:00:22 +02:00
// Start a transaction to perform the sweep.
2009-08-21 11:45:08 +02:00
// Save the transaction's oldest snapshot as it is refreshed
// during the course of the database sweep. Since it is used
// below to advance the OIT we must save it before it changes.
transaction = TRA_start(tdbb, sizeof(sweep_tpb), sweep_tpb);
2001-05-23 15:26:42 +02:00
SLONG transaction_oldest_active = transaction->tra_oldest_active;
tdbb->setTransaction(transaction);
2005-10-20 15:03:33 +02:00
2001-05-23 15:26:42 +02:00
#ifdef GARBAGE_THREAD
2009-08-21 11:45:08 +02:00
// The garbage collector runs asynchronously with respect to
// our database sweep. This isn't good enough since we must
// be absolutely certain that all dead transactions have been
// swept from the database before advancing the OIT. Turn off
// the "notify garbage collector" flag for the attachment and
// synchronously perform the garbage collection ourselves.
2001-05-23 15:26:42 +02:00
attachment->att_flags &= ~ATT_notify_gc;
2001-05-23 15:26:42 +02:00
#endif
if (VIO_sweep(tdbb, transaction, &traceSweep))
2009-08-23 11:49:58 +02:00
{
// At this point, we know that no record versions belonging to dead
// transactions remain anymore. However, there may still be limbo
// transactions, so we need to find the oldest one between tra_oldest and tra_top.
// As our transaction is read-committed (see sweep_tpb), we have to scan
// the global TIP cache.
const SLONG oldest_limbo =
TPC_find_limbo(tdbb, transaction->tra_oldest, transaction->tra_top - 1);
const SLONG active = oldest_limbo ? oldest_limbo : transaction->tra_top;
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// Flush page buffers to insure that no dangling records from
// dead transactions are left on-disk. This must be done before
// the OIT is advanced and the header page is written to disk.
// If the header page was written before flushing the page buffers
// and there was a server crash, the dead records would appear
// committed since their TID would now be less than the OIT recorded
// in the database.
2001-05-23 15:26:42 +02:00
CCH_flush(tdbb, FLUSH_SWEEP, 0);
2001-05-23 15:26:42 +02:00
2006-05-22 00:07:35 +02:00
WIN window(HEADER_PAGE_NUMBER);
2008-12-25 07:09:37 +01:00
header_page* header = (header_page*) CCH_FETCH(tdbb, &window, LCK_write, pag_header);
2001-05-23 15:26:42 +02:00
2009-08-23 11:49:58 +02:00
if (header->hdr_oldest_transaction < --transaction_oldest_active)
{
2001-05-23 15:26:42 +02:00
CCH_MARK_MUST_WRITE(tdbb, &window);
header->hdr_oldest_transaction = MIN((ULONG) active, (ULONG) transaction_oldest_active);
2001-05-23 15:26:42 +02:00
}
traceSweep.update(header);
2001-05-23 15:26:42 +02:00
CCH_RELEASE(tdbb, &window);
traceSweep.finish();
2001-05-23 15:26:42 +02:00
}
TRA_commit(tdbb, transaction, false);
2001-05-23 15:26:42 +02:00
tdbb->tdbb_flags &= ~TDBB_sweeper;
tdbb->setTransaction(tdbb_old_trans);
dbb->clearSweepFlags(tdbb);
2001-12-24 03:51:06 +01:00
} // try
2009-08-23 11:49:58 +02:00
catch (const Firebird::Exception& ex)
{
iscLogException("Error during sweep:", ex);
Firebird::stuff_exception(tdbb->tdbb_status_vector, ex);
if (transaction)
2009-08-23 11:49:58 +02:00
{
try
{
TRA_commit(tdbb, transaction, false);
}
catch (const Firebird::Exception& ex2)
{
Firebird::stuff_exception(tdbb->tdbb_status_vector, ex2);
}
2001-12-24 03:51:06 +01:00
}
tdbb->tdbb_flags &= ~TDBB_sweeper;
tdbb->setTransaction(tdbb_old_trans);
dbb->clearSweepFlags(tdbb);
throw;
}
2001-05-23 15:26:42 +02:00
}
int TRA_wait(thread_db* tdbb, jrd_tra* trans, SLONG number, jrd_tra::wait_t wait)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* T R A _ w a i t
2001-05-23 15:26:42 +02:00
*
**************************************
*
* Functional description
* Wait for a given transaction to drop into a stable state (i.e. non-active)
* state. To do this, we first wait on the transaction number. When we
* are able to get the lock, the transaction is not longer bona fide
* active. Next, we determine the state of the transaction from the
* transaction inventory page. If either committed, dead, or limbo,
* we return the state. If the transaction is still marked active,
* however, declare the transaction dead, and mark the transaction
* inventory page accordingly.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
2001-05-23 15:26:42 +02:00
CHECK_DBB(dbb);
2009-08-21 11:45:08 +02:00
// Create, wait on, and release lock on target transaction. If
// we can't get the lock due to deadlock
2001-05-23 15:26:42 +02:00
2009-08-23 11:49:58 +02:00
if (wait != jrd_tra::tra_no_wait)
{
Lock temp_lock;
2001-05-23 15:26:42 +02:00
temp_lock.lck_dbb = dbb;
temp_lock.lck_type = LCK_tra;
2008-12-25 07:09:37 +01:00
temp_lock.lck_owner_handle = LCK_get_owner_handle(tdbb, temp_lock.lck_type);
2001-05-23 15:26:42 +02:00
temp_lock.lck_parent = dbb->dbb_lock;
temp_lock.lck_length = sizeof(SLONG);
temp_lock.lck_key.lck_long = number;
const SSHORT timeout = (wait == jrd_tra::tra_wait) ? trans->getLockWait() : 0;
if (!LCK_lock(tdbb, &temp_lock, LCK_read, timeout))
2001-05-23 15:26:42 +02:00
return tra_active;
LCK_release(tdbb, &temp_lock);
}
2003-12-31 06:36:12 +01:00
USHORT state = TRA_get_state(tdbb, number);
2001-05-23 15:26:42 +02:00
if (wait != jrd_tra::tra_no_wait && state == tra_committed)
2001-05-23 15:26:42 +02:00
return state;
if (state == tra_precommitted)
return state;
2009-08-21 11:45:08 +02:00
// If the recorded state of the transaction is active, we know better. If
// it were active, he'd be alive now. Mark him dead.
2001-05-23 15:26:42 +02:00
2009-08-23 11:49:58 +02:00
if (state == tra_active)
{
2001-05-23 15:26:42 +02:00
state = tra_dead;
TRA_set_state(tdbb, 0, number, tra_dead);
}
if (number > trans->tra_top)
return state;
2009-08-21 11:45:08 +02:00
// If the transaction disppeared into limbo, died, for constructively
// died, tweak the transaction state snapshot to reflect the new state.
// This is guarenteed safe.
2001-05-23 15:26:42 +02:00
2003-12-31 06:36:12 +01:00
const ULONG byte = TRANS_OFFSET(number - (trans->tra_oldest & ~TRA_MASK));
const USHORT shift = TRANS_SHIFT(number);
2001-05-23 15:26:42 +02:00
if (trans->tra_flags & TRA_read_committed)
TPC_set_state(tdbb, number, state);
2009-08-23 11:49:58 +02:00
else
{
2001-05-23 15:26:42 +02:00
trans->tra_transactions[byte] &= ~(TRA_MASK << shift);
trans->tra_transactions[byte] |= state << shift;
}
return state;
}
#ifdef SUPERSERVER_V2
2009-04-26 12:24:44 +02:00
static SLONG bump_transaction_id(thread_db* tdbb, WIN* window)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* b u m p _ t r a n s a c t i o n _ i d
*
**************************************
*
* Functional description
* Fetch header and bump next transaction id. If necessary,
* extend TIP.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
2001-05-23 15:26:42 +02:00
CHECK_DBB(dbb);
if (dbb->dbb_next_transaction >= MAX_TRA_NUMBER - 1)
{
CCH_RELEASE(tdbb, window);
ERR_post(Arg::Gds(isc_imp_exc) <<
Arg::Gds(isc_tra_num_exc));
}
const SLONG number = ++dbb->dbb_next_transaction;
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// No need to write TID onto the TIP page, for a RO DB
2001-05-23 15:26:42 +02:00
if (dbb->dbb_flags & DBB_read_only)
return number;
2009-08-21 11:45:08 +02:00
// If this is the first transaction on a TIP, allocate the TIP now.
2001-05-23 15:26:42 +02:00
2008-03-08 22:20:26 +01:00
const bool new_tip = (number == 1 || (number % dbb->dbb_page_manager.transPerTIP) == 0);
2003-12-31 06:36:12 +01:00
if (new_tip) {
2009-04-26 12:24:44 +02:00
TRA_extend_tip(tdbb, (ULONG) (number / dbb->dbb_page_manager.transPerTIP)); //, window);
2003-12-31 06:36:12 +01:00
}
2001-05-23 15:26:42 +02:00
return number;
}
#else
2009-04-26 12:24:44 +02:00
static header_page* bump_transaction_id(thread_db* tdbb, WIN* window)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* b u m p _ t r a n s a c t i o n _ i d
*
**************************************
*
* Functional description
* Fetch header and bump next transaction id. If necessary,
* extend TIP.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
2001-05-23 15:26:42 +02:00
CHECK_DBB(dbb);
2006-05-22 00:07:35 +02:00
window->win_page = HEADER_PAGE_NUMBER;
header_page* header = (header_page*) CCH_FETCH(tdbb, window, LCK_write, pag_header);
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// Before incrementing the next transaction Id, make sure the current one is valid
2009-08-23 11:49:58 +02:00
if (header->hdr_next_transaction)
{
2001-05-23 15:26:42 +02:00
if (header->hdr_oldest_active > header->hdr_next_transaction)
2009-08-21 11:45:08 +02:00
BUGCHECK(266); //next transaction older than oldest active
2001-05-23 15:26:42 +02:00
if (header->hdr_oldest_transaction > header->hdr_next_transaction)
2009-08-21 11:45:08 +02:00
BUGCHECK(267); // next transaction older than oldest transaction
2001-05-23 15:26:42 +02:00
}
if (header->hdr_next_transaction >= MAX_TRA_NUMBER - 1)
{
CCH_RELEASE(tdbb, window);
ERR_post(Arg::Gds(isc_imp_exc) <<
Arg::Gds(isc_tra_num_exc));
}
const SLONG number = header->hdr_next_transaction + 1;
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// If this is the first transaction on a TIP, allocate the TIP now.
2001-05-23 15:26:42 +02:00
2008-03-08 22:20:26 +01:00
const bool new_tip = (number == 1 || (number % dbb->dbb_page_manager.transPerTIP) == 0);
2003-12-31 06:36:12 +01:00
if (new_tip) {
2009-04-26 12:24:44 +02:00
TRA_extend_tip(tdbb, (ULONG) (number / dbb->dbb_page_manager.transPerTIP)); //, window);
2003-12-31 06:36:12 +01:00
}
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// Extend, if necessary, has apparently succeeded. Next, update header page
2001-05-23 15:26:42 +02:00
CCH_MARK_MUST_WRITE(tdbb, window);
header->hdr_next_transaction = number;
if (dbb->dbb_oldest_active > header->hdr_oldest_active)
header->hdr_oldest_active = dbb->dbb_oldest_active;
if (dbb->dbb_oldest_transaction > header->hdr_oldest_transaction)
header->hdr_oldest_transaction = dbb->dbb_oldest_transaction;
if (dbb->dbb_oldest_snapshot > header->hdr_oldest_snapshot)
header->hdr_oldest_snapshot = dbb->dbb_oldest_snapshot;
return header;
}
#endif
static Lock* create_transaction_lock(thread_db* tdbb, void* object)
{
/**************************************
*
* c r e a t e _ t r a n s a c t i o n _ l o c k
*
**************************************
*
* Functional description
* Allocate a transaction lock block.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
Lock* lock = FB_NEW_RPT(*tdbb->getDefaultPool(), sizeof(SLONG)) Lock();
lock->lck_type = LCK_tra;
lock->lck_owner_handle = LCK_get_owner_handle(tdbb, lock->lck_type);
lock->lck_length = sizeof(SLONG);
lock->lck_dbb = dbb;
lock->lck_parent = dbb->dbb_lock;
lock->lck_object = object;
return lock;
}
2001-05-23 15:26:42 +02:00
#ifdef VMS
2008-12-25 07:09:37 +01:00
static void compute_oldest_retaining(thread_db* tdbb, jrd_tra* transaction, const bool write_flag)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* c o m p u t e _ o l d e s t _ r e t a i n i n g
*
**************************************
*
* Functional description
* Read the oldest active for all transactions
* younger than us up to the youngest retaining
* transaction. If an "older" oldest active is
2009-06-07 11:49:58 +02:00
* found, by all means use it. Write flag is true
* to write retaining lock and false to read it.
2001-05-23 15:26:42 +02:00
* The retaining lock holds the youngest commit
* retaining transaction.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
2001-05-23 15:26:42 +02:00
CHECK_DBB(dbb);
2009-08-21 11:45:08 +02:00
// Get a commit retaining lock, if not present.
2001-05-23 15:26:42 +02:00
Lock* lock = dbb->dbb_retaining_lock;
2009-08-23 11:49:58 +02:00
if (!lock)
{
lock = FB_NEW_RPT(*dbb->dbb_permanent, sizeof(SLONG)) Lock();
2001-05-23 15:26:42 +02:00
lock->lck_dbb = dbb;
lock->lck_type = LCK_retaining;
lock->lck_owner_handle = LCK_get_owner_handle(tdbb, lock->lck_type);
lock->lck_parent = dbb->dbb_lock;
lock->lck_length = sizeof(SLONG);
lock->lck_object = dbb;
LCK_lock(tdbb, lock, LCK_SR, LCK_WAIT);
2001-05-23 15:26:42 +02:00
dbb->dbb_retaining_lock = lock;
}
2003-12-31 06:36:12 +01:00
SLONG number = transaction->tra_number;
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// Writers must synchronize their lock update so that
// an older retaining is not written over a younger retaining.
// In any case, lock types have been selected so that
// readers and writers don't interfere.
2001-05-23 15:26:42 +02:00
SLONG youngest_retaining;
2008-01-16 13:22:11 +01:00
2009-08-23 11:49:58 +02:00
if (write_flag)
{
LCK_convert(tdbb, lock, LCK_PW, LCK_WAIT);
2001-05-23 15:26:42 +02:00
youngest_retaining = LOCK_read_data(lock->lck_id);
if (number > youngest_retaining)
LCK_write_data(lock, number);
LCK_convert(tdbb, lock, LCK_SR, LCK_WAIT);
2001-05-23 15:26:42 +02:00
}
2009-08-23 11:49:58 +02:00
else
{
2001-05-23 15:26:42 +02:00
youngest_retaining = LOCK_read_data(lock->lck_id);
if (number > youngest_retaining)
return;
2009-08-21 11:45:08 +02:00
// fill out a lock block, zeroing it out first
Lock temp_lock;
2001-05-23 15:26:42 +02:00
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);
2001-05-23 15:26:42 +02:00
temp_lock.lck_parent = dbb->dbb_lock;
temp_lock.lck_length = sizeof(SLONG);
temp_lock.lck_object = transaction;
2001-05-23 15:26:42 +02:00
2009-08-23 11:49:58 +02:00
while (number < youngest_retaining)
{
2001-05-23 15:26:42 +02:00
temp_lock.lck_key.lck_long = ++number;
const SLONG data = LCK_read_data(&temp_lock);
if (data && data < transaction->tra_oldest_active)
transaction->tra_oldest_active = data;
2001-05-23 15:26:42 +02:00
}
}
}
#endif
2008-03-12 08:33:12 +01:00
static void expand_view_lock(thread_db* tdbb, jrd_tra* transaction, jrd_rel* relation,
UCHAR lock_type, const char* option_name, RelationLockTypeMap& lockmap, const int level)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* e x p a n d _ v i e w _ l o c k
*
**************************************
*
* Functional description
* A view in a RESERVING will lead to all tables in the
* view being locked.
2008-03-12 08:33:12 +01:00
* Some checks only apply when the user reserved directly the table or view.
2001-05-23 15:26:42 +02:00
*
**************************************/
2008-03-12 08:33:12 +01:00
SET_TDBB(tdbb);
2008-03-12 08:33:12 +01:00
if (level == 30)
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_reserv_max_recursion) << Arg::Num(30));
2008-03-12 08:33:12 +01:00
}
2001-05-23 15:26:42 +02:00
2008-03-12 08:33:12 +01:00
const char* const relation_name = relation->rel_name.c_str();
2008-03-12 08:33:12 +01:00
// LCK_none < LCK_SR < LCK_PR < LCK_SW < LCK_EX
UCHAR oldlock;
const bool found = lockmap.get(relation->rel_id, oldlock);
2008-03-13 03:43:32 +01:00
2008-03-12 08:33:12 +01:00
if (found && oldlock > lock_type)
{
const char* newname = get_lockname_v3(lock_type);
const char* oldname = get_lockname_v3(oldlock);
2008-03-13 03:43:32 +01:00
2008-03-12 08:33:12 +01:00
if (level)
{
lock_type = oldlock; // Preserve the old, more powerful lock.
ERR_post_warning(Arg::Warning(isc_tpb_reserv_stronger_wng) << Arg::Str(relation_name) <<
Arg::Str(oldname) <<
Arg::Str(newname));
2008-03-12 08:33:12 +01:00
}
else
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_reserv_stronger) << Arg::Str(relation_name) <<
Arg::Str(oldname) <<
Arg::Str(newname));
2008-03-12 08:33:12 +01:00
}
}
2001-05-23 15:26:42 +02:00
2008-03-12 08:33:12 +01:00
if (level == 0)
{
fb_assert(!relation->rel_view_rse && !relation->rel_view_contexts.getCount());
2008-04-09 15:46:22 +02:00
// Reject explicit attempts to take locks on virtual tables.
2008-03-12 08:33:12 +01:00
if (relation->isVirtual())
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_reserv_virtualtbl) << Arg::Str(relation_name));
2008-03-12 08:33:12 +01:00
}
2008-03-12 08:33:12 +01:00
// Reject explicit attempts to take locks on system tables, but RDB$ADMIN role
// can do that for whatever is needed.
if (relation->isSystem() && !tdbb->getAttachment()->locksmith())
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_reserv_systbl) << Arg::Str(relation_name));
2008-03-12 08:33:12 +01:00
}
2001-05-23 15:26:42 +02:00
2008-03-12 08:33:12 +01:00
if (relation->isTemporary() && (lock_type == LCK_PR || lock_type == LCK_EX))
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_reserv_temptbl) << Arg::Str(get_lockname_v3(LCK_PR)) <<
Arg::Str(get_lockname_v3(LCK_EX)) <<
Arg::Str(relation_name));
2008-03-12 08:33:12 +01:00
}
}
else
{
fb_assert(relation->rel_view_rse && relation->rel_view_contexts.getCount());
// Ignore implicit attempts to take locks on special tables through views.
if (relation->isVirtual() || relation->isSystem())
return;
2001-05-23 15:26:42 +02:00
2008-03-12 08:33:12 +01:00
// We can't propagate a view's LCK_PR or LCK_EX to a temporary table.
if (relation->isTemporary())
{
switch (lock_type)
{
case LCK_PR:
lock_type = LCK_SR;
break;
case LCK_EX:
lock_type = LCK_SW;
break;
}
}
}
2008-03-12 08:33:12 +01:00
// set up the lock on the relation/view
Lock* lock = RLCK_transaction_relation_lock(tdbb, transaction, relation);
2001-05-23 15:26:42 +02:00
lock->lck_logical = lock_type;
2008-03-12 08:33:12 +01:00
if (!found)
*lockmap.put(relation->rel_id) = lock_type;
2001-05-23 15:26:42 +02:00
2008-03-12 08:33:12 +01:00
const ViewContexts& ctx = relation->rel_view_contexts;
2001-05-23 15:26:42 +02:00
for (size_t i = 0; i < ctx.getCount(); ++i)
2002-03-31 01:40:08 +01:00
{
if (!ctx[i]->vcx_is_relation)
continue;
jrd_rel* base_rel = MET_lookup_relation(tdbb, ctx[i]->vcx_relation_name);
if (!base_rel)
2002-03-31 01:40:08 +01:00
{
2009-08-21 11:45:08 +02:00
// should be a BUGCHECK
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_reserv_baserelnotfound) << Arg::Str(ctx[i]->vcx_relation_name) <<
Arg::Str(relation_name) <<
Arg::Str(option_name));
2002-03-31 01:40:08 +01:00
}
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// force a scan to read view information
MET_scan_relation(tdbb, base_rel);
2001-05-23 15:26:42 +02:00
2008-03-12 08:33:12 +01:00
expand_view_lock(tdbb, transaction, base_rel, lock_type, option_name, lockmap, level + 1);
2001-05-23 15:26:42 +02:00
}
}
2008-12-25 07:09:37 +01:00
static tx_inv_page* fetch_inventory_page(thread_db* tdbb,
WIN* window,
SLONG sequence,
USHORT lock_level)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* f e t c h _ i n v e n t o r y _ p a g e
*
**************************************
*
* Functional description
* Fetch a transaction inventory page.
* Use the opportunity to cache the info
* in the TIP cache.
*
**************************************/
SET_TDBB(tdbb);
2006-07-27 11:29:04 +02:00
window->win_page = inventory_page(tdbb, sequence);
tx_inv_page* tip = (tx_inv_page*) CCH_FETCH(tdbb, window, lock_level, pag_transactions);
2001-05-23 15:26:42 +02:00
TPC_update_cache(tdbb, tip, sequence);
return tip;
}
2008-03-12 08:33:12 +01:00
static const char* get_lockname_v3(const UCHAR lock)
{
/**************************************
*
* g e t _ l o c k n a m e _ v 3
*
**************************************
*
* Functional description
* Get the lock mnemonic, given its binary value.
* This is for TPB versions 1 & 3.
*
**************************************/
const char* typestr = "unknown";
switch (lock)
{
case LCK_none:
case LCK_SR:
typestr = "isc_tpb_lock_read, isc_tpb_shared";
break;
case LCK_PR:
typestr = "isc_tpb_lock_read, isc_tpb_protected/isc_tpb_exclusive";
break;
case LCK_SW:
typestr = "isc_tpb_lock_write, isc_tpb_shared";
break;
case LCK_EX:
typestr = "isc_tpb_lock_write, isc_tpb_protected/isc_tpb_exclusive";
break;
}
return typestr;
}
static SLONG inventory_page(thread_db* tdbb, SLONG sequence)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* i n v e n t o r y _ p a g e
*
**************************************
*
* Functional description
* Get the physical page number of the n-th transaction inventory
* page. If not found, try to reconstruct using sibling pointer
* from last known TIP page.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
2001-05-23 15:26:42 +02:00
CHECK_DBB(dbb);
2006-05-22 00:07:35 +02:00
WIN window(DB_PAGE_SPACE, -1);
2003-12-31 06:36:12 +01:00
vcl* vector = dbb->dbb_t_pages;
2009-08-23 11:49:58 +02:00
while (!vector || sequence >= (SLONG) vector->count())
{
2001-05-23 15:26:42 +02:00
DPM_scan_pages(tdbb);
2003-04-03 19:19:10 +02:00
if ((vector = dbb->dbb_t_pages) && sequence < (SLONG) vector->count())
2001-05-23 15:26:42 +02:00
break;
2001-12-24 03:51:06 +01:00
if (!vector)
2009-08-21 11:45:08 +02:00
BUGCHECK(165); // msg 165 cannot find tip page
2001-12-24 03:51:06 +01:00
window.win_page = (*vector)[vector->count() - 1];
tx_inv_page* tip = (tx_inv_page*) CCH_FETCH(tdbb, &window, LCK_read, pag_transactions);
2003-12-31 06:36:12 +01:00
const SLONG next = tip->tip_next;
2001-05-23 15:26:42 +02:00
CCH_RELEASE(tdbb, &window);
if (!(window.win_page = next))
2009-08-21 11:45:08 +02:00
BUGCHECK(165); // msg 165 cannot find tip page
// Type check it
tip = (tx_inv_page*) CCH_FETCH(tdbb, &window, LCK_read, pag_transactions);
2001-05-23 15:26:42 +02:00
CCH_RELEASE(tdbb, &window);
DPM_pages(tdbb, 0, pag_transactions, vector->count(), window.win_page.getPageNum());
2001-05-23 15:26:42 +02:00
}
2001-12-24 03:51:06 +01:00
return (*vector)[sequence];
2001-05-23 15:26:42 +02:00
}
static SSHORT limbo_transaction(thread_db* tdbb, SLONG id)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* l i m b o _ t r a n s a c t i o n
*
**************************************
*
* Functional description
*
* limbo_state is called when reconnecting
* to an existing transaction to assure that
* the transaction is actually in limbo.
* It returns the transaction state.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
2001-05-23 15:26:42 +02:00
CHECK_DBB(dbb);
2006-05-22 00:07:35 +02:00
const SLONG trans_per_tip = dbb->dbb_page_manager.transPerTIP;
2001-05-23 15:26:42 +02:00
2003-12-31 06:36:12 +01:00
const SLONG page = id / trans_per_tip;
const SLONG number = id % trans_per_tip;
2001-05-23 15:26:42 +02:00
2006-05-22 00:07:35 +02:00
WIN window(DB_PAGE_SPACE, -1);
const tx_inv_page* tip = fetch_inventory_page(tdbb, &window, page, LCK_write);
2001-05-23 15:26:42 +02:00
2003-12-31 06:36:12 +01:00
const SLONG trans_offset = TRANS_OFFSET(number);
const UCHAR* byte = tip->tip_transactions + trans_offset;
const SSHORT shift = TRANS_SHIFT(number);
const SSHORT state = (*byte >> shift) & TRA_MASK;
2001-05-23 15:26:42 +02:00
CCH_RELEASE(tdbb, &window);
return state;
}
static void link_transaction(thread_db* tdbb, jrd_tra* transaction)
{
/**************************************
*
* l i n k _ t r a n s a c t i o n
*
**************************************
*
* Functional description
* Link transaction block into database attachment.
*
**************************************/
SET_TDBB(tdbb);
Attachment* attachment = tdbb->getAttachment();
transaction->tra_next = attachment->att_transactions;
attachment->att_transactions = transaction;
}
static void restart_requests(thread_db* tdbb, jrd_tra* trans)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* r e s t a r t _ r e q u e s t s
*
**************************************
*
* Functional description
* Restart all requests in the current
* attachment to utilize the passed
2001-05-23 15:26:42 +02:00
* transaction.
*
**************************************/
SET_TDBB(tdbb);
2003-12-31 06:36:12 +01:00
for (jrd_req* request = trans->tra_attachment->att_requests; request;
2008-12-25 07:09:37 +01:00
request = request->req_request)
2003-12-31 06:36:12 +01:00
{
2009-08-23 11:49:58 +02:00
if (request->req_transaction)
{
2001-05-23 15:26:42 +02:00
EXE_unwind(tdbb, request);
EXE_start(tdbb, request, trans);
}
2009-08-21 11:45:08 +02:00
// now take care of any other request levels;
// start at level 1 since level 0 was just handled
2001-05-23 15:26:42 +02:00
vec<jrd_req*>* vector = request->req_sub_requests;
2009-08-23 11:49:58 +02:00
if (vector)
{
for (USHORT level = 1; level < vector->count(); level++)
{
jrd_req* clone = (*vector)[level];
2009-08-23 11:49:58 +02:00
if (clone && clone->req_transaction)
{
2001-05-23 15:26:42 +02:00
EXE_unwind(tdbb, clone);
EXE_start(tdbb, clone, trans);
}
2003-12-31 06:36:12 +01:00
}
}
2001-05-23 15:26:42 +02:00
}
}
2009-08-23 11:49:58 +02:00
static void retain_context(thread_db* tdbb, jrd_tra* transaction, bool commit, SSHORT state)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* r e t a i n _ c o n t e x t
*
**************************************
*
* Functional description
* If 'commit' flag is true, commit the transaction,
* else rollback the transaction.
*
* Commit/rollback a transaction while preserving the
* context, in particular, its snapshot. The
* trick is to insure that the transaction's
* oldest active is seen by other transactions
* simultaneously starting up.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
2001-05-23 15:26:42 +02:00
CHECK_DBB(dbb);
2009-08-21 11:45:08 +02:00
// The new transaction needs to remember the 'commit-retained' transaction
// because it must see the operations of the 'commit-retained' transaction and
// its snapshot doesn't contain these operations.
2001-05-23 15:26:42 +02:00
2003-12-31 06:36:12 +01:00
if (commit) {
2008-12-25 07:09:37 +01:00
SBM_SET(tdbb->getDefaultPool(), &transaction->tra_commit_sub_trans, transaction->tra_number);
2003-12-31 06:36:12 +01:00
}
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// Create a new transaction lock, inheriting oldest active from transaction being committed.
2001-05-23 15:26:42 +02:00
2006-05-22 00:07:35 +02:00
WIN window(DB_PAGE_SPACE, -1);
SLONG new_number;
2001-05-23 15:26:42 +02:00
#ifdef SUPERSERVER_V2
new_number = bump_transaction_id(tdbb, &window);
#else
if (dbb->dbb_flags & DBB_read_only)
new_number = dbb->dbb_next_transaction + dbb->generateTransactionId(tdbb);
2009-08-23 11:49:58 +02:00
else
{
const header_page* header = bump_transaction_id(tdbb, &window);
2001-05-23 15:26:42 +02:00
new_number = header->hdr_next_transaction;
}
#endif
Lock* new_lock = 0;
Lock* old_lock = transaction->tra_lock;
2009-08-23 11:49:58 +02:00
if (old_lock)
{
new_lock = create_transaction_lock(tdbb, transaction);
2001-05-23 15:26:42 +02:00
new_lock->lck_key.lck_long = new_number;
new_lock->lck_data = transaction->tra_lock->lck_data;
2009-08-23 11:49:58 +02:00
if (!LCK_lock(tdbb, new_lock, LCK_write, LCK_WAIT))
{
2001-05-23 15:26:42 +02:00
#ifndef SUPERSERVER_V2
if (!(dbb->dbb_flags & DBB_read_only))
CCH_RELEASE(tdbb, &window);
#endif
ERR_post(Arg::Gds(isc_lock_conflict));
2001-05-23 15:26:42 +02:00
}
}
#ifndef SUPERSERVER_V2
if (!(dbb->dbb_flags & DBB_read_only))
CCH_RELEASE(tdbb, &window);
#endif
2009-08-21 11:45:08 +02:00
// Update database notion of the youngest commit retaining
// transaction before committing the first transaction. This
// secures the original snapshot by insuring the oldest active
// is seen by other transactions.
2001-05-23 15:26:42 +02:00
const SLONG old_number = transaction->tra_number;
2001-05-23 15:26:42 +02:00
#ifdef VMS
transaction->tra_number = new_number;
compute_oldest_retaining(tdbb, transaction, true);
2001-05-23 15:26:42 +02:00
transaction->tra_number = old_number;
#endif
2009-08-23 11:49:58 +02:00
if (!(dbb->dbb_flags & DBB_read_only))
{
2009-08-21 11:45:08 +02:00
// Set the state on the inventory page
TRA_set_state(tdbb, transaction, old_number, state);
2001-05-23 15:26:42 +02:00
}
transaction->tra_number = new_number;
2009-08-21 11:45:08 +02:00
// Release transaction lock since it isn't needed
// anymore and the new one is already in place.
2001-05-23 15:26:42 +02:00
2009-08-23 11:49:58 +02:00
if (old_lock)
{
2001-05-23 15:26:42 +02:00
++transaction->tra_use_count;
LCK_release(tdbb, old_lock);
transaction->tra_lock = new_lock;
--transaction->tra_use_count;
2001-12-24 03:51:06 +01:00
delete old_lock;
2001-05-23 15:26:42 +02:00
}
2009-08-21 11:45:08 +02:00
// Perform any post commit work OR delete entries from deferred list
2001-05-23 15:26:42 +02:00
if (commit)
DFW_perform_post_commit_work(transaction);
else
DFW_delete_deferred(transaction, -1);
transaction->tra_flags &= ~(TRA_write | TRA_prepared);
2009-08-21 11:45:08 +02:00
// We have to mimic a TRA_commit and a TRA_start while reusing the
// 'transaction' control block: get rid of the transaction-level
// savepoint and possibly start a new transaction-level savepoint.
2001-05-23 15:26:42 +02:00
2002-11-03 18:29:51 +01:00
// Get rid of all user savepoints
// Why we can do this in reverse order described in commit method
while (transaction->tra_save_point && transaction->tra_save_point->sav_flags & SAV_user)
2003-12-31 06:36:12 +01:00
{
Savepoint* const next = transaction->tra_save_point->sav_next;
2002-11-03 18:29:51 +01:00
transaction->tra_save_point->sav_next = NULL;
VIO_verb_cleanup(tdbb, transaction);
transaction->tra_save_point = next;
2002-11-03 18:29:51 +01:00
}
2009-08-23 11:49:58 +02:00
if (transaction->tra_save_point)
{
2001-05-23 15:26:42 +02:00
if (!(transaction->tra_save_point->sav_flags & SAV_trans_level))
2009-08-21 11:45:08 +02:00
BUGCHECK(287); // Too many savepoints
VIO_verb_cleanup(tdbb, transaction); // get rid of transaction savepoint
VIO_start_save_point(tdbb, transaction); // start new savepoint
2001-05-23 15:26:42 +02:00
transaction->tra_save_point->sav_flags |= SAV_trans_level;
}
2009-08-23 11:49:58 +02:00
if (transaction->tra_flags & TRA_precommitted)
{
2001-05-23 15:26:42 +02:00
if (!(dbb->dbb_flags & DBB_read_only))
{
transaction->tra_flags &= ~TRA_precommitted;
TRA_set_state(tdbb, transaction, new_number, tra_committed);
transaction->tra_flags |= TRA_precommitted;
}
TRA_precommited(tdbb, old_number, new_number);
2001-05-23 15:26:42 +02:00
}
}
static void start_sweeper(thread_db* tdbb)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* s t a r t _ s w e e p e r
*
**************************************
*
* Functional description
* Start a thread to sweep the database.
*
**************************************/
SET_TDBB(tdbb);
Database* const dbb = tdbb->getDatabase();
2001-05-23 15:26:42 +02:00
if (!dbb->allowSweepThread(tdbb))
return;
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// allocate space for the string and a null at the end
const char* pszFilename = tdbb->getAttachment()->att_filename.c_str();
2001-05-24 16:54:26 +02:00
char* database = (char*) gds__alloc(strlen(pszFilename) + 1);
2001-05-24 16:54:26 +02:00
if (database)
2001-05-24 16:54:26 +02:00
{
strcpy(database, pszFilename);
if (!gds__thread_start(sweep_database, database, THREAD_medium, 0, 0))
return;
gds__free(database);
ERR_log(0, 0, "cannot start sweep thread");
2001-05-23 15:26:42 +02:00
}
else
2001-05-24 16:54:26 +02:00
{
ERR_log(0, 0, "cannot start sweep thread, Out of Memory");
2001-05-24 16:54:26 +02:00
}
dbb->clearSweepFlags(tdbb);
2001-05-23 15:26:42 +02:00
}
static THREAD_ENTRY_DECLARE sweep_database(THREAD_ENTRY_PARAM database)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* s w e e p _ d a t a b a s e
*
**************************************
*
* Functional description
* Sweep database.
*
**************************************/
Firebird::ClumpletWriter dpb(Firebird::ClumpletReader::Tagged, MAX_DPB_SIZE, isc_dpb_version1);
2001-05-23 15:26:42 +02:00
dpb.insertByte(isc_dpb_sweep, isc_dpb_records);
2007-09-04 10:22:48 +02:00
// sometimes security database is also to be swept
dpb.insertByte(isc_dpb_gsec_attach, 1);
2008-01-16 13:22:11 +01:00
// use trusted authentication to attach database
const char* szAuthenticator = "sweeper";
dpb.insertString(isc_dpb_trusted_auth, szAuthenticator, strlen(szAuthenticator));
2004-05-03 01:06:37 +02:00
2007-09-04 10:22:48 +02:00
ISC_STATUS_ARRAY status_vector = {0};
isc_db_handle db_handle = 0;
2008-04-13 08:40:26 +02:00
isc_attach_database(status_vector, 0, (const char*) database,
&db_handle, dpb.getBufferLength(),
reinterpret_cast<const char*>(dpb.getBuffer()));
2001-05-23 15:26:42 +02:00
if (db_handle)
{
2003-11-08 17:40:17 +01:00
isc_detach_database(status_vector, &db_handle);
2001-05-23 15:26:42 +02:00
}
gds__free(database);
return 0;
2001-05-23 15:26:42 +02:00
}
static void transaction_options(thread_db* tdbb,
jrd_tra* transaction,
const UCHAR* tpb, USHORT tpb_length)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* t r a n s a c t i o n _ o p t i o n s
*
**************************************
*
* Functional description
* Process transaction options.
*
**************************************/
SET_TDBB(tdbb);
if (!tpb_length)
return;
const UCHAR* const end = tpb + tpb_length;
2001-05-23 15:26:42 +02:00
2003-11-08 17:40:17 +01:00
if (*tpb != isc_tpb_version3 && *tpb != isc_tpb_version1)
ERR_post(Arg::Gds(isc_bad_tpb_form) <<
Arg::Gds(isc_wrotpbver));
2001-05-23 15:26:42 +02:00
2008-03-12 08:33:12 +01:00
RelationLockTypeMap lockmap;
2007-09-04 10:22:48 +02:00
TriState wait, lock_timeout;
TriState isolation, read_only, rec_version;
2008-03-12 08:33:12 +01:00
bool anylock_write = false;
2001-05-23 15:26:42 +02:00
++tpb;
2007-09-04 10:22:48 +02:00
while (tpb < end)
{
2003-12-31 06:36:12 +01:00
const USHORT op = *tpb++;
2007-09-04 10:22:48 +02:00
switch (op)
{
2003-11-08 17:40:17 +01:00
case isc_tpb_consistency:
2007-09-04 10:22:48 +02:00
if (!isolation.assignOnce(true))
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_multiple_txn_isolation));
2001-05-23 15:26:42 +02:00
transaction->tra_flags |= TRA_degree3;
transaction->tra_flags &= ~TRA_read_committed;
break;
2003-11-08 17:40:17 +01:00
case isc_tpb_concurrency:
2007-09-04 10:22:48 +02:00
if (!isolation.assignOnce(true))
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_multiple_txn_isolation));
2007-09-04 10:22:48 +02:00
2001-05-23 15:26:42 +02:00
transaction->tra_flags &= ~TRA_degree3;
transaction->tra_flags &= ~TRA_read_committed;
break;
2003-11-08 17:40:17 +01:00
case isc_tpb_read_committed:
2007-09-04 10:22:48 +02:00
if (!isolation.assignOnce(true))
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_multiple_txn_isolation));
2007-09-04 10:22:48 +02:00
2001-05-23 15:26:42 +02:00
transaction->tra_flags &= ~TRA_degree3;
transaction->tra_flags |= TRA_read_committed;
break;
2003-11-08 17:40:17 +01:00
case isc_tpb_shared:
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_reserv_before_table) << Arg::Str("isc_tpb_shared"));
break;
2003-11-08 17:40:17 +01:00
case isc_tpb_protected:
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_reserv_before_table) << Arg::Str("isc_tpb_protected"));
break;
2003-11-08 17:40:17 +01:00
case isc_tpb_exclusive:
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_reserv_before_table) << Arg::Str("isc_tpb_exclusive"));
2001-05-23 15:26:42 +02:00
break;
2003-11-08 17:40:17 +01:00
case isc_tpb_wait:
2007-09-04 10:22:48 +02:00
if (!wait.assignOnce(true))
{
if (!wait.asBool())
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_conflicting_options) << Arg::Str("isc_tpb_wait") <<
Arg::Str("isc_tpb_nowait"));
}
else
2008-03-12 08:33:12 +01:00
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_multiple_spec) << Arg::Str("isc_tpb_wait"));
2008-03-12 08:33:12 +01:00
}
}
2001-05-23 15:26:42 +02:00
break;
2003-11-08 17:40:17 +01:00
case isc_tpb_rec_version:
if (isolation.isAssigned() && !(transaction->tra_flags & TRA_read_committed))
2008-03-12 08:33:12 +01:00
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_option_without_rc) << Arg::Str("isc_tpb_rec_version"));
2008-03-12 08:33:12 +01:00
}
2007-09-04 10:22:48 +02:00
if (!rec_version.assignOnce(true))
2008-03-12 08:33:12 +01:00
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_multiple_spec) << Arg::Str("isc_tpb_rec_version"));
2008-03-12 08:33:12 +01:00
}
2007-09-04 10:22:48 +02:00
2001-05-23 15:26:42 +02:00
transaction->tra_flags |= TRA_rec_version;
break;
2003-11-08 17:40:17 +01:00
case isc_tpb_no_rec_version:
if (isolation.isAssigned() && !(transaction->tra_flags & TRA_read_committed))
2008-03-12 08:33:12 +01:00
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_option_without_rc) << Arg::Str("isc_tpb_no_rec_version"));
2008-03-12 08:33:12 +01:00
}
2007-09-04 10:22:48 +02:00
if (!rec_version.assignOnce(false))
2008-03-12 08:33:12 +01:00
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_multiple_spec) << Arg::Str("isc_tpb_no_rec_version"));
2008-03-12 08:33:12 +01:00
}
2007-09-04 10:22:48 +02:00
2001-05-23 15:26:42 +02:00
transaction->tra_flags &= ~TRA_rec_version;
break;
2003-11-08 17:40:17 +01:00
case isc_tpb_nowait:
if (lock_timeout.asBool())
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_conflicting_options) << Arg::Str("isc_tpb_nowait") <<
Arg::Str("isc_tpb_lock_timeout"));
}
if (!wait.assignOnce(false))
{
if (wait.asBool())
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_conflicting_options) << Arg::Str("isc_tpb_nowait") <<
Arg::Str("isc_tpb_wait"));
}
else
2008-03-12 08:33:12 +01:00
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_multiple_spec) << Arg::Str("isc_tpb_nowait"));
2008-03-12 08:33:12 +01:00
}
}
transaction->tra_lock_timeout = 0;
2001-05-23 15:26:42 +02:00
break;
2003-11-08 17:40:17 +01:00
case isc_tpb_read:
2007-09-04 10:22:48 +02:00
if (!read_only.assignOnce(true))
{
if (!read_only.asBool())
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_conflicting_options) << Arg::Str("isc_tpb_read") <<
Arg::Str("isc_tpb_write"));
}
else
2008-03-12 08:33:12 +01:00
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_multiple_spec) << Arg::Str("isc_tpb_read"));
2008-03-12 08:33:12 +01:00
}
}
2008-03-12 08:33:12 +01:00
// Cannot set the whole txn to R/O if we already saw a R/W table reservation.
if (anylock_write)
2008-10-21 01:46:46 +02:00
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_readtxn_after_writelock));
2008-10-21 01:46:46 +02:00
}
2007-09-04 10:22:48 +02:00
2001-05-23 15:26:42 +02:00
transaction->tra_flags |= TRA_readonly;
break;
2003-11-08 17:40:17 +01:00
case isc_tpb_write:
2007-09-04 10:22:48 +02:00
if (!read_only.assignOnce(false))
{
if (read_only.asBool())
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_conflicting_options) << Arg::Str("isc_tpb_write") <<
Arg::Str("isc_tpb_read"));
}
else
2008-03-12 08:33:12 +01:00
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_multiple_spec) << Arg::Str("isc_tpb_write"));
2008-03-12 08:33:12 +01:00
}
}
2007-09-04 10:22:48 +02:00
2001-05-23 15:26:42 +02:00
transaction->tra_flags &= ~TRA_readonly;
break;
2003-11-08 17:40:17 +01:00
case isc_tpb_ignore_limbo:
2001-05-23 15:26:42 +02:00
transaction->tra_flags |= TRA_ignore_limbo;
break;
2003-11-08 17:40:17 +01:00
case isc_tpb_no_auto_undo:
2001-05-23 15:26:42 +02:00
transaction->tra_flags |= TRA_no_auto_undo;
break;
2003-11-08 17:40:17 +01:00
case isc_tpb_lock_write:
2008-03-12 08:33:12 +01:00
// Cannot set a R/W table reservation if the whole txn is R/O.
if (read_only.asBool())
2008-10-21 01:46:46 +02:00
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_writelock_after_readtxn));
2008-10-21 01:46:46 +02:00
}
2008-03-12 08:33:12 +01:00
anylock_write = true;
// fall into
2003-11-08 17:40:17 +01:00
case isc_tpb_lock_read:
2003-12-31 06:36:12 +01:00
{
const char* option_name = (op == isc_tpb_lock_read) ?
"isc_tpb_lock_read" : "isc_tpb_lock_write";
2008-03-12 08:33:12 +01:00
2007-09-04 10:22:48 +02:00
// Do we have space for the identifier length?
if (tpb >= end)
2008-03-12 08:33:12 +01:00
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_reserv_missing_tlen) << Arg::Str(option_name));
2008-03-12 08:33:12 +01:00
}
const USHORT len = *tpb++;
if (len > MAX_SQL_IDENTIFIER_LEN)
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_reserv_long_tlen) << Arg::Num(len) <<
Arg::Str(option_name));
2001-05-23 15:26:42 +02:00
}
if (!len)
2008-03-12 08:33:12 +01:00
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_reserv_null_tlen) << Arg::Str(option_name));
2008-03-12 08:33:12 +01:00
}
2007-09-04 10:22:48 +02:00
// Does the identifier length surpasses the remaining of the TPB?
if (tpb >= end)
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
2008-12-25 07:09:37 +01:00
Arg::Gds(isc_tpb_reserv_missing_tname) << Arg::Num(len) <<
Arg::Str(option_name));
}
if (end - tpb < len)
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
2008-12-25 07:09:37 +01:00
Arg::Gds(isc_tpb_reserv_corrup_tlen) << Arg::Num(len) <<
Arg::Str(option_name));
}
const Firebird::MetaName name(reinterpret_cast<const char*>(tpb), len);
tpb += len;
jrd_rel* relation = MET_lookup_relation(tdbb, name);
if (!relation)
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_reserv_relnotfound) << Arg::Str(name) <<
Arg::Str(option_name));
}
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// force a scan to read view information
MET_scan_relation(tdbb, relation);
2001-05-23 15:26:42 +02:00
UCHAR lock_type = (op == isc_tpb_lock_read) ? LCK_none : LCK_SW;
2007-09-04 10:22:48 +02:00
if (tpb < end)
{
2008-03-12 08:33:12 +01:00
switch (*tpb)
{
2008-03-12 08:33:12 +01:00
case isc_tpb_shared:
++tpb;
break;
case isc_tpb_protected:
case isc_tpb_exclusive:
++tpb;
lock_type = (lock_type == LCK_SW) ? LCK_EX : LCK_PR;
2008-03-12 08:33:12 +01:00
break;
// We'll assume table reservation doesn't make the concurrency type mandatory.
//default:
// ERR_post(isc-arg-end);
}
2001-05-23 15:26:42 +02:00
}
2008-03-12 08:33:12 +01:00
expand_view_lock(tdbb, transaction, relation, lock_type, option_name, lockmap, 0);
2003-12-31 06:36:12 +01:00
}
break;
2001-05-23 15:26:42 +02:00
2003-11-08 17:40:17 +01:00
case isc_tpb_verb_time:
case isc_tpb_commit_time:
2003-12-31 06:36:12 +01:00
{
const char* option_name = (op == isc_tpb_verb_time) ?
"isc_tpb_verb_time" : "isc_tpb_commit_time";
2007-09-04 10:22:48 +02:00
// Harmless for now even if formally invalid.
if (tpb >= end)
2008-03-12 08:33:12 +01:00
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_missing_len) << Arg::Str(option_name));
2008-03-12 08:33:12 +01:00
}
2007-09-04 10:22:48 +02:00
const USHORT len = *tpb++;
if (tpb >= end && len > 0)
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_missing_value) << Arg::Num(len) << Arg::Str(option_name));
}
if (end - tpb < len)
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_corrupt_len) << Arg::Num(len) << Arg::Str(option_name));
}
tpb += len;
2003-12-31 06:36:12 +01:00
}
2007-09-04 10:22:48 +02:00
break;
2001-05-23 15:26:42 +02:00
2003-11-08 17:40:17 +01:00
case isc_tpb_autocommit:
2001-05-23 15:26:42 +02:00
transaction->tra_flags |= TRA_autocommit;
break;
2003-11-08 17:40:17 +01:00
case isc_tpb_restart_requests:
2001-05-23 15:26:42 +02:00
transaction->tra_flags |= TRA_restart_requests;
break;
case isc_tpb_lock_timeout:
{
if (wait.isAssigned() && !wait.asBool())
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_conflicting_options) << Arg::Str("isc_tpb_lock_timeout") <<
Arg::Str("isc_tpb_nowait"));
}
if (!lock_timeout.assignOnce(true))
2008-03-12 08:33:12 +01:00
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_multiple_spec) << Arg::Str("isc_tpb_lock_timeout"));
2008-03-12 08:33:12 +01:00
}
2007-09-04 10:22:48 +02:00
// Do we have space for the identifier length?
if (tpb >= end)
2008-03-12 08:33:12 +01:00
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_missing_len) << Arg::Str("isc_tpb_lock_timeout"));
2008-03-12 08:33:12 +01:00
}
2007-09-04 10:22:48 +02:00
const USHORT len = *tpb++;
2008-03-08 22:20:26 +01:00
2007-09-04 10:22:48 +02:00
// Does the encoded number's length surpasses the remaining of the TPB?
if (tpb >= end)
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_missing_value) << Arg::Num(len) <<
Arg::Str("isc_tpb_lock_timeout"));
}
if (end - tpb < len)
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_corrupt_len) << Arg::Num(len) <<
Arg::Str("isc_tpb_lock_timeout"));
}
if (!len)
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_null_len) << Arg::Str("isc_tpb_lock_timeout"));
}
if (len > sizeof(ULONG))
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_overflow_len) << Arg::Num(len) << Arg::Str("isc_tpb_lock_timeout"));
}
2009-04-10 13:43:20 +02:00
const SLONG value = gds__vax_integer(tpb, len);
2009-04-10 13:43:20 +02:00
if (value <= 0 || value > MAX_SSHORT)
2008-03-12 08:33:12 +01:00
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_invalid_value) << Arg::Num(value) << Arg::Str("isc_tpb_lock_timeout"));
2008-03-12 08:33:12 +01:00
}
2007-09-04 10:22:48 +02:00
transaction->tra_lock_timeout = (SSHORT) value;
tpb += len;
}
2007-09-04 10:22:48 +02:00
break;
2001-05-23 15:26:42 +02:00
default:
ERR_post(Arg::Gds(isc_bad_tpb_form));
2001-05-23 15:26:42 +02:00
}
}
2008-03-12 08:33:12 +01:00
if (rec_version.isAssigned() && !(transaction->tra_flags & TRA_read_committed))
{
if (rec_version.asBool())
2008-03-12 08:33:12 +01:00
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_option_without_rc) << Arg::Str("isc_tpb_rec_version"));
2008-03-12 08:33:12 +01:00
}
else
2008-03-12 08:33:12 +01:00
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
Arg::Gds(isc_tpb_option_without_rc) << Arg::Str("isc_tpb_no_rec_version"));
2008-03-12 08:33:12 +01:00
}
}
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// If there aren't any relation locks to seize, we're done.
2001-05-23 15:26:42 +02:00
vec<Lock*>* vector = transaction->tra_relation_locks;
if (!vector)
2001-05-23 15:26:42 +02:00
return;
2009-08-23 11:49:58 +02:00
// Try to seize all relation locks. If any can't be seized, release all and try again.
2001-05-23 15:26:42 +02:00
2009-08-23 11:49:58 +02:00
for (ULONG id = 0; id < vector->count(); id++)
{
Lock* lock = (*vector)[id];
2003-12-31 06:36:12 +01:00
if (!lock)
2001-05-23 15:26:42 +02:00
continue;
2003-12-31 06:36:12 +01:00
USHORT level = lock->lck_logical;
if (level == LCK_none || LCK_lock(tdbb, lock, level, transaction->getLockWait()))
{
continue;
}
2009-08-23 11:49:58 +02:00
for (ULONG l = 0; l < id; l++)
{
if ( (lock = (*vector)[l]) )
{
2001-05-23 15:26:42 +02:00
level = lock->lck_logical;
LCK_release(tdbb, lock);
lock->lck_logical = level;
}
2003-12-31 06:36:12 +01:00
}
2001-05-23 15:26:42 +02:00
id = 0;
ERR_punt();
2001-05-23 15:26:42 +02:00
}
}
2008-01-16 13:22:11 +01:00
static jrd_tra* transaction_start(thread_db* tdbb, jrd_tra* temp)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
2008-01-16 13:22:11 +01:00
* t r a n s a c t i o n _ s t a r t
2001-05-23 15:26:42 +02:00
*
**************************************
*
* Functional description
2008-01-16 13:22:11 +01:00
* Start a transaction.
2001-05-23 15:26:42 +02:00
*
**************************************/
2008-01-16 13:22:11 +01:00
SET_TDBB(tdbb);
Database* const dbb = tdbb->getDatabase();
Attachment* const attachment = tdbb->getAttachment();
2008-01-16 13:22:11 +01:00
WIN window(DB_PAGE_SPACE, -1);
2001-05-23 15:26:42 +02:00
Lock* lock = create_transaction_lock(tdbb, temp);
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// Read header page and allocate transaction number. Since
// the transaction inventory page was initialized to zero, it
// transaction is automatically marked active.
2001-05-23 15:26:42 +02:00
2008-01-16 13:22:11 +01:00
ULONG oldest, number, active, oldest_active, oldest_snapshot;
2001-05-23 15:26:42 +02:00
2008-01-16 13:22:11 +01:00
#ifdef SUPERSERVER_V2
number = bump_transaction_id(tdbb, &window);
oldest = dbb->dbb_oldest_transaction;
active = MAX(dbb->dbb_oldest_active, dbb->dbb_oldest_transaction);
oldest_active = dbb->dbb_oldest_active;
oldest_snapshot = dbb->dbb_oldest_snapshot;
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
#else // SUPERSERVER_V2
2009-08-23 11:49:58 +02:00
if (dbb->dbb_flags & DBB_read_only)
{
number = dbb->dbb_next_transaction + dbb->generateTransactionId(tdbb);
2008-01-16 13:22:11 +01:00
oldest = dbb->dbb_oldest_transaction;
oldest_active = dbb->dbb_oldest_active;
oldest_snapshot = dbb->dbb_oldest_snapshot;
}
2009-08-23 11:49:58 +02:00
else
{
2008-01-16 13:22:11 +01:00
const header_page* header = bump_transaction_id(tdbb, &window);
number = header->hdr_next_transaction;
oldest = header->hdr_oldest_transaction;
oldest_active = header->hdr_oldest_active;
oldest_snapshot = header->hdr_oldest_snapshot;
}
2001-05-23 15:26:42 +02:00
// oldest (OIT) > oldest_active (OAT) if OIT was advanced by sweep
2008-01-16 13:22:11 +01:00
// and no transactions was started after the sweep starts
active = MAX(oldest_active, oldest);
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
#endif // SUPERSERVER_V2
2001-05-23 15:26:42 +02:00
2009-08-21 11:45:08 +02:00
// Allocate pool and transactions block. Since, by policy,
// all transactions older than the oldest are either committed
// or cleaned up, they can be all considered as committed. To
// make everything simpler, round down the oldest to a multiple
// of four, which puts the transaction on a byte boundary.
2001-05-23 15:26:42 +02:00
2008-01-16 13:22:11 +01:00
ULONG base = oldest & ~TRA_MASK;
2008-12-25 07:09:37 +01:00
const size_t length = (temp->tra_flags & TRA_read_committed) ? 0 : (number - base + TRA_MASK) / 4;
MemoryPool* const pool = tdbb->getDefaultPool();
jrd_tra* const trans = jrd_tra::create(pool, attachment, temp->tra_outer, length);
2008-01-16 13:22:11 +01:00
fb_assert(trans->tra_pool == temp->tra_pool);
trans->tra_relation_locks = temp->tra_relation_locks;
trans->tra_lock_timeout = temp->tra_lock_timeout;
trans->tra_flags = temp->tra_flags;
trans->tra_number = number;
trans->tra_top = number;
trans->tra_oldest = oldest;
trans->tra_oldest_active = active;
trans->tra_lock = lock;
lock->lck_key.lck_long = number;
// Put the TID of the oldest active transaction (from the header page)
// in the new transaction's lock.
// hvlad: it is important to put transaction number for read-committed
// transaction instead of oldest active to correctly calculate new oldest
// active value (look at call to LCK_query_data below which will take into
2008-01-16 13:22:11 +01:00
// account this new lock too)
lock->lck_data = (trans->tra_flags & TRA_read_committed) ? number : active;
lock->lck_object = trans;
2009-08-23 11:49:58 +02:00
if (!LCK_lock(tdbb, lock, LCK_write, LCK_WAIT))
{
2008-01-16 13:22:11 +01:00
#ifndef SUPERSERVER_V2
if (!(dbb->dbb_flags & DBB_read_only))
CCH_RELEASE(tdbb, &window);
#endif
jrd_tra::destroy(dbb, trans);
ERR_post(Arg::Gds(isc_lock_conflict));
2008-01-16 13:22:11 +01:00
}
2009-08-21 11:45:08 +02:00
// Link the transaction to the attachment block before releasing
// header page for handling signals.
2008-01-16 13:22:11 +01:00
link_transaction(tdbb, trans);
#ifndef SUPERSERVER_V2
if (!(dbb->dbb_flags & DBB_read_only))
CCH_RELEASE(tdbb, &window);
#endif
2009-08-23 11:49:58 +02:00
if (dbb->dbb_flags & DBB_read_only)
{
2009-08-21 11:45:08 +02:00
// Set transaction flags to TRA_precommitted, TRA_readonly
2008-01-16 13:22:11 +01:00
trans->tra_flags |= (TRA_readonly | TRA_precommitted);
}
2009-08-21 11:45:08 +02:00
// Next, take a snapshot of all transactions between the oldest interesting
// transaction and the current. Don't bother to get a snapshot for
// read-committed transactions; they use the snapshot off the dbb block
// since they need to know what is currently committed.
2008-01-16 13:22:11 +01:00
if (trans->tra_flags & TRA_read_committed)
TPC_initialize_tpc(tdbb, number);
else
TRA_get_inventory(tdbb, trans->tra_transactions.begin(), base, number);
2008-01-16 13:22:11 +01:00
2009-08-21 11:45:08 +02:00
// Next task is to find the oldest active transaction on the system. This
// is needed for garbage collection. Things are made ever so slightly
// more complicated by the fact that existing transaction may have oldest
// actives older than they are.
2008-01-16 13:22:11 +01:00
Lock temp_lock;
temp_lock.lck_dbb = dbb;
temp_lock.lck_object = trans;
temp_lock.lck_type = LCK_tra;
temp_lock.lck_owner_handle = LCK_get_owner_handle(tdbb, temp_lock.lck_type);
2008-01-16 13:22:11 +01:00
temp_lock.lck_parent = dbb->dbb_lock;
temp_lock.lck_length = sizeof(SLONG);
trans->tra_oldest_active = number;
base = oldest & ~TRA_MASK;
oldest_active = number;
bool cleanup = !(number % TRA_ACTIVE_CLEANUP);
USHORT oldest_state;
2009-08-23 11:49:58 +02:00
for (; active < number; active++)
{
2008-01-16 13:22:11 +01:00
if (trans->tra_flags & TRA_read_committed)
oldest_state = TPC_cache_state(tdbb, active);
2009-08-23 11:49:58 +02:00
else
{
2008-01-16 13:22:11 +01:00
const ULONG byte = TRANS_OFFSET(active - base);
const USHORT shift = TRANS_SHIFT(active);
oldest_state = (trans->tra_transactions[byte] >> shift) & TRA_MASK;
2008-01-16 13:22:11 +01:00
}
2009-08-23 11:49:58 +02:00
if (oldest_state == tra_active)
{
2008-01-16 13:22:11 +01:00
temp_lock.lck_key.lck_long = active;
SLONG data = LCK_read_data(tdbb, &temp_lock);
2009-08-23 11:49:58 +02:00
if (!data)
{
if (cleanup)
{
2008-01-16 13:22:11 +01:00
if (TRA_wait(tdbb, trans, active, jrd_tra::tra_no_wait) == tra_committed)
cleanup = false;
continue;
}
data = active;
}
oldest_active = MIN(oldest_active, active);
2009-08-21 11:45:08 +02:00
// Find the oldest record version that cannot be garbage collected yet
// by taking the minimum of all all versions needed by all active transactions.
2008-01-16 13:22:11 +01:00
if (data < trans->tra_oldest_active)
trans->tra_oldest_active = data;
2009-08-21 11:45:08 +02:00
// If the lock data for any active transaction matches a previously
// computed value then there is no need to continue. There can't be
// an older lock data in the remaining active transactions.
2008-01-16 13:22:11 +01:00
if (trans->tra_oldest_active == (SLONG) oldest_snapshot)
break;
#ifndef VMS
2009-08-21 11:45:08 +02:00
// Query the minimum lock data for all active transaction locks.
// This will be the oldest active snapshot used for regulating garbage collection.
2008-01-16 13:22:11 +01:00
data = LCK_query_data(tdbb, dbb->dbb_lock, LCK_tra, LCK_MIN);
2008-01-16 13:22:11 +01:00
if (data && data < trans->tra_oldest_active)
trans->tra_oldest_active = data;
break;
2001-05-23 15:26:42 +02:00
#endif
2008-01-16 13:22:11 +01:00
}
}
// Calculate oldest active and oldest snapshot numbers looking at current
// attachment's transactions only. Calculated values are used to determine
2011-06-25 05:33:03 +02:00
// garbage collection threshold for attachment-local data such as temporary
// tables (GTTs)
trans->tra_att_oldest_active = number;
SLONG att_oldest_active = number;
SLONG att_oldest_snapshot = number;
for (jrd_tra* tx_att = attachment->att_transactions; tx_att; tx_att = tx_att->tra_next)
{
att_oldest_active = MIN(att_oldest_active, tx_att->tra_number);
att_oldest_snapshot = MIN(att_oldest_snapshot, tx_att->tra_att_oldest_active);
}
trans->tra_att_oldest_active = (trans->tra_flags & TRA_read_committed) ? number : att_oldest_active;
if (attachment->att_oldest_snapshot < att_oldest_snapshot)
attachment->att_oldest_snapshot = att_oldest_snapshot;
2008-01-16 13:22:11 +01:00
// Put the TID of the oldest active transaction (just calculated)
// in the new transaction's lock.
// hvlad: for read-committed transaction put tra_number to prevent
// unnecessary blocking of garbage collection by read-committed
// transactions
2008-01-16 13:22:11 +01:00
const ULONG lck_data = (trans->tra_flags & TRA_read_committed) ? number : oldest_active;
2008-01-16 13:22:11 +01:00
if (lock->lck_data != (SLONG) lck_data)
LCK_write_data(tdbb, lock, lck_data);
2008-01-16 13:22:11 +01:00
2009-08-21 11:45:08 +02:00
// Scan commit retaining transactions which have started after us but which
// want to preserve an oldest active from an already committed transaction.
// If a previously computed oldest snapshot was matched then there's no
// need to worry about commit retaining transactions.
2008-01-16 13:22:11 +01:00
#ifdef VMS
if (trans->tra_oldest_active != oldest_snapshot)
compute_oldest_retaining(tdbb, trans, false);
#endif
2009-08-21 11:45:08 +02:00
// Finally, scan transactions looking for the oldest interesting transaction -- the oldest
// non-commited transaction. This will not be updated immediately, but saved until the
// next update access to the header page
2008-01-16 13:22:11 +01:00
oldest_state = tra_committed;
2009-08-23 11:49:58 +02:00
for (oldest = trans->tra_oldest; oldest < number; oldest++)
{
2008-01-16 13:22:11 +01:00
if (trans->tra_flags & TRA_read_committed)
oldest_state = TPC_cache_state(tdbb, oldest);
2009-08-23 11:49:58 +02:00
else
{
2008-01-16 13:22:11 +01:00
const ULONG byte = TRANS_OFFSET(oldest - base);
const USHORT shift = TRANS_SHIFT(oldest);
oldest_state = (trans->tra_transactions[byte] >> shift) & TRA_MASK;
2008-01-16 13:22:11 +01:00
}
if (oldest_state != tra_committed && oldest_state != tra_precommitted)
break;
}
if (--oldest > (ULONG) dbb->dbb_oldest_transaction)
dbb->dbb_oldest_transaction = oldest;
if (oldest_active > (ULONG) dbb->dbb_oldest_active)
dbb->dbb_oldest_active = oldest_active;
if (trans->tra_oldest_active > dbb->dbb_oldest_snapshot)
{
2008-01-16 13:22:11 +01:00
dbb->dbb_oldest_snapshot = trans->tra_oldest_active;
#if defined(GARBAGE_THREAD)
if (!(dbb->dbb_flags & DBB_gc_active) && (dbb->dbb_flags & DBB_gc_background))
2008-01-16 13:22:11 +01:00
{
dbb->dbb_flags |= DBB_gc_pending;
dbb->dbb_gc_sem.release();
2008-01-16 13:22:11 +01:00
}
#endif
}
2009-08-21 11:45:08 +02:00
// If the transaction block is getting out of hand, force a sweep
2008-01-16 13:22:11 +01:00
if (dbb->dbb_sweep_interval &&
(trans->tra_oldest_active > oldest) &&
(trans->tra_oldest_active - oldest > dbb->dbb_sweep_interval) &&
2008-12-25 15:25:01 +01:00
oldest_state != tra_limbo)
2008-01-16 13:22:11 +01:00
{
start_sweeper(tdbb);
2008-01-16 13:22:11 +01:00
}
2009-08-21 11:45:08 +02:00
// Check in with external file system
2008-01-16 13:22:11 +01:00
EXT_trans_start(trans);
2009-08-21 11:45:08 +02:00
// Start a 'transaction-level' savepoint, unless this is the
// system transaction, or unless the transactions doesn't want
// a savepoint to be started. This savepoint will be used to
// undo the transaction if it rolls back.
2008-01-16 13:22:11 +01:00
if ((trans != dbb->dbb_sys_trans) && !(trans->tra_flags & TRA_no_auto_undo))
2008-01-16 13:22:11 +01:00
{
VIO_start_save_point(tdbb, trans);
trans->tra_save_point->sav_flags |= SAV_trans_level;
}
2009-08-21 11:45:08 +02:00
// if the user asked us to restart all requests in this attachment,
// do so now using the new transaction
2008-01-16 13:22:11 +01:00
if (trans->tra_flags & TRA_restart_requests)
restart_requests(tdbb, trans);
2009-08-21 11:45:08 +02:00
// If the transaction is read-only and read committed, it can be
// precommitted because it can't modify any records and doesn't
// need a snapshot preserved. This transaction type can run
// forever without impacting garbage collection or causing
// transaction bitmap growth.
2008-01-16 13:22:11 +01:00
if (trans->tra_flags & TRA_readonly && trans->tra_flags & TRA_read_committed)
2008-01-16 13:22:11 +01:00
{
TRA_set_state(tdbb, trans, trans->tra_number, tra_committed);
LCK_release(tdbb, lock);
lock->lck_type = LCK_tra_pc;
lock->lck_owner_handle = LCK_get_owner_handle(tdbb, lock->lck_type);
lock->lck_data = 0;
if (!LCK_lock(tdbb, lock, LCK_write, LCK_WAIT))
{
jrd_tra::destroy(dbb, trans);
ERR_post(Arg::Gds(isc_lock_conflict));
}
2008-01-16 13:22:11 +01:00
trans->tra_flags |= TRA_precommitted;
}
if (trans->tra_flags & TRA_precommitted)
TRA_precommited(tdbb, 0, trans->tra_number);
return trans;
}
2009-02-01 23:10:12 +01:00
jrd_tra::~jrd_tra()
{
delete tra_undo_record;
delete tra_undo_space;
delete tra_user_management;
if (!tra_outer)
{
delete tra_blob_space;
}
else
{
fb_assert(!tra_arrays);
}
DFW_delete_deferred(this, -1);
if (tra_autonomous_pool)
{
MemoryPool::deletePool(tra_autonomous_pool);
}
}
2009-02-20 09:14:18 +01:00
UserManagement* jrd_tra::getUserManagement()
{
if (!tra_user_management)
{
tra_user_management = FB_NEW(*tra_pool) UserManagement(this);
}
return tra_user_management;
}
jrd_tra* jrd_tra::getOuter()
{
jrd_tra* tra = this;
while (tra->tra_outer)
{
tra = tra->tra_outer;
}
return tra;
}
MemoryPool* jrd_tra::getAutonomousPool()
{
if (!tra_autonomous_pool)
{
MemoryPool* pool = tra_pool;
jrd_tra* outer = tra_outer;
while (outer)
{
pool = outer->tra_pool;
outer = outer->tra_outer;
}
tra_autonomous_pool = MemoryPool::createPool(pool, tra_memory_stats);
tra_autonomous_cnt = 0;
}
return tra_autonomous_pool;
}
void jrd_tra::releaseAutonomousPool(MemoryPool* toRelease)
{
fb_assert(tra_autonomous_pool == toRelease);
if (++tra_autonomous_cnt > TRA_AUTONOMOUS_PER_POOL)
{
MemoryPool::deletePool(tra_autonomous_pool);
tra_autonomous_pool = NULL;
}
}
/// class TraceSweepEvent
TraceSweepEvent::TraceSweepEvent(thread_db* tdbb) :
m_request(tdbb->getDefaultPool(), NULL)
{
m_tdbb = tdbb;
WIN window(HEADER_PAGE_NUMBER);
Ods::header_page *header = (Ods::header_page*) CCH_FETCH(m_tdbb, &window, LCK_read, pag_header);
m_sweep_info.update(header);
CCH_RELEASE(m_tdbb, &window);
Attachment* att = m_tdbb->getAttachment();
gds__log("Sweep is started by %s\n"
"\tDatabase \"%s\" \n"
"\tOIT %"SLONGFORMAT", OAT %"SLONGFORMAT", OST %"SLONGFORMAT", Next %"SLONGFORMAT,
att->att_user->usr_user_name.c_str(),
att->att_filename.c_str(),
m_sweep_info.getOIT(),
m_sweep_info.getOAT(),
m_sweep_info.getOST(),
m_sweep_info.getNext());
TraceManager* trace_mgr = att->att_trace_manager;
m_need_trace = trace_mgr->needs().event_sweep;
if (!m_need_trace)
return;
m_tdbb->setRequest(&m_request);
m_start_clock = fb_utils::query_performance_counter();
TraceConnectionImpl conn(att);
trace_mgr->event_sweep(&conn, &m_sweep_info, process_state_started);
m_relation_clock = fb_utils::query_performance_counter();
}
TraceSweepEvent::~TraceSweepEvent()
{
m_tdbb->setRequest(NULL);
report(process_state_failed);
}
void TraceSweepEvent::beginSweepRelation(jrd_rel* relation)
{
if (!m_need_trace)
return;
if (relation && relation->rel_name.isEmpty())
{
// don't accumulate per-relation stats for metadata query below
MET_lookup_relation_id(m_tdbb, relation->rel_id, false);
}
m_relation_clock = fb_utils::query_performance_counter();
m_request.req_stats.reset();
}
void TraceSweepEvent::endSweepRelation(jrd_rel* relation)
{
if (!m_need_trace)
return;
// don't report empty relation
if (m_request.req_stats.getValue(RuntimeStatistics::RECORD_SEQ_READS) == 0 &&
m_request.req_stats.getValue(RuntimeStatistics::RECORD_BACKOUTS) == 0 &&
m_request.req_stats.getValue(RuntimeStatistics::RECORD_PURGES) == 0 &&
m_request.req_stats.getValue(RuntimeStatistics::RECORD_EXPUNGES) == 0)
{
return;
}
// we need to compare stats against zero base
m_request.req_base_stats.reset();
Database* dbb = m_tdbb->getDatabase();
TraceRuntimeStats stats(dbb, &m_request.req_base_stats, &m_request.req_stats,
fb_utils::query_performance_counter() - m_relation_clock,
0);
m_sweep_info.setPerf(stats.getPerf());
Attachment* att = m_tdbb->getAttachment();
TraceConnectionImpl conn(att);
TraceManager* trace_mgr = att->att_trace_manager;
trace_mgr->event_sweep(&conn, &m_sweep_info, process_state_progress);
}
void TraceSweepEvent::report(ntrace_process_state_t state)
{
Attachment* att = m_tdbb->getAttachment();
if (state == process_state_finished)
{
gds__log("Sweep is finished\n"
"\tDatabase \"%s\" \n"
"\tOIT %"SLONGFORMAT", OAT %"SLONGFORMAT", OST %"SLONGFORMAT", Next %"SLONGFORMAT,
att->att_filename.c_str(),
m_sweep_info.getOIT(),
m_sweep_info.getOAT(),
m_sweep_info.getOST(),
m_sweep_info.getNext());
}
if (!m_need_trace)
return;
Database* dbb = m_tdbb->getDatabase();
TraceManager* trace_mgr = att->att_trace_manager;
TraceConnectionImpl conn(att);
// we need to compare stats against zero base
m_request.req_base_stats.reset();
TraceRuntimeStats stats(dbb, &m_request.req_base_stats, &att->att_stats,
fb_utils::query_performance_counter() - m_start_clock,
0);
m_sweep_info.setPerf(stats.getPerf());
trace_mgr->event_sweep(&conn, &m_sweep_info, state);
if (state == process_state_failed || state == process_state_finished)
m_need_trace = false;
}