Milán Major

netscape-revival

1 branch
Code

lib/libjava/lj_thred.c

/**********************************************************************
 *
 *  code that allows two threads to merge for a little
 *  while, used by mocha/java glue.
 *
 **********************************************************************/

#include "lj.h"
#include "java.h"
#include "jri.h"

#include "prthread.h"
#include "prmem.h"
#include "prmon.h"
#include "prlog.h"


/* state of each thread */
typedef enum {
    LJ_FREE,		/* not participating yet */
    LJ_RUN,		/* running */
    LJ_WAIT,		/* not done yet, wait for notify */
    LJ_DONE,		/* done, threads go their separate ways */
    LJ_CALL		/* thread has to do something */
} LJState;

/* indices into the state array */
enum {
    LJ_MOZILLA = 0,
    LJ_JAVA = 1
};

/* LJThread represents the relationship between java and mocha code
 * that are running in separate threads but making synchronous calls
 * to each other.
 *
 * (it isn't necessary if the java code is running in the mozilla thread?)
 */
typedef struct {
    /* only one java thread may be synching with mozilla at once */
    PRMonitor		ownerLock;
    JRIEnv		*owner;

    /* we have two monitors here because we need two conditions.
     * but we always grab both monitors together (mozilla then java).
     * it's cheaper to grab extra locks than it is to respond to
     * unnecessary notifications */
    PRMonitor		monitors[2];  /* LJ_MOZILLA or LJ_JAVA */

    /* the state of the two-thread system is encoded into two
     * state variables.  however, there are only six interesting
     * states (of the form Mozilla|Java):
     *    wait|run    - java is running
     *    wait|call   - java should execute the "doit" action
     *    wait|done   - java can return (mozilla is finished)
     *     run|wait   - mozilla is running
     *    call|wait   - mozilla should execute the "doit" action
     *    done|wait   - mozilla can return (java is finished)
     */
    LJState		states[2];  /* LJ_MOZILLA or LJ_JAVA */

    /* if state == LJ_CALL, call this callback */
    LJCallback		doit;
    void		*data;

    /* XXX should probably record the java thread id too */
} LJThread;

/* global instance, there can be only one */
static LJThread _lj_javasync;
LJThread *lj_javasync = 0;

static void lj_Run(LJThread *ljt, int us);
static void lj_CallOther(JRIEnv *env, int us, LJCallback doit, void *data);

/* initialize this part of the system */
void
lj_InitThread()
{
    /* _lj_javasync is static to avoid a PR_NEWZAP */
    lj_javasync = &_lj_javasync;

    /* XXX Kipp sez PR_InitMonitor's going away. */
    PR_InitMonitor(&lj_javasync->ownerLock, 0, "mozillas-pal-java");
    PR_InitMonitor(&lj_javasync->monitors[LJ_MOZILLA],
                   0, "mozilla-waits-on-java");
    PR_InitMonitor(&lj_javasync->monitors[LJ_JAVA],
                   0, "java-waits-on-mozilla");

    lj_javasync->owner = 0;
}

/* call mozilla from a java thread */
/* CALLED FROM THE JAVA THREAD ONLY */
void
LJ_CallMozilla(JRIEnv *env, LJCallback doit, void *data)
{
    /* XXX should have an assertion checking that the env
     * matches PR_CurrentThread() */

    /* XXX assert that the rusty lock is open */

    /* if the java env is the same as the mozilla env, things
     * are easy */
    if (env == mozenv) {
#ifdef XP_UNIX
        /* entering unsafe code */
        PR_XLock();
#endif
        doit(data);
#ifdef XP_UNIX
        /* leaving unsafe code */
        PR_XUnlock();
#endif
        return;
    }

    lj_CallOther(env, LJ_JAVA, doit, data);
}

/* call java from mozilla, using a particular java thread */
/* CALLED FROM MOZILLA THREAD ONLY */
void
LJ_CallJava(JRIEnv *env, LJCallback doit, void *data)
{
    /* XXX assert that the current thread is the mozilla thread */

#ifdef XP_UNIX
    /* entering safe code */
    PR_XUnlock();
#endif

    /* if the java env is the same as the mozilla env, things
     * are easy */
    if (env == mozenv)
        doit(data);
    else
        lj_CallOther(env, LJ_MOZILLA, doit, data);

#ifdef XP_UNIX
    /* leaving safe code */
    PR_XLock();
#endif
}

/*
 * this event is sent to mozilla to capture its thread
 */

