|
Magic Mailing List |
|
From: R. Timothy Edwards (tim AT stravinsky DOT jhuapl.edu) Date: Tue Feb 26 2002 - 10:13:26 EST
Dear Michael,
> Could I just remind you that X11 is not thread-safe. I learned
> this the hard way -- it is not clearly documented. And, it
> does make multi-threading Magic a bit more work: a thread
> running DRC could not directly cause paint.
I found out a way to get around the non-thread-safe issue using a
tricky bit of interrupt signalling. It took a bit more work due
to the lack of documentation, but I eventually determined that the
lack of thread-safeness is due to the fact that X splits "events"
up into two types: one type gets caught by the X event loop and
processed by the user, the other gets processed from inside
XNextEvent(). So while one would normally thing that the event
loop thread should protect itself by:
while(1) {
XtNextEvent(&event);
pthread_mutex_lock(&mutex);
XtDispatchEvent(&event);
pthread_mutex_unlock(&mutex);
}
it turns out that XtNextEvent() (or XNextEvent()) appends things
to the X11 event queue itself, and this is what causes X11 to
suddenly produce "unexpected async reply" messages and lock up
the program. But the blocking select() function is inside
XNextEvent, so you can't put the mutex lock around that. The
way around it is to make sure that any other thread that wants
to make X11 calls first interrupts XNextEvent, forcing it to
stall until it can aquire the mutex lock again. So the X
event loop becomes:
.
.
.
xloopid = getpid();
signal(SIGIO, xloop_lockout);
while(1) {
XtNextEvent(&event);
pthread_mutex_lock(&mutex);
XtDispatchEvent(&event);
pthread_mutex_unlock(&mutex);
}
.
.
.
and the routines needed to handle the interrupting are:
/*----------------------------------------------*/
void xloop_lockout(int signum)
{
pthread_mutex_lock(&mutex);
pthread_mutex_unlock(&mutex);
}
/*----------------------------------------------*/
void xloop_wait()
{
kill(xloopid, SIGIO);
}
/*----------------------------------------------*/
Any other thread making X11 calls needs to do the
following sequence of calls:
.
.
.
pthread_mutex_lock(&mutex);
xloop_wait();
/* Make X11 calls here */
pthread_mutex_unlock(&mutex);
.
.
.
The pthread_mutex_lock() call waits for any other thread
to finish. Then xloop_wait() causes the X event loop to
break out of XNextEvent() and run xloop_lockout().
xloop_lockout() attempts to grab the mutex lock, but
can't do it until the other thread finishes making X11
calls. So the lock/unlock pair in xloop_lockout() stalls
the X event loop, preventing it from making "hidden" X11
calls while the other thread is doing the same.
I implemented this code in magic, changing the "X11Helper"
from a forked process into a separate thread. Most of
the X11 calls which originate from outside the X event
loop thread are wrapped in a GrLock/GrUnlock pair, which
makes a convenient place to put the mutex_lock/mutex_unlock
calls.
---Tim
|
|
|
|