Skip to content

Commit f55f77e

Browse files
TreeHunter9Artyom Ivanov
andauthored
Backport of fix(shutdown): Prevent race condition when GlobalObject destruction routine unlocks global mutex (#8687)
Unlocking global mutex in GlobalObject destruction routine made it possible for a new attachment to slip in, so it will be creating new GlobalObject and using it, while destroying routine still in action. This can lead to an undefined state of the global objects, such as shared memory, where one thread is actively using it while another thread is destroying it. Co-authored-by: Artyom Ivanov <[email protected]>
1 parent c784c8b commit f55f77e

File tree

2 files changed

+49
-8
lines changed

2 files changed

+49
-8
lines changed

src/jrd/Database.cpp

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -613,12 +613,39 @@ namespace Jrd
613613
{
614614
MutexLockGuard guard(g_mutex, FB_FUNCTION);
615615

616-
Database::GlobalObjectHolder::DbId* entry = g_hashTable->lookup(id);
616+
// Get entry with incrementing ref counter, so if someone is currently destroying it, the object itself
617+
// will remain alive.
618+
RefPtr<Database::GlobalObjectHolder::DbId> entry(g_hashTable->lookup(id));
619+
if (entry)
620+
{
621+
auto& shutdownMutex = entry->shutdownMutex;
622+
// Check if someone else currently destroying GlobalObject.
623+
if (shutdownMutex.tryEnter(FB_FUNCTION))
624+
{
625+
// No one is destroying GlobalObject, continue init routine.
626+
shutdownMutex.leave();
627+
}
628+
else
629+
{
630+
// Someone is currently destroying GlobalObject, wait until he finish it to eliminate potential
631+
// race conditions.
632+
{
633+
MutexUnlockGuard unlockGuard(g_mutex, FB_FUNCTION);
634+
635+
MutexLockGuard guard(shutdownMutex, FB_FUNCTION);
636+
}
637+
// Now we are the one who owned DbId object.
638+
// It also was removed from hash table, so simply delete it and recreate it next.
639+
fb_assert(entry->holder == nullptr);
640+
entry = nullptr;
641+
}
642+
}
617643
if (!entry)
618644
{
619645
const auto holder = FB_NEW Database::GlobalObjectHolder(id, filename, config);
620-
entry = FB_NEW Database::GlobalObjectHolder::DbId(id, holder);
646+
entry = makeRef(FB_NEW Database::GlobalObjectHolder::DbId(id, holder));
621647
g_hashTable->add(entry);
648+
entry->addRef();
622649
}
623650

624651
entry->holder->addRef();
@@ -628,9 +655,18 @@ namespace Jrd
628655
Database::GlobalObjectHolder::~GlobalObjectHolder()
629656
{
630657
// dtor is executed under g_mutex protection
631-
Database::GlobalObjectHolder::DbId* entry = g_hashTable->lookup(m_id);
632-
if (!g_hashTable->remove(m_id))
633-
fb_assert(false);
658+
659+
// Stole the object from the hash table without incrementing ref counter, so we will be the one who will delete the object
660+
// at the end of this function.
661+
RefPtr<Database::GlobalObjectHolder::DbId> entry(REF_NO_INCR, g_hashTable->lookup(m_id));
662+
fb_assert(entry);
663+
fb_assert(entry->holder == this);
664+
// We need to unlock the global mutex to safely shutdown some managers, so lock shutdown mutex to make sure that
665+
// other threads will wait until we done our shutdown routine.
666+
// This is done to eliminate potential race conditions involving global objects, such as shared memory.
667+
//! Be careful with order of mutex locking: first - `g_mutex`, second - `shutdownMutex`, but after `MutexUnlockGuard`
668+
//! this order will be reversed, so potentially a deadlock situation may occur here.
669+
MutexLockGuard guard(entry->shutdownMutex, FB_FUNCTION);
634670

635671
{ // scope
636672
// here we cleanup what should not be globally protected
@@ -643,7 +679,10 @@ namespace Jrd
643679
m_eventMgr = nullptr;
644680
m_replMgr = nullptr;
645681

646-
delete entry;
682+
if (!g_hashTable->remove(m_id))
683+
fb_assert(false);
684+
685+
entry->holder = nullptr;
647686

648687
fb_assert(m_tempCacheUsage == 0);
649688
}

src/jrd/Database.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ class Database : public pool_alloc<type_dbb>
260260
typedef Firebird::HashTable<DbId, Firebird::DEFAULT_HASH_SIZE,
261261
Firebird::string, DbId, DbId > DbIdHash;
262262

263-
struct DbId : public DbIdHash::Entry, public Firebird::GlobalStorage
263+
struct DbId : public DbIdHash::Entry, public Firebird::GlobalStorage, public Firebird::RefCounted
264264
{
265265
DbId(const Firebird::string& x, GlobalObjectHolder* h)
266266
: id(getPool(), x), holder(h)
@@ -289,7 +289,9 @@ class Database : public pool_alloc<type_dbb>
289289
}
290290

291291
const Firebird::string id;
292-
GlobalObjectHolder* const holder;
292+
GlobalObjectHolder* holder;
293+
// This mutex is working very tight with `g_mutex`, so use it carefully to avoid possible deadlocks.
294+
Firebird::Mutex shutdownMutex;
293295
};
294296

295297
static Firebird::GlobalPtr<DbIdHash> g_hashTable;

0 commit comments

Comments
 (0)