From fc26fcbc4f73e3a65c590c58f7a9767c43b2af2a Mon Sep 17 00:00:00 2001 From: "aleksey.mochalov" Date: Tue, 4 Aug 2026 10:12:35 +0300 Subject: [PATCH] Fix self-deadlock when acquiring a shared SyncObject latch Detect attempts to acquire SYNC_SHARED when the current thread already owns SYNC_EXCLUSIVE and unwind page-cache latches when propagating the resulting exception from VIO operations. --- src/common/classes/SyncObject.cpp | 8 + src/dsql/StmtNodes.cpp | 40 ++- src/include/firebird/impl/msg/jrd.h | 2 + src/include/gen/Firebird.pas | 1 + src/jrd/Savepoint.cpp | 1 + src/jrd/cch.cpp | 10 + src/jrd/replication/Applier.cpp | 31 ++- src/jrd/vio.cpp | 394 ++++++++++++++-------------- 8 files changed, 288 insertions(+), 199 deletions(-) diff --git a/src/common/classes/SyncObject.cpp b/src/common/classes/SyncObject.cpp index 4e8214e5965..066d7cda790 100644 --- a/src/common/classes/SyncObject.cpp +++ b/src/common/classes/SyncObject.cpp @@ -35,6 +35,7 @@ #include "SyncObject.h" #include "Synchronize.h" +#include "../jrd/err_proto.h" namespace Firebird { @@ -91,6 +92,13 @@ bool SyncObject::lock(Sync* sync, SyncType type, const char* from, int timeOut) thread = ThreadSync::findThread(); fb_assert(thread); + + if (thread == exclusiveThread) + { + --waiters; + mutex.leave(); + ERR_post(Arg::Gds(isc_sync_object_self_deadlock)); + } } else { diff --git a/src/dsql/StmtNodes.cpp b/src/dsql/StmtNodes.cpp index 6c14b8cc8b6..066005b346d 100644 --- a/src/dsql/StmtNodes.cpp +++ b/src/dsql/StmtNodes.cpp @@ -44,6 +44,7 @@ #include "../jrd/trace/TraceManager.h" #include "../jrd/trace/TraceJrdHelpers.h" #include "../jrd/cmp_proto.h" +#include "../jrd/cch_proto.h" #include "../jrd/dfw_proto.h" #include "../jrd/dpm_proto.h" #include "../jrd/evl_proto.h" @@ -2753,7 +2754,19 @@ const StmtNode* EraseNode::erase(thread_db* tdbb, Request* request, WhichTrigger // setting req_update_conflict flag) so re-fetch should see new data. // b) record is locked by another transaction and should be skipped. - if (!VIO_erase(tdbb, rpb, transaction)) + bool erased; + + try + { + erased = VIO_erase(tdbb, rpb, transaction); + } + catch (const Exception&) + { + CCH_unwind(tdbb, false); + throw; + } + + if (!erased) { // Record was not deleted, flow control should be passed to the parent // ForNode. Note, If RETURNING clause was specified and SKIP LOCKED was @@ -7132,7 +7145,19 @@ const StmtNode* ModifyNode::modify(thread_db* tdbb, Request* request, WhichTrigg // setting req_update_conflict flag) so re-fetch should see new data. // b) record is locked by another transaction and should be skipped. - if (!VIO_modify(tdbb, orgRpb, newRpb, transaction)) + bool modified; + + try + { + modified = VIO_modify(tdbb, orgRpb, newRpb, transaction); + } + catch (const Exception&) + { + CCH_unwind(tdbb, false); + throw; + } + + if (!modified) { if (!skipLocked) { @@ -8215,7 +8240,16 @@ const StmtNode* StoreNode::store(thread_db* tdbb, Request* request, WhichTrigger VirtualTable::store(tdbb, rpb); else if (!relation->rel_view_rse) { - VIO_store(tdbb, rpb, transaction); + try + { + VIO_store(tdbb, rpb, transaction); + } + catch (const Exception&) + { + CCH_unwind(tdbb, false); + throw; + } + IDX_store(tdbb, rpb, transaction); REPL_store(tdbb, rpb, transaction); } diff --git a/src/include/firebird/impl/msg/jrd.h b/src/include/firebird/impl/msg/jrd.h index 000ee43615d..59e34bb9a50 100644 --- a/src/include/firebird/impl/msg/jrd.h +++ b/src/include/firebird/impl/msg/jrd.h @@ -979,3 +979,5 @@ FB_IMPL_MSG(JRD, 998, no_user_att_while_restore, -901, "HY", "000", "User attach FB_IMPL_MSG(JRD, 1005, update_overwrite, -901, "27", "000", "UPDATE will overwrite changes made by the trigger or by the another UPDATE in the same cursor") // Codes 1006..1015 are used in v6 FB_IMPL_MSG(JRD, 1016, temp_space_invalid_pos, -901, "HY", "000", "Invalid position to read/write in a temporary file (positon: @1, size: @2)") +// Codes 1017..1022 are used in v6 +FB_IMPL_MSG(JRD, 1023, sync_object_self_deadlock, -901, "HY", "000", "Acquire a SYNC_SHARED latch on a SYNC_EXCLUSIVE latch that is already held by current thread") diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index 8509c12c5c5..1aea078c62e 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -5766,6 +5766,7 @@ IProfilerStatsImpl = class(IProfilerStats) isc_no_user_att_while_restore = 335545318; isc_update_overwrite = 335545325; isc_temp_space_invalid_pos = 335545336; + isc_sync_object_self_deadlock = 335545343; isc_gfix_db_name = 335740929; isc_gfix_invalid_sw = 335740930; isc_gfix_incmp_sw = 335740932; diff --git a/src/jrd/Savepoint.cpp b/src/jrd/Savepoint.cpp index 64bae3b0516..0eb7f76b191 100644 --- a/src/jrd/Savepoint.cpp +++ b/src/jrd/Savepoint.cpp @@ -472,6 +472,7 @@ Savepoint* Savepoint::rollback(thread_db* tdbb, Savepoint* prior, bool preserveL tdbb->setTransaction(old_tran); m_transaction->tra_flags |= TRA_invalidated; error.prepend(Arg::Gds(isc_savepoint_backout_err)); + CCH_unwind(tdbb, false); error.raise(); } diff --git a/src/jrd/cch.cpp b/src/jrd/cch.cpp index 8213673999a..aede8d5dc66 100644 --- a/src/jrd/cch.cpp +++ b/src/jrd/cch.cpp @@ -2033,6 +2033,16 @@ void CCH_release(thread_db* tdbb, WIN* window, const bool release_tail) **************************************/ SET_TDBB(tdbb); + // If TDBB_cache_unwound is set, return here to prevent + // changing bdb_flags below because it can be dangerous + // in a concurrent environment. + if (tdbb->tdbb_flags & TDBB_cache_unwound) + { + fb_assert(tdbb->tdbb_bdbs.isEmpty()); + window->win_bdb = NULL; + return; + } + BufferDesc* const bdb = window->win_bdb; BLKCHK(bdb, type_bdb); diff --git a/src/jrd/replication/Applier.cpp b/src/jrd/replication/Applier.cpp index 46bb151ab03..1ca50e01cb5 100644 --- a/src/jrd/replication/Applier.cpp +++ b/src/jrd/replication/Applier.cpp @@ -1296,7 +1296,15 @@ void Applier::doInsert(thread_db* tdbb, record_param* rpb, jrd_tra* transaction) // This allows to use RDB$RECORD_VERSION in indices. rpb->rpb_record->setTransactionNumber(transaction->tra_number); - VIO_store(tdbb, rpb, transaction); + try + { + VIO_store(tdbb, rpb, transaction); + } + catch (const Exception&) + { + CCH_unwind(tdbb, false); + throw; + } IDX_store(tdbb, rpb, transaction); if (m_enableCascade) REPL_store(tdbb, rpb, transaction); @@ -1396,7 +1404,15 @@ void Applier::doUpdate(thread_db* tdbb, record_param* orgRpb, record_param* newR // This allows to use NEW.RDB$RECORD_VERSION in indices. newRpb->rpb_record->setTransactionNumber(transaction->tra_number); - VIO_modify(tdbb, orgRpb, newRpb, transaction); + try + { + VIO_modify(tdbb, orgRpb, newRpb, transaction); + } + catch (const Exception&) + { + CCH_unwind(tdbb, false); + throw; + } IDX_modify(tdbb, orgRpb, newRpb, transaction); if (m_enableCascade) REPL_modify(tdbb, orgRpb, newRpb, transaction); @@ -1410,7 +1426,16 @@ void Applier::doDelete(thread_db* tdbb, record_param* rpb, jrd_tra* transaction) Savepoint::ChangeMarker marker(transaction->tra_save_point); - VIO_erase(tdbb, rpb, transaction); + try + { + VIO_erase(tdbb, rpb, transaction); + } + catch (const Exception&) + { + CCH_unwind(tdbb, false); + throw; + } + if (m_enableCascade) REPL_erase(tdbb, rpb, transaction); } diff --git a/src/jrd/vio.cpp b/src/jrd/vio.cpp index 8af9653e407..04faf5973b7 100644 --- a/src/jrd/vio.cpp +++ b/src/jrd/vio.cpp @@ -834,160 +834,212 @@ void VIO_backout(thread_db* tdbb, record_param* rpb, const jrd_tra* transaction) relation->rel_id, rpb->rpb_number.getValue(), transaction ? transaction->tra_number : 0); #endif - // If there is data in the record, fetch it now. If the old version - // is a differences record, we will need it sooner. In any case, we - // will need it eventually to clean up blobs and indices. If the record - // has changed in between, stop now before things get worse. - - record_param temp = *rpb; - if (!DPM_get(tdbb, &temp, LCK_read)) - return; + try + { + // If there is data in the record, fetch it now. If the old version + // is a differences record, we will need it sooner. In any case, we + // will need it eventually to clean up blobs and indices. If the record + // has changed in between, stop now before things get worse. -#ifdef VIO_DEBUG - VIO_trace(DEBUG_WRITES_INFO, - " record %" SLONGFORMAT":%d, rpb_trans %" SQUADFORMAT - ", flags %d, back %" SLONGFORMAT":%d, fragment %" SLONGFORMAT":%d\n", - temp.rpb_page, temp.rpb_line, temp.rpb_transaction_nr, - temp.rpb_flags, temp.rpb_b_page, temp.rpb_b_line, - temp.rpb_f_page, temp.rpb_f_line); + record_param temp = *rpb; + if (!DPM_get(tdbb, &temp, LCK_read)) + return; - if (temp.rpb_b_page != rpb->rpb_b_page || temp.rpb_b_line != rpb->rpb_b_line || - temp.rpb_transaction_nr != rpb->rpb_transaction_nr) - { + #ifdef VIO_DEBUG VIO_trace(DEBUG_WRITES_INFO, - " wrong record!)\n"); - } -#endif + " record %" SLONGFORMAT":%d, rpb_trans %" SQUADFORMAT + ", flags %d, back %" SLONGFORMAT":%d, fragment %" SLONGFORMAT":%d\n", + temp.rpb_page, temp.rpb_line, temp.rpb_transaction_nr, + temp.rpb_flags, temp.rpb_b_page, temp.rpb_b_line, + temp.rpb_f_page, temp.rpb_f_line); - if (temp.rpb_b_page != rpb->rpb_b_page || temp.rpb_b_line != rpb->rpb_b_line || - temp.rpb_transaction_nr != rpb->rpb_transaction_nr) - { - CCH_RELEASE(tdbb, &temp.getWindow(tdbb)); - return; - } + if (temp.rpb_b_page != rpb->rpb_b_page || temp.rpb_b_line != rpb->rpb_b_line || + temp.rpb_transaction_nr != rpb->rpb_transaction_nr) + { + VIO_trace(DEBUG_WRITES_INFO, + " wrong record!)\n"); + } + #endif - AutoLock gcLockGuard(tdbb, lockGCActive(tdbb, transaction, &temp)); + if (temp.rpb_b_page != rpb->rpb_b_page || temp.rpb_b_line != rpb->rpb_b_line || + temp.rpb_transaction_nr != rpb->rpb_transaction_nr) + { + CCH_RELEASE(tdbb, &temp.getWindow(tdbb)); + return; + } - if (!gcLockGuard) - { - CCH_RELEASE(tdbb, &temp.getWindow(tdbb)); - return; - } + AutoLock gcLockGuard(tdbb, lockGCActive(tdbb, transaction, &temp)); - RecordStack going, staying; - Record* data = NULL; - Record* old_data = NULL; + if (!gcLockGuard) + { + CCH_RELEASE(tdbb, &temp.getWindow(tdbb)); + return; + } - AutoTempRecord gc_rec1; - AutoTempRecord gc_rec2; + RecordStack going, staying; + Record* data = NULL; + Record* old_data = NULL; - bool samePage; - bool deleted; + AutoTempRecord gc_rec1; + AutoTempRecord gc_rec2; - if ((temp.rpb_flags & rpb_deleted) && (!(temp.rpb_flags & rpb_delta))) - CCH_RELEASE(tdbb, &temp.getWindow(tdbb)); - else - { - temp.rpb_record = gc_rec1 = VIO_gc_record(tdbb, relation); - VIO_data(tdbb, &temp, relation->rel_pool); - data = temp.rpb_prior; - old_data = temp.rpb_record; - rpb->rpb_prior = temp.rpb_prior; - going.push(temp.rpb_record); - } + bool samePage; + bool deleted; - // Set up an extra record parameter block. This will be used to preserve - // the main record information while we chase fragments. + if ((temp.rpb_flags & rpb_deleted) && (!(temp.rpb_flags & rpb_delta))) + CCH_RELEASE(tdbb, &temp.getWindow(tdbb)); + else + { + temp.rpb_record = gc_rec1 = VIO_gc_record(tdbb, relation); + VIO_data(tdbb, &temp, relation->rel_pool); + data = temp.rpb_prior; + old_data = temp.rpb_record; + rpb->rpb_prior = temp.rpb_prior; + going.push(temp.rpb_record); + } - record_param temp2 = temp = *rpb; + // Set up an extra record parameter block. This will be used to preserve + // the main record information while we chase fragments. - // If there is an old version of the record, fetch it's data now. + record_param temp2 = temp = *rpb; - RuntimeStatistics::Accumulator backversions(tdbb, relation, - RuntimeStatistics::RECORD_BACKVERSION_READS); + // If there is an old version of the record, fetch it's data now. - if (rpb->rpb_b_page) - { - temp.rpb_record = gc_rec2 = VIO_gc_record(tdbb, relation); + RuntimeStatistics::Accumulator backversions(tdbb, relation, + RuntimeStatistics::RECORD_BACKVERSION_READS); - while (true) + if (rpb->rpb_b_page) { - if (!DPM_get(tdbb, &temp, LCK_read)) - return; + temp.rpb_record = gc_rec2 = VIO_gc_record(tdbb, relation); - if (temp.rpb_b_page != rpb->rpb_b_page || temp.rpb_b_line != rpb->rpb_b_line || - temp.rpb_transaction_nr != rpb->rpb_transaction_nr) + while (true) { - CCH_RELEASE(tdbb, &temp.getWindow(tdbb)); - return; - } + if (!DPM_get(tdbb, &temp, LCK_read)) + return; + + if (temp.rpb_b_page != rpb->rpb_b_page || temp.rpb_b_line != rpb->rpb_b_line || + temp.rpb_transaction_nr != rpb->rpb_transaction_nr) + { + CCH_RELEASE(tdbb, &temp.getWindow(tdbb)); + return; + } - if (temp.rpb_flags & rpb_delta) - temp.rpb_prior = data; + if (temp.rpb_flags & rpb_delta) + temp.rpb_prior = data; - if (!DPM_fetch_back(tdbb, &temp, LCK_read, -1)) - { - fb_utils::init_status(tdbb->tdbb_status_vector); - continue; + if (!DPM_fetch_back(tdbb, &temp, LCK_read, -1)) + { + fb_utils::init_status(tdbb->tdbb_status_vector); + continue; + } + + ++backversions; + + if (temp.rpb_flags & rpb_deleted) + CCH_RELEASE(tdbb, &temp.getWindow(tdbb)); + else + VIO_data(tdbb, &temp, relation->rel_pool); + + temp.rpb_page = rpb->rpb_b_page; + temp.rpb_line = rpb->rpb_b_line; + + break; } + } - ++backversions; + // Re-fetch the record. - if (temp.rpb_flags & rpb_deleted) - CCH_RELEASE(tdbb, &temp.getWindow(tdbb)); - else - VIO_data(tdbb, &temp, relation->rel_pool); + if (!DPM_get(tdbb, rpb, LCK_write)) + return; - temp.rpb_page = rpb->rpb_b_page; - temp.rpb_line = rpb->rpb_b_line; + #ifdef VIO_DEBUG + if (temp2.rpb_b_page != rpb->rpb_b_page || temp.rpb_b_line != rpb->rpb_b_line || + temp.rpb_transaction_nr != rpb->rpb_transaction_nr) + { + VIO_trace(DEBUG_WRITES_INFO, + " record changed!)\n"); + } + #endif - break; + // If the record is in any way suspicious, release the record and give up. + + if (rpb->rpb_b_page != temp2.rpb_b_page || rpb->rpb_b_line != temp2.rpb_b_line || + rpb->rpb_transaction_nr != temp2.rpb_transaction_nr) + { + CCH_RELEASE(tdbb, &rpb->getWindow(tdbb)); + return; } - } - // Re-fetch the record. + // even if the record isn't suspicious, it may have changed a little - if (!DPM_get(tdbb, rpb, LCK_write)) - return; + temp2 = *rpb; + rpb->rpb_undo = old_data; -#ifdef VIO_DEBUG - if (temp2.rpb_b_page != rpb->rpb_b_page || temp.rpb_b_line != rpb->rpb_b_line || - temp.rpb_transaction_nr != rpb->rpb_transaction_nr) - { - VIO_trace(DEBUG_WRITES_INFO, - " record changed!)\n"); - } -#endif + if (rpb->rpb_flags & rpb_delta) + rpb->rpb_prior = data; - // If the record is in any way suspicious, release the record and give up. + // Handle the case of no old version simply. - if (rpb->rpb_b_page != temp2.rpb_b_page || rpb->rpb_b_line != temp2.rpb_b_line || - rpb->rpb_transaction_nr != temp2.rpb_transaction_nr) - { - CCH_RELEASE(tdbb, &rpb->getWindow(tdbb)); - return; - } + if (!rpb->rpb_b_page) + { + if (!(rpb->rpb_flags & rpb_deleted)) + { + DPM_backout_mark(tdbb, rpb, transaction); + + RecordStack empty_staying; + IDX_garbage_collect(tdbb, rpb, going, empty_staying); + BLB_garbage_collect(tdbb, going, empty_staying, rpb->rpb_page, relation); + going.pop(); + + if (!DPM_get(tdbb, rpb, LCK_write)) + { + fb_assert(false); + return; + } + + if (rpb->rpb_b_page != temp2.rpb_b_page || rpb->rpb_b_line != temp2.rpb_b_line || + rpb->rpb_transaction_nr != temp2.rpb_transaction_nr) + { + fb_assert(false); + CCH_RELEASE(tdbb, &rpb->getWindow(tdbb)); + return; + } - // even if the record isn't suspicious, it may have changed a little + fb_assert(rpb->rpb_flags & rpb_gc_active); + rpb->rpb_flags &= ~rpb_gc_active; - temp2 = *rpb; - rpb->rpb_undo = old_data; + temp2 = *rpb; + rpb->rpb_undo = old_data; - if (rpb->rpb_flags & rpb_delta) - rpb->rpb_prior = data; + if (rpb->rpb_flags & rpb_delta) + rpb->rpb_prior = data; + } - // Handle the case of no old version simply. + gcLockGuard.release(); + delete_record(tdbb, rpb, 0, NULL); - if (!rpb->rpb_b_page) - { - if (!(rpb->rpb_flags & rpb_deleted)) + tdbb->bumpRelStats(RuntimeStatistics::RECORD_BACKOUTS, relation->rel_id); + return; + } + + // If both record versions are on the same page, things are a little simpler + + samePage = (rpb->rpb_page == temp.rpb_page && !rpb->rpb_prior); + deleted = (temp2.rpb_flags & rpb_deleted); + + if (!deleted) { DPM_backout_mark(tdbb, rpb, transaction); - RecordStack empty_staying; - IDX_garbage_collect(tdbb, rpb, going, empty_staying); - BLB_garbage_collect(tdbb, going, empty_staying, rpb->rpb_page, relation); - going.pop(); + rpb->rpb_prior = NULL; + list_staying_fast(tdbb, rpb, staying, &temp); + IDX_garbage_collect(tdbb, rpb, going, staying); + BLB_garbage_collect(tdbb, going, staying, rpb->rpb_page, relation); + + if (going.hasData()) + going.pop(); + + clearRecordStack(staying); if (!DPM_get(tdbb, rpb, LCK_write)) { @@ -1014,94 +1066,50 @@ void VIO_backout(thread_db* tdbb, record_param* rpb, const jrd_tra* transaction) } gcLockGuard.release(); - delete_record(tdbb, rpb, 0, NULL); - - tdbb->bumpRelStats(RuntimeStatistics::RECORD_BACKOUTS, relation->rel_id); - return; - } - - // If both record versions are on the same page, things are a little simpler - - samePage = (rpb->rpb_page == temp.rpb_page && !rpb->rpb_prior); - deleted = (temp2.rpb_flags & rpb_deleted); - - if (!deleted) - { - DPM_backout_mark(tdbb, rpb, transaction); - - rpb->rpb_prior = NULL; - list_staying_fast(tdbb, rpb, staying, &temp); - IDX_garbage_collect(tdbb, rpb, going, staying); - BLB_garbage_collect(tdbb, going, staying, rpb->rpb_page, relation); - - if (going.hasData()) - going.pop(); - clearRecordStack(staying); - - if (!DPM_get(tdbb, rpb, LCK_write)) + if (samePage) { - fb_assert(false); - return; - } + DPM_backout(tdbb, rpb); - if (rpb->rpb_b_page != temp2.rpb_b_page || rpb->rpb_b_line != temp2.rpb_b_line || - rpb->rpb_transaction_nr != temp2.rpb_transaction_nr) - { - fb_assert(false); - CCH_RELEASE(tdbb, &rpb->getWindow(tdbb)); - return; + if (!deleted) + delete_tail(tdbb, &temp2, rpb->rpb_page); } - - fb_assert(rpb->rpb_flags & rpb_gc_active); - rpb->rpb_flags &= ~rpb_gc_active; - - temp2 = *rpb; - rpb->rpb_undo = old_data; - - if (rpb->rpb_flags & rpb_delta) - rpb->rpb_prior = data; - } - - gcLockGuard.release(); - - if (samePage) - { - DPM_backout(tdbb, rpb); - - if (!deleted) - delete_tail(tdbb, &temp2, rpb->rpb_page); - } - else - { - // Bring the old version forward. If the outgoing version was deleted, - // there is no garbage collection to be done. - - rpb->rpb_address = temp.rpb_address; - rpb->rpb_length = temp.rpb_length; - rpb->rpb_flags = temp.rpb_flags & rpb_deleted; - if (temp.rpb_prior) - rpb->rpb_flags |= rpb_delta; - rpb->rpb_b_page = temp.rpb_b_page; - rpb->rpb_b_line = temp.rpb_b_line; - rpb->rpb_transaction_nr = temp.rpb_transaction_nr; - rpb->rpb_format_number = temp.rpb_format_number; - - if (deleted) - replace_record(tdbb, rpb, 0, transaction); else { - // There is cleanup to be done. Bring the old version forward first - DPM_update(tdbb, rpb, 0, transaction); - delete_tail(tdbb, &temp2, rpb->rpb_page); - } + // Bring the old version forward. If the outgoing version was deleted, + // there is no garbage collection to be done. + + rpb->rpb_address = temp.rpb_address; + rpb->rpb_length = temp.rpb_length; + rpb->rpb_flags = temp.rpb_flags & rpb_deleted; + if (temp.rpb_prior) + rpb->rpb_flags |= rpb_delta; + rpb->rpb_b_page = temp.rpb_b_page; + rpb->rpb_b_line = temp.rpb_b_line; + rpb->rpb_transaction_nr = temp.rpb_transaction_nr; + rpb->rpb_format_number = temp.rpb_format_number; + + if (deleted) + replace_record(tdbb, rpb, 0, transaction); + else + { + // There is cleanup to be done. Bring the old version forward first + DPM_update(tdbb, rpb, 0, transaction); + delete_tail(tdbb, &temp2, rpb->rpb_page); + } - // Next, delete the old copy of the now current version. + // Next, delete the old copy of the now current version. - if (!DPM_fetch(tdbb, &temp, LCK_write)) - BUGCHECK(291); // msg 291 cannot find record back version + if (!DPM_fetch(tdbb, &temp, LCK_write)) + BUGCHECK(291); // msg 291 cannot find record back version - delete_record(tdbb, &temp, rpb->rpb_page, NULL); + delete_record(tdbb, &temp, rpb->rpb_page, NULL); + } + } + catch (const Exception&) + { + CCH_unwind(tdbb, false); + throw; } tdbb->bumpRelStats(RuntimeStatistics::RECORD_BACKOUTS, relation->rel_id);