I do agree !
I wrote it yesterday, and so far it seems to work perfectly (no regressions, only improvements). Here is the drop-in replacement for juce_linux_Messaging.cpp
[code]/*
This file is part of the JUCE library - "Jules’ Utility Class Extensions"
Copyright 2004-7 by Raw Material Software ltd.
JUCE can be redistributed and/or modified under the terms of the
GNU General Public License, as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later version.
JUCE is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with JUCE; if not, visit www.gnu.org/licenses or write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
If you’d like to release a closed-source product which uses JUCE, commercial
licenses are also available: visit www.rawmaterialsoftware.com/juce for
more information.
==============================================================================
*/
#include “…/…/…/juce_Config.h”
#include “linuxincludes.h”
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xresource.h>
#include <X11/Xutil.h>
#include “…/…/…/src/juce_core/basics/juce_StandardHeader.h”
BEGIN_JUCE_NAMESPACE
#include “…/…/…/src/juce_appframework/events/juce_MessageManager.h”
#include “…/…/…/src/juce_core/threads/juce_WaitableEvent.h”
#include “…/…/…/src/juce_core/threads/juce_Process.h”
#include “…/…/…/src/juce_core/threads/juce_ScopedLock.h”
#include “…/…/…/src/juce_core/containers/juce_OwnedArray.h”
#ifdef JUCE_DEBUG
#define JUCE_DEBUG_XERRORS 1
#endif
Display* display = 0; // This is also referenced from WindowDriver.cpp
Window juce_messageWindowHandle = None;
#define SpecialAtom “JUCESpecialAtom”
#define BroadcastAtom “JUCEBroadcastAtom”
#define SpecialCallbackAtom “JUCESpecialCallbackAtom”
static Atom specialId;
static Atom broadcastId;
static Atom specialCallbackId;
// This is referenced from WindowDriver.cpp
XContext improbableNumber;
// Defined in WindowDriver.cpp
extern void juce_windowMessageReceive (XEvent* event);
struct MessageThreadFuncCall
{
MessageCallbackFunction* func;
void* parameter;
void* result;
CriticalSection lock;
WaitableEvent event;
};
struct InternalMessageQueue {
CriticalSection mutex;
OwnedArray queue;
int fd[2];
public:
InternalMessageQueue() {
int ret = ::socketpair(AF_LOCAL, SOCK_STREAM, 0, fd);
jassert(ret == 0);
}
~InternalMessageQueue() {
close(fd[0]);
close(fd[1]);
}
void postMessage(Message *msg) {
ScopedLock lock(mutex); queue.add(msg);
unsigned char x = 0xff;
write(fd[0], &x, 1);
}
bool isEmpty() { ScopedLock lock(mutex); return queue.size() == 0; }
Message *popMessage() {
ScopedLock lock(mutex);
Message m = 0;
if (queue.size()) {
unsigned char x;
read(fd[1], &x, 1);
m = queue.getUnchecked(0);
queue.remove(0, false / deleteObject */);
}
return m;
}
int getWaitHandle() { return fd[1]; }
};
static InternalMessageQueue *internal_message_queue;
static bool errorCondition = false;
static XErrorHandler oldErrorHandler = (XErrorHandler) 0;
static XIOErrorHandler oldIOErrorHandler = (XIOErrorHandler) 0;
// (defined in another file to avoid problems including certain headers in this one)
extern bool juce_isRunningAsApplication();
extern void juce_x11_handleSelectionRequest(XSelectionRequestEvent &evt);
// Usually happens when client-server connection is broken
static int ioErrorHandler (Display* display)
{
DBG (T(“ERROR: connection to X server broken… terminating.”));
errorCondition = true;
if (juce_isRunningAsApplication())
Process::terminate();
return 0;
}
// A protocol error has occurred
static int errorHandler (Display* display, XErrorEvent* event)
{
#ifdef JUCE_DEBUG_XERRORS
char errorStr[64] = { 0 };
char requestStr[64] = { 0 };
XGetErrorText (display, event->error_code, errorStr, 64);
XGetErrorDatabaseText (display,
"XRequest",
(const char*) String (event->request_code),
"Unknown",
requestStr,
64);
DBG (T("ERROR: X returned ") + String (errorStr) + T(" for operation ") + String (requestStr));
#endif
return 0;
}
static bool breakIn = false;
// Breakin from keyboard
static void sig_handler (int sig)
{
if (sig == SIGINT)
{
breakIn = true;
return;
}
static bool reentrant = false;
if (reentrant == false)
{
reentrant = true;
// Illegal instruction
fflush (stdout);
Logger::outputDebugString ("ERROR: Program executed illegal instruction.. terminating");
errorCondition = true;
if (juce_isRunningAsApplication())
Process::terminate();
}
else
{
if (juce_isRunningAsApplication())
exit(0);
}
}
//==============================================================================
void MessageManager::doPlatformSpecificInitialisation()
{
// Initialise xlib for multiple thread support
static bool initThreadCalled = false;
if (! initThreadCalled)
{
if (! XInitThreads())
{
// This is fatal! Print error and closedown
Logger::outputDebugString ("Failed to initialise xlib thread support.");
if (juce_isRunningAsApplication())
Process::terminate();
return;
}
initThreadCalled = true;
}
// This is called if the client/server connection is broken
oldIOErrorHandler = XSetIOErrorHandler (ioErrorHandler);
// This is called if a protocol error occurs
oldErrorHandler = XSetErrorHandler (errorHandler);
// Install signal handler for break-in
struct sigaction saction;
sigset_t maskSet;
sigemptyset (&maskSet);
saction.sa_handler = sig_handler;
saction.sa_mask = maskSet;
saction.sa_flags = 0;
sigaction (SIGINT, &saction, NULL);
#ifndef _DEBUG
// Setup signal handlers for various fatal errors
sigaction (SIGILL, &saction, NULL);
sigaction (SIGBUS, &saction, NULL);
sigaction (SIGFPE, &saction, NULL);
sigaction (SIGSEGV, &saction, NULL);
sigaction (SIGSYS, &saction, NULL);
#endif
internal_message_queue = new InternalMessageQueue();
String displayName (getenv ("DISPLAY"));
if (displayName.isEmpty())
displayName = T(":0.0");
display = XOpenDisplay (displayName);
if (display == 0)
{
// This is no more fatal! check if display was opened with Desktop::isAvailable()
/*Logger::outputDebugString ("Failed to open the X display.");
if (juce_isRunningAsApplication())
Process::terminate();*/
return;
}
// Get defaults for various properties
int screen = DefaultScreen (display);
Window root = RootWindow (display, screen);
Visual* visual = DefaultVisual (display, screen);
// Create atoms for our ClientMessages (these cannot be deleted)
specialId = XInternAtom (display, SpecialAtom, false);
broadcastId = XInternAtom (display, BroadcastAtom, false);
specialCallbackId = XInternAtom (display, SpecialCallbackAtom, false);
// Create a context to store user data associated with Windows we
// create in WindowDriver
improbableNumber = XUniqueContext();
// We're only interested in client messages for this window
// which are always sent
XSetWindowAttributes swa;
swa.event_mask = NoEventMask;
// Create our message window (this will never be mapped)
juce_messageWindowHandle = XCreateWindow (display, root,
0, 0, 1, 1, 0, 0, InputOnly,
visual, CWEventMask, &swa);
}
void MessageManager::doPlatformSpecificShutdown()
{
if (internal_message_queue) {
deleteAndZero(internal_message_queue);
}
if (errorCondition == false && display)
{
XDestroyWindow (display, juce_messageWindowHandle);
XCloseDisplay (display);
// reset pointers
juce_messageWindowHandle = 0;
display = 0;
// Restore original error handlers
XSetIOErrorHandler (oldIOErrorHandler);
oldIOErrorHandler = 0;
XSetErrorHandler (oldErrorHandler);
oldErrorHandler = 0;
}
}
bool juce_postMessageToSystemQueue (void* message)
{
if (errorCondition)
return false;
internal_message_queue->postMessage((Message*)message);
return true;
}
bool juce_postMessageToX11Queue(void *message)
{
XClientMessageEvent clientMsg;
clientMsg.display = display;
clientMsg.window = juce_messageWindowHandle;
clientMsg.type = ClientMessage;
clientMsg.format = 32;
clientMsg.message_type = specialId;
#if JUCE_64BIT
clientMsg.data.l[0] = (long) (0x00000000ffffffff & (((uint64) message) >> 32));
clientMsg.data.l[1] = (long) (0x00000000ffffffff & (long) message);
#else
clientMsg.data.l[0] = (long) message;
#endif
XSendEvent (display, juce_messageWindowHandle, false,
NoEventMask, (XEvent*) &clientMsg);
XFlush (display); // This is necessary to ensure the event is delivered
return true;
}
void MessageManager::broadcastMessage (const String& value) throw()
{
/* TODO */
}
struct InternalCallFunctionMessage : public Message {
MessageThreadFuncCall *ctx;
InternalCallFunctionMessage(MessageThreadFuncCall *ctx_) : ctx(ctx_) {
intParameter1 = 0x73774623;
}
};
void* MessageManager::callFunctionOnMessageThread (MessageCallbackFunction* func,
void* parameter)
{
void* retVal = 0;
if (! errorCondition)
{
if (! isThisTheMessageThread())
{
static MessageThreadFuncCall messageFuncCallContext;
const ScopedLock sl (messageFuncCallContext.lock);
messageFuncCallContext.func = func;
messageFuncCallContext.parameter = parameter;
internal_message_queue->postMessage(new InternalCallFunctionMessage(&messageFuncCallContext));
// Wait for it to complete before continuing
messageFuncCallContext.event.wait();
retVal = messageFuncCallContext.result;
}
else
{
// Just call the function directly
retVal = func (parameter);
}
}
return retVal;
}
/* wait for an event (either XEvent, or an internal Message) */
static bool juce_sleepUntilEvent(int timeout_ms)
{
if (display && XPending(display)) return true;
if (!internal_message_queue->isEmpty()) return true;
struct timeval tv;
tv.tv_sec=0;
tv.tv_usec=timeout_ms * 1000;
int fd0 = internal_message_queue->getWaitHandle();
int fdmax = fd0;
fd_set readset;
FD_ZERO(&readset);
FD_SET(fd0, &readset);
if (display) {
int fd1 = XConnectionNumber(display);
FD_SET(fd1, &readset);
fdmax = jmax(fd0, fd1);
}
int ret = select( fdmax+1, &readset, 0, 0, &tv );
return (ret > 0); // ret <= 0 if error or timeout
}
/* handle next XEvent (if any) /
static bool juce_dispatchNextXEvent()
{
if (!display || !XPending(display)) return false;
XEvent evt;
XNextEvent(display, &evt);
/ requires my version of juce_linux_Clipboard.cpp /
//if (evt.type == SelectionRequest && evt.xany.window == juce_messageWindowHandle) {
// juce_x11_handleSelectionRequest(evt.xselectionrequest);
//} else if (evt.type == SelectionClear && evt.xany.window == juce_messageWindowHandle) {
// / another window just grabbed the selection – we just don’t care */
//} else
if (evt.xany.window != juce_messageWindowHandle) {
juce_windowMessageReceive (&evt);
}
return true;
}
/* handle next internal Message (if any) /
static bool juce_dispatchNextInternalMessage()
{
if (internal_message_queue->isEmpty()) return false;
Message m = internal_message_queue->popMessage();
InternalCallFunctionMessage cfm = 0;
/ handle function call /
if (m->intParameter1 == 0x73774623 && (cfm = dynamic_cast<InternalCallFunctionMessage>(m))) {
MessageThreadFuncCall const call = cfm->ctx;
MessageCallbackFunction func = call->func;
call->result = (func) (call->parameter);
call->event.signal();
deleteAndZero(m);
} else {
/ handle “normal” messages */
MessageManager::getInstance()->deliverMessage(m);
}
return true;
}
/* this function expects that it will NEVER be called simultaneously for two concurrent threads */
bool juce_dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages)
{
while (1) {
if (errorCondition)
return false;
if (breakIn) {
errorCondition = true;
if (juce_isRunningAsApplication())
Process::terminate();
return false;
}
static int event_cnt=0;
++event_cnt;
/* the purpose here is to give either priority to XEvents or
to internal messages This is necessary to keep a "good"
behaviour when the cpu is overloaded
*/
if ((event_cnt & 1)) {
if (juce_dispatchNextXEvent() || juce_dispatchNextInternalMessage()) return true;
} else {
if (juce_dispatchNextInternalMessage() || juce_dispatchNextXEvent()) return true;
}
if (returnIfNoPendingMessages) // early exit
return false;
juce_sleepUntilEvent(4000 /* milliseconds */); // the timeout is to be on the safe side, but it does not seem to be useful
}
return true;
}
END_JUCE_NAMESPACE
[/code]
(I don’t claim any copyright on anything in this file, so anyone is free to do whatever they want with my changes)
As you can see , it is really simple indeed. Just an OwnedArray for the queue of internal juce messages, a pair of sockets for notifying the event loop. The juce_dispatchNextMessageOnSystemQueue now waits for 2 types of events instead of only XEvents. The only significant change, is that the order of events is not the same as it used to be since there are two events queues instead of one, and no queue should starve the other when they are both flooded with events (see the comment about the “event_cnt” variable). I have built the juce demo, the juce plugin host, and my own application and did not notice any regression.
A side effect is that is fixes the issue that I mentionned here:
http://www.rawmaterialsoftware.com/juceforum/viewtopic.php?t=4000 (I have spent days on this one)
(As a side note, I now believe that this issue was due to the non-threadsafe use of X11 functions – as far as I understand, when more that one thread may simultaneously use the same ‘display*’ object, each X function call should be protected by XLockDisplay()/XUnlockDisplay() . By default these function do nothing, except when XInitThreads has been called before XOpenDisplay)
Other small changes that I had to do to allow my app to run “headless”, is to add a function Desktop::isAvailable() function which always return juce_isRunningAsApplication() on windows and mac, and also tests if display != 0 on linux:
bool Desktop::isAvailable() throw() {
return juce_isRunningAsApplication() && display != 0;
}
And to allow the app to startup when no DISPLAY is available, I had to add “if (!display) return;” at the beginning of juce_updateMultiMonitorInfo .
(this is not the part i am most proud of , to be honest)
Jules: when you have time, (and after a few weeks in order to let me test this a bit more), I hope that you will review this change to the juce_linux_Messaging.cpp file. I think that fixing the thread-safety problem of the original juce_postMessageToSystemQueue is enough to justify it.