Linux: the message loop never dispatches a quit posted after a burst of more than 128 messages
Detailed steps on how to reproduce the bug
Build this console application on Linux against JUCE 9.0.2 (juce_events only) and run it:
#include <juce_events/juce_events.h>
#include <chrono>
#include <cstdio>
#include <thread>
int main()
{
juce::ScopedJuceInitialiser_GUI init;
auto* manager = juce::MessageManager::getInstance();
manager->callAsync ([]
{
for (int i = 0; i < 300; ++i)
juce::MessageManager::callAsync ([] { std::this_thread::sleep_for (std::chrono::milliseconds (2)); });
juce::MessageManager::getInstance()->stopDispatchLoop();
});
manager->runDispatchLoop();
std::printf ("the dispatch loop exited\n");
return 0;
}What is the expected behaviour?
The 300 messages run and the loop exits, in well under a second.
What is the actual behaviour?
The loop never exits. Run in a container built from Ubuntu 22.04, the program is still running when killed after 20 seconds; with the change described below it prints its line and exits in 0.7 seconds.
The mechanism, in modules/juce_events/native/juce_Messaging_linux.cpp:
InternalMessageQueue::postMessageappends the message toqueueand writes one wake byte to the socket pair, but only whilebytesInSocket < maxBytesInSocketQueue(128). A message posted beyond that carries no byte.- The fd callback dispatches messages in a loop and breaks after 100 ms "to avoid starving other LinuxEventLoop callbacks".
popNextMessageconsumes a byte per message while any are left and pops the queue head regardless. - So a queue that still holds messages when the 100 ms budget breaks, after the last byte has been consumed, has nothing left to wake the loop for them, and they wait for some later post to write a byte.
- Once
stopDispatchLoophas run,quitMessagePostedmakesMessageBase::postdrop every later post (juce_MessageManager.cpp), so no byte can ever arrive: the quit message, itself posted without a byte because the socket was full, is never dispatched, and the process runs forever.
In the program above, 300 posts write 128 bytes; three callbacks dispatch about 50 messages each before their budget breaks and between them consume the 128 bytes; 150 messages and the quit remain with no byte. The same shape arises in an application that closes many windows at once on the way to quitting.
The fix that works here: when the callback's budget breaks with messages still queued and no bytes in the socket, write one byte, so the remainder is dispatched on the next pass. Pull request to follow.
Operating systems
Linux
What versions of the operating systems?
Ubuntu 22.04 (container), reproduced; Ubuntu 24.04 on a user's machine
Architectures
x86_64
Stacktrace
None; the process hangs in poll with nothing to wait for.
Plug-in formats (if applicable)
No response
Plug-in host applications (DAWs) (if applicable)
No response
Testing on the develop branch
The bug is present on the develop branch (72782788c, 9.0.2)
Code of Conduct
- I agree to follow the Code of Conduct
Source: juce-framework/JUCE