More openGL deadlocking


#1

Hi!
I have a bug that causes my UIs to freeze sometimes (seems rather random, but high load increases the likelyhood) when I close the editor.

I tried to read up on it on the forum, and so far the only thing I could gather is to use no MessageManagerLock in the painting code. Ok, no Messagemanager locks in my code, but still it is hanging. SInce the bug is rather random, it is really hard to debug, but I managed to get the stacktraces of the threads that are like involved.

Main Thread:

1 0x00007fffa845a96a in _pthread_cond_wait ()
2 0x0000000123eb8b6e in juce::WaitableEvent::wait(int) const ()
3 0x0000000123ebab5d in juce::ThreadPool::waitForJobToFinish(juce::ThreadPoolJob const*, int) const ()
4 0x0000000123ebad1b in juce::ThreadPool::removeJob(juce::ThreadPoolJob*, bool, int) ()
5 0x0000000124017749 in juce::OpenGLContext::CachedImage::stop() ()
6 0x0000000123f5cf2c in juce::Component::ComponentHelpers::releaseAllCachedImageResources(juce::Component&) ()
7 0x0000000123f5c334 in juce::Component::removeChildComponent(int, bool, bool) ()
8 0x0000000123f60924 in juce::Component::deleteAllChildren() ()
9 0x0000000123cb2549 in JuceVSTWrapper::EditorCompWrapper::~EditorCompWrapper() ()
10 0x0000000123cb16a4 in JuceVSTWrapper::deleteEditor(bool) ()
11 0x0000000123cb0803 in JuceVSTWrapper::dispatcher(int, JuceVSTWrapper::VstOpCodeArguments) ()
12 0x0000000123cafd77 in JuceVSTWrapper::dispatcherCB(VstEffectInterface*, int, int, long long, void*, float) ()
13 0x0000000100357d37 in VST_HostedPlugin::QuitConfig() ()
14 0x00000001003617fb in FxDsp::delConfig() ()

Pool thread 1:

1 0x00007fffa845a5e9 in sched_yield ()
2 0x000000016d95f575 in juce::MessageManagerLock::attemptLock(juce::MessageManagerLock::BailOutChecker*) ()
3 0x000000016d95f821 in juce::MessageManagerLock::MessageManagerLock(juce::MessageManagerLock::BailOutChecker&) ()
4 0x000000016da84288 in juce::OpenGLContext::CachedImage::renderFrame() ()
5 0x000000016da83438 in juce::OpenGLContext::CachedImage::runJob() ()
6 0x000000016da8358d in non-virtual thunk to juce::OpenGLContext::CachedImage::runJob() ()
7 0x000000016d930cb8 in juce::ThreadPool::runNextJob(juce::ThreadPool::ThreadPoolThread&) ()
8 0x000000016d95359f in juce::ThreadPool::ThreadPoolThread::run() ()
9 0x000000016d92f0ab in juce::Thread::threadEntryPoint() ()
10 0x000000016d9457ea in threadEntryProc ()

Pool thread 2:

1 0x00007fffa845a96a in _pthread_cond_wait ()
2 0x000000016d92e6ee in juce::WaitableEvent::wait(int) const ()
3 0x000000016d95f6e0 in juce::MessageManagerLock::attemptLock(juce::MessageManagerLock::BailOutChecker*) ()
4 0x000000016d95f821 in juce::MessageManagerLock::MessageManagerLock(juce::MessageManagerLock::BailOutChecker&) ()
5 0x000000016da84288 in juce::OpenGLContext::CachedImage::renderFrame() ()
6 0x000000016da83438 in juce::OpenGLContext::CachedImage::runJob() ()
7 0x000000016da8358d in non-virtual thunk to juce::OpenGLContext::CachedImage::runJob() ()
8 0x000000016d930cb8 in juce::ThreadPool::runNextJob(juce::ThreadPool::ThreadPoolThread&) ()
9 0x000000016d95359f in juce::ThreadPool::ThreadPoolThread::run() ()
10 0x000000016d92f0ab in juce::Thread::threadEntryPoint() ()
11 0x000000016d9457ea in threadEntryProc ()

Pool thread 3:

1 0x00007fffa845a96a in _pthread_cond_wait ()
2 0x0000000123eb8b8b in juce::WaitableEvent::wait(int) const ()
3 0x0000000124016c6d in juce::OpenGLContext::CachedImage::runJob() ()
4 0x0000000124016d8d in non-virtual thunk to juce::OpenGLContext::CachedImage::runJob() ()
5 0x0000000123ebb148 in juce::ThreadPool::runNextJob(juce::ThreadPool::ThreadPoolThread&) ()
6 0x0000000123edda7f in juce::ThreadPool::ThreadPoolThread::run() ()
7 0x0000000123eb952b in juce::Thread::threadEntryPoint() ()
8 0x0000000123ecfcca in threadEntryProc ()

The OpenGL context code is attach and detached in the top component’s constructor and destructor:

if(frame->product->openGL.isOn())
{
    gl.attachTo(*this);
}

gl.detach();

The situation is a busy project, with multiple instances of different plugins using the openGL contexts on the top UI class. I close a UI, freeze! This is what I get when I attach the debugger. I tried deleting all child components to eliminate al timers that could fire a “repaint” call, but I suppose these calls would have been thread safe anyways.

