I have a simple utility for destroying a bunch of Components in a worker thread:
/**
Disposes components in a background thread. The dispose method takes a juce::OwnedArray of components,
clears the array and returns. The contained components are disposed later. This is useful when you need to
get rid of a lot of components, but don't want to keep the message thread waiting and UI irresponsive.
*/
template <typename T> class ComponentDisposer : public juce::Thread {
public:
ComponentDisposer () : Thread ("Component Disposer") {}
~ComponentDisposer () {
signalThreadShouldExit ();
lock.abort ();
notify ();
waitForThreadToExit (-1);
}
void dispose (juce::OwnedArray<T>& components) {
const juce::MessageManager::Lock::ScopedLockType l (lock);
while (!components.isEmpty ()) {
this->components.add (components.removeAndReturn (components.size () - 1));
notify ();
}
}
void run () override {
while (!threadShouldExit ()) {
bool isDirty = false;
{
const juce::MessageManager::Lock::ScopedTryLockType l (lock);
if (l.isLocked () && !components.isEmpty ()) {
components.remove (components.size () - 1);
isDirty = !components.isEmpty ();
}
}
if (!isDirty) {
wait (-1);
}
}
}
private:
juce::OwnedArray<T> components;
juce::MessageManager::Lock lock;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentDisposer);
};
After updating from JUCE 7.0.5 to 7.0.6, every time i close the app, i hit the below assertion in juce_ReferenceCountedObjectPtr
line 394
// the -> operator is called on the referenced object
ReferencedType* operator->() const noexcept
{
jassert (referencedObject != nullptr); // null pointer method call!
return referencedObject;
}
This occurs while the destructor is in waitForThreadToExit (-1);
Here’s the stack trace
It looks as if the MessageManager
itself is in invalid state at this point - trying to access an internal object through a null pointer.
How to fix this? Something has changed in JUCE and i would desperately need to know how to make this work again.
Here’s the entire MessageManager method for quick reference:
bool MessageManager::Lock::tryAcquire (bool lockIsMandatory) const noexcept
{
auto* mm = MessageManager::instance;
if (mm == nullptr)
{
jassertfalse;
return false;
}
if (! lockIsMandatory && [&]
{
const std::scoped_lock lock { mutex };
return std::exchange (abortWait, false);
}())
{
return false;
}
if (mm->currentThreadHasLockedMessageManager())
return true;
try
{
blockingMessage = *new BlockingMessage (this);
}
catch (...)
{
jassert (! lockIsMandatory);
return false;
}
if (! blockingMessage->post())
{
// post of message failed while trying to get the lock
jassert (! lockIsMandatory);
blockingMessage = nullptr;
return false;
}
for (;;)
{
{
std::unique_lock lock { mutex };
condvar.wait (lock, [&] { return std::exchange (abortWait, false); });
}
// ------------------------ //
// THIS FAILS //
// ------------------------ //
if (blockingMessage->wasAcquired())
// ------------------------ //
{
mm->threadWithLock = Thread::getCurrentThreadId();
return true;
}
if (! lockIsMandatory)
break;
}
// we didn't get the lock
blockingMessage->stopWaiting();
blockingMessage = nullptr;
return false;
}