#1733·JUCE

Linux: the message loop never dispatches a quit posted after a burst of more than 128 messages

Author: jdfCreated Sep 7, 2026Updated Sep 11, 2026

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:

cpp
#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::postMessage appends the message to queue and writes one wake byte to the socket pair, but only while bytesInSocket < 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". popNextMessage consumes 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 stopDispatchLoop has run, quitMessagePosted makes MessageBase::post drop 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