8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-23 07:23:04 +01:00

Major refactoring of savepoints. Beware of possible regressions.

This commit is contained in:
Dmitry Yemanov 2016-05-06 20:16:14 +03:00
parent a5fcb82173
commit 174c252e0c
13 changed files with 1241 additions and 1149 deletions

View File

@ -34,7 +34,7 @@
#include "../common/classes/array.h"
#include "../common/classes/ByteChunk.h"
#include "../common/classes/Nullable.h"
#include "../jrd/vio_proto.h"
#include "../jrd/Savepoint.h"
#include "../dsql/errd_proto.h"
namespace Jrd {

View File

@ -11091,7 +11091,7 @@ dsc* UdfCallNode::execute(thread_db* tdbb, jrd_req* request) const
jrd_tra* transaction = request->req_transaction;
const SLONG savePointNumber = transaction->tra_save_point ?
transaction->tra_save_point->sav_number : 0;
transaction->tra_save_point->getNumber() : 0;
jrd_req* funcRequest = function->getStatement()->findRequest(tdbb);
@ -11115,10 +11115,10 @@ dsc* UdfCallNode::execute(thread_db* tdbb, jrd_req* request) const
// Clean up all savepoints started during execution of the procedure.
if (transaction != attachment->getSysTransaction())
if (!(transaction->tra_flags & TRA_system))
{
while (transaction->tra_save_point &&
transaction->tra_save_point->sav_number > savePointNumber)
transaction->tra_save_point->getNumber() > savePointNumber)
{
transaction->rollforwardSavepoint(tdbb);
}

View File

@ -223,6 +223,16 @@ namespace
AutoSetRestore<USHORT> autoFlags;
AutoSetRestore<USHORT> autoScopeLevel;
};
class SavepointChangeMarker : public Savepoint::ChangeMarker
{
public:
explicit SavepointChangeMarker(jrd_tra* transaction)
: Savepoint::ChangeMarker(transaction->tra_flags & TRA_system ?
NULL : transaction->tra_save_point)
{}
};
} // namespace
@ -515,17 +525,15 @@ BlockNode* BlockNode::pass2(thread_db* tdbb, CompilerScratch* csb)
const StmtNode* BlockNode::execute(thread_db* tdbb, jrd_req* request, ExeState* exeState) const
{
jrd_tra* transaction = request->req_transaction;
jrd_tra* sysTransaction = request->req_attachment->getSysTransaction();
SLONG count;
switch (request->req_operation)
{
case jrd_req::req_evaluate:
if (transaction != sysTransaction)
if (!(transaction->tra_flags & TRA_system))
{
VIO_start_save_point(tdbb, transaction);
const Savepoint* save_point = transaction->tra_save_point;
count = save_point->sav_number;
const Savepoint* const savepoint = Savepoint::start(transaction);
count = savepoint->getNumber();
*request->getImpure<SLONG>(impureOffset) = count;
}
return action;
@ -540,12 +548,12 @@ const StmtNode* BlockNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
// BREAK/LEAVE/CONTINUE statement in the SP/trigger code.
// Do not perform the error handling stuff.
if (transaction != sysTransaction)
if (!(transaction->tra_flags & TRA_system))
{
count = *request->getImpure<SLONG>(impureOffset);
while (transaction->tra_save_point &&
transaction->tra_save_point->sav_number >= count)
transaction->tra_save_point->getNumber() >= count)
{
transaction->rollforwardSavepoint(tdbb);
}
@ -559,7 +567,7 @@ const StmtNode* BlockNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
if (handlers && handlers->statements.getCount() > 0)
{
// First of all rollback failed work
if (transaction != sysTransaction)
if (!(transaction->tra_flags & TRA_system))
{
count = *request->getImpure<SLONG>(impureOffset);
@ -568,20 +576,22 @@ const StmtNode* BlockNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
// That's why transaction->rollbackToSavepoint() cannot be used here
// The savepoint of this block will be dealt with below.
// Do this only if error handlers exist. If not - leave rollbacking to caller node
while (transaction->tra_save_point &&
count < transaction->tra_save_point->sav_number &&
transaction->tra_save_point->sav_next &&
count < transaction->tra_save_point->sav_next->sav_number)
count < transaction->tra_save_point->getNumber() &&
transaction->tra_save_point->getNext() &&
count < transaction->tra_save_point->getNext()->getNumber())
{
transaction->rollforwardSavepoint(tdbb);
}
// There can be no savepoints above the given one
if (transaction->tra_save_point && transaction->tra_save_point->sav_number > count)
{
if (transaction->tra_save_point && transaction->tra_save_point->getNumber() > count)
transaction->rollbackSavepoint(tdbb);
}
// after that we still have to have our savepoint. If not - CORE-4424/4483 is sneaking around
fb_assert(transaction->tra_save_point && transaction->tra_save_point->sav_number == count);
fb_assert(transaction->tra_save_point && transaction->tra_save_point->getNumber() == count);
}
temp = parentStmt;
@ -646,7 +656,7 @@ const StmtNode* BlockNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
// The error is dealt with by the application, cleanup
// this block's savepoint.
if (handled && transaction != sysTransaction)
if (handled && !(transaction->tra_flags & TRA_system))
{
// Check that exception handlers were executed in context of right savepoint.
// If not - mirror copy of CORE-4424 or CORE-4483 is around here.
@ -656,10 +666,11 @@ const StmtNode* BlockNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
// outer before
// outer after (this block)
// inner after
// Because of this following assert is commentd out
//fb_assert(transaction->tra_save_point && transaction->tra_save_point->sav_number == count);
// Because of this following assert is commented out
//fb_assert(transaction->tra_save_point && transaction->tra_save_point->getNumber() == count);
for (const Savepoint* save_point = transaction->tra_save_point;
save_point && count <= save_point->sav_number;
save_point && count <= save_point->getNumber();
save_point = transaction->tra_save_point)
{
transaction->rollforwardSavepoint(tdbb);
@ -676,13 +687,13 @@ const StmtNode* BlockNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
}
case jrd_req::req_return:
if (transaction != sysTransaction)
if (!(transaction->tra_flags & TRA_system))
{
count = *request->getImpure<SLONG>(impureOffset);
// rollforward all savepoints
for (const Savepoint* save_point = transaction->tra_save_point;
save_point && save_point->sav_next && count <= save_point->sav_number;
save_point && save_point->getNext() && count <= save_point->getNumber();
save_point = transaction->tra_save_point)
{
transaction->rollforwardSavepoint(tdbb);
@ -2351,7 +2362,6 @@ const StmtNode* EraseNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
// Perform erase operation.
const StmtNode* EraseNode::erase(thread_db* tdbb, jrd_req* request, WhichTrigger whichTrig) const
{
Jrd::Attachment* attachment = tdbb->getAttachment();
jrd_tra* transaction = request->req_transaction;
record_param* rpb = &request->req_rpb[stream];
jrd_rel* relation = rpb->rpb_relation;
@ -2397,8 +2407,7 @@ const StmtNode* EraseNode::erase(thread_db* tdbb, jrd_req* request, WhichTrigger
rpb->rpb_runtime_flags &= ~RPB_refetch;
}
if (transaction != attachment->getSysTransaction())
++transaction->tra_save_point->sav_verb_count;
SavepointChangeMarker scMarker(transaction);
// Handle pre-operation trigger.
preModifyEraseTriggers(tdbb, &relation->rel_pre_erase, whichTrig, rpb, NULL, TRIGGER_DELETE);
@ -2445,9 +2454,6 @@ const StmtNode* EraseNode::erase(thread_db* tdbb, jrd_req* request, WhichTrigger
request->req_records_affected.bumpModified(true);
}
if (transaction != attachment->getSysTransaction())
--transaction->tra_save_point->sav_verb_count;
rpb->rpb_number.setValid(false);
return parentStmt;
@ -2930,8 +2936,6 @@ void ExecProcedureNode::executeProcedure(thread_db* tdbb, jrd_req* request) cons
Arg::Str(procedure->getName().identifier) << Arg::Str(procedure->getName().package));
}
Jrd::Attachment* attachment = tdbb->getAttachment();
ULONG inMsgLength = 0;
UCHAR* inMsg = NULL;
@ -2972,7 +2976,7 @@ void ExecProcedureNode::executeProcedure(thread_db* tdbb, jrd_req* request) cons
jrd_tra* transaction = request->req_transaction;
const SLONG savePointNumber = transaction->tra_save_point ?
transaction->tra_save_point->sav_number : 0;
transaction->tra_save_point->getNumber() : 0;
jrd_req* procRequest = procedure->getStatement()->findRequest(tdbb);
@ -2994,10 +2998,10 @@ void ExecProcedureNode::executeProcedure(thread_db* tdbb, jrd_req* request) cons
// Clean up all savepoints started during execution of the procedure.
if (transaction != attachment->getSysTransaction())
if (!(transaction->tra_flags & TRA_system))
{
while (transaction->tra_save_point &&
transaction->tra_save_point->sav_number > savePointNumber)
transaction->tra_save_point->getNumber() > savePointNumber)
{
transaction->rollforwardSavepoint(tdbb);
}
@ -3704,8 +3708,8 @@ const StmtNode* InAutonomousTransactionNode::execute(thread_db* tdbb, jrd_req* r
request->req_auto_trans.push(org_transaction);
impure->traNumber = transaction->tra_number;
VIO_start_save_point(tdbb, transaction);
impure->savNumber = transaction->tra_save_point->sav_number;
const Savepoint* const savepoint = Savepoint::start(transaction);
impure->savNumber = savepoint->getNumber();
if (!(attachment->att_flags & ATT_no_db_triggers))
{
@ -3717,7 +3721,7 @@ const StmtNode* InAutonomousTransactionNode::execute(thread_db* tdbb, jrd_req* r
}
jrd_tra* transaction = request->req_transaction;
fb_assert(transaction && transaction != attachment->getSysTransaction());
fb_assert(transaction && !(transaction->tra_flags & TRA_system));
if (!impure->traNumber)
return parentStmt;
@ -3734,8 +3738,8 @@ const StmtNode* InAutonomousTransactionNode::execute(thread_db* tdbb, jrd_req* r
}
if (transaction->tra_save_point &&
!(transaction->tra_save_point->sav_flags & SAV_user) &&
!transaction->tra_save_point->sav_verb_count)
transaction->tra_save_point->isSystem() &&
transaction->tra_save_point->isChanging())
{
transaction->rollforwardSavepoint(tdbb);
}
@ -3759,8 +3763,8 @@ const StmtNode* InAutonomousTransactionNode::execute(thread_db* tdbb, jrd_req* r
}
if (transaction->tra_save_point &&
!(transaction->tra_save_point->sav_flags & SAV_user) &&
!transaction->tra_save_point->sav_verb_count)
transaction->tra_save_point->isSystem() &&
transaction->tra_save_point->isChanging())
{
transaction->rollforwardSavepoint(tdbb);
}
@ -4697,18 +4701,17 @@ StmtNode* ForNode::pass2(thread_db* tdbb, CompilerScratch* csb)
const StmtNode* ForNode::execute(thread_db* tdbb, jrd_req* request, ExeState* /*exeState*/) const
{
jrd_tra* transaction = request->req_transaction;
jrd_tra* sysTransaction = request->req_attachment->getSysTransaction();
switch (request->req_operation)
{
case jrd_req::req_evaluate:
*request->getImpure<SLONG>(impureOffset) = 0;
if (transaction != sysTransaction &&
transaction->tra_save_point && transaction->tra_save_point->sav_verb_actions)
if (!(transaction->tra_flags & TRA_system) &&
transaction->tra_save_point &&
transaction->tra_save_point->hasChanges())
{
VIO_start_save_point(tdbb, transaction);
const Savepoint* save_point = transaction->tra_save_point;
*request->getImpure<SLONG>(impureOffset) = save_point->sav_number;
const Savepoint* const savepoint = Savepoint::start(transaction);
*request->getImpure<SLONG>(impureOffset) = savepoint->getNumber();
}
cursor->open(tdbb);
request->req_records_affected.clear();
@ -4750,9 +4753,12 @@ const StmtNode* ForNode::execute(thread_db* tdbb, jrd_req* request, ExeState* /*
if (sav_number)
{
while (transaction->tra_save_point &&
transaction->tra_save_point->sav_number >= sav_number)
transaction->tra_save_point->getNumber() >= sav_number)
{
VIO_verb_cleanup(tdbb, transaction);
if (transaction->tra_save_point->isChanging()) // we must rollback this savepoint
transaction->rollbackSavepoint(tdbb);
else
transaction->rollforwardSavepoint(tdbb);
}
}
@ -6107,7 +6113,6 @@ const StmtNode* ModifyNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
// Execute a MODIFY statement.
const StmtNode* ModifyNode::modify(thread_db* tdbb, jrd_req* request, WhichTrigger whichTrig) const
{
Jrd::Attachment* attachment = tdbb->getAttachment();
jrd_tra* transaction = request->req_transaction;
impure_state* impure = request->getImpure<impure_state>(impureOffset);
@ -6142,8 +6147,7 @@ const StmtNode* ModifyNode::modify(thread_db* tdbb, jrd_req* request, WhichTrigg
// varchar field whose tail may contain garbage.
cleanupRpb(tdbb, newRpb);
if (transaction != attachment->getSysTransaction())
++transaction->tra_save_point->sav_verb_count;
SavepointChangeMarker scMarker(transaction);
preModifyEraseTriggers(tdbb, &relation->rel_pre_modify, whichTrig, orgRpb, newRpb,
TRIGGER_UPDATE);
@ -6177,9 +6181,6 @@ const StmtNode* ModifyNode::modify(thread_db* tdbb, jrd_req* request, WhichTrigg
if (!relation->rel_file && !relation->rel_view_rse && !relation->isVirtual())
IDX_modify_check_constraints(tdbb, orgRpb, newRpb, transaction);
if (transaction != attachment->getSysTransaction())
--transaction->tra_save_point->sav_verb_count;
// CVC: Increment the counter only if we called VIO/EXT_modify() and
// we were successful.
if (!(request->req_view_flags & req_first_modify_return))
@ -6959,7 +6960,6 @@ const StmtNode* StoreNode::execute(thread_db* tdbb, jrd_req* request, ExeState*
// Execute a STORE statement.
const StmtNode* StoreNode::store(thread_db* tdbb, jrd_req* request, WhichTrigger whichTrig) const
{
Jrd::Attachment* attachment = tdbb->getAttachment();
jrd_tra* transaction = request->req_transaction;
impure_state* impure = request->getImpure<impure_state>(impureOffset);
@ -6979,79 +6979,76 @@ const StmtNode* StoreNode::store(thread_db* tdbb, jrd_req* request, WhichTrigger
break;
case jrd_req::req_return:
if (impure->sta_state)
return parentStmt;
if (transaction != attachment->getSysTransaction())
++transaction->tra_save_point->sav_verb_count;
if (relation->rel_pre_store && whichTrig != POST_TRIG)
if (!impure->sta_state)
{
EXE_execute_triggers(tdbb, &relation->rel_pre_store, NULL, rpb,
TRIGGER_INSERT, PRE_TRIG);
}
SavepointChangeMarker scMarker(transaction);
if (validations.hasData())
validateExpressions(tdbb, validations);
if (relation->rel_pre_store && whichTrig != POST_TRIG)
{
EXE_execute_triggers(tdbb, &relation->rel_pre_store, NULL, rpb,
TRIGGER_INSERT, PRE_TRIG);
}
// For optimum on-disk record compression, zero all unassigned
// fields. In addition, zero the tail of assigned varying fields
// so that previous remnants don't defeat compression efficiency.
if (validations.hasData())
validateExpressions(tdbb, validations);
// CVC: The code that was here was moved to its own routine: cleanupRpb()
// and replaced by the call shown below.
// For optimum on-disk record compression, zero all unassigned
// fields. In addition, zero the tail of assigned varying fields
// so that previous remnants don't defeat compression efficiency.
cleanupRpb(tdbb, rpb);
// CVC: The code that was here was moved to its own routine: cleanupRpb()
// and replaced by the call shown below.
if (relation->rel_file)
EXT_store(tdbb, rpb);
else if (relation->isVirtual())
VirtualTable::store(tdbb, rpb);
else if (!relation->rel_view_rse)
{
VIO_store(tdbb, rpb, transaction);
IDX_store(tdbb, rpb, transaction);
}
cleanupRpb(tdbb, rpb);
rpb->rpb_number.setValid(true);
if (relation->rel_file)
EXT_store(tdbb, rpb);
else if (relation->isVirtual())
VirtualTable::store(tdbb, rpb);
else if (!relation->rel_view_rse)
{
VIO_store(tdbb, rpb, transaction);
IDX_store(tdbb, rpb, transaction);
}
if (relation->rel_post_store && whichTrig != PRE_TRIG)
{
EXE_execute_triggers(tdbb, &relation->rel_post_store, NULL, rpb,
TRIGGER_INSERT, POST_TRIG);
}
rpb->rpb_number.setValid(true);
// CVC: Increment the counter only if we called VIO/EXT_store() and we were successful.
if (!(request->req_view_flags & req_first_store_return))
{
request->req_view_flags |= req_first_store_return;
if (relation->rel_view_rse)
request->req_top_view_store = relation;
}
if (relation->rel_post_store && whichTrig != PRE_TRIG)
{
EXE_execute_triggers(tdbb, &relation->rel_post_store, NULL, rpb,
TRIGGER_INSERT, POST_TRIG);
}
if (relation == request->req_top_view_store)
{
if (!subStore && (whichTrig == ALL_TRIGS || whichTrig == POST_TRIG))
// CVC: Increment the counter only if we called VIO/EXT_store() and we were successful.
if (!(request->req_view_flags & req_first_store_return))
{
request->req_view_flags |= req_first_store_return;
if (relation->rel_view_rse)
request->req_top_view_store = relation;
}
if (relation == request->req_top_view_store)
{
if (!subStore && (whichTrig == ALL_TRIGS || whichTrig == POST_TRIG))
{
request->req_records_inserted++;
request->req_records_affected.bumpModified(true);
}
}
else if (relation->rel_file || !relation->rel_view_rse)
{
request->req_records_inserted++;
request->req_records_affected.bumpModified(true);
}
}
else if (relation->rel_file || !relation->rel_view_rse)
{
request->req_records_inserted++;
request->req_records_affected.bumpModified(true);
}
if (transaction != attachment->getSysTransaction())
--transaction->tra_save_point->sav_verb_count;
if (statement2)
{
impure->sta_state = 1;
request->req_operation = jrd_req::req_evaluate;
return statement2;
if (statement2)
{
impure->sta_state = 1;
request->req_operation = jrd_req::req_evaluate;
return statement2;
}
}
// fall into
default:
return parentStmt;
@ -7139,83 +7136,75 @@ const StmtNode* UserSavepointNode::execute(thread_db* tdbb, jrd_req* request, Ex
jrd_tra* transaction = request->req_transaction;
if (request->req_operation == jrd_req::req_evaluate &&
transaction != request->req_attachment->getSysTransaction())
!(transaction->tra_flags & TRA_system))
{
// Skip the savepoint created by EXE_start
Savepoint* savepoint = transaction->tra_save_point->sav_next;
Savepoint* previous = transaction->tra_save_point;
Savepoint* const previous = transaction->tra_save_point;
// Find savepoint
bool found = false;
while (true)
Savepoint* savepoint = NULL;
for (Savepoint::Iterator iter(previous); *iter; ++iter)
{
if (!savepoint || !(savepoint->sav_flags & SAV_user))
Savepoint* const current = *iter;
if (current == previous)
continue;
if (current->isSystem())
break;
if (name == savepoint->sav_name)
if (current->getName() == name)
{
found = true;
savepoint = current;
break;
}
previous = savepoint;
savepoint = savepoint->sav_next;
}
if (!found && command != CMD_SET)
if (!savepoint && command != CMD_SET)
ERR_post(Arg::Gds(isc_invalid_savepoint) << Arg::Str(name));
switch (command)
{
case CMD_SET:
// Release the savepoint
if (found)
{
savepoint->rollforward(tdbb);
previous->sav_next = savepoint->sav_next;
savepoint->sav_next = transaction->tra_save_free;
transaction->tra_save_free = savepoint;
}
if (savepoint)
savepoint->rollforward(tdbb, previous);
// Use the savepoint created by EXE_start
transaction->tra_save_point->sav_flags |= SAV_user;
transaction->tra_save_point->sav_name = name;
transaction->tra_save_point->setName(name);
break;
case CMD_RELEASE_ONLY:
{
// Release the savepoint
savepoint->rollforward(tdbb);
previous->sav_next = savepoint->sav_next;
savepoint->sav_next = transaction->tra_save_free;
transaction->tra_save_free = savepoint;
savepoint->rollforward(tdbb, previous);
break;
}
case CMD_RELEASE:
{
const SLONG sav_number = savepoint->sav_number;
const SLONG sav_number = savepoint->getNumber();
// Release the savepoint and all subsequent ones
while (transaction->tra_save_point &&
transaction->tra_save_point->sav_number >= sav_number)
transaction->tra_save_point->getNumber() >= sav_number)
{
transaction->rollforwardSavepoint(tdbb);
}
// Restore the savepoint initially created by EXE_start
VIO_start_save_point(tdbb, transaction);
Savepoint::start(transaction);
break;
}
case CMD_ROLLBACK:
{
transaction->rollbackToSavepoint(tdbb, savepoint->sav_number);
transaction->rollbackToSavepoint(tdbb, savepoint->getNumber());
// Now set the savepoint again to allow to return to it later
VIO_start_save_point(tdbb, transaction);
transaction->tra_save_point->sav_flags |= SAV_user;
transaction->tra_save_point->sav_name = name;
Savepoint* const savepoint = Savepoint::start(transaction);
savepoint->setName(name);
break;
}
@ -7817,7 +7806,6 @@ void SavePointNode::genBlr(DsqlCompilerScratch* dsqlScratch)
const StmtNode* SavePointNode::execute(thread_db* tdbb, jrd_req* request, ExeState* exeState) const
{
jrd_tra* transaction = request->req_transaction;
jrd_tra* sysTransaction = request->req_attachment->getSysTransaction();
switch (blrOp)
{
@ -7825,8 +7813,8 @@ const StmtNode* SavePointNode::execute(thread_db* tdbb, jrd_req* request, ExeSta
if (request->req_operation == jrd_req::req_evaluate)
{
// Start a save point.
if (transaction != sysTransaction)
VIO_start_save_point(tdbb, transaction);
if (!(transaction->tra_flags & TRA_system))
Savepoint::start(transaction);
request->req_operation = jrd_req::req_return;
}
@ -7837,7 +7825,7 @@ const StmtNode* SavePointNode::execute(thread_db* tdbb, jrd_req* request, ExeSta
request->req_operation == jrd_req::req_unwind)
{
// If any requested modify/delete/insert ops have completed, forget them.
if (transaction != sysTransaction)
if (!(transaction->tra_flags & TRA_system))
{
// If an error is still pending when the savepoint is supposed to end, then the
// application didn't handle the error and the savepoint should be undone.

View File

@ -36,12 +36,10 @@ enum BlockType
type_irl,
type_idl,
type_sdw,
type_vct,
type_blf,
type_arr,
type_map,
type_prm,
type_sav,
type_idb,
type_tpc,
type_svc,

629
src/jrd/Savepoint.cpp Normal file
View File

@ -0,0 +1,629 @@
/*
* The contents of this file are subject to the Interbase Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy
* of the License at http://www.Inprise.com/IPL.html
*
* Software distributed under the License is distributed on an
* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
* or implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code was created by Inprise Corporation
* and its predecessors. Portions created by Inprise Corporation are
* Copyright (C) Inprise Corporation.
*
* All Rights Reserved.
* Contributor(s): ______________________________________.
*/
#include "firebird.h"
#include "../common/gdsassert.h"
#include "../jrd/tra.h"
#include "../jrd/blb_proto.h"
#include "../jrd/cch_proto.h"
#include "../jrd/dfw_proto.h"
#include "../jrd/dpm_proto.h"
#include "../jrd/idx_proto.h"
#include "../jrd/vio_proto.h"
#include "Savepoint.h"
using namespace Firebird;
using namespace Jrd;
// UndoItem implementation
UndoItem::UndoItem(jrd_tra* transaction, RecordNumber recordNumber, const Record* record)
: m_number(recordNumber.getValue()), m_format(record->getFormat())
{
fb_assert(m_format);
m_offset = transaction->getUndoSpace()->allocateSpace(m_format->fmt_length);
transaction->getUndoSpace()->write(m_offset, record->getData(), record->getLength());
}
Record* UndoItem::setupRecord(jrd_tra* transaction) const
{
if (m_format)
{
Record* const record = transaction->getUndoRecord(m_format);
transaction->getUndoSpace()->read(m_offset, record->getData(), record->getLength());
return record;
}
return NULL;
}
void UndoItem::release(jrd_tra* transaction)
{
if (m_format)
{
transaction->getUndoSpace()->releaseSpace(m_offset, m_format->fmt_length);
m_format = NULL;
}
}
// VerbAction implementation
void VerbAction::garbageCollectIdxLite(thread_db* tdbb, jrd_tra* transaction, SINT64 recordNumber,
VerbAction* nextAction, Record* goingRecord)
{
// Clean up index entries and referenced BLOBs.
// This routine uses smaller set of staying record than original VIO_garbage_collect_idx().
//
// Notes:
//
// This speed trick is possible only because btr.cpp:insert_node() allows duplicate nodes
// which work as an index entry reference counter.
record_param rpb;
rpb.rpb_relation = vct_relation;
rpb.rpb_number.setValue(recordNumber);
rpb.rpb_record = NULL;
rpb.getWindow(tdbb).win_flags = 0;
rpb.rpb_transaction_nr = transaction->tra_number;
Record* next_ver;
AutoUndoRecord undo_next_ver(transaction->findNextUndo(this, vct_relation, recordNumber));
AutoPtr<Record> real_next_ver;
next_ver = undo_next_ver;
if (!DPM_get(tdbb, &rpb, LCK_read))
BUGCHECK(186); // msg 186 record disappeared
else
{
if (next_ver || (rpb.rpb_flags & rpb_deleted))
CCH_RELEASE(tdbb, &rpb.getWindow(tdbb));
else
{
VIO_data(tdbb, &rpb, transaction->tra_pool);
next_ver = real_next_ver = rpb.rpb_record;
}
}
if (rpb.rpb_transaction_nr != transaction->tra_number)
BUGCHECK(185); // msg 185 wrong record version
Record* prev_ver = NULL;
AutoUndoRecord undo_prev_ver;
AutoPtr<Record> real_prev_ver;
if (nextAction && nextAction->vct_undo && nextAction->vct_undo->locate(recordNumber))
{
prev_ver = undo_prev_ver = nextAction->vct_undo->current().setupRecord(transaction);
}
else if (rpb.rpb_b_page) // previous version exists and we have to find it in a hard way
{
record_param temp = rpb;
temp.rpb_record = NULL;
temp.rpb_page = rpb.rpb_b_page;
temp.rpb_line = rpb.rpb_b_line;
if (!DPM_fetch(tdbb, &temp, LCK_read))
BUGCHECK(291); // Back version disappeared
if (temp.rpb_flags & rpb_deleted)
CCH_RELEASE(tdbb, &temp.getWindow(tdbb));
else
VIO_data(tdbb, &temp, transaction->tra_pool);
prev_ver = real_prev_ver = temp.rpb_record;
}
RecordStack going, staying;
going.push(goingRecord);
if (prev_ver)
staying.push(prev_ver);
if (next_ver)
staying.push(next_ver);
IDX_garbage_collect(tdbb, &rpb, going, staying);
BLB_garbage_collect(tdbb, going, staying, rpb.rpb_page, vct_relation);
}
void VerbAction::mergeTo(thread_db* tdbb, jrd_tra* transaction, VerbAction* nextAction)
{
// Post bitmap of modified records and undo data to the next savepoint.
//
// Notes:
//
// If previous savepoint already touched a record, undo data must be dropped and
// all BLOBs it refers to should be cleaned out because under no circumstances
// this undo data can become an active record.
// Merge undo records first
if (vct_undo && vct_undo->getFirst())
{
do
{
UndoItem& item = vct_undo->current();
if (item.hasData()) // this item wasn't released yet
{
const SINT64 recordNumber = item.generate(NULL, item);
if (nextAction && !(RecordBitmap::test(nextAction->vct_records, recordNumber)))
{
if (!nextAction->vct_undo)
{
nextAction->vct_undo =
FB_NEW_POOL(*transaction->tra_pool) UndoItemTree(*transaction->tra_pool);
// We cannot just push whole current undo items list, some items still may be released
}
else
{
if (nextAction->vct_undo->locate(recordNumber))
{
// It looks like something went wrong on previous loop and undo record
// was moved to next action but record bit in bitmap wasn't set
fb_assert(false);
item.clear();
continue;
}
}
nextAction->vct_undo->add(item);
item.clear(); // Do not release undo data, it now belongs to next action
continue;
}
// garbage cleanup and release
// because going version for sure has all index entries successfully set up (in contrast with undo)
// we can use lightweigth version of garbage collection without collection of full staying list
AutoUndoRecord this_ver(item.setupRecord(transaction));
garbageCollectIdxLite(tdbb, transaction, recordNumber, nextAction, this_ver);
item.release(transaction);
}
} while (vct_undo->getNext());
delete vct_undo;
vct_undo = NULL;
}
// Now merge bitmap
if (nextAction)
{
if (nextAction->vct_records)
{
RecordBitmap** bm_or = RecordBitmap::bit_or(&vct_records, &nextAction->vct_records);
if (*bm_or == vct_records) // if next bitmap has been merged into this bitmap - swap them
{
RecordBitmap* temp = nextAction->vct_records;
nextAction->vct_records = vct_records;
vct_records = temp;
}
}
else // just push current bitmap as is
{
nextAction->vct_records = vct_records;
vct_records = NULL;
}
}
release(transaction);
}
void VerbAction::undo(thread_db* tdbb, jrd_tra* transaction)
{
// Undo changes recorded for this verb action.
// After that, clear the verb action and prepare it for later reuse.
record_param rpb;
rpb.rpb_relation = vct_relation;
rpb.rpb_number.setValue(BOF_NUMBER);
rpb.rpb_record = NULL;
rpb.getWindow(tdbb).win_flags = 0;
rpb.rpb_transaction_nr = transaction->tra_number;
RecordBitmap::Accessor accessor(vct_records);
if (accessor.getFirst())
{
do
{
rpb.rpb_number.setValue(accessor.current());
const bool have_undo = vct_undo && vct_undo->locate(rpb.rpb_number.getValue());
if (!DPM_get(tdbb, &rpb, LCK_read))
BUGCHECK(186); // msg 186 record disappeared
if (have_undo && !(rpb.rpb_flags & rpb_deleted))
VIO_data(tdbb, &rpb, transaction->tra_pool);
else
CCH_RELEASE(tdbb, &rpb.getWindow(tdbb));
if (rpb.rpb_transaction_nr != transaction->tra_number)
BUGCHECK(185); // msg 185 wrong record version
if (!have_undo)
VIO_backout(tdbb, &rpb, transaction);
else
{
AutoUndoRecord record(vct_undo->current().setupRecord(transaction));
Record* const save_record = rpb.rpb_record;
record_param new_rpb = rpb;
if (rpb.rpb_flags & rpb_deleted)
rpb.rpb_record = NULL;
new_rpb.rpb_record = record;
new_rpb.rpb_address = record->getData();
new_rpb.rpb_length = record->getLength();
new_rpb.rpb_flags = 0;
Record* const dead_record = rpb.rpb_record;
// This record will be in staying list twice. Ignorable overhead.
VIO_update_in_place(tdbb, transaction, &rpb, &new_rpb);
if (dead_record)
{
rpb.rpb_record = NULL; // VIO_garbage_collect_idx will play with this record dirty tricks
VIO_garbage_collect_idx(tdbb, transaction, &rpb, dead_record);
}
rpb.rpb_record = save_record;
}
} while (accessor.getNext());
delete rpb.rpb_record;
}
release(transaction);
}
void VerbAction::release(jrd_tra* transaction)
{
// Release resources used by this verb action
RecordBitmap::reset(vct_records);
if (vct_undo)
{
if (vct_undo->getFirst())
{
do {
vct_undo->current().release(transaction);
} while (vct_undo->getNext());
}
delete vct_undo;
vct_undo = NULL;
}
}
// Savepoint implementation
Savepoint* Savepoint::start(jrd_tra* transaction, bool root)
{
// Start a new savepoint. Reuse some priorly allocated one, if exists.
Savepoint* savepoint = transaction->tra_save_free;
if (savepoint)
transaction->tra_save_free = savepoint->m_next;
else
savepoint = FB_NEW_POOL(*transaction->tra_pool) Savepoint(transaction);
savepoint->m_number = ++transaction->tra_save_point_number;
savepoint->m_flags = root ? SAV_root : 0;
savepoint->m_next = transaction->tra_save_point;
transaction->tra_save_point = savepoint;
return savepoint;
}
VerbAction* Savepoint::createAction(jrd_rel* relation)
{
// Create action for the given relation. If it already exists, just return.
VerbAction* action = getAction(relation);
if (!action)
{
if ( (action = m_freeActions) )
m_freeActions = action->vct_next;
else
action = FB_NEW_POOL(*m_transaction->tra_pool) VerbAction();
action->vct_next = m_actions;
m_actions = action;
action->vct_relation = relation;
}
return action;
}
void Savepoint::cleanupTempData()
{
// Find all global temporary tables with DELETE ROWS action
// and release their undo data
for (VerbAction* action = m_actions; action; action = action->vct_next)
{
if (action->vct_relation->rel_flags & REL_temp_tran)
{
RecordBitmap::reset(action->vct_records);
if (action->vct_undo)
{
if (action->vct_undo->getFirst())
{
do
{
action->vct_undo->current().release(m_transaction);
} while (action->vct_undo->getNext());
}
delete action->vct_undo;
action->vct_undo = NULL;
}
}
}
}
Savepoint* Savepoint::rollback(thread_db* tdbb, Savepoint* prior)
{
// Undo changes made in this savepoint.
// Perform index and BLOB cleanup if needed.
// At the exit savepoint is clear and safe to reuse.
jrd_tra* const old_tran = tdbb->getTransaction();
try
{
DFW_delete_deferred(m_transaction, m_number);
m_flags &= ~SAV_force_dfw;
tdbb->tdbb_flags |= TDBB_verb_cleanup;
tdbb->setTransaction(m_transaction);
while (m_actions)
{
m_actions->undo(tdbb, m_transaction);
releaseAction(m_actions);
}
tdbb->setTransaction(old_tran);
tdbb->tdbb_flags &= ~TDBB_verb_cleanup;
}
catch (const Exception& ex)
{
Arg::StatusVector error(ex);
tdbb->setTransaction(old_tran);
tdbb->tdbb_flags &= ~TDBB_verb_cleanup;
m_transaction->tra_flags |= TRA_invalidated;
error.prepend(Arg::Gds(isc_savepoint_backout_err));
error.raise();
}
return release(prior);
}
Savepoint* Savepoint::rollforward(thread_db* tdbb, Savepoint* prior)
{
// Merge changes made in this savepoint into next one.
// Perform index and BLOB cleanup if needed.
// At the exit savepoint is clear and safe to reuse.
jrd_tra* const old_tran = tdbb->getTransaction();
try
{
// If the current to-be-cleaned-up savepoint is very big, and the next
// level savepoint is the transaction level savepoint, then get rid of
// the transaction level savepoint now (instead of after making the
// transaction level savepoint very very big).
if (m_next && m_next->isRoot() && this->isLarge())
{
fb_assert(!m_next->m_next); // check that transaction savepoint is the last in list
// get rid of tx-level savepoint
m_next->rollforward(tdbb);
m_next->release();
m_next = NULL;
}
// Cleanup/merge deferred work/event post
if (m_actions || (m_flags & SAV_force_dfw))
{
DFW_merge_work(m_transaction, m_number, m_next ? m_next->m_number : 0);
if (m_next && (m_flags & SAV_force_dfw))
m_next->m_flags |= SAV_force_dfw;
m_flags &= ~SAV_force_dfw;
}
tdbb->tdbb_flags |= TDBB_verb_cleanup;
tdbb->setTransaction(m_transaction);
while (m_actions)
{
VerbAction* nextAction = NULL;
if (m_next)
{
nextAction = m_next->getAction(m_actions->vct_relation);
if (!nextAction) // next savepoint didn't touch this table yet - send whole action
{
propagateAction(m_actions);
continue;
}
}
// No luck, merge actions in a slow way
m_actions->mergeTo(tdbb, m_transaction, nextAction);
releaseAction(m_actions);
}
tdbb->setTransaction(old_tran);
tdbb->tdbb_flags &= ~TDBB_verb_cleanup;
}
catch (...)
{
m_transaction->tra_flags |= TRA_invalidated;
tdbb->setTransaction(old_tran);
tdbb->tdbb_flags &= ~TDBB_verb_cleanup;
throw;
}
// If the only remaining savepoint is the 'transaction-level' savepoint
// that was started by TRA_start, then check if it hasn't grown out of
// bounds yet. If it has, then give up on this transaction-level savepoint.
if (m_next && m_next->isRoot() && m_next->isLarge())
{
fb_assert(!m_next->m_next); // check that transaction savepoint is the last in list
// get rid of tx-level savepoint
m_next->rollforward(tdbb);
m_next->release();
m_next = NULL;
}
return release(prior);
}
bool Savepoint::isLarge() const
{
// Returns whether the current savepoint is large enough (has many verbs posted).
//
// Notes:
//
// - This routine does not take into account the data allocated to 'vct_undo'.
// Why? Because this routine is used to estimate size of transaction-level
// savepoint and transaction-level savepoint may not contain undo data as it is
// always the first savepoint in transaction.
//
// - We use U_IPTR, not ULONG to care of case when user savepoint gets very,
// very big on 64-bit machine. Its size may overflow 32 significant bits of
// ULONG in this case
U_IPTR size = 0;
// Iterate all tables changed under this savepoint
for (VerbAction* action = m_actions; action; action = action->vct_next)
{
// Estimate size used for record backout bitmaps for this table
if (action->vct_records)
{
size += action->vct_records->approxSize();
if (size > SIZE_THRESHOLD)
return true;
}
}
return false;
}
Savepoint* Savepoint::release(Savepoint* prior)
{
// Clear savepoint and prepare it for later reuse.
// If prior savepoint is specified, relink its next pointer.
// Return the next savepoint, if exists.
m_flags = 0;
m_count = 0;
m_name = "";
Savepoint* const next = m_next;
if (prior)
prior->m_next = next;
m_next = m_transaction->tra_save_free;
m_transaction->tra_save_free = this;
return next;
}
// AutoSavePoint implementation
AutoSavePoint::AutoSavePoint(thread_db* tdbb, jrd_tra* trans)
: m_tdbb(tdbb), m_transaction(trans), m_released(false)
{
Savepoint::start(trans);
}
AutoSavePoint::~AutoSavePoint()
{
if (!(m_tdbb->getDatabase()->dbb_flags & DBB_bugcheck))
{
if (m_released)
m_transaction->rollforwardSavepoint(m_tdbb);
else
m_transaction->rollbackSavepoint(m_tdbb);
}
}
// StableCursorSavePoint implementation
StableCursorSavePoint::StableCursorSavePoint(thread_db* tdbb, jrd_tra* trans, bool start)
: m_tdbb(tdbb), m_transaction(trans), m_number(0)
{
if (!start)
return;
if (trans->tra_flags & TRA_system)
return;
if (!trans->tra_save_point)
return;
const Savepoint* const savepoint = Savepoint::start(trans);
m_number = savepoint->getNumber();
}
void StableCursorSavePoint::release()
{
if (!m_number)
return;
while (m_transaction->tra_save_point && m_transaction->tra_save_point->getNumber() >= m_number)
m_transaction->rollforwardSavepoint(m_tdbb);
m_number = 0;
}

355
src/jrd/Savepoint.h Normal file
View File

@ -0,0 +1,355 @@
/*
* 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): ______________________________________.
*/
#ifndef JRD_SAVEPOINT_H
#define JRD_SAVEPOINT_H
#include "../common/classes/File.h"
#include "../common/classes/MetaName.h"
#include "../jrd/Record.h"
#include "../jrd/RecordNumber.h"
namespace Jrd
{
class jrd_tra;
// Verb actions
class UndoItem
{
public:
static const SINT64& generate(const void* /*sender*/, const UndoItem& item)
{
return item.m_number;
}
UndoItem()
: m_number(0), m_offset(0), m_format(NULL)
{}
UndoItem(RecordNumber recordNumber)
: m_number(recordNumber.getValue()), m_offset(0), m_format(NULL)
{}
UndoItem(jrd_tra* transaction, RecordNumber recordNumber, const Record* record);
Record* setupRecord(jrd_tra* transaction) const;
void release(jrd_tra* transaction);
void clear()
{
m_format = NULL;
}
bool hasData() const
{
return (m_format != NULL);
}
bool isEmpty() const
{
return (m_format == NULL);
}
private:
SINT64 m_number;
offset_t m_offset;
const Format* m_format;
};
typedef Firebird::BePlusTree<UndoItem, SINT64, MemoryPool, UndoItem> UndoItemTree;
class VerbAction
{
public:
VerbAction()
: vct_next(NULL), vct_relation(NULL), vct_records(NULL), vct_undo(NULL)
{}
~VerbAction()
{
delete vct_records;
delete vct_undo;
}
VerbAction* vct_next; // Next action within verb
jrd_rel* vct_relation; // Relation involved
RecordBitmap* vct_records; // Record involved
UndoItemTree* vct_undo; // Data for undo records
void mergeTo(thread_db* tdbb, jrd_tra* transaction, VerbAction* nextAction);
void undo(thread_db* tdbb, jrd_tra* transaction);
void garbageCollectIdxLite(thread_db* tdbb, jrd_tra* transaction, SINT64 recordNumber,
VerbAction* nextAction, Record* goingRecord);
private:
void release(jrd_tra* transaction);
};
// Savepoint class
class Savepoint
{
// Maximum size in bytes of transaction-level savepoint data.
// When transaction-level savepoint gets past this size we drop it and use GC
// mechanisms to clean out changes done in transaction
static const U_IPTR SIZE_THRESHOLD = 1024 * 32;
// Savepoint flags
static const USHORT SAV_root = 1; // transaction-level savepoint
static const USHORT SAV_force_dfw = 2; // DFW is present even if savepoint is empty
public:
explicit Savepoint(jrd_tra* transaction)
: m_transaction(transaction), m_number(0), m_flags(0), m_count(0),
m_next(NULL), m_actions(NULL), m_freeActions(NULL)
{}
~Savepoint()
{
while (m_actions)
{
VerbAction* next = m_actions->vct_next;
delete m_actions;
m_actions = next;
}
while (m_freeActions)
{
VerbAction* next = m_freeActions->vct_next;
delete m_freeActions;
m_freeActions = next;
}
}
VerbAction* getAction(const jrd_rel* relation) const
{
for (VerbAction* action = m_actions; action; action = action->vct_next)
{
if (action->vct_relation == relation)
return action;
}
return NULL;
}
Savepoint* getNext() const
{
return m_next;
}
SLONG getNumber() const
{
return m_number;
}
const Firebird::MetaName& getName() const
{
return m_name;
}
void setName(const Firebird::MetaName& name)
{
m_name = name;
}
bool isSystem() const
{
return m_name.isEmpty();
}
bool isRoot() const
{
return (m_flags & SAV_root);
}
bool isChanging() const
{
return (m_count != 0);
}
bool hasChanges() const
{
return (m_actions != NULL);
}
void forceDeferredWork()
{
m_flags |= SAV_force_dfw;
}
Savepoint* mergeTo(Savepoint*& target)
{
Savepoint* const next = m_next;
m_next = target;
target = this;
return next;
}
VerbAction* createAction(jrd_rel* relation);
void releaseAction(VerbAction* action)
{
m_actions = action->vct_next;
action->vct_next = m_freeActions;
m_freeActions = action;
}
void propagateAction(VerbAction* action)
{
m_actions = action->vct_next;
action->vct_next = m_next->m_actions;
m_next->m_actions = action;
}
void cleanupTempData();
Savepoint* rollback(thread_db* tdbb, Savepoint* prior = NULL);
Savepoint* rollforward(thread_db* tdbb, Savepoint* prior = NULL);
static Savepoint* start(jrd_tra* transaction, bool root = false);
static void destroy(Savepoint*& savepoint)
{
while (savepoint)
{
Savepoint* const next = savepoint->m_next;
delete savepoint;
savepoint = next;
}
}
static void merge(Savepoint*& target, Savepoint*& source)
{
while (source)
source = source->mergeTo(target);
}
class Iterator
{
public:
explicit Iterator(Savepoint* savepoint)
: m_savepoint(savepoint)
{}
Iterator& operator++()
{
if (m_savepoint)
m_savepoint = m_savepoint->m_next;
return *this;
}
Savepoint* operator*() const
{
return m_savepoint;
}
private:
Iterator(const Iterator&);
Iterator& operator=(const Iterator&);
Savepoint* m_savepoint;
};
class ChangeMarker
{
public:
explicit ChangeMarker(Savepoint* savepoint)
: m_savepoint(savepoint)
{
if (m_savepoint)
++m_savepoint->m_count;
}
~ChangeMarker()
{
if (m_savepoint)
--m_savepoint->m_count;
}
private:
ChangeMarker(const ChangeMarker&);
ChangeMarker& operator=(const ChangeMarker&);
Savepoint* const m_savepoint;
};
private:
// Prohibit unwanted creation/copying
Savepoint(const Savepoint&);
Savepoint& operator=(const Savepoint&);
bool isLarge() const;
Savepoint* release(Savepoint* prior = NULL);
jrd_tra* m_transaction; // transaction this savepoint belongs to
SLONG m_number; // savepoint number
USHORT m_flags; // misc flags
USHORT m_count; // active verb count
Firebird::MetaName m_name; // savepoint name
Savepoint* m_next; // next savepoint in the list
VerbAction* m_actions; // verb action list
VerbAction* m_freeActions; // free verb actions
};
// Starts a savepoint and rollback it in destructor if release() is not called
class AutoSavePoint
{
public:
AutoSavePoint(thread_db* tdbb, jrd_tra* trans);
~AutoSavePoint();
void release()
{
m_released = true;
}
private:
thread_db* const m_tdbb;
jrd_tra* const m_transaction;
bool m_released;
};
class StableCursorSavePoint
{
public:
StableCursorSavePoint(thread_db* tdbb, jrd_tra* trans, bool start);
~StableCursorSavePoint()
{
release();
}
void release();
private:
thread_db* const m_tdbb;
jrd_tra* const m_transaction;
SLONG m_number;
};
} // namespace
#endif // JRD_SAVEPOINT_H

View File

@ -1490,7 +1490,7 @@ DeferredWork* DFW_post_work(jrd_tra* transaction, enum dfw_t type, const string&
// get the current save point number
const SLONG sav_number = transaction->tra_save_point ?
transaction->tra_save_point->sav_number : 0;
transaction->tra_save_point->getNumber() : 0;
// initialize transaction if needed
@ -1533,7 +1533,7 @@ DeferredWork* DFW_post_work(jrd_tra* transaction, enum dfw_t type, const string&
// fall down ...
case dfw_post_event:
if (transaction->tra_save_point)
transaction->tra_save_point->sav_flags |= SAV_force_dfw;
transaction->tra_save_point->forceDeferredWork();
break;
default:
transaction->tra_flags |= TRA_deferred_meta;

View File

@ -195,7 +195,6 @@ void StatusXcp::as_sqlstate(char* sqlstate) const
static void execute_looper(thread_db*, jrd_req*, jrd_tra*, const StmtNode*, jrd_req::req_s);
static void looper_seh(thread_db*, jrd_req*, const StmtNode*);
static void release_blobs(thread_db*, jrd_req*);
static void release_proc_save_points(jrd_req*);
static void trigger_failure(thread_db*, jrd_req*);
static void stuff_stack_trace(const jrd_req*);
@ -616,26 +615,22 @@ void EXE_receive(thread_db* tdbb,
if (transaction->tra_save_point)
{
merge_sav_number = transaction->tra_save_point->sav_number;
merge_sav_number = transaction->tra_save_point->getNumber();
if (request->req_proc_sav_point)
{
// Push all saved savepoints to the top of transaction savepoints stack
while (request->req_proc_sav_point)
{
Savepoint* const sav_point = request->req_proc_sav_point;
request->req_proc_sav_point = sav_point->sav_next;
sav_point->sav_next = transaction->tra_save_point;
transaction->tra_save_point = sav_point;
}
Savepoint::merge(transaction->tra_save_point, request->req_proc_sav_point);
fb_assert(!request->req_proc_sav_point);
}
else
{
VIO_start_save_point(tdbb, transaction);
Savepoint::start(transaction);
}
}
else
{
VIO_start_save_point(tdbb, transaction);
Savepoint::start(transaction);
}
}
@ -690,22 +685,27 @@ void EXE_receive(thread_db* tdbb,
if (request->req_flags & req_proc_fetch)
{
// At this point request->req_proc_sav_point == NULL that is assured by code above
fb_assert(!request->req_proc_sav_point);
try
{
// merge work into target savepoint and save request's savepoints (with numbers!!!) till the next loop
while (transaction->tra_save_point && transaction->tra_save_point->sav_number > merge_sav_number)
// Merge work into target savepoint and save request's savepoints (with numbers!!!)
// till the next looper iteration
while (transaction->tra_save_point &&
transaction->tra_save_point->getNumber() > merge_sav_number)
{
Savepoint* const save_sav_point = transaction->tra_save_point;
save_sav_point->rollforward(tdbb);
transaction->tra_save_point = save_sav_point->sav_next;
save_sav_point->sav_next = request->req_proc_sav_point;
request->req_proc_sav_point = save_sav_point;
Savepoint* const savepoint = transaction->tra_save_point;
transaction->rollforwardSavepoint(tdbb);
fb_assert(transaction->tra_save_free == savepoint);
transaction->tra_save_free = savepoint->mergeTo(request->req_proc_sav_point);
fb_assert(request->req_proc_sav_point == savepoint);
}
}
catch (...)
{
// If something went wrong, drop already stored savepoints to prevent memory leak
release_proc_save_points(request);
Savepoint::destroy(request->req_proc_sav_point);
fb_assert(!request->req_proc_sav_point);
throw;
}
}
@ -863,7 +863,6 @@ void EXE_start(thread_db* tdbb, jrd_req* request, jrd_tra* transaction)
*
**************************************/
SET_TDBB(tdbb);
Jrd::Attachment* attachment = tdbb->getAttachment();
BLKCHK(request, type_req);
BLKCHK(transaction, type_tra);
@ -984,7 +983,11 @@ void EXE_unwind(thread_db* tdbb, jrd_req* request)
request->req_sorts.unlinkAll();
if (request->req_proc_sav_point && (request->req_flags & req_proc_fetch))
release_proc_save_points(request);
{
// Release savepoints used by this request
Savepoint::destroy(request->req_proc_sav_point);
fb_assert(!request->req_proc_sav_point);
}
TRA_detach_request(request);
@ -1029,8 +1032,8 @@ static void execute_looper(thread_db* tdbb,
if (!(request->req_flags & req_proc_fetch) && request->req_transaction)
{
if (transaction && (transaction != attachment->getSysTransaction()))
VIO_start_save_point(tdbb, transaction);
if (transaction && !(transaction->tra_flags & TRA_system))
Savepoint::start(transaction);
}
request->req_flags &= ~req_stall;
@ -1042,10 +1045,10 @@ static void execute_looper(thread_db* tdbb,
if (!(request->req_flags & req_proc_fetch) && request->req_transaction)
{
if (transaction && (transaction != attachment->getSysTransaction()) &&
if (transaction && !(transaction->tra_flags & TRA_system) &&
transaction->tra_save_point &&
!(transaction->tra_save_point->sav_flags & SAV_user) &&
!transaction->tra_save_point->sav_verb_count)
transaction->tra_save_point->isSystem() &&
!transaction->tra_save_point->isChanging())
{
// Forget about any undo for this verb
transaction->rollforwardSavepoint(tdbb);
@ -1269,8 +1272,6 @@ const StmtNode* EXE_looper(thread_db* tdbb, jrd_req* request, const StmtNode* no
ERR_post(Arg::Gds(isc_req_no_trans));
SET_TDBB(tdbb);
Jrd::Attachment* attachment = tdbb->getAttachment();
jrd_tra* sysTransaction = attachment->getSysTransaction();
Database* dbb = tdbb->getDatabase();
if (!node || node->kind != DmlNode::KIND_STATEMENT)
@ -1284,7 +1285,7 @@ const StmtNode* EXE_looper(thread_db* tdbb, jrd_req* request, const StmtNode* no
request->req_caller = exeState.oldRequest;
const SLONG save_point_number = (request->req_transaction->tra_save_point) ?
request->req_transaction->tra_save_point->sav_number : 0;
request->req_transaction->tra_save_point->getNumber() : 0;
tdbb->tdbb_flags &= ~(TDBB_stack_trace_done | TDBB_sys_error);
@ -1387,10 +1388,8 @@ const StmtNode* EXE_looper(thread_db* tdbb, jrd_req* request, const StmtNode* no
if (exeState.errorPending)
{
if (request->req_transaction != sysTransaction)
{
if (!(request->req_transaction->tra_flags & TRA_system))
request->req_transaction->rollbackToSavepoint(tdbb, save_point_number);
}
ERR_punt();
}
@ -1504,28 +1503,6 @@ static void release_blobs(thread_db* tdbb, jrd_req* request)
}
}
static void release_proc_save_points(jrd_req* request)
{
/**************************************
*
* r e l e a s e _ p r o c _ s a v e _ p o i n t s
*
**************************************
*
* Functional description
* Release savepoints used by this request.
*
**************************************/
Savepoint* sav_point = request->req_proc_sav_point;
while (sav_point)
{
Savepoint* const temp_sav_point = sav_point->sav_next;
delete sav_point;
sav_point = temp_sav_point;
}
request->req_proc_sav_point = NULL;
}
static void trigger_failure(thread_db* tdbb, jrd_req* trigger)
{

View File

@ -6986,9 +6986,8 @@ static void run_commit_triggers(thread_db* tdbb, jrd_tra* transaction)
*
**************************************/
SET_TDBB(tdbb);
Jrd::Attachment* attachment = tdbb->getAttachment();
if (transaction == attachment->getSysTransaction())
if (transaction->tra_flags & TRA_system)
return;
// start a savepoint to rollback changes of all triggers

View File

@ -353,11 +353,10 @@ void TRA_commit(thread_db* tdbb, jrd_tra* transaction, const bool retaining_flag
transaction_flush(tdbb, FLUSH_SYSTEM, 0);
transaction->tra_flags &= ~TRA_prepared;
// Get rid of all user savepoints
while (transaction->tra_save_point && !(transaction->tra_save_point->sav_flags & SAV_trans_level))
{
while (transaction->tra_save_point && !transaction->tra_save_point->isRoot())
transaction->rollforwardSavepoint(tdbb);
}
trace.finish(ITracePlugin::RESULT_SUCCESS);
return;
@ -1318,15 +1317,17 @@ void TRA_rollback(thread_db* tdbb, jrd_tra* transaction, const bool retaining_fl
{
// Free all savepoint data
// Undo data space and BLOBs will be released in destructor
while (transaction->tra_save_point)
{
Savepoint* const next = transaction->tra_save_point->sav_next;
delete transaction->tra_save_point;
transaction->tra_save_point = next;
}
Savepoint::destroy(transaction->tra_save_point);
fb_assert(!transaction->tra_save_point);
}
else
VIO_temp_cleanup(transaction);
{
// Remove undo data for GTT ON COMMIT DELETE ROWS as their data will be released
// at transaction end anyway and we don't need to waste time backing it out
for (Savepoint::Iterator iter(transaction->tra_save_point); *iter; ++iter)
(*iter)->cleanupTempData();
}
int state = tra_dead;
@ -1340,10 +1341,8 @@ void TRA_rollback(thread_db* tdbb, jrd_tra* transaction, const bool retaining_fl
// Release all user savepoints except transaction one
// It will clean up blob ids and temporary space anyway but faster than rollback
// because record data won't be updated with intermediate versions
while (transaction->tra_save_point && !(transaction->tra_save_point->sav_flags & SAV_trans_level))
{
while (transaction->tra_save_point && !transaction->tra_save_point->isRoot())
transaction->rollforwardSavepoint(tdbb);
}
if (transaction->tra_save_point) // we still can use undo log for rollback, it wasn't reset because of no_auto_undo flag or size
{
@ -2447,10 +2446,7 @@ static void retain_context(thread_db* tdbb, jrd_tra* transaction, bool commit, i
// All savepoint were already released in TRA_commit/TRA_rollback except, may be, empty transaction-level one
if (!transaction->tra_save_point && !(transaction->tra_flags & TRA_no_auto_undo))
{
VIO_start_save_point(tdbb, transaction); // start new savepoint if necessary
transaction->tra_save_point->sav_flags |= SAV_trans_level;
}
Savepoint::start(transaction, true); // start new savepoint if necessary
if (transaction->tra_flags & TRA_precommitted)
{
@ -3324,11 +3320,8 @@ static void transaction_start(thread_db* tdbb, jrd_tra* trans)
// a savepoint to be started. This savepoint will be used to
// undo the transaction if it rolls back.
if (trans != attachment->getSysTransaction() && !(trans->tra_flags & TRA_no_auto_undo))
{
VIO_start_save_point(tdbb, trans);
trans->tra_save_point->sav_flags |= SAV_trans_level;
}
if (!(trans->tra_flags & TRA_system) && !(trans->tra_flags & TRA_no_auto_undo))
Savepoint::start(trans, true);
// if the user asked us to restart all requests in this attachment,
// do so now using the new transaction
@ -3465,7 +3458,7 @@ MemoryPool* jrd_tra::getAutonomousPool()
return tra_autonomous_pool;
}
Record* jrd_tra::findNextUndo(VerbAction* before_this, jrd_rel* relation, SINT64 number)
Record* jrd_tra::findNextUndo(VerbAction* stopAction, jrd_rel* relation, SINT64 number)
/**************************************
*
* f i n d N e x t U n d o
@ -3473,22 +3466,24 @@ Record* jrd_tra::findNextUndo(VerbAction* before_this, jrd_rel* relation, SINT64
**************************************
*
* Functional description
* for given record find next undo data in stack of savepoint (if any).
* For given record find next undo data in stack of savepoint (if any).
*
**************************************/
{
UndoItem* result = NULL;
for (Savepoint* itr = tra_save_point; itr; itr = itr->sav_next)
for (Savepoint::Iterator iter(tra_save_point); *iter; ++iter)
{
for (VerbAction* action = itr->sav_verb_actions; action; action = action->vct_next)
{
if (action == before_this)
return result?result->setupRecord(this):NULL;
if (action->vct_relation == relation && action->vct_undo && action->vct_undo->locate(number))
result = &(action->vct_undo->current());
}
VerbAction* const action = (*iter)->getAction(relation);
if (action == stopAction)
return result ? result->setupRecord(this) : NULL;
if (action && action->vct_undo && action->vct_undo->locate(number))
result = &(action->vct_undo->current());
}
fb_assert(false); // verb_action disappeared from savepoint stack.
fb_assert(false); // verb_action disappeared from savepoint stack
return NULL;
}
@ -3500,18 +3495,17 @@ void jrd_tra::listStayingUndo(jrd_rel* relation, SINT64 number, RecordStack &sta
**************************************
*
* Functional description
* for given record find undo data in stack of savepoint (if any) and push it into list.
* For given record find undo data in stack of savepoint (if any) and push it into list.
* Except one from given verb action.
*
**************************************/
{
for (Savepoint* itr = tra_save_point; itr; itr = itr->sav_next)
for (Savepoint::Iterator iter(tra_save_point); *iter; ++iter)
{
for (VerbAction* action = itr->sav_verb_actions; action; action = action->vct_next)
{
if (action->vct_relation == relation && action->vct_undo && action->vct_undo->locate(number))
staying.push(action->vct_undo->current().setupRecord(this));
}
VerbAction* const action = (*iter)->getAction(relation);
if (action && action->vct_undo && action->vct_undo->locate(number))
staying.push(action->vct_undo->current().setupRecord(this));
}
}
@ -3528,35 +3522,26 @@ void jrd_tra::releaseAutonomousPool(MemoryPool* toRelease)
void jrd_tra::rollbackSavepoint(thread_db* tdbb)
/**************************************
*
* ro l l b a c k S a v e p o i n t
* r o l l b a c k S a v e p o i n t
*
**************************************
*
* Functional description
* Rollback one savepoint and free it.
* Rollback last savepoint and free it.
*
**************************************/
{
if (tra_flags & TRA_system)
{
return;
}
if (tra_save_point)
if (tra_save_point && !(tra_flags & TRA_system))
{
Jrd::ContextPoolHolder context(tdbb, tra_pool);
tra_save_point->rollback(tdbb); // this call can change sav_next
Savepoint* temp = tra_save_point->sav_next;
tra_save_point->sav_next = tra_save_free;
tra_save_free = tra_save_point;
tra_save_point = temp;
tra_save_point = tra_save_point->rollback(tdbb);
}
}
void jrd_tra::rollbackToSavepoint(thread_db* tdbb, SLONG number)
/**************************************
*
* ro l l b a c k T o S a v e p o i n t
* r o l l b a c k T o S a v e p o i n t
*
**************************************
*
@ -3568,15 +3553,17 @@ void jrd_tra::rollbackToSavepoint(thread_db* tdbb, SLONG number)
*
**************************************/
{
// merge all but one folowing savepoints into one
while (tra_save_point && tra_save_point->sav_number > number &&
tra_save_point->sav_next && tra_save_point->sav_next->sav_number >= number)
// Merge all but one folowing savepoints into one
while (tra_save_point && tra_save_point->getNumber() > number &&
tra_save_point->getNext() && tra_save_point->getNext()->getNumber() >= number)
{
rollforwardSavepoint(tdbb);
}
// Check that savepoint with given number really exists
fb_assert(tra_save_point && tra_save_point->sav_number == number);
if (tra_save_point && tra_save_point->sav_number >= number) // second line of defence
fb_assert(tra_save_point && tra_save_point->getNumber() == number);
if (tra_save_point && tra_save_point->getNumber() >= number) // second line of defence
// under no circumstances a savepoint with smaller number should be rolled back
{
// Undo the savepoint
@ -3588,29 +3575,19 @@ void jrd_tra::rollbackToSavepoint(thread_db* tdbb, SLONG number)
void jrd_tra::rollforwardSavepoint(thread_db* tdbb)
/**************************************
*
* ro l l f o r w a r d S a v e p o i n t
* r o l l f o r w a r d S a v e p o i n t
*
**************************************
*
* Functional description
* Apply one savepoint and free it.
* Apply last savepoint and free it.
*
**************************************/
{
if (tra_flags & TRA_system)
{
return;
}
if (tra_save_point)
if (tra_save_point && !(tra_flags & TRA_system))
{
Jrd::ContextPoolHolder context(tdbb, tra_pool);
tra_save_point->rollforward(tdbb); // this call can change sav_next
Savepoint* temp = tra_save_point->sav_next;
tra_save_point->sav_next = tra_save_free;
tra_save_free = tra_save_point;
tra_save_point = temp;
tra_save_point = tra_save_point->rollforward(tdbb);
}
}
@ -3790,457 +3767,3 @@ void jrd_tra::eraseSecDbContext()
delete tra_sec_db_context;
tra_sec_db_context = NULL;
}
void Savepoint::rollback(thread_db* tdbb)
/**************************************
*
* r o l l b a c k
*
**************************************
*
* Functional description
* Undo changes made in this savepoint.
* Perform index and BLOB cleanup if needed.
* At the exit Savepoint is clear and safe to reuse.
*
**************************************/
{
jrd_tra* old_tran = tdbb->getTransaction();
try
{
DFW_delete_deferred(sav_trans, sav_number);
sav_flags &= ~SAV_force_dfw;
tdbb->tdbb_flags |= TDBB_verb_cleanup;
tdbb->setTransaction(sav_trans);
while (sav_verb_actions)
{
sav_verb_actions->undo(tdbb, sav_trans);
VerbAction* temp = sav_verb_actions;
sav_verb_actions = sav_verb_actions->vct_next;
temp->vct_next = sav_verb_free;
sav_verb_free = temp;
}
tdbb->setTransaction(old_tran);
tdbb->tdbb_flags &= ~TDBB_verb_cleanup;
}
catch (const Exception& ex)
{
Arg::StatusVector error(ex);
tdbb->setTransaction(old_tran);
tdbb->tdbb_flags &= ~TDBB_verb_cleanup;
sav_trans->tra_flags |= TRA_invalidated;
error.prepend(Arg::Gds(isc_savepoint_backout_err));
error.raise();
}
sav_verb_count = 0;
sav_flags = 0;
}
void Savepoint::rollforward(thread_db* tdbb)
/**************************************
*
* r o l l f o r w a r d
*
**************************************
*
* Functional description
* Merge changes made in this savepoint into next one.
* Perform index and BLOB cleanup if needed.
* At the exit Savepoint is clear and safe to reuse.
*
**************************************/
{
jrd_tra* old_tran = tdbb->getTransaction();
try
{
// If the current to-be-cleaned-up savepoint is very big, and the next
// level savepoint is the transaction level savepoint, then get rid of
// the transaction level savepoint now (instead of after making the
// transaction level savepoint very very big).
if (sav_next && (sav_next->sav_flags & SAV_trans_level) && is_large(SAV_LARGE) < 0)
{
fb_assert(!sav_next->sav_next); // check that transaction savepoint is the last in list
// get rid of tx-level savepoint
sav_next->rollforward(tdbb);
sav_next->sav_next = sav_trans->tra_save_free;
sav_trans->tra_save_free = sav_next;
sav_next = NULL;
}
// Cleanup/merge deferred work/event post
if (sav_verb_actions || (sav_flags & SAV_force_dfw))
{
DFW_merge_work(sav_trans, sav_number, sav_next ? sav_next->sav_number : 0);
if (sav_next && (sav_flags & SAV_force_dfw))
{
sav_next->sav_flags |= SAV_force_dfw;
}
sav_flags &= ~SAV_force_dfw;
}
tdbb->tdbb_flags |= TDBB_verb_cleanup;
tdbb->setTransaction(sav_trans);
while (sav_verb_actions)
{
VerbAction* next_action = NULL;
if (sav_next)
{
next_action = sav_next->getAction(sav_verb_actions->vct_relation);
if (!next_action) // next savepoint didn't touch this table yet - send whole action
{
VerbAction* temp = sav_verb_actions->vct_next;
sav_verb_actions->vct_next = sav_next->sav_verb_actions;
sav_next->sav_verb_actions = sav_verb_actions;
sav_verb_actions = temp;
continue;
}
}
// No luck, merge actions in a slow way
sav_verb_actions->mergeTo(tdbb, sav_trans, next_action);
// Save merged action for reuse because allocation-deallocation is slow
VerbAction* temp = sav_verb_actions->vct_next;
sav_verb_actions->vct_next = sav_verb_free;
sav_verb_free = sav_verb_actions;
sav_verb_actions = temp;
}
tdbb->setTransaction(old_tran);
tdbb->tdbb_flags &= ~TDBB_verb_cleanup;
}
catch (...)
{
sav_trans->tra_flags |= TRA_invalidated;
tdbb->setTransaction(old_tran);
tdbb->tdbb_flags &= ~TDBB_verb_cleanup;
throw;
}
sav_verb_count = 0;
sav_flags = 0;
// preserve sav_number for further reuse
// If the only remaining savepoint is the 'transaction-level' savepoint
// that was started by TRA_start, then check if it hasn't grown out of
// bounds yet. If it has, then give up on this transaction-level savepoint.
if (sav_next && (sav_next->sav_flags & SAV_trans_level) && sav_next->is_large(SAV_LARGE) < 0)
{
fb_assert(!sav_next->sav_next); // check that transaction savepoint is the last in list
// get rid of tx-level savepoint
sav_next->rollforward(tdbb);
sav_next->sav_next = sav_trans->tra_save_free;
sav_trans->tra_save_free = sav_next;
sav_next = NULL;
}
}
VerbAction* Savepoint::getAction(jrd_rel* relation)
{
for (VerbAction* result = sav_verb_actions; result; result=result->vct_next)
{
if (result->vct_relation == relation)
return result;
}
return NULL;
}
IPTR Savepoint::is_large(IPTR size)
{
/**************************************
*
* S a v e p o i n t : : i s _ l a r g e
*
**************************************
*
* Functional description
* Returns an approximate size in bytes of savepoint in-memory data, i.e. a
* measure of how big the current savepoint has gotten.
*
* Notes:
*
* - This routine does not take into account the data allocated to 'vct_undo'.
* Why? Because this routine is used to estimate size of transaction-level
* savepoint and transaction-level savepoint may not contain undo data as it is
* always the first savepoint in transaction.
*
* - Function stops counting when return value gets negative.
*
* - We use IPTR, not SLONG to care of case when user savepoint gets very,
* very big on 64-bit machine. Its size may overflow 32 significant bits of
* SLONG in this case
*
**************************************/
const VerbAction* verb_actions = sav_verb_actions;
// Iterate all tables changed under this savepoint
while (verb_actions)
{
// Estimate size used for record backout bitmaps for this table
if (verb_actions->vct_records) {
size -= verb_actions->vct_records->approxSize();
}
if (size < 0) {
break;
}
verb_actions = verb_actions->vct_next;
}
return size;
}
void VerbAction::garbage_collect_idx_lite(thread_db* tdbb, jrd_tra* transaction, SINT64 RecNumber, VerbAction* next_action, Record* going_record)
/**************************************
*
* g a r b a g e _ c o l l e c t _ i d x _ l i t e
*
**************************************
*
* Functional description
* Clean up index entries and referenced BLOBs.
* This routine uses smaller set of staying record than original VIO_garbage_collect_idx().
*
* Notes:
*
* This speed trick is possible only because btr.cpp:insert_node() allows duplicate nodes
* which work as an index entry reference counter.
*
**************************************/
{
record_param rpb;
rpb.rpb_relation = vct_relation;
rpb.rpb_number.setValue(RecNumber);
rpb.rpb_record = NULL;
rpb.getWindow(tdbb).win_flags = 0;
rpb.rpb_transaction_nr = transaction->tra_number;
Record* next_ver;
AutoUndoRecord undo_next_ver(transaction->findNextUndo(this, vct_relation, RecNumber));
AutoPtr<Record> real_next_ver;
next_ver = undo_next_ver;
if (!DPM_get(tdbb, &rpb, LCK_read))
{
BUGCHECK(186); // msg 186 record disappeared
}
else
{
if (next_ver || (rpb.rpb_flags & rpb_deleted))
{
CCH_RELEASE(tdbb, &rpb.getWindow(tdbb));
}
else
{
VIO_data(tdbb, &rpb, transaction->tra_pool);
next_ver = real_next_ver = rpb.rpb_record;
}
}
if (rpb.rpb_transaction_nr != transaction->tra_number)
BUGCHECK(185); // msg 185 wrong record version
Record* prev_ver(NULL);
AutoUndoRecord undo_prev_ver;
AutoPtr<Record> real_prev_ver;
if (next_action && next_action->vct_undo && next_action->vct_undo->locate(RecNumber))
{
prev_ver = undo_prev_ver = next_action->vct_undo->current().setupRecord(transaction);
}
else if (rpb.rpb_b_page) // previous version exists and we have to find it in a hard way
{
record_param temp = rpb;
temp.rpb_record = NULL;
temp.rpb_page = rpb.rpb_b_page;
temp.rpb_line = rpb.rpb_b_line;
if (!DPM_fetch(tdbb, &temp, LCK_read))
BUGCHECK(291); // Back version disappeared
if (temp.rpb_flags & rpb_deleted)
CCH_RELEASE(tdbb, &temp.getWindow(tdbb));
else
VIO_data(tdbb, &temp, transaction->tra_pool);
prev_ver = real_prev_ver = temp.rpb_record;
}
RecordStack going, staying;
going.push(going_record);
if (prev_ver)
staying.push(prev_ver);
if (next_ver)
staying.push(next_ver);
IDX_garbage_collect(tdbb, &rpb, going, staying);
BLB_garbage_collect(tdbb, going, staying, rpb.rpb_page, vct_relation);
}
void VerbAction::mergeTo(thread_db* tdbb, jrd_tra* transaction, VerbAction* next_action)
/**************************************
*
* m e r g e T o
*
**************************************
*
* Functional description
* Post bitmap of modified records and undo data to the next savepoint.
*
* Notes:
*
* If previous savepoint already touched a record, undo data must be dropped and
* all BLOBs it refers to should be cleaned out because under no circumstances
* this undo data can become an active record.
*
**************************************/
{
// Merge undo records first
if (vct_undo && vct_undo->getFirst())
{
do
{
UndoItem& item = vct_undo->current();
if (item.hasData()) // this item wasn't released yet
{
SINT64 RecNumber = item.generate(NULL, item);
if (next_action && !(RecordBitmap::test(next_action->vct_records, RecNumber)))
{
if (!next_action->vct_undo)
{
next_action->vct_undo = new UndoItemTree(transaction->tra_pool);
// We cannot just push whole current undo items list, some items still may to be released
}
else
{
if (next_action->vct_undo->locate(RecNumber))
// It looks like something went wrong on previous loop and undo record was moved to next action but
// record bit in bitmap wasn't set
{
fb_assert(false);
item.clear();
continue;
}
}
next_action->vct_undo->add(item);
item.clear(); // Do not release undo data, it now belongs to next action
continue;
}
// garbage cleanup and release
// because going version for sure has all index entries successfully set up (in contrast with undo)
// we can use lightweigth version of garbage collection without collection of full staying list
AutoUndoRecord this_ver(item.setupRecord(transaction));
garbage_collect_idx_lite(tdbb, transaction, RecNumber, next_action, this_ver);
item.release(transaction);
}
}
while (vct_undo->getNext());
delete vct_undo;
vct_undo = NULL;
}
// Now - bitmap
if (next_action)
{
if (next_action->vct_records)
{
RecordBitmap** bm_or = RecordBitmap::bit_or(&vct_records, &next_action->vct_records);
if (*bm_or == vct_records) // if next bitmap has been merged into this bitmap - swap them
{
RecordBitmap* temp = next_action->vct_records;
next_action->vct_records = vct_records;
vct_records = temp;
}
RecordBitmap::reset(vct_records);
}
else // just push current bitmap as is
{
next_action->vct_records = vct_records;
vct_records = NULL;
}
}
else
RecordBitmap::reset(vct_records);
}
void VerbAction::undo(thread_db* tdbb, jrd_tra* transaction)
{
record_param rpb;
rpb.rpb_relation = vct_relation;
rpb.rpb_number.setValue(BOF_NUMBER);
rpb.rpb_record = NULL;
rpb.getWindow(tdbb).win_flags = 0;
rpb.rpb_transaction_nr = transaction->tra_number;
RecordBitmap::Accessor accessor(vct_records);
if (accessor.getFirst())
{
do
{
rpb.rpb_number.setValue(accessor.current());
bool have_undo = vct_undo && vct_undo->locate(rpb.rpb_number.getValue());
if (!DPM_get(tdbb, &rpb, LCK_read))
{
BUGCHECK(186); // msg 186 record disappeared
}
if (have_undo && !(rpb.rpb_flags & rpb_deleted))
{
VIO_data(tdbb, &rpb, transaction->tra_pool);
}
else
{
CCH_RELEASE(tdbb, &rpb.getWindow(tdbb));
}
if (rpb.rpb_transaction_nr != transaction->tra_number) {
BUGCHECK(185); // msg 185 wrong record version
}
if (!have_undo)
{
VIO_backout(tdbb, &rpb, transaction);
}
else
{
AutoUndoRecord record(vct_undo->current().setupRecord(transaction));
Record* save_record = rpb.rpb_record;
record_param new_rpb = rpb;
if (rpb.rpb_flags & rpb_deleted)
{
rpb.rpb_record = NULL;
}
new_rpb.rpb_record = record;
new_rpb.rpb_address = record->getData();
new_rpb.rpb_length = record->getLength();
new_rpb.rpb_flags = 0;
Record* dead_record = rpb.rpb_record;
// This record will be in staying list twice. Ignorable overhead.
VIO_update_in_place(tdbb, transaction, &rpb, &new_rpb);
if (dead_record)
{
rpb.rpb_record = NULL; // VIO_garbage_collect_idx will play with this record dirty tricks
VIO_garbage_collect_idx(tdbb, transaction, &rpb, dead_record);
}
rpb.rpb_record = save_record;
}
} while (accessor.getNext());
delete rpb.rpb_record;
}
RecordBitmap::reset(vct_records);
if (vct_undo)
{
if (vct_undo->getFirst())
{
do {
vct_undo->current().release(transaction);
} while (vct_undo->getNext());
}
delete vct_undo;
vct_undo = NULL;
}
}

View File

@ -45,6 +45,7 @@
#include "../jrd/TempSpace.h"
#include "../jrd/obj.h"
#include "../jrd/EngineInterface.h"
#include "../jrd/Savepoint.h"
namespace EDS {
class Transaction;
@ -56,14 +57,11 @@ class blb;
class Lock;
class jrd_rel;
template <typename T> class vec;
class Savepoint;
class Record;
class VerbAction;
class ArrayField;
class Attachment;
class DeferredWork;
class DeferredJob;
class dsql_opn;
class UserManagement;
class MappingList;
class DbCreatorsList;
@ -430,50 +428,6 @@ const int tra_committed = 3;
const int tra_us = 4; // Transaction is us
const int tra_precommitted = 5; // Transaction is precommitted
// Savepoint block
class Savepoint : public pool_alloc<type_sav>
{
public:
Savepoint(jrd_tra* transaction) { sav_trans = transaction; }
~Savepoint()
{
deleteActions(sav_verb_actions);
deleteActions(sav_verb_free);
}
VerbAction* sav_verb_actions; // verb action list
VerbAction* sav_verb_free; // free verb actions
USHORT sav_verb_count; // active verb count
SLONG sav_number; // save point number
Savepoint* sav_next;
USHORT sav_flags;
Firebird::MetaName sav_name; // savepoint name
void rollback(thread_db* tdbb);
void rollforward(thread_db* tdbb);
VerbAction* getAction(jrd_rel* relation);
IPTR is_large(IPTR size);
private:
Savepoint(); // Disable default constructor
void deleteActions(VerbAction* list);
jrd_tra* sav_trans; // Savepoint must know which transaction it belongs to
};
// Savepoint block flags.
const int SAV_trans_level = 1; // savepoint was started by TRA_start
const int SAV_force_dfw = 2; // DFW is present even if savepoint is empty
const int SAV_user = 4; // named user savepoint as opposed to system ones
// Maximum size in bytes of transaction-level savepoint data.
// When transaction-level savepoint gets past this size we drop it and use GC
// mechanisms to clean out changes done in transaction
const IPTR SAV_LARGE = 1024 * 32;
// Deferred work blocks are used by the meta data handler to keep track
// of work deferred to commit time. This are usually used to perform
// meta data updates
@ -545,104 +499,6 @@ enum dfw_t {
dfw_clear_mapping // clear user mapping cache
};
// Verb actions
class UndoItem
{
public:
static const SINT64& generate(const void* /*sender*/, const UndoItem& item)
{
return item.m_number;
}
UndoItem() {}
UndoItem(RecordNumber recordNumber)
: m_number(recordNumber.getValue()), m_offset(0), m_format(NULL)
{
}
UndoItem(jrd_tra* transaction, RecordNumber recordNumber, const Record* record)
: m_number(recordNumber.getValue()), m_format(record->getFormat())
{
m_offset = transaction->getUndoSpace()->allocateSpace(m_format->fmt_length);
transaction->getUndoSpace()->write(m_offset, record->getData(), record->getLength());
}
Record* setupRecord(jrd_tra* transaction) const
{
if (m_format)
{
Record* const record = transaction->getUndoRecord(m_format);
transaction->getUndoSpace()->read(m_offset, record->getData(), record->getLength());
return record;
}
return NULL;
}
void release(jrd_tra* transaction)
{
if (m_format)
{
transaction->getUndoSpace()->releaseSpace(m_offset, m_format->fmt_length);
m_format = NULL;
}
}
void clear()
{
m_format = NULL;
}
bool hasData() const
{
return (m_format != NULL);
}
bool isEmpty() const
{
return (m_format == NULL);
}
private:
SINT64 m_number;
offset_t m_offset;
const Format* m_format;
};
typedef Firebird::BePlusTree<UndoItem, SINT64, MemoryPool, UndoItem> UndoItemTree;
class VerbAction : public pool_alloc<type_vct>
{
public:
~VerbAction()
{
delete vct_records;
delete vct_undo;
}
VerbAction* vct_next; // Next action within verb
jrd_rel* vct_relation; // Relation involved
RecordBitmap* vct_records; // Record involved
UndoItemTree* vct_undo; // Data for undo records
void mergeTo(thread_db* tdbb, jrd_tra* transaction, VerbAction* next_action);
void undo(thread_db* tdbb, jrd_tra* transaction);
void garbage_collect_idx_lite(thread_db* tdbb, jrd_tra* transaction, SINT64 RecNumber, VerbAction* next_action, Record* going_record);
};
inline void Savepoint::deleteActions(VerbAction* list)
{
while (list)
{
VerbAction* next = list->vct_next;
delete list;
list = next;
}
};
} //namespace Jrd
#endif // JRD_TRA_H

View File

@ -1824,10 +1824,10 @@ void VIO_erase(thread_db* tdbb, record_param* rpb, jrd_tra* transaction)
if (rpb->rpb_transaction_nr == transaction->tra_number)
{
VIO_update_in_place(tdbb, transaction, rpb, &temp);
if (transaction->tra_save_point && transaction->tra_save_point->sav_verb_count)
{
if (transaction->tra_save_point && transaction->tra_save_point->isChanging())
verb_post(tdbb, transaction, rpb, rpb->rpb_undo);
}
return;
}
@ -1883,10 +1883,9 @@ void VIO_erase(thread_db* tdbb, record_param* rpb, jrd_tra* transaction)
MET_revoke(tdbb, transaction, object_name, revokee, privilege);
}
}
if (transaction->tra_save_point && transaction->tra_save_point->sav_verb_count)
{
if (transaction->tra_save_point && transaction->tra_save_point->isChanging())
verb_post(tdbb, transaction, rpb, 0);
}
// for an autocommit transaction, mark a commit as necessary
@ -1895,9 +1894,7 @@ void VIO_erase(thread_db* tdbb, record_param* rpb, jrd_tra* transaction)
// VIO_erase
if ((tdbb->getDatabase()->dbb_flags & DBB_gc_background) && !rpb->rpb_relation->isTemporary())
{
notify_garbage_collector(tdbb, rpb, transaction->tra_number);
}
}
@ -2814,11 +2811,13 @@ void VIO_modify(thread_db* tdbb, record_param* org_rpb, record_param* new_rpb, j
{
IDX_modify_flag_uk_modified(tdbb, org_rpb, new_rpb, transaction);
VIO_update_in_place(tdbb, transaction, org_rpb, new_rpb);
if (!(transaction->tra_flags & TRA_system) &&
transaction->tra_save_point && transaction->tra_save_point->sav_verb_count)
transaction->tra_save_point && transaction->tra_save_point->isChanging())
{
verb_post(tdbb, transaction, org_rpb, org_rpb->rpb_undo);
}
tdbb->bumpRelStats(RuntimeStatistics::RECORD_UPDATES, relation->rel_id);
return;
}
@ -2849,7 +2848,7 @@ void VIO_modify(thread_db* tdbb, record_param* org_rpb, record_param* new_rpb, j
replace_record(tdbb, org_rpb, &stack, transaction);
if (!(transaction->tra_flags & TRA_system) &&
transaction->tra_save_point && transaction->tra_save_point->sav_verb_count)
transaction->tra_save_point && transaction->tra_save_point->isChanging())
{
verb_post(tdbb, transaction, org_rpb, 0);
}
@ -3058,32 +3057,6 @@ bool VIO_refetch_record(thread_db* tdbb, record_param* rpb, jrd_tra* transaction
}
void VIO_start_save_point(thread_db* tdbb, jrd_tra* transaction)
{
/**************************************
*
* V I O _ s t a r t _ s a v e _ p o i n t
*
**************************************
*
* Functional description
* Start a new save point for a transaction.
*
**************************************/
SET_TDBB(tdbb);
Savepoint* sav_point = transaction->tra_save_free;
if (sav_point)
transaction->tra_save_free = sav_point->sav_next;
else
sav_point = FB_NEW_POOL(*transaction->tra_pool) Savepoint(transaction);
sav_point->sav_number = ++transaction->tra_save_point_number;
sav_point->sav_next = transaction->tra_save_point;
transaction->tra_save_point = sav_point;
}
void VIO_store(thread_db* tdbb, record_param* rpb, jrd_tra* transaction)
{
/**************************************
@ -3466,7 +3439,7 @@ void VIO_store(thread_db* tdbb, record_param* rpb, jrd_tra* transaction)
#endif
if (!(transaction->tra_flags & TRA_system) &&
transaction->tra_save_point && transaction->tra_save_point->sav_verb_count)
transaction->tra_save_point && transaction->tra_save_point->isChanging())
{
verb_post(tdbb, transaction, rpb, 0);
}
@ -3589,91 +3562,6 @@ bool VIO_sweep(thread_db* tdbb, jrd_tra* transaction, TraceSweepEvent* traceSwee
}
void VIO_temp_cleanup(jrd_tra* transaction)
/**************************************
*
* V I O _ t e m p _ c l e a n u p
*
**************************************
*
* Functional description
* Remove undo data for GTT ON COMMIT DELETE ROWS as their data will be released
* at transaction end anyway and we don't need to waste time backing it out on
* rollback.
*
**************************************/
{
Savepoint* sav_point = transaction->tra_save_point;
for (; sav_point; sav_point = sav_point->sav_next)
{
for (VerbAction* action = sav_point->sav_verb_actions; action; action = action->vct_next)
{
if (action->vct_relation->rel_flags & REL_temp_tran)
{
RecordBitmap::reset(action->vct_records);
if (action->vct_undo)
{
if (action->vct_undo->getFirst())
{
do
{
action->vct_undo->current().release(transaction);
} while (action->vct_undo->getNext());
}
delete action->vct_undo;
action->vct_undo = NULL;
}
}
}
}
}
void VIO_verb_cleanup(thread_db* tdbb, jrd_tra* transaction)
{
/**************************************
*
* V I O _ v e r b _ c l e a n u p
*
**************************************
*
* Functional description
* Cleanup after a verb. If the verb count in the transaction block
* is non zero, the verb failed and should be cleaned up. Cleaning
* up ordinarily means just backing out the change, but if we have
* an old version we created in this transaction, we replace the current
* version with old version.
*
* All changes made by the transaction are kept in one bitmap per
* relation. A second bitmap per relation tracks records for which
* we have old data. The actual data is kept in a linked list stack.
* Note that although a record may be changed several times, it will
* have only ONE old value -- the value it had before this verb
* started.
*
**************************************/
SET_TDBB(tdbb);
#ifdef VIO_DEBUG
VIO_trace(DEBUG_TRACE,
"VIO_verb_cleanup (transaction %" SQUADFORMAT")\n",
transaction ? transaction->tra_number : 0);
#endif
if (transaction->tra_save_point->sav_verb_count) // we must rollback this savepoint
{
transaction->rollbackSavepoint(tdbb);
}
else
{
transaction->rollforwardSavepoint(tdbb);
}
}
bool VIO_writelock(thread_db* tdbb, record_param* org_rpb, jrd_tra* transaction)
{
/**************************************
@ -4705,34 +4593,31 @@ static UndoDataRet get_undo_data(thread_db* tdbb, jrd_tra* transaction,
if (!transaction->tra_save_point)
return udNone;
VerbAction* action = transaction->tra_save_point->sav_verb_actions;
VerbAction* const action = transaction->tra_save_point->getAction(rpb->rpb_relation);
for (; action; action = action->vct_next)
if (action)
{
if (action->vct_relation == rpb->rpb_relation)
{
const SINT64 recno = rpb->rpb_number.getValue();
if (!RecordBitmap::test(action->vct_records, recno))
return udNone;
const SINT64 recno = rpb->rpb_number.getValue();
if (!RecordBitmap::test(action->vct_records, recno))
return udNone;
rpb->rpb_runtime_flags |= RPB_undo_read;
rpb->rpb_runtime_flags |= RPB_undo_read;
if (!action->vct_undo || !action->vct_undo->locate(recno))
return udForceBack;
if (!action->vct_undo || !action->vct_undo->locate(recno))
return udForceBack;
const UndoItem& undo = action->vct_undo->current();
const UndoItem& undo = action->vct_undo->current();
rpb->rpb_runtime_flags |= RPB_undo_data;
CCH_RELEASE(tdbb, &rpb->getWindow(tdbb));
rpb->rpb_runtime_flags |= RPB_undo_data;
CCH_RELEASE(tdbb, &rpb->getWindow(tdbb));
AutoUndoRecord undoRecord(undo.setupRecord(transaction));
AutoUndoRecord undoRecord(undo.setupRecord(transaction));
Record* const record = VIO_record(tdbb, rpb, undoRecord->getFormat(), pool);
record->copyFrom(undoRecord);
Record* const record = VIO_record(tdbb, rpb, undoRecord->getFormat(), pool);
record->copyFrom(undoRecord);
rpb->rpb_flags &= ~rpb_deleted;
return udExists;
}
rpb->rpb_flags &= ~rpb_deleted;
return udExists;
}
return udNone;
@ -5938,31 +5823,11 @@ static void verb_post(thread_db* tdbb,
**************************************/
SET_TDBB(tdbb);
Jrd::ContextPoolHolder context(tdbb, transaction->tra_pool);
// Find action block for relation
VerbAction* action;
for (action = transaction->tra_save_point->sav_verb_actions; action; action = action->vct_next)
{
if (action->vct_relation == rpb->rpb_relation)
break;
}
if (!action)
{
if ( (action = transaction->tra_save_point->sav_verb_free) )
transaction->tra_save_point->sav_verb_free = action->vct_next;
else
action = FB_NEW_POOL(*tdbb->getDefaultPool()) VerbAction();
action->vct_next = transaction->tra_save_point->sav_verb_actions;
transaction->tra_save_point->sav_verb_actions = action;
action->vct_relation = rpb->rpb_relation;
}
VerbAction* const action = transaction->tra_save_point->createAction(rpb->rpb_relation);
if (!RecordBitmap::test(action->vct_records, rpb->rpb_number.getValue()))
{
RBM_SET(tdbb->getDefaultPool(), &action->vct_records, rpb->rpb_number.getValue());
RBM_SET(transaction->tra_pool, &action->vct_records, rpb->rpb_number.getValue());
if (old_data)
{
@ -5970,7 +5835,10 @@ static void verb_post(thread_db* tdbb,
// savepoint hasn't seen this record before.
if (!action->vct_undo)
action->vct_undo = FB_NEW UndoItemTree(tdbb->getDefaultPool());
{
action->vct_undo =
FB_NEW_POOL(*transaction->tra_pool) UndoItemTree(*transaction->tra_pool);
}
action->vct_undo->add(UndoItem(transaction, rpb->rpb_number, old_data));
}
@ -5980,66 +5848,6 @@ static void verb_post(thread_db* tdbb,
// Double update us posting. The old_data will not be used,
// so make sure we garbage collect before we lose track of the
// in-place-updated record.
action->garbage_collect_idx_lite(tdbb, transaction, rpb->rpb_number.getValue(), action, old_data);
action->garbageCollectIdxLite(tdbb, transaction, rpb->rpb_number.getValue(), action, old_data);
}
}
//----------------------
AutoSavePoint::AutoSavePoint(thread_db* tdbb, jrd_tra* aTransaction)
: transaction(aTransaction),
released(false)
{
VIO_start_save_point(tdbb, transaction);
}
AutoSavePoint::~AutoSavePoint()
{
thread_db* tdbb = JRD_get_thread_data();
if (!(tdbb->getDatabase()->dbb_flags & DBB_bugcheck))
{
if (released)
transaction->rollforwardSavepoint(tdbb);
else
transaction->rollbackSavepoint(tdbb);
}
}
/// class StableCursorSavePoint
StableCursorSavePoint::StableCursorSavePoint(thread_db* tdbb, jrd_tra* transaction, bool start)
: m_tdbb(tdbb),
m_tran(transaction),
m_number(0)
{
if (!start)
return;
if (m_tran == m_tdbb->getAttachment()->getSysTransaction())
return;
const Savepoint* save_point = m_tran->tra_save_point;
if (!save_point)
return;
VIO_start_save_point(m_tdbb, m_tran);
m_number = m_tran->tra_save_point->sav_number;
}
void StableCursorSavePoint::release()
{
if (!m_number)
return;
while (m_tran->tra_save_point && m_tran->tra_save_point->sav_number >= m_number)
{
m_tran->rollforwardSavepoint(m_tdbb);
}
m_number = 0;
}

View File

@ -55,50 +55,9 @@ void VIO_modify(Jrd::thread_db*, Jrd::record_param*, Jrd::record_param*, Jrd::jr
bool VIO_next_record(Jrd::thread_db*, Jrd::record_param*, Jrd::jrd_tra*, MemoryPool*, bool);
Jrd::Record* VIO_record(Jrd::thread_db*, Jrd::record_param*, const Jrd::Format*, MemoryPool*);
bool VIO_refetch_record(Jrd::thread_db*, Jrd::record_param*, Jrd::jrd_tra*, bool, bool);
void VIO_start_save_point(Jrd::thread_db*, Jrd::jrd_tra*);
void VIO_store(Jrd::thread_db*, Jrd::record_param*, Jrd::jrd_tra*);
bool VIO_sweep(Jrd::thread_db*, Jrd::jrd_tra*, Jrd::TraceSweepEvent*);
void VIO_verb_cleanup(Jrd::thread_db*, Jrd::jrd_tra*);
void VIO_temp_cleanup(Jrd::jrd_tra*);
void VIO_garbage_collect_idx(Jrd::thread_db*, Jrd::jrd_tra*, Jrd::record_param*, Jrd::Record*);
void VIO_update_in_place(Jrd::thread_db*, Jrd::jrd_tra*, Jrd::record_param*, Jrd::record_param*);
namespace Jrd
{
// Starts a savepoint and rollback it in destructor if release() is not called.
class AutoSavePoint
{
public:
AutoSavePoint(thread_db* tdbb, jrd_tra* aTransaction);
~AutoSavePoint();
void release()
{
released = true;
}
private:
jrd_tra* transaction;
bool released;
};
class StableCursorSavePoint
{
public:
StableCursorSavePoint(thread_db* tdbb, jrd_tra* transaction, bool start);
~StableCursorSavePoint()
{
release();
}
void release();
private:
thread_db* m_tdbb;
jrd_tra* m_tran;
SLONG m_number;
};
}
#endif // JRD_VIO_PROTO_H