typedef struct MozillaEvent_SlaveToJava {
    MWContextEvent	ce;
    LJThread		*ljt;
} MozillaEvent_SlaveToJava;

/* this is what mozilla calls when it gets the initial event
 * telling it to execute some mocha code.  once we arrive
 * here, we have control of both the mozilla and the java threads
 * and the fun can really begin */
/* CALLED FROM MOZILLA THREAD ONLY */
static void
lj_HandleEvent_SlaveToJava(MozillaEvent_SlaveToJava* e)
{
    LJThread *ljt = e->ljt;

#ifdef XP_UNIX
    /* entering safe code */
    PR_XUnlock();
#endif

    /* initial state is call|wait */

    /* go into the main loop for mozilla, starting by doing
     * whatever the java thread wanted us to do */
    lj_Run(ljt, LJ_MOZILLA);

    /* state now should be done|done */

    /* once we come back to here, synchronization is over and
     * the two threads can go their merry ways */

#ifdef XP_UNIX
    /* leaving safe code */
    PR_XLock();
#endif

    /* return to the event loop */
}

static void
lj_DestroyEvent_SlaveToJava(MozillaEvent_SlaveToJava* event)
{
    XP_FREE(event);
}

static void
LJ_PostSlaveToJava(LJThread *ljt)
{
    MozillaEvent_SlaveToJava* event = PR_NEW(MozillaEvent_SlaveToJava);
    if (!event) {
        /* XXX java thread calling us will wait forever for mozilla
         * to receive the event.  what else can we do? */
        /* make another attempt for the Mac */
        event = PR_NEW(MozillaEvent_SlaveToJava);
        if (!event)
            return;
    }
    PR_InitEvent(&event->ce.event,
                 (PRHandleEventProc)lj_HandleEvent_SlaveToJava,
                 (PRDestroyEventProc)lj_DestroyEvent_SlaveToJava);

    /* XXX we don't really have a context in mind, we just want
     * the thread. */
    event->ce.context = 0;
    event->ljt = ljt;
    PR_PostEvent(mozilla_event_queue, &event->ce.event);
}

/* this routine is sort of the main loop for both of the
 * cooperating threads.  "us" is the LJThreadMonitor for
 * the currently running thread, "them" is the other one.
 */
/* CALLED FROM EITHER THREAD */
static void
lj_Run(LJThread *ljt, int us)
{
    int them = 1 - us;
    PRBool done;
    LJCallback doit;
    void *data;

    /* event loop */
    do {
        done = PR_FALSE;

        /* this Enter is matched by an Exit in each branch of the switch */
        PR_EnterMonitor(&ljt->monitors[us]);

        switch (ljt->states[us]) {

        case LJ_CALL:   /* call|wait -> run|wait -> wait|done */
            ljt->states[us] = LJ_RUN;

            /* make copies of data we need from inside the
             * monitor and release it */
            doit = ljt->doit;
            data = ljt->data;
            PR_ExitMonitor(&ljt->monitors[us]);
            
            /* do what was asked, outside the monitor */
            /* if this results in a call to the other side, it must
             * set them->state=LJ_DOIT and us->state=LJ_WAIT, then
             * recursively enter lj_Run */
#ifdef XP_UNIX
            if (us == LJ_MOZILLA)
                /* entering unsafe code */
                PR_XLock();
#endif
            doit(data);
#ifdef XP_UNIX
            if (us == LJ_MOZILLA)
                /* leaving unsafe code */
                PR_XUnlock();
#endif

            /* notify the other thread that we're done, and
             * wait for their next order */

            PR_EnterMonitor(&ljt->monitors[us]);
            ljt->states[us] = LJ_WAIT;
            PR_ExitMonitor(&ljt->monitors[us]);

            PR_EnterMonitor(&ljt->monitors[them]);
            ljt->states[them] = LJ_DONE;
            PR_Notify(&ljt->monitors[them]);
            PR_ExitMonitor(&ljt->monitors[them]);

            break;

        case LJ_WAIT:	/* wait|? */
            /* the other thread is active on our behalf */
            PR_Wait(&ljt->monitors[us], LL_MAXINT);

            PR_ExitMonitor(&ljt->monitors[us]);

            break;

        case LJ_DONE:	/* done|wait -> run|wait */
            done = PR_TRUE;
            ljt->states[us] = LJ_RUN;

            PR_ExitMonitor(&ljt->monitors[us]);

            break;

        default:	/* uh oh */
	    done = PR_TRUE;
            fprintf(stderr, "unexpected state %d|%d in LJThread, us=%d\n",
                    &ljt->states[LJ_MOZILLA], &ljt->states[LJ_JAVA], us);
            PR_ExitMonitor(&ljt->monitors[us]);
            break;
        }
    } while (!done);
}

