-
-
Notifications
You must be signed in to change notification settings - Fork 243
fix(shutdown): Prevent race condition when GlobalObject destruction routine unlocks global mutex #8652
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
fix(shutdown): Prevent race condition when GlobalObject destruction routine unlocks global mutex #8652
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -586,12 +586,39 @@ namespace Jrd | |
{ | ||
MutexLockGuard guard(g_mutex, FB_FUNCTION); | ||
|
||
Database::GlobalObjectHolder::DbId* entry = g_hashTable->lookup(id); | ||
// Get entry with incrementing ref counter, so if someone is currently destroying it, the object itself | ||
// will remain alive. | ||
RefPtr<Database::GlobalObjectHolder::DbId> entry(g_hashTable->lookup(id)); | ||
if (entry) | ||
{ | ||
auto& shutdownMutex = entry->shutdownMutex; | ||
// Check if someone else currently destroying GlobalObject. | ||
if (shutdownMutex.tryEnter(FB_FUNCTION)) | ||
{ | ||
// No one is destroying GlobalObject, continue init routine. | ||
shutdownMutex.leave(); | ||
} | ||
else | ||
{ | ||
// Someone is currently destroying GlobalObject, wait until he finish it to eliminate potential | ||
// race conditions. | ||
{ | ||
MutexUnlockGuard unlockGuard(g_mutex, FB_FUNCTION); | ||
|
||
MutexLockGuard guard(shutdownMutex, FB_FUNCTION); | ||
} | ||
// Now we are the one who owned DbId object. | ||
// It also was removed from hash table, so simply delete it and recreate it next. | ||
fb_assert(entry->getRefCount() == 1); | ||
entry = nullptr; | ||
} | ||
} | ||
if (!entry) | ||
{ | ||
const auto holder = FB_NEW Database::GlobalObjectHolder(id, filename, config); | ||
entry = FB_NEW Database::GlobalObjectHolder::DbId(id, holder); | ||
entry = makeRef(FB_NEW Database::GlobalObjectHolder::DbId(id, holder)); | ||
g_hashTable->add(entry); | ||
entry->addRef(); | ||
} | ||
|
||
entry->holder->addRef(); | ||
|
@@ -601,9 +628,15 @@ namespace Jrd | |
Database::GlobalObjectHolder::~GlobalObjectHolder() | ||
{ | ||
// 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); | ||
|
||
// Stole the object from the hash table without incrementing ref counter, so we will be the one who will delete the object | ||
// at the end of this function. | ||
RefPtr<Database::GlobalObjectHolder::DbId> entry(REF_NO_INCR, g_hashTable->lookup(m_id)); | ||
fb_assert(entry); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add also |
||
// We need to unlock the global mutex to safely shutdown some managers, so lock shutdown mutex to make sure that | ||
// other threads will wait until we done our shutdown routine. | ||
// This is done to eliminate potential race conditions involving global objects, such as shared memory. | ||
MutexLockGuard guard(entry->shutdownMutex, FB_FUNCTION); | ||
|
||
{ // scope | ||
// here we cleanup what should not be globally protected | ||
|
@@ -616,7 +649,8 @@ namespace Jrd | |
m_eventMgr = nullptr; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On enter At line 643 Am I wrong ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I can see every instance of |
||
m_replMgr = nullptr; | ||
|
||
delete entry; | ||
if (!g_hashTable->remove(m_id)) | ||
fb_assert(false); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add |
||
fb_assert(m_tempCacheUsage == 0); | ||
} | ||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there are many concurrent initializers, then this assert could be violated.
I think it is not needed.
Instead, you may nullify
entry->holder
at~GlobalObjectHolder()
and check it for nullptr here.