Hello, All!
We have encountered subtle errors and data corruptions when using complex triggers/stored procedures
in READ COMMITTED transactions.
Most applied programmers don't think that while their SP/trigger is executing the world is changing
underneath them.
So lost updates and inconsistent data changes are happening during concurrent
operation.
To address this problem, in our engine builds we changed behavior of READ COMMITTED + REC_VERSION
transactions to ensure cursor stability.
Each request is executed in its own snapshot, that is released when execution
of request ends.
We tried to be extra careful not to hurt performance while establishing these snapshots. We do it
much more efficiently than done for isc_tpb_concurrency.
This mimics Oracle's behavior in that regard but Oracle doesn't always implement nesting correctly,
we do.
I attach patch for this functionality to give you an idea of implementation. It depends on a couple
other changes so it doesn't apply to FB2.5 cleanly.
Any objections if I start porting this functionality to FB3?
Nikolay Samofatov
Index: builds/install/misc/firebird.conf.in
===================================================================
--- builds/install/misc/firebird.conf.in (revision 10835)
+++ builds/install/misc/firebird.conf.in (revision 10840)
@@ -1176,4 +1176,23 @@
#
# Type: boolean
#
-#TraceAuthentication = 0
\ No newline at end of file
+#TraceAuthentication = 0
+
+# ----------------------------
+# This option provides an ability to disable stable curors in READ COMMITTED
+# (REC_VERSION) mode. In this mode each SQL query is now executed with its own
+# consistent snapshot of data. UPDATE, DELETE and SELECT...WITH LOCK queries may
+# return isc_update_conflict errors more often than before. On the other hand,
+# disabling read consistency can increase frequency of concurrency errors during
+# SELECT operations and expose you to subtle bugs in your procedures and
+# triggers, as most of them are written without regard to unstable legacy
+# behavior of cursors. This option has no effect on READ COMMITTED
+# (NO_REC_VERSION) mode, which retains unstable cursors behavior.
+#
+# CAUTION!
+# There is no guarantee that this setting will be available in future Firebird
+# versions.
+#
+# Type: boolean
+#
+#ReadConsistency = 1
Index: src/jrd/vio.cpp
===================================================================
--- src/jrd/vio.cpp (revision 10835)
+++ src/jrd/vio.cpp (revision 10840)
@@ -623,7 +623,7 @@
}
if (state == tra_committed) {
- state = TRA_pc_active(tdbb, rpb->rpb_transaction_nr) ? tra_precommitted : tra_dead;
+ state = TRA_is_active(tdbb, rpb->rpb_transaction_nr) ? tra_precommitted : tra_dead;
}
if (state == tra_dead) {
@@ -686,6 +686,7 @@
// option, wait for reads also!
if ((transaction->tra_flags & TRA_read_committed) &&
+ !(transaction->tra_flags & TRA_stable_cursors) &&
(!(transaction->tra_flags & TRA_rec_version) || writelock))
{
if (state == tra_limbo)
@@ -1064,7 +1065,7 @@
}
if (state == tra_committed) {
- state = TRA_pc_active(tdbb, rpb->rpb_transaction_nr) ? tra_precommitted : tra_dead;
+ state = TRA_is_active(tdbb, rpb->rpb_transaction_nr) ? tra_precommitted : tra_dead;
}
if (state == tra_dead) {
@@ -1683,7 +1684,7 @@
if (rpb->rpb_flags & rpb_gc_active)
{
if (state == tra_committed) {
- state = TRA_pc_active(tdbb, rpb->rpb_transaction_nr) ? tra_precommitted : tra_dead;
+ state = TRA_is_active(tdbb, rpb->rpb_transaction_nr) ? tra_precommitted : tra_dead;
}
if (state == tra_dead) {
@@ -1951,7 +1952,7 @@
if (rpb->rpb_flags & rpb_gc_active)
{
if (state == tra_committed) {
- state = TRA_pc_active(tdbb, rpb->rpb_transaction_nr) ? tra_precommitted : tra_dead;
+ state = TRA_is_active(tdbb, rpb->rpb_transaction_nr) ? tra_precommitted : tra_dead;
}
if (state == tra_dead) {
@@ -4723,7 +4724,7 @@
{
// There is no reason why this record would disappear for a
// snapshot transaction.
- if (!(transaction->tra_flags & TRA_read_committed))
+ if (!(transaction->tra_flags & TRA_read_committed) || (transaction->tra_flags & TRA_stable_cursors))
{
BUGCHECK(186); // msg 186 record disappeared
}
@@ -4751,7 +4752,7 @@
if (rpb->rpb_flags & rpb_gc_active)
{
if (state == tra_committed) {
- state = TRA_pc_active(tdbb, rpb->rpb_transaction_nr) ? tra_precommitted : tra_dead;
+ state = TRA_is_active(tdbb, rpb->rpb_transaction_nr) ? tra_precommitted : tra_dead;
}
if (state == tra_dead) {
@@ -4922,7 +4923,8 @@
{
case tra_committed:
// We need to loop waiting in read committed transactions only
- if (!(transaction->tra_flags & TRA_read_committed))
+ if (!(transaction->tra_flags & TRA_read_committed) ||
+ (transaction->tra_flags & TRA_stable_cursors))
{
ERR_post(Arg::Gds(isc_deadlock) <<
Arg::Gds(isc_update_conflict) <<
Index: src/jrd/rse.cpp
===================================================================
--- src/jrd/rse.cpp (revision 10835)
+++ src/jrd/rse.cpp (revision 10840)
@@ -2993,8 +2993,13 @@
if (same_txn)
break;
- // Validate whether any of the underlying fields has been changed
+ // NS: there is only a couple cases when unstable set of data is possible
+ fb_assert(transaction->tra_flags & TRA_system ||
+ ((transaction->tra_flags & TRA_read_committed) &&
+ !(transaction->tra_flags & TRA_stable_cursors)));
+ // Validate whether any of the underlying fields have been changed
+
bool same_fields = true;
for (smb_repeat* item = map->smb_rpt; item < end_item; item++)
Index: src/jrd/tra.h
===================================================================
--- src/jrd/tra.h (revision 10835)
+++ src/jrd/tra.h (revision 10840)
@@ -337,6 +337,7 @@
const ULONG TRA_restart_requests = 1048576L; // restart all requests in attachment
const ULONG TRA_no_auto_undo = 2097152L; // don't start a savepoint in TRA_start
const ULONG TRA_precommitted = 4194304L; // transaction committed at startup
+const ULONG TRA_stable_cursors = 8388608L; // ensure read consistency for cursors in this transaction
// flags derived from TPB, see also transaction_options() at tra.cpp
const ULONG TRA_OPTIONS_MASK = (TRA_degree3 | TRA_readonly | TRA_ignore_limbo | TRA_read_committed |
Index: src/jrd/lck_proto.h
===================================================================
--- src/jrd/lck_proto.h (revision 10835)
+++ src/jrd/lck_proto.h (revision 10840)
@@ -43,6 +43,7 @@
bool LCK_lock(Jrd::thread_db*, Jrd::Lock*, USHORT, SSHORT);
bool LCK_lock_opt(Jrd::thread_db*, Jrd::Lock*, USHORT, SSHORT);
SLONG LCK_query_data(Jrd::thread_db*, Jrd::Lock*, Jrd::lck_t, USHORT);
+void LCK_query_data2(Jrd::thread_db*, Jrd::Lock*, Jrd::lck_t, Jrd::LockManager::LockCallbackFunction, void*);
SLONG LCK_read_data(Jrd::thread_db*, Jrd::Lock*);
void LCK_release(Jrd::thread_db*, Jrd::Lock*);
void LCK_re_post(Jrd::thread_db*, Jrd::Lock*);
Index: src/jrd/req.h
===================================================================
--- src/jrd/req.h (revision 10835)
+++ src/jrd/req.h (revision 10840)
@@ -288,6 +288,11 @@
Firebird::Stack<jrd_tra*> req_auto_trans; // Autonomous transactions
SortOwner req_sorts;
+ // Fields to support READ COMMITTED cursor stability
+ SLONG req_top_transaction; // Anything above this number is assumed to be active
+ UInt32Bitmap* req_transactions; // List of transactions active when snapshot was taken
+ jrd_req* req_snapshot_owner; // Snapshot owner for this request
+
enum req_ta {
// order should be maintained because the numbers are stored in BLR
req_trigger_insert = 1,
Index: src/jrd/pag.cpp
===================================================================
--- src/jrd/pag.cpp (revision 10835)
+++ src/jrd/pag.cpp (revision 10840)
@@ -1466,13 +1466,8 @@
RelationPages* relPages = relation->getBasePages();
if (!relPages->rel_pages)
{
- // 21-Dec-2003 Nickolay Samofatov
- // No need to re-set first page for RDB$PAGES relation since
+ // NS: There no need to reassign first page for RDB$PAGES relation since
// current code cannot change its location after database creation.
- // Currently, this change only affects isc_database_info call,
- // the only call which may call PAG_header multiple times.
- // In fact, this isc_database_info behavior seems dangerous to me,
- // but let somebody else fix that problem, I just fix the memory leak.
vcl* vector = vcl::newVector(*dbb->dbb_permanent, 1);
relPages->rel_pages = vector;
(*vector)[0] = header->hdr_PAGES;
Index: src/jrd/lck.cpp
===================================================================
--- src/jrd/lck.cpp (revision 10835)
+++ src/jrd/lck.cpp (revision 10840)
@@ -513,7 +513,6 @@
case LCK_page_space:
case LCK_relation:
case LCK_tra:
- case LCK_tra_pc:
case LCK_update_shadow:
case LCK_dsql_cache:
case LCK_backup_end:
@@ -698,7 +697,26 @@
return true;
}
+void LCK_query_data2(thread_db* tdbb, Lock* parent, enum lck_t lock_type, LockManager::LockCallbackFunction callback, void* callback_arg) {
+/**************************************
+ *
+ * L C K _ q u e r y _ d a t a 2
+ *
+ **************************************
+ *
+ * Functional description
+ * Iterate over locks associated with a lock series for a lock hierarchy rooted
+ * at a parent lock.
+ *
+ **************************************/
+ SET_TDBB(tdbb);
+ Database* const dbb = tdbb->getDatabase();
+ fb_assert(LCK_CHECK_LOCK(parent));
+
+ dbb->dbb_lock_mgr->queryData2(parent->lck_id, lock_type, callback, callback_arg);
+}
+
SLONG LCK_query_data(thread_db* tdbb, Lock* parent, enum lck_t lock_type, USHORT aggregate)
{
/**************************************
Index: src/jrd/Database.h
===================================================================
--- src/jrd/Database.h (revision 10835)
+++ src/jrd/Database.h (revision 10840)
@@ -337,10 +337,12 @@
~SharedCounter();
SLONG generate(thread_db* tdbb, ULONG space, ULONG prefetch = DEFAULT_CACHE_SIZE);
+ void assignTopValue(thread_db* tdbb, ULONG space, SLONG value);
+ SLONG queryTopValue(thread_db* tdbb, ULONG space);
void shutdown(thread_db* tdbb);
private:
-
+ void initLock(thread_db* tdbb, ULONG space, USHORT level);
ValueCache m_counters[TOTAL_ITEMS];
};
Index: src/jrd/lck.h
===================================================================
--- src/jrd/lck.h (revision 10835)
+++ src/jrd/lck.h (revision 10840)
@@ -36,7 +36,6 @@
LCK_relation, // Individual relation lock
LCK_bdb, // Individual buffer block
LCK_tra, // Individual transaction lock
- LCK_tra_pc, // Precommitted transaction lock
LCK_rel_exist, // Relation existence lock
LCK_idx_exist, // Index existence lock
LCK_attachment, // Attachment lock
Index: src/jrd/tra.cpp
===================================================================
--- src/jrd/tra.cpp (revision 10835)
+++ src/jrd/tra.cpp (revision 10840)
@@ -120,6 +120,194 @@
GlobalPtr<Mutex> tra_mutex;
+void lock_list_callback(void* arg, const UCHAR* value, const USHORT length, SLONG data) {
+ Jrd::jrd_req* request = reinterpret_cast<Jrd::jrd_req*>(arg);
+
+ fb_assert(data); // Callback is called only for locks with data
+ fb_assert(length == sizeof(SLONG));
+
+ SLONG transaction_number;
+
+ memcpy(&transaction_number, value, sizeof(SLONG));
+
+ if (transaction_number != request->req_transaction->tra_number) {
+ if (!request->req_transactions)
+ request->req_transactions = FB_NEW(*request->req_pool) UInt32Bitmap(*request->req_pool);
+ request->req_transactions->set(transaction_number);
+ }
+}
+
+
+void TRA_setup_request_snapshot(Jrd::thread_db* tdbb, Jrd::jrd_req* request) {
+ // This function is called whenever request is started in a transaction.
+ // Setup context to preserve cursor stability in READ COMMITTED transactions.
+
+ SET_TDBB(tdbb);
+
+ Jrd::jrd_tra* transaction = request->req_transaction;
+
+ // We assume that request is already attached to a transaction
+ fb_assert(transaction);
+
+ // If we are not READ COMMITTED or stable cursors are not needed then nothing to do here
+ if (!(transaction->tra_flags & TRA_read_committed) ||
+ !(transaction->tra_flags & TRA_stable_cursors)) return;
+
+ // See if there is any request right above us in the call stack
+ jrd_req* org_request;
+ thread_db* jrd_ctx = tdbb;
+ do {
+ // Check regular request call stack
+ org_request = jrd_ctx->getRequest();
+ if (org_request) break;
+
+ // Check for engine context switch (EXECUTE STATEMENT, etc)
+ ThreadData* ctx = jrd_ctx;
+ jrd_ctx = NULL;
+ while( (ctx = ctx->getPriorContext()) ) {
+ if (ctx->getType() == ThreadData::tddDBB) {
+ jrd_ctx = static_cast<thread_db*>(ctx);
+ break;
+ }
+ }
+ } while (jrd_ctx);
+
+ if (org_request && org_request->req_transaction == transaction) {
+ fb_assert(org_request->req_snapshot_owner);
+ request->req_snapshot_owner = org_request->req_snapshot_owner;
+ return;
+ }
+
+ Database* dbb = tdbb->getDatabase();
+ CHECK_DBB(dbb);
+
+ // If we are a top-level request or caller is executed in a different transaction,
+ // we need to set up statement snapshot for cursor stability and own it
+
+ request->req_snapshot_owner = request;
+
+ // Populate request->req_transactions with the list of currently active transactions
+ for(;;) {
+ if (request->req_transactions)
+ request->req_transactions->clear();
+
+ LCK_query_data2(tdbb, dbb->dbb_lock, LCK_tra, &lock_list_callback, request);
+
+ // Obtain current value of transaction counter. For now this is a hack, refactoring is needed:
+ // 1. Each shared counter needs to become its own object with explicitly specified counter management policy
+ // 2. Transaction counter should hold actual transaction number both for read-only and read-write databases
+ SLONG top_transaction = dbb->dbb_shared_counter.queryTopValue(tdbb, Database::SharedCounter::TRANSACTION_ID_SPACE);
+ if (dbb->dbb_flags & DBB_read_only)
+ top_transaction += dbb->dbb_next_transaction;
+
+ SLONG oldest_active;
+ if (transaction->tra_flags & TRA_readonly)
+ oldest_active = top_transaction;
+ else
+ oldest_active = transaction->tra_number;
+
+ if (request->req_transactions && request->req_transactions->getFirst()) {
+ oldest_active = request->req_transactions->current();
+ }
+
+ request->req_top_transaction = top_transaction;
+
+ SLONG org_oldest_active = transaction->tra_lock->lck_data;
+
+ if (!org_oldest_active || org_oldest_active > oldest_active) {
+ LCK_write_data(tdbb, transaction->tra_lock, oldest_active);
+
+ if (request->req_transactions && request->req_transactions->getFirst()) {
+ // It is possible, though unlikely, that between LCK_query_data2 and LCK_write_data
+ // oldest active transaction was committed, new transaction has started
+ // and it garbage-collected some changes made in the previously oldest active transaction.
+ // To avoid this possiblity, we check that oldest active transaction is still active
+ // after our LCK_write_data, and if it so no longer the case we repeat the whole process again.
+ if (!TRA_is_active(tdbb, oldest_active)) {
+ LCK_write_data(tdbb, transaction->tra_lock, org_oldest_active);
+ continue;
+ }
+ }
+ }
+
+ break;
+ }
+}
+
+void TRA_release_request_snapshot(Jrd::thread_db* tdbb, Jrd::jrd_req* request) {
+ // This function is called whenever request has completed processing
+ // in a transaction (normally or abnormally)
+
+ SET_TDBB(tdbb);
+
+ Jrd::jrd_tra* transaction = request->req_transaction;
+
+ // If request is already detached from transaction, or if transaction is
+ // not READ COMMITTED, or stable cursors are not needed then nothing to do here
+ if (!transaction || !(transaction->tra_flags & TRA_read_committed) ||
+ !(transaction->tra_flags & TRA_stable_cursors))
+ return;
+
+ request->req_snapshot_owner = NULL;
+
+ // If we don't keep snapshot for a request, we cannot possibly hold things back
+ if (!request->req_top_transaction)
+ return;
+
+ SLONG req_oldest_active;
+ if (request->req_transactions && request->req_transactions->getFirst())
+ req_oldest_active = request->req_transactions->current();
+ else
+ req_oldest_active = request->req_top_transaction;
+
+ // Release the snapshot
+ if (request->req_transactions)
+ request->req_transactions->clear();
+ request->req_top_transaction = 0;
+
+ // If transaction is holding GC because of some other request we are done
+ if (req_oldest_active > transaction->tra_lock->lck_data)
+ return;
+
+ // No way we can move oldest active counter past the number of transaction itself
+ // unless it is read-only
+ if (!(transaction->tra_flags & TRA_readonly) &&
+ (transaction->tra_lock->lck_data == transaction->tra_number))
+ {
+ return;
+ }
+
+ SLONG new_oldest_active = MAX_SLONG;
+
+ // Iterate over all requests for transaction and find request that needs oldest snapshot.
+ // This scan should be a relatively rare case, this is why we don't optimize it away.
+ for (jrd_req* req_itr = transaction->tra_requests; req_itr; req_itr = req_itr->req_tra_next)
+ {
+ if ((request->req_flags & req_active) && request->req_top_transaction)
+ {
+ if (request->req_transactions && request->req_transactions->getFirst()) {
+ SLONG tra_number = request->req_transactions->current();
+ if (tra_number < new_oldest_active)
+ new_oldest_active = tra_number;
+ } else {
+ if (request->req_top_transaction < new_oldest_active)
+ new_oldest_active = request->req_top_transaction;
+ }
+ }
+ }
+
+ // If there are no active requests holding snapshot
+ // use oldest active value appropriate for transaction type
+ if (new_oldest_active == MAX_SLONG)
+ if (transaction->tra_flags & TRA_readonly)
+ new_oldest_active = 0;
+ else
+ new_oldest_active = transaction->tra_number;
+
+ LCK_write_data(tdbb, transaction->tra_lock, new_oldest_active);
+}
+
+
void TRA_attach_request(Jrd::jrd_tra* transaction, Jrd::jrd_req* request)
{
// When request finishes normally transaction reference is not cleared.
@@ -914,16 +1102,16 @@
}
-bool TRA_pc_active(thread_db* tdbb, SLONG number)
+bool TRA_is_active(thread_db* tdbb, SLONG number)
{
/**************************************
*
- * T R A _ p c _ a c t i v e
+ * T R A _ i s _ a c t i v e
*
**************************************
*
* Functional description
- * Returns whether a given precommitted transaction
+ * Returns whether a given transaction
* owned by some other guy active or not.
*
**************************************/
@@ -933,7 +1121,7 @@
Lock temp_lock;
temp_lock.lck_dbb = dbb;
- temp_lock.lck_type = LCK_tra_pc;
+ temp_lock.lck_type = LCK_tra;
temp_lock.lck_owner_handle = LCK_get_owner_handle(tdbb, temp_lock.lck_type);
temp_lock.lck_parent = dbb->dbb_lock;
temp_lock.lck_length = sizeof(SLONG);
@@ -1573,7 +1761,6 @@
}
-
int TRA_snapshot_state(thread_db* tdbb, const jrd_tra* trans, SLONG number)
{
/**************************************
@@ -1607,13 +1794,8 @@
if (number == TRA_system_transaction)
return tra_committed;
- // Look in the transaction cache for read committed transactions
- // fast, and the system transaction. The system transaction can read
- // data from active transactions.
+ // The system transaction can read data from active transactions.
- if (trans->tra_flags & TRA_read_committed)
- return TPC_snapshot_state(tdbb, number);
-
if (trans->tra_flags & TRA_system)
{
int state = TPC_snapshot_state(tdbb, number);
@@ -1630,6 +1812,30 @@
return tra_committed;
}
+ // Look in the transaction cache for read committed transactions
+
+ if (trans->tra_flags & TRA_read_committed) {
+ if (trans->tra_flags & TRA_stable_cursors) {
+ jrd_req* current_request = tdbb->getRequest();
+
+ // Note: GC thread accesses data directly without any request
+ if (current_request) {
+ jrd_req* snapshot_request = current_request->req_snapshot_owner;
+
+ // Any request with stable cursors should execute in snapshot context
+ fb_assert(snapshot_request);
+
+ if (number > snapshot_request->req_top_transaction)
+ return tra_active;
+
+ if (UInt32Bitmap::test(snapshot_request->req_transactions, number))
+ return tra_active;
+ }
+ }
+
+ return TPC_snapshot_state(tdbb, number);
+ }
+
// If the transaction is younger than we are and we are not read committed
// or the system transaction, the transaction must be considered active.
@@ -2639,8 +2845,10 @@
}
#ifndef SUPERSERVER_V2
- if (!(dbb->dbb_flags & DBB_read_only))
+ if (!(dbb->dbb_flags & DBB_read_only)) {
CCH_RELEASE(tdbb, &window);
+ dbb->dbb_shared_counter.assignTopValue(tdbb, Database::SharedCounter::TRANSACTION_ID_SPACE, new_number);
+ }
#endif
// Update database notion of the youngest commit retaining
@@ -3220,7 +3428,11 @@
}
}
+ if ((transaction->tra_flags & TRA_read_committed) && (transaction->tra_flags & TRA_rec_version))
+ if (Config::getReadConsistency())
+ transaction->tra_flags |= TRA_stable_cursors;
+
// If there aren't any relation locks to seize, we're done.
vec<Lock*>* vector = transaction->tra_relation_locks;
@@ -3360,8 +3572,10 @@
link_transaction(tdbb, trans);
#ifndef SUPERSERVER_V2
- if (!(dbb->dbb_flags & DBB_read_only))
+ if (!(dbb->dbb_flags & DBB_read_only)) {
CCH_RELEASE(tdbb, &window);
+ dbb->dbb_shared_counter.assignTopValue(tdbb, Database::SharedCounter::TRANSACTION_ID_SPACE, number);
+ }
#endif
if (dbb->dbb_flags & DBB_read_only)
@@ -3571,17 +3785,9 @@
if (trans->tra_flags & TRA_readonly && trans->tra_flags & TRA_read_committed)
{
TRA_set_state(tdbb, trans, trans->tra_number, tra_committed);
- LCK_release(tdbb, lock);
- lock->lck_type = LCK_tra_pc;
- lock->lck_owner_handle = LCK_get_owner_handle(tdbb, lock->lck_type);
- lock->lck_data = 0;
- if (!LCK_lock(tdbb, lock, LCK_write, LCK_WAIT))
- {
- jrd_tra::destroy(dbb, trans);
- ERR_post(Arg::Gds(isc_lock_conflict));
- }
-
+ LCK_write_data(tdbb, lock, 0); // This is enough to disinhibit garbage collection
+
trans->tra_flags |= TRA_precommitted;
}
Index: src/jrd/ThreadData.h
===================================================================
--- src/jrd/ThreadData.h (revision 10835)
+++ src/jrd/ThreadData.h (revision 10840)
@@ -65,6 +65,8 @@
return threadDataType;
}
+ ThreadData* getPriorContext() { return threadDataPriorContext; } ;
+
static ThreadData* getSpecific();
void putSpecific();
static void restoreSpecific();
Index: src/jrd/tra_proto.h
===================================================================
--- src/jrd/tra_proto.h (revision 10835)
+++ src/jrd/tra_proto.h (revision 10840)
@@ -47,7 +47,7 @@
void TRA_link_cursor(Jrd::jrd_tra*, Jrd::dsql_req*);
void TRA_unlink_cursor(Jrd::jrd_tra*, Jrd::dsql_req*);
void TRA_post_resources(Jrd::thread_db*, Jrd::jrd_tra*, Jrd::ResourceList&);
-bool TRA_pc_active(Jrd::thread_db*, SLONG);
+bool TRA_is_active(Jrd::thread_db*, SLONG);
bool TRA_precommited(Jrd::thread_db*, SLONG, SLONG);
void TRA_prepare(Jrd::thread_db*, Jrd::jrd_tra*, USHORT, const UCHAR*);
Jrd::jrd_tra* TRA_reconnect(Jrd::thread_db*, const UCHAR*, USHORT);
@@ -62,6 +62,8 @@
int TRA_wait(Jrd::thread_db*, Jrd::jrd_tra*, SLONG, Jrd::jrd_tra::wait_t);
void TRA_attach_request(Jrd::jrd_tra* transaction, Jrd::jrd_req* request);
void TRA_detach_request(Jrd::jrd_req* request);
+void TRA_setup_request_snapshot(Jrd::thread_db*, Jrd::jrd_req* request);
+void TRA_release_request_snapshot(Jrd::thread_db*, Jrd::jrd_req* request);
#endif // JRD_TRA_PROTO_H
Index: src/jrd/Database.cpp
===================================================================
--- src/jrd/Database.cpp (revision 10835)
+++ src/jrd/Database.cpp (revision 10840)
@@ -242,28 +242,36 @@
}
}
+ void Database::SharedCounter::initLock(thread_db* tdbb, ULONG space, USHORT level) {
+ fb_assert(space < TOTAL_ITEMS);
+ ValueCache* const counter = &m_counters[space];
+ Database* const dbb = tdbb->getDatabase();
+
+ fb_assert(!counter->lock);
+
+ Lock* const lock = FB_NEW_RPT(*dbb->dbb_permanent, sizeof(SLONG)) Lock();
+ counter->lock = lock;
+ lock->lck_type = LCK_shared_counter;
+ 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 = space;
+ lock->lck_dbb = dbb;
+ LCK_lock(tdbb, lock, level, LCK_WAIT);
+
+ counter->curVal = 1;
+ counter->maxVal = 0;
+ }
+
+
SLONG Database::SharedCounter::generate(thread_db* tdbb, ULONG space, ULONG prefetch)
{
fb_assert(space < TOTAL_ITEMS);
ValueCache* const counter = &m_counters[space];
- Database* const dbb = tdbb->getDatabase();
if (!counter->lock)
- {
- Lock* const lock = FB_NEW_RPT(*dbb->dbb_permanent, sizeof(SLONG)) Lock();
- counter->lock = lock;
- lock->lck_type = LCK_shared_counter;
- 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 = space;
- lock->lck_dbb = dbb;
- LCK_lock(tdbb, lock, LCK_PW, LCK_WAIT);
+ initLock(tdbb, space, LCK_PW);
- counter->curVal = 1;
- counter->maxVal = 0;
- }
-
if (counter->curVal > counter->maxVal)
{
LCK_convert(tdbb, counter->lock, LCK_PW, LCK_WAIT);
@@ -283,4 +291,29 @@
return counter->curVal++;
}
+ void Database::SharedCounter::assignTopValue(thread_db* tdbb, ULONG space, SLONG value) {
+ fb_assert(space < TOTAL_ITEMS);
+ ValueCache* const counter = &m_counters[space];
+
+ if (!counter->lock)
+ initLock(tdbb, space, LCK_PW);
+
+ LCK_convert(tdbb, counter->lock, LCK_PW, LCK_WAIT);
+ SLONG current_value = LCK_read_data(tdbb, counter->lock);
+ // This assures that counter can never go backwards
+ if (value > current_value)
+ LCK_write_data(tdbb, counter->lock, value);
+ LCK_convert(tdbb, counter->lock, LCK_SR, LCK_WAIT);
+ }
+
+ SLONG Database::SharedCounter::queryTopValue(thread_db* tdbb, ULONG space) {
+ fb_assert(space < TOTAL_ITEMS);
+ ValueCache* const counter = &m_counters[space];
+
+ if (!counter->lock)
+ initLock(tdbb, space, LCK_SR);
+
+ return LCK_read_data(tdbb, counter->lock);
+ }
+
} // namespace
Index: src/jrd/exe.cpp
===================================================================
--- src/jrd/exe.cpp (revision 10835)
+++ src/jrd/exe.cpp (revision 10840)
@@ -1069,6 +1069,8 @@
request->req_src_line = 0;
request->req_src_column = 0;
+ TRA_setup_request_snapshot(tdbb, request);
+
execute_looper(tdbb, request, transaction, request->req_top_node, jrd_req::req_evaluate);
}
@@ -1135,6 +1137,7 @@
if (request->req_proc_sav_point && (request->req_flags & req_proc_fetch))
release_proc_save_points(request);
+ TRA_release_request_snapshot(tdbb, request);
TRA_detach_request(request);
request->req_flags &= ~(req_active | req_proc_fetch | req_reserved);
@@ -3005,6 +3008,7 @@
}
}
+ TRA_release_request_snapshot(tdbb, request);
request->req_flags &= ~(req_active | req_reserved);
request->req_timestamp.invalidate();
release_blobs(tdbb, request);
Index: src/lock/lock_proto.h
===================================================================
--- src/lock/lock_proto.h (revision 10835)
+++ src/lock/lock_proto.h (revision 10840)
@@ -402,6 +402,13 @@
SLONG readData2(SRQ_PTR, USHORT, const UCHAR*, USHORT, SRQ_PTR);
SLONG writeData(SRQ_PTR, SLONG);
+ // Callback function can do whatever is necessary, but it should do it quickly
+ // (as it is called with LM mutex held), and should not call lock manager.
+ // Callback function is allowed to throw exceptions, if needed
+ typedef void (*LockCallbackFunction)(void* arg, const UCHAR* value, const USHORT length, SLONG data);
+
+ void queryData2(SRQ_PTR parent_request, const USHORT series, LockCallbackFunction callback, void* callback_arg);
+
private:
explicit LockManager(const Firebird::string&);
~LockManager();
Index: src/lock/lock.cpp
===================================================================
--- src/lock/lock.cpp (revision 10835)
+++ src/lock/lock.cpp (revision 10840)
@@ -867,7 +867,57 @@
return true;
}
+void LockManager::queryData2(SRQ_PTR parent_request, const USHORT series, LockCallbackFunction callback, void* callback_arg) {
+/**************************************
+ *
+ * q u e r y D a t a 2
+ *
+ **************************************
+ *
+ * Functional description
+ * Query lock series with respect to a rooted
+ * lock hierarchy calling callback function for each lock.
+ *
+ **************************************/
+ if (!parent_request || series >= LCK_MAX_SERIES)
+ {
+ CHECK(false);
+ return;
+ }
+ LocalGuard guard(this);
+
+ // Get root of lock hierarchy
+
+ lrq* parent = get_request(parent_request);
+ acquire_shmem(parent->lrq_owner);
+ try {
+ parent = (lrq*) SRQ_ABS_PTR(parent_request); // remap
+
+ ++m_header->lhb_query_data;
+ const srq& data_header = m_header->lhb_data[series];
+ SLONG data = 0, count = 0;
+
+ for (const srq* lock_srq = (SRQ) SRQ_ABS_PTR(data_header.srq_forward);
+ lock_srq != &data_header; lock_srq = (SRQ) SRQ_ABS_PTR(lock_srq->srq_forward))
+ {
+ const lbl* lock = (lbl*) ((UCHAR*) lock_srq - OFFSET(lbl*, lbl_lhb_data));
+ CHECK(lock->lbl_series == series);
+ if (lock->lbl_parent != parent->lrq_lock)
+ continue;
+
+ (*callback)(callback_arg, lock->lbl_key, lock->lbl_length, lock->lbl_data);
+ }
+ } catch(...) {
+ release_shmem(parent->lrq_owner);
+ throw;
+ }
+
+ release_shmem(parent->lrq_owner);
+}
+
+
+
SLONG LockManager::queryData(SRQ_PTR parent_request, const USHORT series, const USHORT aggregate)
{
/**************************************
Index: src/common/config/config.cpp
===================================================================
--- src/common/config/config.cpp (revision 10835)
+++ src/common/config/config.cpp (revision 10840)
@@ -162,7 +162,8 @@
{TYPE_BOOLEAN, "TraceAuthentication", (ConfigValue) false},
{TYPE_STRING, "OptimizationStrategy", (ConfigValue) OptDefault},
{TYPE_INTEGER, "MaxOpenFileBlobs", (ConfigValue) 0},
- {TYPE_BOOLEAN, "LDAPReadOnly", (ConfigValue) false}
+ {TYPE_BOOLEAN, "LDAPReadOnly", (ConfigValue) false},
+ {TYPE_BOOLEAN, "ReadConsistency", (ConfigValue) true}
};
/******************************************************************************
@@ -745,4 +746,9 @@
bool Config::getLdapReadOnly()
{
return (bool) sysConfig().values[KEY_LDAP_READ_ONLY];
-}
\ No newline at end of file
+}
+
+bool Config::getReadConsistency()
+{
+ return (bool) sysConfig().values[KEY_READ_CONSISTENCY];
+}
Index: src/common/config/config.h
===================================================================
--- src/common/config/config.h (revision 10835)
+++ src/common/config/config.h (revision 10840)
@@ -154,7 +154,8 @@
KEY_TRACE_AUTHENTICATION, // 74
KEY_OPTIMIZATION_STRATEGY, // 75
KEY_MAX_OPEN_FILE_BLOBS, // 76
- KEY_LDAP_READ_ONLY // 77
+ KEY_LDAP_READ_ONLY, // 77
+ KEY_READ_CONSISTENCY // 79
};
public:
@@ -370,6 +371,8 @@
static int getMaxOpenFileBlobs();
static bool getLdapReadOnly();
+
+ static bool getReadConsistency();
};
#endif // COMMON_CONFIG_H
------------------------------------------------------------------------------
HPCC Systems Open Source Big Data Platform from LexisNexis Risk Solutions
Find What Matters Most in Your Big Data with HPCC Systems
Open Source. Fast. Scalable. Simple. Ideal for Dirty Data.
Leverages Graph Analysis for Fast Processing & Easy Data Exploration
http://p.sf.net/sfu/hpccsystems
Firebird-Devel mailing list, web interface at
https://lists.sourceforge.net/lists/listinfo/firebird-devel