/* this is what a thread calls when it wants to run some code
 * in the other thread, after the initial rendezvous.
 * it delegates the call to the other thread and tells it to run,
 * then waits for it to come back.
 */
/* THIS MUST BE CALLED BY JAVA FIRST, SINCE MOZILLA CAN'T LURE
 * A JAVA THREAD INTO A RENDEZVOUS */
/* CALLED FROM EITHER THREAD */
static void
lj_CallOther(JRIEnv *env, int us, LJCallback doit, void *data)
{
    int them = 1 - us;
    PRBool owned = PR_TRUE;
    LJThread *ljt = lj_javasync;
    PRBool java_called_mozilla_first = PR_TRUE;

    /* otherwise, life is more complicated.  start by making
     * sure that the given env has exclusive access to mozilla */
    PR_EnterMonitor(&ljt->ownerLock);
    while (ljt->owner != env) {
        /* if this is the mozilla thread and we haven't rendezvoused
         * already there is no way to proceed */
        if (us == LJ_MOZILLA) {
            java_called_mozilla_first = PR_FALSE;
            break;
        }
        if (ljt->owner) {        /* someone else has it */
            PR_Wait(&ljt->ownerLock, LL_MAXINT);
        } else {
            ljt->owner = env;
            owned = PR_FALSE;
        }
    }
    PR_ExitMonitor(&ljt->ownerLock);

    /* if you die here, you tried to initiate the java/mozilla
     * connection from the mozilla side, which can't be done */
    /* XXX we should really fail utterly regardless of DEBUG */
    PR_ASSERT(java_called_mozilla_first);

    /* if the connection was already made, our state is run|wait,
     * otherwise it's unknown.  either way, the state we want
     * to reach is wait|call */

    PR_EnterMonitor(&ljt->monitors[us]);
    /* this thread starts by waiting */
    ljt->states[us] = LJ_WAIT;
    PR_ExitMonitor(&ljt->monitors[us]);

    PR_EnterMonitor(&ljt->monitors[them]);

    /* tell the other thread what to do */
    ljt->states[them] = LJ_CALL;
    ljt->doit = doit;
    ljt->data = data;

    /* if we just claimed the lj_javasync structure, we have to
     * rendezvous first.  a java thread can rendezvous with mozilla by
     * sending a callback event, but the other way is impossible */
    if (!owned) {
        /* put out the bait which will lure mozilla into our trap */
        LJ_PostSlaveToJava(ljt);
    } else {
        PR_Notify(&ljt->monitors[them]);
    }

    PR_ExitMonitor(&ljt->monitors[them]);

    /* wait for the other thread to finish or ask us for
     * something by entering lj_Run (possibly recursively) */
    lj_Run(ljt, us);

    /* if this was the first call (from java to mozilla) we
     * release mozilla and the ljthread afterward */
    if (us == LJ_JAVA && !owned) {
        /* free the monster! */
        PR_EnterMonitor(&ljt->monitors[LJ_MOZILLA]);
        ljt->states[LJ_MOZILLA] = LJ_DONE;
        PR_Notify(&ljt->monitors[LJ_MOZILLA]);
        PR_ExitMonitor(&ljt->monitors[LJ_MOZILLA]);

        PR_EnterMonitor(&ljt->ownerLock);
        ljt->owner = 0;
        PR_Notify(&ljt->ownerLock);
        PR_ExitMonitor(&ljt->ownerLock);
    }
}

/* return PR_TRUE if the mozilla thread is running and able to handle
 * requests.  if it returns PR_FALSE, then any synchronous network
 * activity (i.e. java stuf) will deadlock the browser.
 * see also sun-java/netscape/net/inStr.c */
PRBool
LJ_MozillaRunning(JRIEnv *env)
{
    /* mac doesn't have this problem */
    extern PRThread* mozilla_thread;
    PRThread *curthread = PR_CurrentThread();
    /* if this is the mozilla thread, then mozilla is not
     * available to service any requests we might make */
    if (curthread == mozilla_thread
	/* if the current thread owns the lj_javasync structure,
	 * then mozilla is tied up in a mocha call somewhere.
	 * we could look at lj_javasync->monitors[0] to see if
	 * the moz thread is waiting there but this should work
	 * as well. */
	|| lj_javasync->owner == env)
      return PR_TRUE;
    return PR_FALSE;
}