8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-25 01:23:03 +01:00
firebird-mirror/src/jrd/tra.cpp

3520 lines
97 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/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;
static int blocking_ast_transaction(void*);
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*, Database*);
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.
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
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
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;
}
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;
}
2006-04-06 10:18:53 +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
/* Read header page and allocate transaction number. */
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
if (dbb->dbb_flags & DBB_read_only) {
number = dbb->dbb_next_transaction;
oldest = dbb->dbb_oldest_transaction;
active = MAX(dbb->dbb_oldest_active, dbb->dbb_oldest_transaction);
}
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);
}
#endif /* SUPERSERVER_V2 */
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
/* Build transaction bitmap to scan for active transactions. */
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);
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;
2001-05-23 15:26:42 +02:00
if (state == tra_active) {
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);
/* Return without cleaning up the TIP's for a ReadOnly database */
if (dbb->dbb_flags & DBB_read_only)
return;
/* First, make damn sure there are no outstanding transactions */
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
/* Read header page and allocate transaction number. Since
the transaction inventory page was initialized to zero, it
transaction is automatically marked active. */
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;
/* Zip thru transactions from the "oldest active" to the next looking for
active transactions. When one is found, declare it dead. */
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
2008-12-18 13:09:35 +01:00
if (retaining_flag && !(transaction->tra_flags & TRA_write || transaction->tra_deferred_work))
{
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
/* Flush pages if transaction logically modified data */
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
else if (transaction->tra_flags & (TRA_prepare2 | TRA_reconnected)) {
/* If the transaction only read data but is a member of a
multi-database transaction with a transaction description
message then flush RDB$TRANSACTIONS. */
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;
}
/* Set the state on the inventory page to be committed */
TRA_set_state(tdbb, transaction, transaction->tra_number, tra_committed);
/* Perform any post commit work */
DFW_perform_post_commit_work(transaction);
/* notify any waiting locks that this transaction is committing;
there could be no lock if this transaction is being reconnected */
++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;
2009-02-01 23:10:12 +01:00
trace.finish(res_successful);
2001-05-23 15:26:42 +02:00
TRA_release_transaction(tdbb, transaction);
}
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);
/* 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
/* 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);
/* Release prior page */
if (sequence) {
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);
}
/* Link into internal data structures */
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
/* Write into pages relation */
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);
/* locate and fetch the proper TIP page */
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
/* calculate the state of the desired transaction */
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
/* fetch the first inventory page */
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
/* move the first page into the bit vector */
2003-12-31 06:36:12 +01:00
UCHAR* p = bit_vector;
if (p) {
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;
}
/* move successive pages into the bit vector */
while (sequence <= last) {
base = sequence * trans_per_tip;
/* release the read lock as we go, so that some one else can
* commit without having to signal all other transactions.
*/
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);
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);
/* If transaction number is already on disk just return. */
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
if (header->hdr_next_transaction) {
if (header->hdr_oldest_active > header->hdr_next_transaction)
BUGCHECK(266); /*next transaction older than oldest active */
if (header->hdr_oldest_transaction > header->hdr_next_transaction)
BUGCHECK(267); /* next transaction older than oldest transaction */
}
/* 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. */
if (!number || dbb->dbb_last_header_write < number) {
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_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;
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;
2006-02-10 04:28:43 +01: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;
2001-05-23 15:26:42 +02:00
if (zp)
*zp = new_number;
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
}
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
/* If there's a transaction description message, log it to RDB$TRANSACTION
2001-05-23 15:26:42 +02:00
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
2001-05-23 15:26:42 +02:00
*/
2003-11-04 00:59:24 +01:00
fb_assert(!(!msg && length) || (msg && (!*msg && length)));
2001-05-23 15:26:42 +02:00
if (msg && length) {
MET_prepare(tdbb, transaction, length, msg);
transaction->tra_flags |= TRA_prepare2;
}
/* Check in with external file system */
EXT_trans_prepare(transaction);
/* Perform any meta data work deferred */
2008-03-18 14:04:05 +01:00
DFW_perform_work(tdbb, transaction);
2001-05-23 15:26:42 +02:00
#ifdef GARBAGE_THREAD
/* Flush pages if transaction logically modified data */
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
else if (transaction->tra_flags & TRA_prepare2) {
/* If the transaction only read data but is a member of a
multi-database transaction with a transaction description
message then flush RDB$TRANSACTIONS. */
CCH_flush(tdbb, FLUSH_SYSTEM, 0);
2001-05-23 15:26:42 +02:00
}
#endif
/* Set the state on the inventory page to be limbo */
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
/* Cannot work on limbo transactions for ReadOnly database */
if (dbb->dbb_flags & DBB_read_only)
ERR_post(Arg::Gds(isc_read_only_database));
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 = gds__vax_integer(id, length);
2001-05-23 15:26:42 +02:00
trans->tra_flags |= TRA_prepared | TRA_reconnected | TRA_write;
const UCHAR state = limbo_transaction(tdbb, trans->tra_number);
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:
message = 262; /* ACTIVE */
break;
case tra_dead:
message = 264; /* ROLLED BACK */
break;
case tra_committed:
message = 263; /* COMMITTED */
break;
default:
message = 265; /* ILL DEFINED */
break;
}
const SLONG number = trans->tra_number;
jrd_tra::destroy(dbb, trans);
2001-05-23 15:26:42 +02:00
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
}
link_transaction(tdbb, trans);
2001-05-23 15:26:42 +02:00
return trans;
}
void TRA_release_transaction(thread_db* tdbb, jrd_tra* transaction)
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();
if (current->bli_materialized) {
if (!transaction->tra_blobs->getNext())
break;
}
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
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
if (transaction->tra_cancel_lock)
LCK_release(tdbb, transaction->tra_cancel_lock);
vec<Lock*>* vector = transaction->tra_relation_locks;
2003-12-31 06:36:12 +01: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
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)
{
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
/* Check in with external file system */
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
/* If force flag is true, get rid of all savepoints to mark the transaction as dead */
2009-02-02 04:35:52 +01:00
if (force_flag)
{
// 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;
}
}
2002-11-03 18:29:51 +01: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)
{
2002-11-03 18:29:51 +01: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;
}
}
2002-11-03 18:29:51 +01: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;
2002-11-03 18:29:51 +01: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
if (tran_sav && count > 0) {
// Undo all user savepoints work
while (transaction->tra_save_point->sav_flags & SAV_user) {
++transaction->tra_save_point->sav_verb_count; /* cause undo */
VIO_verb_cleanup(tdbb, transaction);
}
2003-12-31 06:36:12 +01: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;
}
2002-11-03 18:29:51 +01:00
if (transaction->tra_save_point) {
if (!(transaction->tra_save_point->sav_flags & SAV_trans_level))
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 */
}
}
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))
BUGCHECK(287); /* Too many savepoints */
/* Make sure that any error during savepoint undo is handled by marking
the transaction as dead. */
2001-12-24 03:51:06 +01:00
try {
2001-05-23 15:26:42 +02:00
/* In an attempt to avoid deadlocks, clear the precedence by writing
all dirty buffers for this transaction. */
if (transaction->tra_flags & TRA_write) {
CCH_flush(tdbb, FLUSH_TRAN, transaction->tra_number);
2001-05-23 15:26:42 +02:00
++transaction->tra_save_point->sav_verb_count; /* cause undo */
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
}
catch (const Firebird::Exception&) {
2001-12-24 03:51:06 +01: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
}
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
trace.finish(res_successful);
2001-05-23 15:26:42 +02:00
TRA_release_transaction(tdbb, transaction);
}
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);
/* If we're terminating ourselves and we've
been precommitted then just return. */
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
/* If it is a ReadOnly DB, set the new state in the TIP cache and return */
if ((dbb->dbb_flags & DBB_read_only) && dbb->dbb_tip_cache) {
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
/* set the state on the TIP page */
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;
/* set the new state in the TIP cache as well */
if (dbb->dbb_tip_cache)
TPC_set_state(tdbb, number, state);
CCH_RELEASE(tdbb, &window);
#ifdef SUPERSERVER_V2
/* 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. */
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
}
void TRA_shutdown_attachment(thread_db* tdbb, Attachment* attachment)
2001-05-23 15:26:42 +02:00
{
/**************************************
*
* T R A _ s h u t d o w n _ a t t a c h m e n t
*
**************************************
*
* Functional description
* Release locks associated with transactions for attachment.
*
**************************************/
SET_TDBB(tdbb);
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
{
2001-05-23 15:26:42 +02:00
/* Release the relation locks associated with the transaction */
vec<Lock*>* vector = transaction->tra_relation_locks;
2003-12-31 06:36:12 +01:00
if (vector) {
vec<Lock*>::iterator lock = vector->begin();
for (ULONG i = 0; i < vector->count(); ++i, ++lock) {
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
/* Release transaction lock itself */
++transaction->tra_use_count;
if (transaction->tra_lock)
LCK_release(tdbb, transaction->tra_lock);
--transaction->tra_use_count;
}
}
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->tra_pool : 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
2008-01-16 13:22:11 +01:00
temp->tra_flags = flags;
temp->tra_lock_timeout = lock_timeout;
jrd_tra* transaction = NULL;
2009-04-04 18:39:31 +02:00
try
{
transaction = transaction_start(tdbb, 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->tra_pool : 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);
}
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
2008-01-16 13:22:11 +01:00
bool TRA_sweep(thread_db* tdbb, jrd_tra* trans)
{
/**************************************
*
* T R A _ s w e e p
*
**************************************
*
* Functional description
* Make a garbage collection pass thru database.
*
**************************************/
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
CHECK_DBB(dbb);
2001-05-23 15:26:42 +02:00
2008-01-16 13:22:11 +01:00
/* No point trying to sweep a ReadOnly database */
if (dbb->dbb_flags & DBB_read_only)
return true;
2001-05-23 15:26:42 +02:00
2008-01-16 13:22:11 +01:00
if (dbb->dbb_flags & DBB_sweep_in_progress)
return true;
2001-05-23 15:26:42 +02:00
2008-01-16 13:22:11 +01:00
/* fill out a lock block, zeroing it out first */
2001-05-23 15:26:42 +02:00
Lock temp_lock;
2001-05-23 15:26:42 +02:00
temp_lock.lck_dbb = dbb;
temp_lock.lck_object = trans;
2008-01-16 13:22:11 +01:00
temp_lock.lck_type = LCK_sweep;
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);
if (!LCK_lock(tdbb, &temp_lock, LCK_EX, LCK_NO_WAIT))
2008-01-16 13:22:11 +01:00
{
// clear lock error from status vector
fb_utils::init_status(tdbb->tdbb_status_vector);
2008-01-16 13:22:11 +01:00
return true;
}
2001-05-23 15:26:42 +02:00
2008-01-16 13:22:11 +01:00
dbb->dbb_flags |= DBB_sweep_in_progress;
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
2008-01-16 13:22:11 +01: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
2008-01-16 13:22:11 +01: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
2008-01-16 13:22:11 +01:00
/* Start a transaction, if necessary, to perform the sweep.
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. */
2001-05-23 15:26:42 +02:00
if (!(transaction = trans))
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
/* 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. */
transaction->tra_attachment->att_flags &= ~ATT_notify_gc;
#endif
if (VIO_sweep(tdbb, transaction)) {
const ULONG base = transaction->tra_oldest & ~TRA_MASK;
ULONG active = transaction->tra_oldest;
for (; active < (ULONG) transaction->tra_top; active++)
{
if (transaction->tra_flags & TRA_read_committed) {
if (TPC_cache_state(tdbb, active) == tra_limbo)
break;
}
else {
const ULONG byte = TRANS_OFFSET(active - base);
const USHORT shift = TRANS_SHIFT(active);
2008-03-08 22:20:26 +01:00
if (((transaction->tra_transactions[byte] >> shift) & TRA_MASK) == tra_limbo)
{
break;
2001-05-23 15:26:42 +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. */
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
if (header->hdr_oldest_transaction < --transaction_oldest_active) {
CCH_MARK_MUST_WRITE(tdbb, &window);
header->hdr_oldest_transaction = MIN(active, (ULONG) transaction_oldest_active);
2001-05-23 15:26:42 +02:00
}
CCH_RELEASE(tdbb, &window);
}
if (!trans)
TRA_commit(tdbb, transaction, false);
2001-05-23 15:26:42 +02:00
LCK_release(tdbb, &temp_lock);
dbb->dbb_flags &= ~DBB_sweep_in_progress;
tdbb->tdbb_flags &= ~TDBB_sweeper;
tdbb->setTransaction(tdbb_old_trans);
2001-12-24 03:51:06 +01:00
} // try
catch (const Firebird::Exception& ex) {
Firebird::stuff_exception(tdbb->tdbb_status_vector, ex);
2001-12-24 03:51:06 +01:00
try {
if (!trans && transaction)
TRA_commit(tdbb, transaction, false);
LCK_release(tdbb, &temp_lock);
dbb->dbb_flags &= ~DBB_sweep_in_progress;
tdbb->tdbb_flags &= ~TDBB_sweeper;
tdbb->setTransaction(tdbb_old_trans);
2001-12-24 03:51:06 +01:00
}
catch (const Firebird::Exception& ex2) {
2004-05-12 21:39:17 +02:00
Firebird::stuff_exception(tdbb->tdbb_status_vector, ex2);
2001-12-24 03:51:06 +01:00
LCK_release(tdbb, &temp_lock);
dbb->dbb_flags &= ~DBB_sweep_in_progress;
tdbb->tdbb_flags &= ~TDBB_sweeper;
tdbb->setTransaction(tdbb_old_trans);
2001-12-24 03:51:06 +01:00
}
return false;
2001-12-24 03:51:06 +01:00
}
return true;
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);
/* Create, wait on, and release lock on target transaction. If
we can't get the lock due to deadlock */
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;
/* If the recorded state of the transaction is active, we know better. If
it were active, he'd be alive now. Mark him dead. */
if (state == tra_active) {
state = tra_dead;
TRA_set_state(tdbb, 0, number, tra_dead);
}
if (number > trans->tra_top)
return state;
/* If the transaction disppeared into limbo, died, for constructively
died, tweak the transaction state snapshot to reflect the new state.
This is guarenteed safe. */
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);
else {
trans->tra_transactions[byte] &= ~(TRA_MASK << shift);
trans->tra_transactions[byte] |= state << shift;
}
return state;
}
static int blocking_ast_transaction(void* ast_object)
{
/**************************************
*
* b l o c k i n g _ a s t _ t r a n s a c t i o n
*
**************************************
*
* Functional description
* Mark the transaction to cancel its active requests.
*
**************************************/
jrd_tra* transaction = static_cast<jrd_tra*>(ast_object);
try
{
Database* dbb = transaction->tra_cancel_lock->lck_dbb;
Database::SyncGuard dsGuard(dbb, true);
ThreadContextHolder tdbb;
tdbb->setDatabase(dbb);
2008-12-03 02:05:53 +01:00
Attachment* att = transaction->tra_cancel_lock->lck_attachment;
tdbb->setAttachment(att);
Jrd::ContextPoolHolder context(tdbb, 0);
if (transaction->tra_cancel_lock)
LCK_release(tdbb, transaction->tra_cancel_lock);
transaction->tra_flags |= TRA_cancel_request;
att->cancelExternalConnection(tdbb);
}
catch (const Firebird::Exception&)
{} // no-op
return 0;
}
2001-05-23 15:26:42 +02:00
#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
/* No need to write TID onto the TIP page, for a RO DB */
if (dbb->dbb_flags & DBB_read_only)
return number;
/* If this is the first transaction on a TIP, allocate the TIP now. */
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
/* Before incrementing the next transaction Id, make sure the current one is valid */
if (header->hdr_next_transaction) {
if (header->hdr_oldest_active > header->hdr_next_transaction)
BUGCHECK(266); /*next transaction older than oldest active */
if (header->hdr_oldest_transaction > header->hdr_next_transaction)
BUGCHECK(267); /* next transaction older than oldest transaction */
}
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
/* If this is the first transaction on a TIP, allocate the TIP now. */
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
/* Extend, if necessary, has apparently succeeded. Next, update header
page */
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
* found, by all means use it. Write flag is TRUE
* to write retaining lock and FALSE to read it.
* 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);
/* Get a commit retaining lock, if not present. */
Lock* lock = dbb->dbb_retaining_lock;
2003-12-31 06:36:12 +01: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
/* 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. */
SLONG youngest_retaining;
2008-01-16 13:22:11 +01:00
2001-05-23 15:26:42 +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
}
else {
youngest_retaining = LOCK_read_data(lock->lck_id);
if (number > youngest_retaining)
return;
/* 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
while (number < youngest_retaining) {
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
{
jrd_rel* base_rel = MET_lookup_relation(tdbb, ctx[i]->vcx_relation_name);
if (!base_rel)
2002-03-31 01:40:08 +01:00
{
ERR_post(Arg::Gds(isc_bad_tpb_content) <<
/* should be a BUGCHECK */
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
/* 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;
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)
2001-05-23 15:26:42 +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))
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
{
2001-05-23 15:26:42 +02:00
if (request->req_transaction) {
EXE_unwind(tdbb, request);
EXE_start(tdbb, request, trans);
}
/* now take care of any other request levels;
start at level 1 since level 0 was just handled */
vec<jrd_req*>* vector = request->req_sub_requests;
2003-12-31 06:36:12 +01:00
if (vector) {
for (USHORT level = 1; level < vector->count(); level++) {
jrd_req* clone = (*vector)[level];
2003-12-31 06:36:12 +01: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
}
}
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);
/* 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. */
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
/* Create a new transaction lock, inheriting oldest active
from transaction being committed. */
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->genSharedUniqueNumber(tdbb);
2001-05-23 15:26:42 +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;
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;
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
/* 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. */
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
if (!(dbb->dbb_flags & DBB_read_only)) {
/* 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;
/* Release transaction lock since it isn't needed
anymore and the new one is already in place. */
if (old_lock) {
++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
}
/* Perform any post commit work OR delete entries from deferred list */
if (commit)
DFW_perform_post_commit_work(transaction);
else
DFW_delete_deferred(transaction, -1);
transaction->tra_flags &= ~(TRA_write | TRA_prepared);
/* 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. */
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
}
2001-05-23 15:26:42 +02:00
if (transaction->tra_save_point) {
if (!(transaction->tra_save_point->sav_flags & SAV_trans_level))
BUGCHECK(287); /* Too many savepoints */
2002-11-03 18:29:51 +01:00
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;
}
if (transaction->tra_flags & TRA_precommitted) {
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, Database* dbb)
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.
*
**************************************/
2008-12-18 13:09:35 +01:00
if ((dbb->dbb_flags & DBB_sweep_in_progress) || (dbb->dbb_ast_flags & DBB_shutdown))
2001-05-24 16:54:26 +02:00
{
return; // false;
2001-05-24 16:54:26 +02:00
}
2001-05-23 15:26:42 +02:00
SET_TDBB(tdbb);
2001-12-24 03:51:06 +01:00
// fill out the lock block
Lock temp_lock;
2001-12-24 03:51:06 +01:00
temp_lock.lck_dbb = dbb;
temp_lock.lck_type = LCK_sweep;
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);
2001-05-23 15:26:42 +02:00
if (!LCK_lock(tdbb, &temp_lock, LCK_EX, LCK_NO_WAIT))
2001-05-24 16:54:26 +02:00
{
// clear lock error from status vector
fb_utils::init_status(tdbb->tdbb_status_vector);
return; // false;
2001-05-24 16:54:26 +02:00
}
2001-05-23 15:26:42 +02:00
LCK_release(tdbb, &temp_lock);
2001-05-24 16:54:26 +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);
if (!database)
{
2001-05-23 15:26:42 +02:00
ERR_log(0, 0, "cannot start sweep thread, Out of Memory");
return; // false;
2001-05-23 15:26:42 +02:00
}
2001-05-24 16:54:26 +02:00
strcpy(database, pszFilename);
2008-12-25 07:09:37 +01:00
if (gds__thread_start(sweep_database, database, THREAD_medium, 0, 0))
2001-05-24 16:54:26 +02:00
{
gds__free(database);
ERR_log(0, 0, "cannot start sweep thread");
}
return; // true;
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
/* 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
/* If there aren't any relation locks to seize, we're done. */
vec<Lock*>* vector = transaction->tra_relation_locks;
if (!vector)
2001-05-23 15:26:42 +02:00
return;
/* Try to seize all relation locks.
2001-05-23 15:26:42 +02:00
If any can't be seized, release all and try again. */
2003-04-07 21:16:57 +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;
}
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
2008-01-16 13:22:11 +01: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
2008-01-16 13:22:11 +01:00
#else /* SUPERSERVER_V2 */
if (dbb->dbb_flags & DBB_read_only) {
number = dbb->dbb_next_transaction + dbb->genSharedUniqueNumber(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;
}
else {
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
2008-01-16 13:22:11 +01:00
#endif /* SUPERSERVER_V2 */
2001-05-23 15:26:42 +02:00
2008-01-16 13:22:11 +01: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;
delete temp;
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;
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
}
/* Link the transaction to the attachment block before releasing
header page for handling signals. */
link_transaction(tdbb, trans);
#ifndef SUPERSERVER_V2
if (!(dbb->dbb_flags & DBB_read_only))
CCH_RELEASE(tdbb, &window);
#endif
if (dbb->dbb_flags & DBB_read_only) {
/* Set transaction flags to TRA_precommitted, TRA_readonly */
trans->tra_flags |= (TRA_readonly | TRA_precommitted);
}
/* 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. */
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
/* 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. */
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;
for (; active < number; active++) {
if (trans->tra_flags & TRA_read_committed)
oldest_state = TPC_cache_state(tdbb, active);
else {
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
}
if (oldest_state == tra_active) {
temp_lock.lck_key.lck_long = active;
SLONG data = LCK_read_data(tdbb, &temp_lock);
2008-01-16 13:22:11 +01:00
if (!data) {
if (cleanup) {
if (TRA_wait(tdbb, trans, active, jrd_tra::tra_no_wait) == tra_committed)
cleanup = false;
continue;
}
data = active;
}
oldest_active = MIN(oldest_active, active);
/* Find the oldest record version that cannot be garbage collected yet
by taking the minimum of all all versions needed by all active
transactions. */
if (data < trans->tra_oldest_active)
trans->tra_oldest_active = data;
/* 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. */
if (trans->tra_oldest_active == (SLONG) oldest_snapshot)
break;
#ifndef VMS
/* Query the minimum lock data for all active transaction locks.
This will be the oldest active snapshot used for regulating
garbage collection. */
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
}
}
// 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
/* 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. */
#ifdef VMS
if (trans->tra_oldest_active != oldest_snapshot)
compute_oldest_retaining(tdbb, trans, false);
#endif
/* 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 */
oldest_state = tra_committed;
for (oldest = trans->tra_oldest; oldest < number; oldest++) {
if (trans->tra_flags & TRA_read_committed)
oldest_state = TPC_cache_state(tdbb, oldest);
else {
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
}
/* If the transaction block is getting out of hand, force a sweep */
if (dbb->dbb_sweep_interval &&
!(tdbb->getAttachment()->att_flags & ATT_no_cleanup) &&
2008-12-25 15:25:01 +01:00
(trans->tra_oldest_active - trans->tra_oldest > dbb->dbb_sweep_interval) &&
oldest_state != tra_limbo)
2008-01-16 13:22:11 +01:00
{
// Why nobody checks the result? Changed the function to return nothing.
start_sweeper(tdbb, dbb);
}
/* Check in with external file system */
EXT_trans_start(trans);
/* 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. */
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;
}
/* Allocate the cancellation lock */
lock = FB_NEW_RPT(*trans->tra_pool, sizeof(SLONG)) Lock();
trans->tra_cancel_lock = lock;
lock->lck_type = LCK_cancel;
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_key.lck_long = trans->tra_number;
lock->lck_dbb = dbb;
lock->lck_ast = blocking_ast_transaction;
lock->lck_object = trans;
/* if the user asked us to restart all requests in this attachment,
do so now using the new transaction */
if (trans->tra_flags & TRA_restart_requests)
restart_requests(tdbb, trans);
/* 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. */
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, trans->tra_lock);
delete trans->tra_lock;
trans->tra_lock = NULL;
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;
}
DFW_delete_deferred(this, -1);
}
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;
}