I am implementing an OpenGLRenderer which reads and modifies some variables shared with the Message Thread. The API documentation of OpenGLRenderer::renderOpenGL() is unclear to me when it says that the Message Manager is locked only “If the context is attached to a component in order to do component rendering”.
My renderer attaches its context to a component in the constructor and never removes it. Also the render is not in a loop and it’s triggered only upon user actions with triggerRepaint(). However it looks like I can make no safe assumption that the message manager will be locked in renderOpenGL().
I put a call to lock the message manager at the beginning of renderOpenGL() as follows:
const MessageManagerLock mmLock(ThreadPoolJob::getCurrentThreadPoolJob());
if (!mmLock.lockWasGained()) return;
to be on the safe side and lock the MessageManager every time if not already locked. However it looks like this can lead to a deadlock with the Message Manager trying to lock the native context (already locked by the GL thread with a NativeContext::Locker in juce::OpenGLContext::renderFrame()).
So is there an easy workaround to avoid using a mutex for all the variables that are shared with the message thread ? How can I be sure that the Message Manager is always locked when my renderer’s renderOpenGL() is called ?
Absolutely, that’s why I put the lock on Message Manager in my render callback even though most of the time it’s already taken.
I did experience a deadlock once though, and the gl thread was stuck on my message manager lock, although I am not sure where the Message Thread was stuck on.
Having had a look at juce::OpenGLContext::renderFrame(), I see that it first takes a lock to the message manager and then another lock to the native context. Sometimes the lock on the Message manager is not taken though, so I am wondering if there is a chance that the following would happen in a case like mine where the renderOpenGL() asks for a lock on the Message Manager:
Message Thread takes a lock on the Message Thread, and then sits waiting to take a lock on the native context (not sure this can happen or not cause I am not familiar with juce implementation)
OpenGL thread doesn’t take the first lock on the Message Manager, then takes a lock on the native context (this is always taken) and then tries to take a lock on the Message Manager, waiting forever.
The API of OpenGLRenderer::renderOpenGL() is not very clear to me as to what’s going on under the hood. I think it should mention explicitly whether or not the user is allowed to take a lock on the Message Manager without risking a deadlock.
You can read this topic, I had issues with this as well:
The only safe synchronization between the main thread and the OpenGL thread that cannot deadlock is using setComponentPaintingEnabled(true), in which case the MessageManagerLock is acquired for you. Everything else is a potential deadlock, unfortunately.
Mhm…thank you for your replay first of all, I see I am not the only one who run into this problem.
I have to flag that, in my case, setComponentPaintingEnabled(true) doesn’t seem to have any effect on the locking. Besides it is set to true by default.
I tried to see how the juce Demo example handles things in OpenGLDemo.cpp
Having put the following lines in renderOpenGL() :
if (MessageManager::getInstance()->currentThreadHasLockedMessageManager()) DBG("yes");
I see that the lock is only taken when the sliders are changed. The variable called scale is accessed by the Message Manager and by the OpenGL Context without any synchronization. Which looks like a race condition to me.
In my case I am triggering renders manually upon user action. And in this case, the message manager is almost always locked. Although sporadically it happens to be unlocked - for example on Windows, when I hover on some component within the component with the OpenGL renderer attached to it.