Browse Source

MessageManager::Lock: Protect Lock from concurrent accesses

Previously, the Lock was not thread-safe when multiple threads were
locking/unlocking a single Lock instance simultaneously. This
isn't normally a problem when using the MessageManagerLock type, because
each MessageManagerLock contains its own private MessageManager::Lock,
and it's not possible for multiple threads to modify that private lock.

This change improves safety when using a MessageManager::Lock in the
manner of a CriticalSection or other JUCE lock type.
v7.0.9
reuk 2 years ago
parent
commit
d47a7d18c1
No known key found for this signature in database GPG Key ID: FCB43929F012EE5C
2 changed files with 52 additions and 19 deletions
  1. +41
    -18
      modules/juce_events/messages/juce_MessageManager.cpp
  2. +11
    -1
      modules/juce_events/messages/juce_MessageManager.h

+ 41
- 18
modules/juce_events/messages/juce_MessageManager.cpp View File

@@ -292,10 +292,7 @@ struct MessageManager::Lock::BlockingMessage : public MessageManager::MessageB
std::unique_lock lock { mutex };
if (owner != nullptr)
{
owner->abort();
acquired = true;
}
owner->setAcquired (true);
condvar.wait (lock, [&] { return owner == nullptr; });
}
@@ -307,18 +304,11 @@ struct MessageManager::Lock::BlockingMessage : public MessageManager::MessageB
owner = nullptr;
}
bool wasAcquired()
{
const std::scoped_lock lock { mutex };
return acquired;
}
private:
std::mutex mutex;
std::condition_variable condvar;
const MessageManager::Lock* owner = nullptr;
bool acquired = false;
JUCE_DECLARE_NON_COPYABLE (BlockingMessage)
};
@@ -326,8 +316,23 @@ private:
//==============================================================================
MessageManager::Lock::Lock() {}
MessageManager::Lock::~Lock() { exit(); }
void MessageManager::Lock::enter() const noexcept { tryAcquire (true); }
bool MessageManager::Lock::tryEnter() const noexcept { return tryAcquire (false); }
void MessageManager::Lock::enter() const noexcept { exclusiveTryAcquire (true); }
bool MessageManager::Lock::tryEnter() const noexcept { return exclusiveTryAcquire (false); }
bool MessageManager::Lock::exclusiveTryAcquire (bool lockIsMandatory) const noexcept
{
if (lockIsMandatory)
entryMutex.enter();
else if (! entryMutex.tryEnter())
return false;
const auto result = tryAcquire (lockIsMandatory);
if (! result)
entryMutex.exit();
return result;
}
bool MessageManager::Lock::tryAcquire (bool lockIsMandatory) const noexcept
{
@@ -376,7 +381,7 @@ bool MessageManager::Lock::tryAcquire (bool lockIsMandatory) const noexcept
condvar.wait (lock, [&] { return std::exchange (abortWait, false); });
}
if (blockingMessage->wasAcquired())
if (acquired)
{
mm->threadWithLock = Thread::getCurrentThreadId();
return true;
@@ -395,16 +400,28 @@ bool MessageManager::Lock::tryAcquire (bool lockIsMandatory) const noexcept
void MessageManager::Lock::exit() const noexcept
{
const auto wasAcquired = [&]
{
const std::scoped_lock lock { mutex };
return acquired;
}();
if (! wasAcquired)
return;
const ScopeGuard unlocker { [&] { entryMutex.exit(); } };
if (blockingMessage == nullptr)
return;
const ScopeGuard scope { [&] { blockingMessage = nullptr; } };
const ScopeGuard scope { [&]
{
blockingMessage = nullptr;
acquired = false;
} };
blockingMessage->stopWaiting();
if (! blockingMessage->wasAcquired())
return;
if (auto* mm = MessageManager::instance)
{
jassert (mm->currentThreadHasLockedMessageManager());
@@ -413,10 +430,16 @@ void MessageManager::Lock::exit() const noexcept
}
void MessageManager::Lock::abort() const noexcept
{
setAcquired (false);
}
void MessageManager::Lock::setAcquired (bool x) const noexcept
{
const ScopeGuard scope { [&] { condvar.notify_one(); } };
const std::scoped_lock lock { mutex };
abortWait = true;
acquired = x;
}
//==============================================================================


+ 11
- 1
modules/juce_events/messages/juce_MessageManager.h View File

@@ -297,11 +297,21 @@ public:
struct BlockingMessage;
friend class ReferenceCountedObjectPtr<BlockingMessage>;
bool exclusiveTryAcquire (bool) const noexcept;
bool tryAcquire (bool) const noexcept;
void setAcquired (bool success) const noexcept;
//==============================================================================
mutable ReferenceCountedObjectPtr<BlockingMessage> blockingMessage;
// This mutex is used to make this lock type behave like a normal mutex.
// If multiple threads call enter() simultaneously, only one will succeed in gaining
// this mutex. The mutex is released again in exit().
mutable CriticalSection entryMutex;
// This mutex protects the other data members of the lock from concurrent access, which
// happens when the BlockingMessage calls setAcquired to indicate that the lock was gained.
mutable std::mutex mutex;
mutable ReferenceCountedObjectPtr<BlockingMessage> blockingMessage;
mutable std::condition_variable condvar;
mutable bool abortWait = false, acquired = false;
};


Loading…
Cancel
Save