I’ll keep investigating this, but so far I did not get very far, and this is a looong standing bug, so any help is appreciated :slight_smile:

I’m still on branch develop bc78b2f, but could not find anything related in the change log.

Cheers!

JM


#2

I have seen some stuff related to OpenGL and editor closes recently. Could you just try again with the last develop branch version, and tell us if the problem is still happening ?


#3

Thanks IvanC !
So far the bug has not appeared again!
That has been the case before, I already tried fixing it like this in the past when I though there was some related commits, but had no luck and the changes broke a lot in my code, so I was a bit too pessimistic :frowning:

Cheers!


#4

Hi there!
The bug resurfaced.
Here are the (in all likelihood) involved threads:

juce::WaitableEvent::wait(int) const at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_core/native/juce_posix_SharedCode.h:92
juce::ThreadPool::waitForJobToFinish(juce::ThreadPoolJob const*, int) const at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_core/threads/juce_ThreadPool.cpp:179
juce::ThreadPool::removeJob(juce::ThreadPoolJob*, bool, int) at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_core/threads/juce_ThreadPool.cpp:214
juce::OpenGLContext::CachedImage::pause() [inlined] at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_opengl/opengl/juce_OpenGLContext.cpp:139
juce::OpenGLContext::CachedImage::stop() at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_opengl/opengl/juce_OpenGLContext.cpp:126
juce::OpenGLContext::overrideCanBeAttached(bool) [inlined] at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_opengl/opengl/juce_OpenGLContext.cpp:1063
juce::componentPeerAboutToChange(juce::ComponentPeer&, bool) at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_opengl/native/juce_OpenGL_osx.h:265
juce::NSViewComponentPeer::redirectWillMoveToWindow(NSWindow*) [inlined] at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm:675
juce::NSViewComponentPeer::~NSViewComponentPeer() at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm:177
juce::NSViewComponentPeer::~NSViewComponentPeer() [inlined] at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm:171
juce::NSViewComponentPeer::~NSViewComponentPeer() at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm:171
juce::Component::removeFromDesktop() at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_gui_basics/components/juce_Component.cpp:715
juce::detachComponentFromWindowRefVST(juce::Component*, void*, bool) at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.mm:245
JuceVSTWrapper::EditorCompWrapper::detachHostWindow() [inlined] at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp:1233
JuceVSTWrapper::deleteEditor(bool) at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp:1082
JuceVSTWrapper::handleCloseEditor(JuceVSTWrapper::VstOpCodeArguments) [inlined] at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp:1716
JuceVSTWrapper::dispatcher(int, JuceVSTWrapper::VstOpCodeArguments) at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp:1117
JuceVSTWrapper::dispatcherCB(VstEffectInterface*, int, int, long long, void*, float) at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp:1162


juce::WaitableEvent::wait(int) const at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_core/native/juce_posix_SharedCode.h:71
juce::OpenGLContext::CachedImage::runJob() at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_opengl/opengl/juce_OpenGLContext.cpp:456
juce::OpenGLContext::CachedImage::runJob() at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_opengl/opengl/juce_OpenGLContext.cpp:426
juce::ThreadPool::runNextJob(juce::ThreadPool::ThreadPoolThread&) at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_core/threads/juce_ThreadPool.cpp:340
juce::ThreadPool::ThreadPoolThread::run() at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_core/threads/juce_ThreadPool.cpp:34
juce::Thread::threadEntryPoint() at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_core/threads/juce_Thread.cpp:96
juce::juce_threadEntryPoint(void*) [inlined] at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_core/threads/juce_Thread.cpp:111
::threadEntryProc(void *) at /Users/jms/Development/audioD3CK/lib/JUCE/modules/juce_core/native/juce_posix_SharedCode.h:922

Maybe just not wait forever(-1) in one of those places?

if (! renderFrame())
repaintEvent.wait (5); // failed to render, so avoid a tight fail-loop.
else if (! context.continuousRepaint && ! shouldExit())
repaintEvent.wait (-1);
}


if (renderThread != nullptr)
{
repaintEvent.signal();
renderThread->removeJob (this, true, -1);
}


#5

I am also seeing this. I have a component that inherits from OpenGlRenderer to do some of the more intensive rendering to an Image, which is later drawn to screen in the paint call. On the message manager thread I can be required to hide the component, which calls releaseAllCachedImageResources and causes the lock you have also seen. In this instance, the OpenGlRenderer thread pool object waits in CachedImage::runjob for a repaint event while releaseAllCacheImageResources waits for the cached image to close itself down.


#6

@dasdeckone you haven’t mentioned a platform but I’m guessing macOS from the “/Users” paths.
I think we had similar problems before we changed JUCE to use CVDisplayLink on macOS. You can give it a try from our branch at https://github.com/soundradix/JUCE/tree/juce5 and see if this resolves your problem.


#7

To my eye, our problem seems to be a race condition, in the pause of OpenGLContext::CachedImage a repaint is signalled before the attempt to remove the job, which waits forever for the job to exit. However, the ‘shouldExit’ flag is set in removeJob, so if the repaint is quite quick it could just lock up

void pause()
{
    if (renderThread != nullptr)
    {
        repaintEvent.signal();
        renderThread->removeJob (this, true, -1);
    }
}

I believe a simple fix is to call signalJobShouldExit() before the call to repaintEvent.signal()