diff --git a/src/burp/OdsDetection.epp b/src/burp/OdsDetection.epp index 644521a176..be195bf448 100644 --- a/src/burp/OdsDetection.epp +++ b/src/burp/OdsDetection.epp @@ -61,8 +61,6 @@ namespace } -DATABASE DB = STATIC FILENAME "yachts.lnk"; - #define DB tdgbl->db_handle #define fbTrans tdgbl->tr_handle #define gds_trans tdgbl->tr_handle @@ -70,6 +68,12 @@ DATABASE DB = STATIC FILENAME "yachts.lnk"; #define isc_status (&tdgbl->status_vector) #define gds_status (&tdgbl->status_vector) +// unused +#define fbProvider +#define fbStatus2 + +DATABASE DB = STATIC FILENAME "yachts.lnk"; + void detectRuntimeODS() { diff --git a/src/burp/backup.epp b/src/burp/backup.epp index 927c737447..9098301f1e 100644 --- a/src/burp/backup.epp +++ b/src/burp/backup.epp @@ -72,8 +72,6 @@ using namespace Burp; // GPRE. This is to avoid multiple threading problems with module // level statics. -DATABASE DB = STATIC FILENAME "yachts.lnk" RUNTIME * dbb_file; - #define DB tdgbl->db_handle #define fbTrans tdgbl->tr_handle #define gds_trans tdgbl->tr_handle @@ -81,6 +79,12 @@ DATABASE DB = STATIC FILENAME "yachts.lnk" RUNTIME * dbb_file; #define isc_status (&tdgbl->status_vector) #define gds_status (&tdgbl->status_vector) +// unused +#define fbProvider +#define fbStatus2 + +DATABASE DB = STATIC FILENAME "yachts.lnk" RUNTIME * dbb_file; + namespace // unnamed, private { diff --git a/src/burp/restore.epp b/src/burp/restore.epp index f7f4b8e555..fd72dc696d 100644 --- a/src/burp/restore.epp +++ b/src/burp/restore.epp @@ -73,8 +73,6 @@ using namespace Burp; // GPRE. This is to avoid multiple threading problems with module // level statics. -DATABASE DB = STATIC FILENAME "yachts.lnk"; - #define DB tdgbl->db_handle #define fbTrans tdgbl->tr_handle #define gds_trans tdgbl->tr_handle @@ -82,6 +80,12 @@ DATABASE DB = STATIC FILENAME "yachts.lnk"; #define isc_status (&tdgbl->status_vector) #define gds_status (&tdgbl->status_vector) +// unused +#define fbProvider +#define fbStatus2 + +DATABASE DB = STATIC FILENAME "yachts.lnk"; + namespace // unnamed, private { @@ -1682,16 +1686,22 @@ void get_array(BurpGlobals* tdgbl, burp_rel* relation, UCHAR* record_buffer) burp_fld* field = NULL; FbLocalStatus status_vector; USHORT count, field_number, field_length = 0; - UCHAR* buffer = NULL; + UCHAR** buffer = NULL; UCHAR* p = NULL; UCHAR blr_buffer[200]; // enough for a sdl with 16 dimensions lstring xdr_slice; - // don't free something you don't allocate lstring xdr_buffer; xdr_buffer.lstr_allocated = 0; xdr_buffer.lstr_address = NULL; + Firebird::Cleanup datClean( [&] { + if (buffer && *buffer) + BURP_free(*buffer); + if (tdgbl->gbl_sw_transportable && xdr_buffer.lstr_allocated) + BURP_free(xdr_buffer.lstr_address); + } ); + // Pick up attributes SLONG fld_ranges[2 * MAX_DIMENSION]; SLONG slice_length = 0; @@ -1990,7 +2000,6 @@ void get_array(BurpGlobals* tdgbl, burp_rel* relation, UCHAR* record_buffer) if (data_at == 0) { - buffer = BURP_alloc (return_length); SLONG lcount = 0; if (tdgbl->gbl_sw_transportable) { @@ -2005,19 +2014,23 @@ void get_array(BurpGlobals* tdgbl, burp_rel* relation, UCHAR* record_buffer) lcount |= get(tdgbl) << 8; lcount |= get(tdgbl) << 16; lcount |= get(tdgbl) << 24; + xdr_buffer.lstr_length = xdr_buffer.lstr_allocated = lcount; - xdr_buffer.lstr_address = BURP_alloc(lcount); + p = xdr_buffer.lstr_address = BURP_alloc(lcount); + xdr_slice.lstr_allocated = xdr_slice.lstr_length = return_length; - xdr_slice.lstr_address = buffer; - p = xdr_buffer.lstr_address; + buffer = &xdr_slice.lstr_address; } } else { - p = buffer; + buffer = &p; lcount = return_length; } + fb_assert(buffer); + *buffer = BURP_alloc(return_length); + if (lcount) get_block(tdgbl, p, lcount); @@ -2027,7 +2040,7 @@ void get_array(BurpGlobals* tdgbl, burp_rel* relation, UCHAR* record_buffer) DB->putSlice(&status_vector, gds_trans, blob_id, blr_length, blr_buffer, 0, NULL, // parameters for subset of an array handling - elements_written * field->fld_length, buffer + data_at); + elements_written * field->fld_length, (*buffer) + data_at); if (status_vector->hasData()) { BURP_print (false, 81, field->fld_name); @@ -2116,7 +2129,6 @@ void get_array(BurpGlobals* tdgbl, burp_rel* relation, UCHAR* record_buffer) const USHORT blr_length = blr - blr_buffer; - buffer = BURP_alloc (return_length); SLONG lcount = 0; if (tdgbl->gbl_sw_transportable) { @@ -2132,18 +2144,21 @@ void get_array(BurpGlobals* tdgbl, burp_rel* relation, UCHAR* record_buffer) xdr_buffer.lstr_allocated |= get(tdgbl) << 16; xdr_buffer.lstr_allocated |= get(tdgbl) << 24; lcount = xdr_buffer.lstr_length = xdr_buffer.lstr_allocated; - xdr_buffer.lstr_address = BURP_alloc (xdr_buffer.lstr_allocated); + p = xdr_buffer.lstr_address = BURP_alloc(xdr_buffer.lstr_allocated); + xdr_slice.lstr_allocated = xdr_slice.lstr_length = return_length; - xdr_slice.lstr_address = buffer; - p = xdr_buffer.lstr_address; + buffer = &xdr_slice.lstr_address; } } else { - p = buffer; + buffer = &p; lcount = return_length; } + fb_assert(buffer); + *buffer = BURP_alloc (return_length); + if (lcount) get_block(tdgbl, p, lcount); @@ -2153,7 +2168,7 @@ void get_array(BurpGlobals* tdgbl, burp_rel* relation, UCHAR* record_buffer) DB->putSlice(&status_vector, gds_trans, blob_id, blr_length, blr_buffer, 0, NULL, // parameters for subset of an array handling - return_length, buffer); + return_length, *buffer); if (status_vector->hasData()) { BURP_print (false, 81, field->fld_name); @@ -2165,10 +2180,6 @@ void get_array(BurpGlobals* tdgbl, burp_rel* relation, UCHAR* record_buffer) return; } } - - BURP_free (buffer); - if (tdgbl->gbl_sw_transportable && xdr_buffer.lstr_allocated) - BURP_free (xdr_buffer.lstr_address); } @@ -3199,9 +3210,13 @@ void get_data(BurpGlobals* tdgbl, burp_rel* relation, WriteRelationReq* req) lstring data; data.lstr_allocated = 0; data.lstr_address = NULL; - Array dataBuffer; - RCRD_LENGTH old_length = 0; + Firebird::Cleanup datClean( [&] { + if (data.lstr_address) + BURP_free(data.lstr_address); + } ); + + ULONG old_length = 0; IBatch* batch = req->getBatch(); UCHAR* sql = batch ? sql = req->getBatchMsgData() : nullptr; tdgbl->batchInlineBlobLimit = batch ? req->getBatchInlineBlobLimit() : 0; @@ -3247,7 +3262,9 @@ void get_data(BurpGlobals* tdgbl, burp_rel* relation, WriteRelationReq* req) if (len > data.lstr_allocated) { data.lstr_allocated = len; - data.lstr_address = dataBuffer.getBuffer(data.lstr_allocated); + if (data.lstr_address) + BURP_free (data.lstr_address); + data.lstr_address = BURP_alloc(data.lstr_allocated); } p = data.lstr_address; } diff --git a/src/common/classes/auto.h b/src/common/classes/auto.h index 5c7b3f59da..785cc8364f 100644 --- a/src/common/classes/auto.h +++ b/src/common/classes/auto.h @@ -32,6 +32,7 @@ #define CLASSES_AUTO_PTR_H #include +#include namespace Firebird { @@ -317,6 +318,22 @@ private: }; +class Cleanup +{ +public: + Cleanup(std::function clFunc) + : clean(clFunc) + { } + + ~Cleanup() + { + clean(); + } + +private: + std::function clean; +}; + } //namespace Firebird #endif // CLASSES_AUTO_PTR_H diff --git a/src/common/classes/init.cpp b/src/common/classes/init.cpp index fa1c998395..ce1ae272f1 100644 --- a/src/common/classes/init.cpp +++ b/src/common/classes/init.cpp @@ -27,6 +27,7 @@ #include "firebird.h" #include "init.h" #include "alloc.h" +#include "auto.h" #include "../common/SimpleStatusVector.h" #include "../common/dllinst.h" #include "../common/os/os_utils.h" @@ -142,17 +143,10 @@ namespace } #ifndef DEBUG_INIT - // This class with it's single instance ensures global cleanup - class Cleanup - { - public: - ~Cleanup() - { - allClean(); - } - }; - Cleanup global; + // This instance ensures dtors run when program exits + Firebird::Cleanup global(allClean); + #endif //DEBUG_INIT void init() diff --git a/src/gpre/obj_cxx.cpp b/src/gpre/obj_cxx.cpp index 6a41b7d449..5486a902bc 100644 --- a/src/gpre/obj_cxx.cpp +++ b/src/gpre/obj_cxx.cpp @@ -1413,6 +1413,16 @@ static int gen_cursor_open( const act* action, const gpre_req* request, int colu // Generate insertion text for the database statement. // +static void ifndef(const char* nm, const char* nm2 = "") +{ + fprintf(gpreGlob.out_file, "\n#ifndef %s%s", nm, nm2); +} + +static void endif() +{ + fprintf(gpreGlob.out_file, "\n#endif"); +} + static void gen_database(int column) { if (global_first_flag) @@ -1427,8 +1437,10 @@ static void gen_database(int column) fprintf(gpreGlob.out_file, "#define CAST_CONST_MSG(A) (reinterpret_cast(A))\n"); fprintf(gpreGlob.out_file, "#define CAST_MSG(A) (reinterpret_cast(A))\n"); + ifndef("fbBlobNull"); printa(column, "static %sISC_QUAD", CONST_STR); printa(column + INDENT, "fbBlobNull = {0, 0};\t/* initializer for blobs */"); + endif(); const TEXT* scope = ""; @@ -1440,6 +1452,8 @@ static void gen_database(int column) { all_static = all_static && (db->dbb_scope == DBB_STATIC); all_extern = all_extern && (db->dbb_scope == DBB_EXTERN); + + ifndef(db->dbb_name->sym_string); if (db->dbb_scope == DBB_STATIC) scope = "static "; else if (db->dbb_scope == DBB_EXTERN) @@ -1449,6 +1463,7 @@ static void gen_database(int column) printa(column + INDENT, "%s = 0;\t\t/* database handle */\n", db->dbb_name->sym_string); else printa(column + INDENT, "%s;\t\t/* database handle */\n", db->dbb_name->sym_string); + endif(); } if (all_static) @@ -1456,6 +1471,7 @@ static void gen_database(int column) else if (all_extern) scope = "extern "; + ifndef(gpreGlob.transaction_name); printa(column, "%sFirebird::ITransaction*", scope); if (!all_extern) printa(column + INDENT, "%s = 0;\t\t/* default transaction handle */", @@ -1463,33 +1479,43 @@ static void gen_database(int column) else printa(column + INDENT, "%s;\t\t/* default transaction handle */", gpreGlob.transaction_name); + endif(); + ifndef("fbMaster"); printa(column, "%sFirebird::IMaster* fbMaster%s;\t\t/* master interface */", scope, all_extern ? "" : " = Firebird::fb_get_master_interface()"); + endif(); + + ifndef("fbProvider"); printa(column, "%sFirebird::IProvider* fbProvider%s;\t\t/* provider interface */", scope, all_extern ? "" : " = fbMaster->getDispatcher()"); + endif(); + ifndef(global_status_name); printa(column, "%sFirebird::CheckStatusWrapper %sObj%s;\t/* status vector */", scope, global_status_name, all_extern ? "" : "(fbMaster->getStatus())"); - printa(column, "%sFirebird::CheckStatusWrapper %s2Obj%s;\t/* status vector */", - scope, global_status_name, all_extern ? "" : "(fbMaster->getStatus())"); - if (all_extern) - { printa(column, "%sFirebird::CheckStatusWrapper* %s;\t/* status vector */", scope, global_status_name); - printa(column, "%sFirebird::CheckStatusWrapper* %s2;\t/* status vector */", - scope, global_status_name); - } else - { printa(column, "%sFirebird::CheckStatusWrapper* %s = &%sObj;\t/* status vector */", scope, global_status_name, global_status_name); + endif(); + + ifndef(global_status_name, "2"); + printa(column, "%sFirebird::CheckStatusWrapper %s2Obj%s;\t/* status vector */", + scope, global_status_name, all_extern ? "" : "(fbMaster->getStatus())"); + if (all_extern) + printa(column, "%sFirebird::CheckStatusWrapper* %s2;\t/* status vector */", + scope, global_status_name); + else printa(column, "%sFirebird::CheckStatusWrapper* %s2 = &%s2Obj;\t/* status vector */", scope, global_status_name, global_status_name); - } + endif(); + ifndef("fbIStatus"); printa(column, "%sint fbIStatus;\t/* last completion code */", scope); + endif(); for (db = gpreGlob.isc_databases; db; db = db->dbb_next) for (const tpb* tpb_iterator = db->dbb_tpbs; tpb_iterator; diff --git a/src/jrd/Database.cpp b/src/jrd/Database.cpp index 70107785f4..c5678c94e9 100644 --- a/src/jrd/Database.cpp +++ b/src/jrd/Database.cpp @@ -493,6 +493,15 @@ namespace Jrd // Database::GlobalObjectHolder class implementation + int Database::GlobalObjectHolder::release() const + { + // Release should be executed under g_mutex protection + // in order to modify reference counter & hash table atomically + MutexLockGuard guard(g_mutex, FB_FUNCTION); + + return RefCounted::release(); + } + Database::GlobalObjectHolder* Database::GlobalObjectHolder::init(const string& id, const PathName& filename, RefPtr config) @@ -512,17 +521,18 @@ namespace Jrd Database::GlobalObjectHolder::~GlobalObjectHolder() { - // here we cleanup what should not be globally protected - if (m_replMgr) - m_replMgr->shutdown(); - - MutexLockGuard guard(g_mutex, FB_FUNCTION); - + // dtor is executed under g_mutex protection Database::GlobalObjectHolder::DbId* entry = g_hashTable->lookup(m_id); if (!g_hashTable->remove(m_id)) fb_assert(false); - // these objects should be deleted under g_mutex protection + { // scope + // here we cleanup what should not be globally protected + MutexUnlockGuard guard(g_mutex, FB_FUNCTION); + if (m_replMgr) + m_replMgr->shutdown(); + } + m_lockMgr = nullptr; m_eventMgr = nullptr; m_replMgr = nullptr; diff --git a/src/jrd/Database.h b/src/jrd/Database.h index 64a46534ce..69045bd7bc 100644 --- a/src/jrd/Database.h +++ b/src/jrd/Database.h @@ -306,6 +306,8 @@ class Database : public pool_alloc const Firebird::PathName& filename, Firebird::RefPtr config); + int release() const override; + ~GlobalObjectHolder(); LockManager* getLockManager(); diff --git a/src/jrd/ThreadCollect.h b/src/jrd/ThreadCollect.h new file mode 100644 index 0000000000..6969415ad8 --- /dev/null +++ b/src/jrd/ThreadCollect.h @@ -0,0 +1,138 @@ +/* + * PROGRAM: JRD threading support + * MODULE: ThreadCollect.h + * DESCRIPTION: Threads' group completion handling + * + * The contents of this file are subject to the Initial + * Developer's 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.firebirdsql.org/en/initial-developer-s-public-license-version-1-0/ + * + * Software distributed under the License is distributed AS IS, + * 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 Alexander Peshkov + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2018, 2022 Alexander Peshkov + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + * + * + */ + +#ifndef JRD_THREADCOLLECT_H +#define JRD_THREADCOLLECT_H + +#include "../common/ThreadStart.h" +#include "../common/classes/alloc.h" +#include "../common/classes/array.h" +#include "../common/classes/locks.h" + + +namespace Jrd { + + class ThreadCollect + { + public: + ThreadCollect(MemoryPool& p) + : threads(p) + { } + + void join() + { + if (!threads.hasData()) + return; + + waitFor(threads); + } + + void ending(Thread::Handle& h) + { + // put thread into completion wait queue when it finished running + Firebird::MutexLockGuard g(threadsMutex, FB_FUNCTION); + + for (unsigned n = 0; n < threads.getCount(); ++n) + { + if (threads[n].hndl == h) + { + threads[n].ending = true; + return; + } + } + + Thrd t = {h, true}; + threads.add(t); + } + + void running(Thread::Handle& h) + { + // put thread into completion wait queue when it starts running + Firebird::MutexLockGuard g(threadsMutex, FB_FUNCTION); + + Thrd t = {h, false}; + threads.add(t); + } + + void houseKeeping() + { + if (!threads.hasData()) + return; + + // join finished threads + AllThreads t; + { // mutex scope + Firebird::MutexLockGuard g(threadsMutex, FB_FUNCTION); + + for (unsigned n = 0; n < threads.getCount(); ) + { + if (threads[n].ending) + { + t.add(threads[n]); + threads.remove(n); + } + else + ++n; + } + } + + waitFor(t); + } + + private: + struct Thrd + { + Thread::Handle hndl; + bool ending; + }; + typedef Firebird::HalfStaticArray AllThreads; + + void waitFor(AllThreads& thr) + { + Firebird::MutexLockGuard g(threadsMutex, FB_FUNCTION); + while (thr.hasData()) + { + FB_SIZE_T n = thr.getCount() - 1; + Thrd& t = thr[n]; + { + Firebird::MutexUnlockGuard u(threadsMutex, FB_FUNCTION); + Thread::waitForCompletion(t.hndl); + fb_assert(t.ending); + } + thr.remove(n); + } + } + + AllThreads threads; + Firebird::Mutex threadsMutex; + }; + +} // namespace Jrd + + +#endif // JRD_THREADCOLLECT_H diff --git a/src/jrd/build_no.h b/src/jrd/build_no.h index f656750555..b61968805e 100644 --- a/src/jrd/build_no.h +++ b/src/jrd/build_no.h @@ -3,16 +3,16 @@ *** DO NOT EDIT *** TO CHANGE ANY INFORMATION IN HERE PLEASE EDIT src/misc/writeBuildNum.sh - FORMAL BUILD NUMBER:501 + FORMAL BUILD NUMBER:516 */ -#define PRODUCT_VER_STRING "5.0.0.501" -#define FILE_VER_STRING "WI-T5.0.0.501" -#define LICENSE_VER_STRING "WI-T5.0.0.501" -#define FILE_VER_NUMBER 5, 0, 0, 501 +#define PRODUCT_VER_STRING "5.0.0.516" +#define FILE_VER_STRING "WI-T5.0.0.516" +#define LICENSE_VER_STRING "WI-T5.0.0.516" +#define FILE_VER_NUMBER 5, 0, 0, 516 #define FB_MAJOR_VER "5" #define FB_MINOR_VER "0" #define FB_REV_NO "0" -#define FB_BUILD_NO "501" +#define FB_BUILD_NO "516" #define FB_BUILD_TYPE "T" #define FB_BUILD_SUFFIX "Firebird 5.0 Initial" diff --git a/src/jrd/jrd.cpp b/src/jrd/jrd.cpp index 631e2e6b95..fdfc5e224e 100644 --- a/src/jrd/jrd.cpp +++ b/src/jrd/jrd.cpp @@ -110,6 +110,7 @@ #include "../yvalve/why_proto.h" #include "../jrd/flags.h" #include "../jrd/Mapping.h" +#include "../jrd/ThreadCollect.h" #include "../jrd/Database.h" #include "../jrd/WorkerAttachment.h" @@ -127,6 +128,7 @@ #include "../common/classes/ClumpletWriter.h" #include "../common/classes/RefMutex.h" #include "../common/classes/ParsedList.h" +#include "../common/classes/semaphore.h" #include "../common/utils_proto.h" #include "../jrd/DebugInterface.h" #include "../jrd/CryptoManager.h" @@ -475,6 +477,16 @@ namespace { using Jrd::Attachment; + // Required to sync attachment shutdown threads with provider shutdown + GlobalPtr shutThreadCollect; + + struct AttShutParams + { + Semaphore thdStartedSem; + Thread::Handle thrHandle; + AttachmentsRefHolder* attachments; + }; + // Flag engineShutdown guarantees that no new attachment is created after setting it // and helps avoid more than 1 shutdown threads running simultaneously. bool engineShutdown = false; @@ -1329,7 +1341,7 @@ static JAttachment* initAttachment(thread_db*, const PathName&, const PathName&, const DatabaseOptions&, RefMutexUnlock&, IPluginConfig*, JProvider*); static JAttachment* create_attachment(const PathName&, Database*, JProvider* provider, const DatabaseOptions&, bool newDb); static void prepare_tra(thread_db*, jrd_tra*, USHORT, const UCHAR*); -static void release_attachment(thread_db*, Attachment*); +static void release_attachment(thread_db*, Attachment*, XThreadEnsureUnlock* = nullptr); static void start_transaction(thread_db* tdbb, bool transliterate, jrd_tra** tra_handle, Jrd::Attachment* attachment, unsigned int tpb_length, const UCHAR* tpb); static void rollback(thread_db*, jrd_tra*, const bool); @@ -1541,7 +1553,7 @@ static void trace_warning(thread_db* tdbb, FbStatusVector* userStatus, const cha static void trace_failed_attach(TraceManager* traceManager, const char* filename, - const DatabaseOptions& options, bool create, FbStatusVector* status) + const DatabaseOptions& options, bool create, FbStatusVector* status, ICryptKeyCallback* callback) { // Report to Trace API that attachment has not been created const char* origFilename = filename; @@ -1556,15 +1568,24 @@ static void trace_failed_attach(TraceManager* traceManager, const char* filename ITracePlugin::RESULT_UNAUTHORIZED : ITracePlugin::RESULT_FAILED; const char* func = create ? "JProvider::createDatabase" : "JProvider::attachDatabase"; + Attachment* att = traceManager ? traceManager->getAttachment() : nullptr; + if (traceManager && (! traceManager->isActive())) + traceManager = nullptr; + if (!traceManager) { - TraceManager tempMgr(origFilename); + if (!options.dpb_map_attach) + { + EngineCheckout guard(att, FB_FUNCTION, true); - if (tempMgr.needs(ITraceFactory::TRACE_EVENT_ATTACH)) - tempMgr.event_attach(&conn, create, result); + TraceManager tempMgr(origFilename, callback); - if (tempMgr.needs(ITraceFactory::TRACE_EVENT_ERROR)) - tempMgr.event_error(&conn, &traceStatus, func); + if (tempMgr.needs(ITraceFactory::TRACE_EVENT_ATTACH)) + tempMgr.event_attach(&conn, create, result); + + if (tempMgr.needs(ITraceFactory::TRACE_EVENT_ERROR)) + tempMgr.event_error(&conn, &traceStatus, func); + } } else { @@ -1696,7 +1717,7 @@ JAttachment* JProvider::internalAttach(CheckStatusWrapper* user_status, const ch catch (const Exception& ex) { ex.stuffException(user_status); - trace_failed_attach(NULL, filename, options, false, user_status); + trace_failed_attach(NULL, filename, options, false, user_status, cryptCallback); throw; } @@ -1704,7 +1725,7 @@ JAttachment* JProvider::internalAttach(CheckStatusWrapper* user_status, const ch const VdnResult vdn = verifyDatabaseName(expanded_name, tdbb->tdbb_status_vector, is_alias); if (!is_alias && vdn == VDN_FAIL) { - trace_failed_attach(NULL, filename, options, false, tdbb->tdbb_status_vector); + trace_failed_attach(NULL, filename, options, false, tdbb->tdbb_status_vector, cryptCallback); status_exception::raise(tdbb->tdbb_status_vector); } @@ -2275,9 +2296,8 @@ JAttachment* JProvider::internalAttach(CheckStatusWrapper* user_status, const ch } else { - trace_failed_attach(attachment && attachment->att_trace_manager && - attachment->att_trace_manager->isActive() ? attachment->att_trace_manager : NULL, - filename, options, false, user_status); + trace_failed_attach(attachment ? attachment->att_trace_manager : NULL, + filename, options, false, user_status, cryptCallback); } mapping.clearMainHandle(); @@ -2878,7 +2898,7 @@ JAttachment* JProvider::createDatabase(CheckStatusWrapper* user_status, const ch catch (const Exception& ex) { ex.stuffException(user_status); - trace_failed_attach(NULL, filename, options, true, user_status); + trace_failed_attach(NULL, filename, options, true, user_status, cryptCallback); throw; } @@ -2886,7 +2906,7 @@ JAttachment* JProvider::createDatabase(CheckStatusWrapper* user_status, const ch const VdnResult vdn = verifyDatabaseName(expanded_name, tdbb->tdbb_status_vector, is_alias); if (!is_alias && vdn == VDN_FAIL) { - trace_failed_attach(NULL, filename, options, true, tdbb->tdbb_status_vector); + trace_failed_attach(NULL, filename, options, true, tdbb->tdbb_status_vector, cryptCallback); status_exception::raise(tdbb->tdbb_status_vector); } @@ -3213,9 +3233,8 @@ JAttachment* JProvider::createDatabase(CheckStatusWrapper* user_status, const ch catch (const Exception& ex) { ex.stuffException(user_status); - trace_failed_attach(attachment && attachment->att_trace_manager && - attachment->att_trace_manager->isActive() ? attachment->att_trace_manager : NULL, - filename, options, true, user_status); + trace_failed_attach(attachment ? attachment->att_trace_manager : NULL, + filename, options, true, user_status, cryptCallback); mapping.clearMainHandle(); unwindAttach(tdbb, ex, user_status, false); @@ -3425,6 +3444,7 @@ void JAttachment::internalDropDatabase(CheckStatusWrapper* user_status) // Prepare to set ODS to 0 WIN window(HEADER_PAGE_NUMBER); Ods::header_page* header = NULL; + XThreadEnsureUnlock threadGuard(dbb->dbb_thread_mutex, FB_FUNCTION); try { @@ -3450,6 +3470,13 @@ void JAttachment::internalDropDatabase(CheckStatusWrapper* user_status) ERR_post(Arg::Gds(isc_att_shutdown)); } + // try to block special threads before taking exclusive lock on database + if (!threadGuard.tryEnter()) + { + ERR_post(Arg::Gds(isc_no_meta_update) << + Arg::Gds(isc_obj_in_use) << Arg::Str("DATABASE")); + } + if (!CCH_exclusive(tdbb, LCK_PW, WAIT_PERIOD, NULL)) { ERR_post(Arg::Gds(isc_lock_timeout) << @@ -3505,7 +3532,7 @@ void JAttachment::internalDropDatabase(CheckStatusWrapper* user_status) } // Unlink attachment from database - release_attachment(tdbb, attachment); + release_attachment(tdbb, attachment, &threadGuard); att = NULL; attachment = NULL; guard.leave(); @@ -4581,62 +4608,66 @@ void JProvider::shutdown(CheckStatusWrapper* status, unsigned int timeout, const **************************************/ try { - MutexLockGuard guard(shutdownMutex, FB_FUNCTION); - - if (engineShutdown) - { - return; - } { // scope - MutexLockGuard guard(newAttachmentMutex, FB_FUNCTION); - engineShutdown = true; + MutexLockGuard guard(shutdownMutex, FB_FUNCTION); + + if (engineShutdown) + { + return; + } + { // scope + MutexLockGuard guard(newAttachmentMutex, FB_FUNCTION); + engineShutdown = true; + } + + ThreadContextHolder tdbb; + WorkerAttachment::shutdown(); + EDS::Manager::shutdown(); + + ULONG attach_count, database_count, svc_count; + JRD_enum_attachments(NULL, attach_count, database_count, svc_count); + + if (attach_count > 0 || svc_count > 0) + { + gds__log("Shutting down the server with %d active connection(s) to %d database(s), " + "%d active service(s)", + attach_count, database_count, svc_count); + } + + if (reason == fb_shutrsn_exit_called) + { + // Starting threads may fail when task is going to close. + // This happens at least with some microsoft C runtimes. + // If people wish to have timeout, they should better call fb_shutdown() themselves. + // Therefore: + timeout = 0; + } + + if (timeout) + { + Semaphore shutdown_semaphore; + + Thread::Handle h; + Thread::start(shutdown_thread, &shutdown_semaphore, THREAD_medium, &h); + + if (!shutdown_semaphore.tryEnter(0, timeout)) + waitForShutdown(shutdown_semaphore); + + Thread::waitForCompletion(h); + } + else + { + shutdown_thread(NULL); + } + + // Do not put it into separate shutdown thread - during shutdown of TraceManager + // PluginManager wants to lock a mutex, which is sometimes already locked in current thread + TraceManager::shutdown(); + Mapping::shutdownIpc(); } - ThreadContextHolder tdbb; - - WorkerAttachment::shutdown(); - EDS::Manager::shutdown(); - - ULONG attach_count, database_count, svc_count; - JRD_enum_attachments(NULL, attach_count, database_count, svc_count); - - if (attach_count > 0 || svc_count > 0) - { - gds__log("Shutting down the server with %d active connection(s) to %d database(s), " - "%d active service(s)", - attach_count, database_count, svc_count); - } - - if (reason == fb_shutrsn_exit_called) - { - // Starting threads may fail when task is going to close. - // This happens at least with some microsoft C runtimes. - // If people wish to have timeout, they should better call fb_shutdown() themselves. - // Therefore: - timeout = 0; - } - - if (timeout) - { - Semaphore shutdown_semaphore; - - Thread::Handle h; - Thread::start(shutdown_thread, &shutdown_semaphore, THREAD_medium, &h); - - if (!shutdown_semaphore.tryEnter(0, timeout)) - waitForShutdown(shutdown_semaphore); - - Thread::waitForCompletion(h); - } - else - { - shutdown_thread(NULL); - } - - // Do not put it into separate shutdown thread - during shutdown of TraceManager - // PluginManager wants to lock a mutex, which is sometimes already locked in current thread - TraceManager::shutdown(); - Mapping::shutdownIpc(); + // Wait for completion of all attacment shutdown threads + shutThreadCollect->join(); } catch (const Exception& ex) { @@ -7605,7 +7636,7 @@ static void prepare_tra(thread_db* tdbb, jrd_tra* transaction, USHORT length, co } -void release_attachment(thread_db* tdbb, Jrd::Attachment* attachment) +void release_attachment(thread_db* tdbb, Jrd::Attachment* attachment, XThreadEnsureUnlock* dropGuard) { /************************************** * @@ -7686,8 +7717,14 @@ void release_attachment(thread_db* tdbb, Jrd::Attachment* attachment) Sync sync(&dbb->dbb_sync, "jrd.cpp: release_attachment"); // avoid races with special threads + // take into an account lock earlier taken in DROP DATABASE XThreadEnsureUnlock threadGuard(dbb->dbb_thread_mutex, FB_FUNCTION); - threadGuard.enter(); + XThreadEnsureUnlock* activeThreadGuard = dropGuard; + if (!activeThreadGuard) + { + threadGuard.enter(); + activeThreadGuard = &threadGuard; + } sync.lock(SYNC_EXCLUSIVE); @@ -7721,7 +7758,7 @@ void release_attachment(thread_db* tdbb, Jrd::Attachment* attachment) } // Notify special threads - threadGuard.leave(); + activeThreadGuard->leave(); // Sync with special threads if (!other) @@ -8725,22 +8762,26 @@ namespace ThreadModuleRef thdRef(attachmentShutdownThread, &engineShutdown); #endif + AttShutParams* params = static_cast(arg); + AttachmentsRefHolder* attachments = params->attachments; + Thread::Handle th = params->thrHandle; + fb_assert(th); + try { - MutexLockGuard guard(shutdownMutex, FB_FUNCTION); - if (engineShutdown) - { - // Shutdown was done, all attachmnets are gone - return 0; - } + shutThreadCollect->running(th); + params->thdStartedSem.release(); - shutdownAttachments(static_cast(arg), isc_att_shut_db_down); + MutexLockGuard guard(shutdownMutex, FB_FUNCTION); + if (!engineShutdown) + shutdownAttachments(attachments, isc_att_shut_db_down); } catch (const Exception& ex) { iscLogException("attachmentShutdownThread", ex); } + shutThreadCollect->ending(th); return 0; } } // anonymous namespace @@ -9527,13 +9568,18 @@ void JRD_shutdown_attachment(Attachment* attachment) fb_assert(attachment->att_flags & ATT_shutdown); MemoryPool& pool = *getDefaultMemoryPool(); - AttachmentsRefHolder* queue = FB_NEW_POOL(pool) AttachmentsRefHolder(pool); + AutoPtr queue(FB_NEW_POOL(pool) AttachmentsRefHolder(pool)); fb_assert(attachment->getStable()); attachment->getStable()->addRef(); queue->add(attachment->getStable()); - Thread::start(attachmentShutdownThread, queue, THREAD_high); + AttShutParams params; + params.attachments = queue; + Thread::start(attachmentShutdownThread, ¶ms, THREAD_high, ¶ms.thrHandle); + queue.release(); + shutThreadCollect->houseKeeping(); + params.thdStartedSem.enter(); } catch (const Exception&) {} // no-op @@ -9582,7 +9628,14 @@ void JRD_shutdown_attachments(Database* dbb) } if (queue.hasData()) - Thread::start(attachmentShutdownThread, queue.release(), THREAD_high); + { + AttShutParams params; + params.attachments = queue; + Thread::start(attachmentShutdownThread, ¶ms, THREAD_high, ¶ms.thrHandle); + queue.release(); + shutThreadCollect->houseKeeping(); + params.thdStartedSem.enter(); + } } catch (const Exception&) {} // no-op diff --git a/src/jrd/jrd.h b/src/jrd/jrd.h index 5818ce3efb..bdbefcd55a 100644 --- a/src/jrd/jrd.h +++ b/src/jrd/jrd.h @@ -1092,10 +1092,10 @@ namespace Jrd { m_ref->getSync()->leave(); } - EngineCheckout(Attachment* att, const char* from) + EngineCheckout(Attachment* att, const char* from, bool optional = false) : m_tdbb(nullptr), m_from(from) { - fb_assert(att); + fb_assert(optional || att); if (att && att->att_use_count) { diff --git a/src/jrd/sort.cpp b/src/jrd/sort.cpp index ac291d571e..502bd3b2e3 100644 --- a/src/jrd/sort.cpp +++ b/src/jrd/sort.cpp @@ -200,7 +200,7 @@ Sort::Sort(Database* dbb, m_longs = record_size >> SHIFTLONG; m_min_alloc_size = record_size * MIN_RECORDS_TO_ALLOC; - m_max_alloc_size = MAX(record_size * MIN_RECORDS_TO_ALLOC, MAX_SORT_BUFFER_SIZE); + m_max_alloc_size = MAX(m_min_alloc_size, MAX_SORT_BUFFER_SIZE); m_dup_callback = call_back; m_dup_callback_arg = user_arg; @@ -619,6 +619,7 @@ void Sort::allocateBuffer(MemoryPool& pool) // The sort buffer cache has at least one big block, let's use it m_size_memory = MAX_SORT_BUFFER_SIZE; m_memory = m_dbb->dbb_sort_buffers.pop(); + m_flags |= scb_reuse_buffer; return; } } @@ -635,6 +636,10 @@ void Sort::allocateBuffer(MemoryPool& pool) { m_size_memory = m_max_alloc_size; m_memory = FB_NEW_POOL(*m_dbb->dbb_permanent) UCHAR[m_size_memory]; + + // Mark the buffer as cacheable for future reuse + if (m_size_memory == MAX_SORT_BUFFER_SIZE) + m_flags |= scb_reuse_buffer; } catch (const BadAlloc&) { @@ -646,6 +651,7 @@ void Sort::allocateBuffer(MemoryPool& pool) { m_size_memory /= 2; m_memory = FB_NEW_POOL(pool) UCHAR[m_size_memory]; + m_flags &= ~scb_reuse_buffer; break; } catch (const BadAlloc&) @@ -666,13 +672,17 @@ void Sort::releaseBuffer() SyncLockGuard guard(&m_dbb->dbb_sortbuf_sync, SYNC_EXCLUSIVE, "Sort::releaseBuffer"); - if (m_size_memory == MAX_SORT_BUFFER_SIZE && + if ((m_flags & scb_reuse_buffer) && m_dbb->dbb_sort_buffers.getCount() < MAX_CACHED_SORT_BUFFERS) { + fb_assert(m_size_memory == MAX_SORT_BUFFER_SIZE); + m_dbb->dbb_sort_buffers.push(m_memory); } else delete[] m_memory; + + m_flags &= ~scb_reuse_buffer; } diff --git a/src/jrd/sort.h b/src/jrd/sort.h index a0dde8bc3b..a2fd9b6a21 100644 --- a/src/jrd/sort.h +++ b/src/jrd/sort.h @@ -270,7 +270,8 @@ typedef bool (*FPTR_REJECT_DUP_CALLBACK)(const UCHAR*, const UCHAR*, void*); // flags as set in m_flags -const int scb_sorted = 1; // stream has been sorted +const int scb_sorted = 1; // stream has been sorted +const int scb_reuse_buffer = 2; // reuse buffer if possible class Sort { diff --git a/src/jrd/svc.cpp b/src/jrd/svc.cpp index 93d8f0caec..e2819d1aa9 100644 --- a/src/jrd/svc.cpp +++ b/src/jrd/svc.cpp @@ -74,6 +74,7 @@ #include "../utilities/nbackup/nbkswi.h" #include "../jrd/trace/traceswi.h" #include "../jrd/val_proto.h" +#include "../jrd/ThreadCollect.h" // Service threads #include "../burp/burp_proto.h" @@ -120,6 +121,7 @@ int main_gstat(Firebird::UtilSvc* uSvc); using namespace Firebird; +using namespace Jrd; const int SVC_user_dba = 2; const int SVC_user_any = 1; @@ -138,64 +140,9 @@ namespace { GlobalPtr globalServicesMutex; // All that we need to shutdown service threads when shutdown in progress - typedef Array AllServices; + typedef Array AllServices; GlobalPtr allServices; // protected by globalServicesMutex volatile bool svcShutdown = false; - - class ThreadCollect - { - public: - ThreadCollect(MemoryPool& p) - : threads(p) - { } - - void join() - { - // join threads to be sure they are gone when shutdown is complete - // no need locking something cause this is expected to run when services are closing - waitFor(threads); - } - - void add(const Thread::Handle& h) - { - // put thread into completion wait queue when it finished running - MutexLockGuard g(threadsMutex, FB_FUNCTION); - fb_assert(h); - threads.add(h); - } - - void houseKeeping() - { - if (!threads.hasData()) - return; - - // join finished threads - AllThreads t; - { // mutex scope - MutexLockGuard g(threadsMutex, FB_FUNCTION); - t.assign(threads); - threads.clear(); - } - - waitFor(t); - } - - private: - typedef Array AllThreads; - - static void waitFor(AllThreads& thr) - { - while (thr.hasData()) - { - Thread::Handle h(thr.pop()); - Thread::waitForCompletion(h); - } - } - - AllThreads threads; - Mutex threadsMutex; - }; - GlobalPtr threadCollect; void spbVersionError() @@ -207,8 +154,6 @@ namespace { } // anonymous namespace -using namespace Jrd; - namespace { const serv_entry services[] = { @@ -1975,7 +1920,7 @@ THREAD_ENTRY_DECLARE Service::run(THREAD_ENTRY_PARAM arg) svc->unblockQueryGet(); svc->finish(SVC_finished); - threadCollect->add(thrHandle); + threadCollect->ending(thrHandle); } catch (const Exception& ex) { diff --git a/src/jrd/tra.cpp b/src/jrd/tra.cpp index 23d47dbc93..44e6e55fc0 100644 --- a/src/jrd/tra.cpp +++ b/src/jrd/tra.cpp @@ -4061,13 +4061,17 @@ void jrd_tra::checkBlob(thread_db* tdbb, const bid* blob_id, jrd_fld* fld, bool vec* vector = tra_attachment->att_relations; jrd_rel* blb_relation; - if (rel_id < vector->count() && (blb_relation = (*vector)[rel_id])) + if ((rel_id < vector->count() && (blb_relation = (*vector)[rel_id])) || + (blb_relation = MET_relation(tdbb, rel_id))) { - const MetaName security_name = fld ? + MetaName security_name = (fld && fld->fld_security_name.hasData()) ? fld->fld_security_name : blb_relation->rel_security_name; if (security_name.isEmpty()) + { MET_scan_relation(tdbb, blb_relation); + security_name = blb_relation->rel_security_name; + } SecurityClass* s_class = SCL_get_class(tdbb, security_name.c_str()); diff --git a/src/jrd/trace/TraceManager.cpp b/src/jrd/trace/TraceManager.cpp index e25be60b04..470b54e7e7 100644 --- a/src/jrd/trace/TraceManager.cpp +++ b/src/jrd/trace/TraceManager.cpp @@ -88,6 +88,7 @@ TraceManager::TraceManager(Attachment* in_att) : attachment(in_att), service(NULL), filename(NULL), + callback(NULL), trace_sessions(*in_att->att_pool), active(false) { @@ -98,16 +99,18 @@ TraceManager::TraceManager(Service* in_svc) : attachment(NULL), service(in_svc), filename(NULL), + callback(NULL), trace_sessions(in_svc->getPool()), active(true) { init(); } -TraceManager::TraceManager(const char* in_filename) : +TraceManager::TraceManager(const char* in_filename, ICryptKeyCallback* cb) : attachment(NULL), service(NULL), filename(in_filename), + callback(cb), trace_sessions(*getDefaultMemoryPool()), active(true) { @@ -251,7 +254,8 @@ void TraceManager::update_session(const TraceSession& session) } // if this session is not from administrator, it may trace connections - // only created by the same user + // only created by the same user, or when it has TRACE_ANY_ATTACHMENT + // privilege in current context if (!(session.ses_flags & (trs_admin | trs_system))) { const char* curr_user = nullptr; @@ -265,10 +269,11 @@ void TraceManager::update_session(const TraceSession& session) if (attachment) { - if ((!attachment->att_user) || (attachment->att_flags & ATT_mapping)) + if (attachment->att_flags & ATT_mapping) return; - curr_user = attachment->getUserName().c_str(); + if (attachment->att_user) + curr_user = attachment->att_user->getUserName().c_str(); if (session.ses_auth.hasData()) { @@ -290,7 +295,7 @@ void TraceManager::update_session(const TraceSession& session) } else if (service) { - curr_user = service->getUserName().c_str(); + curr_user = service->getUserName().nullStr(); if (session.ses_auth.hasData()) { @@ -308,6 +313,26 @@ void TraceManager::update_session(const TraceSession& session) mapResult = mapping.mapUser(s_user, t_role); } } + else if (filename) + { + if (session.ses_auth.hasData()) + { + Mapping mapping(Mapping::MAP_NO_FLAGS, callback); + mapping.needSystemPrivileges(priv); + mapping.setAuthBlock(session.ses_auth); + mapping.setSqlRole(session.ses_role); + + RefPtr config; + PathName org_filename(filename), expanded_name; + if (! expandDatabaseName(org_filename, expanded_name, &config)) + expanded_name = filename; + + mapping.setSecurityDbAlias(config->getSecurityDatabase(), expanded_name.c_str()); + mapping.setDb(filename, expanded_name.c_str(), nullptr); + + mapResult = mapping.mapUser(s_user, t_role); + } + } else { // failed attachment attempts traced by admin trace only @@ -327,7 +352,7 @@ void TraceManager::update_session(const TraceSession& session) t_role.upper(); if (s_user != DBA_USER_NAME && t_role != ADMIN_ROLE && - s_user != curr_user && (!priv.test(TRACE_ANY_ATTACHMENT))) + ((!curr_user) || (s_user != curr_user)) && (!priv.test(TRACE_ANY_ATTACHMENT))) { return; } diff --git a/src/jrd/trace/TraceManager.h b/src/jrd/trace/TraceManager.h index 924ed7f18b..26f91086f3 100644 --- a/src/jrd/trace/TraceManager.h +++ b/src/jrd/trace/TraceManager.h @@ -39,6 +39,12 @@ #include "../../jrd/trace/TraceConfigStorage.h" #include "../../jrd/trace/TraceSession.h" +namespace Firebird { + +class ICryptKeyCallback; + +} + namespace Jrd { class Database; @@ -52,7 +58,7 @@ public: /* Initializes plugins. */ explicit TraceManager(Attachment* in_att); explicit TraceManager(Service* in_svc); - explicit TraceManager(const char* in_filename); + TraceManager(const char* in_filename, Firebird::ICryptKeyCallback* callback); /* Finalize plugins. Called when database is closed by the engine */ ~TraceManager(); @@ -142,6 +148,12 @@ public: return active; } + // external access to stored attachment + Attachment* getAttachment() + { + return attachment; + } + /* DSQL-friendly routines to call Trace API hooks. Needed because DSQL cannot include JRD for the current engine */ static bool need_dsql_prepare(Attachment* att); @@ -163,6 +175,7 @@ private: Attachment* attachment; Service* service; const char* filename; + Firebird::ICryptKeyCallback* callback; NotificationNeeds trace_needs, new_needs; // This structure should be POD-like to be stored in Array diff --git a/src/misc/writeBuildNum.sh b/src/misc/writeBuildNum.sh index 9f63608352..8e4627d546 100755 --- a/src/misc/writeBuildNum.sh +++ b/src/misc/writeBuildNum.sh @@ -9,7 +9,7 @@ BuildType=T MajorVer=5 MinorVer=0 RevNo=0 -BuildNum=501 +BuildNum=516 NowAt=`pwd` cd `dirname $0` diff --git a/src/yvalve/DistributedTransaction.cpp b/src/yvalve/DistributedTransaction.cpp index b770051546..1e053d47ea 100644 --- a/src/yvalve/DistributedTransaction.cpp +++ b/src/yvalve/DistributedTransaction.cpp @@ -66,6 +66,10 @@ public: void deprecatedDisconnect(CheckStatusWrapper* status); private: + void internalCommit(CheckStatusWrapper* status); + void internalRollback(CheckStatusWrapper* status); + void internalDisconnect(CheckStatusWrapper* status); + typedef HalfStaticArray SubArray; typedef HalfStaticArray TdrBuffer; SubArray sub; @@ -248,7 +252,7 @@ void DTransaction::prepare(CheckStatusWrapper* status, } } -void DTransaction::commit(CheckStatusWrapper* status) +void DTransaction::internalCommit(CheckStatusWrapper* status) { try { @@ -309,7 +313,7 @@ void DTransaction::commitRetaining(CheckStatusWrapper* status) } } -void DTransaction::rollback(CheckStatusWrapper* status) +void DTransaction::internalRollback(CheckStatusWrapper* status) { try { @@ -361,7 +365,7 @@ void DTransaction::rollbackRetaining(CheckStatusWrapper* status) } } -void DTransaction::disconnect(CheckStatusWrapper* status) +void DTransaction::internalDisconnect(CheckStatusWrapper* status) { try { @@ -392,17 +396,38 @@ void DTransaction::disconnect(CheckStatusWrapper* status) void DTransaction::deprecatedCommit(CheckStatusWrapper* status) { - commit(status); + internalCommit(status); } void DTransaction::deprecatedRollback(CheckStatusWrapper* status) { - rollback(status); + internalRollback(status); } void DTransaction::deprecatedDisconnect(CheckStatusWrapper* status) { - disconnect(status); + internalDisconnect(status); +} + +void DTransaction::disconnect(CheckStatusWrapper* status) +{ + internalDisconnect(status); + if (status->isEmpty()) + release(); +} + +void DTransaction::rollback(CheckStatusWrapper* status) +{ + internalRollback(status); + if (status->isEmpty()) + release(); +} + +void DTransaction::commit(CheckStatusWrapper* status) +{ + internalCommit(status); + if (status->isEmpty()) + release(); } diff --git a/src/yvalve/why.cpp b/src/yvalve/why.cpp index bded8718a7..f4e525120b 100644 --- a/src/yvalve/why.cpp +++ b/src/yvalve/why.cpp @@ -5432,8 +5432,6 @@ YTransaction* YTransaction::enterDtc(CheckStatusWrapper* status) YEntry entry(status, this); YTransaction* copy = FB_NEW YTransaction(this); - // copy is created with zero handle - copy->addRef(); copy->addRef(); next->addRef(); // We use NoIncr in YTransaction ctor