JUCE potentially locking on the audio thread

Briefly:

On Windows Juce may be calling the PostMessage function on the audio thread. I
wonder if this is okay.

This can be triggered easily by running the gain tutorial plugin inside the
Audio Plugin Host, creating a Slider with a SliderAttachment, and then moving
it vigorously.

The call chain will be:

JuceVST3Component::process -> JuceVST3Component::processParameterChanges ->
AudioProcessorParameter::sendValueChangedMessageToListeners ->
AudioProcessorValueTreeState::ParameterAdapter::parameterValueChanged ->
ListenerList::call -> AttachedControlBase::parameterChanged ->
AsyncUpdater::triggerAsyncUpdate -> MessageManager::MessageBase::post

I placed the breakpoint in juce_win32_Messaging.cpp:179.


Thoughts:

The documentation in juce_AsyncUpdater.h warns not to call triggerAsyncUpdate
on the audio thread, as it may (and will on most OSes) block. Yet, the
AudioProcessorValueTreeState::SliderAttachment does that.

I know that in practice you can kind of get away with locking on the audio
thread, but after having the advice been beaten into me by every single video
and blog post, it’s hard to know what to do.

2 Likes

Jules back in 2008 said: “Well I certainly don’t trust the PostMessage function to be safe for real-time use! Who knows what allocations and locking might happen in there!”

And if you search there are a number of threads about this very point … maybe someone at JUCE HQ could comment :slight_smile:

1 Like

it only works if you tag em lol

@jules @ed95 @t0m

1 Like

laughs . I’d swear they said to stop tagging them in things :wink:

A Message on the message thread is a refcounted pointer. Usually, the message queue is the only one holding a reference to the message - so after it was delievered, it goes out of scope and is deleted.

AsyncUpdater however keeps a refcounted pointer to the message that it posts to the message queue. That means, after the message was delivered, it remains alive and is re-used the next time by AsyncUpdater.
So that means, no memory allocation is required. AsyncUpdater keeps posting the exact same message object to the queue again and again.
If your message queue is very full, the RefCountedArray inside the MessageQueue class must be increased in size - this includes a memory allocation but it only happens when the queue is full - in this case, you probably have a slowed-down system anyway so it doesn’t really matter.

As for the locking - there is a CriticalSection involved when the MessageQueue adds the message in MessageQueue::post(). The array of messages is a RefCountedArray with a CriticalSection. Theoretically, the MessageThread could be removing a message from the queue and be interrupted right in that moment.

I guess, it would be a nice improvement to switch to a lock free fifo (AbstractFifo class) for the message queue. We could request that, however it would limit the number of messages allowed to a fixed number.

Oh and as a side note: We use AsyncUpdater a lot - also from the audio thread - and so far, it seems fine. I would still prefer a safer solution, because right now there is no other way to send messages or data from the audio thread, apart from polling.

We use this: https://github.com/jcredland/juce-toys/blob/master/multithreading/source/nonblocking_call_queue.h

It’s a wrapper around AbstractFIFO. (make things super easy - though you do need a timer on the other side to process the queue)

Interesting idea. It’s basically what I wrote above, but I wonder if there is a way to make the queue variable-sized. If the queue is full, I’d rather accept a dropout due to allocation of memory, than dropped messages which might make the whole software behave completely wrong.

Thanks for the detailed answer.

It did avoid touching upon what the opaque function Win32 PostMessage does. It might allocate, it might lock.

So it seems, this “don’t lock, don’t allocate” commandment should not be taken to the extremes, and in practice one just gets away with it. As I know I have, having found locks and allocations in my code upon careful inspection, that weren’t causing glitches even at a buffer size of 64.

So maybe it’s just the comment in the JUCE source code that is outdated, warning against using the function on the audio thread. It seems to be a well tested and well working way to notify other threads from the audio thread.