/* gthread-jni.c -- JNI threading routines for GLIB Copyright (C) 1998, 2004 Free Software Foundation, Inc. This file is part of GNU Classpath. GNU Classpath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Linking this library statically or dynamically with other modules is making a combined work based on this library. Thus, the terms and conditions of the GNU General Public License cover the whole combination. As a special exception, the copyright holders of this library give you permission to link this library with independent modules to produce an executable, regardless of the license terms of these independent modules, and to copy and distribute the resulting executable under terms of your choice, provided that you also meet, for each linked independent module, the terms and conditions of the license of that module. An independent module is a module which is not derived from or based on this library. If you modify this library, you may extend this exception to your version of the library, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ /************************************************************************/ /* Header */ /************************************************************************/ /* * @author Julian Dolby (dolby@us.ibm.com) * @date February 7, 2003 implemented for GLIB v.1 * * * @author Steven Augart * , * @date April 30, 2004 -- May 10 2004: Support new functions for Glib v.2, * fix cond_wait to free and re-acquire the mutex, * replaced trylock stub implementation with a full one. * * This code implements the GThreadFunctions interface for GLIB using * Java threading primitives. All of the locking and conditional variable * functionality required by GThreadFunctions is implemented using the * monitor and wait/notify functionality of Java objects. The thread- * local functionality uses the java.lang.ThreadLocal class. * * Classpath's AWT support uses GTK+ peers. GTK+ uses GLIB. GLIB by default * uses the platform's native threading model -- pthreads in most cases. If * the Java runtime doesn't use the native threading model, then it needs this * code in order to use Classpath's (GTK+-based) AWT routines. * * This code should be portable; I believe it makes no assumptions * about the underlying VM beyond that it implements the JNI functionality * that this code uses. * * Currently, use of this code is governed by the configuration option * --enable-portable-native-sync. We will soon add a VM hook so the VM can * select which threading model it wants to use at run time; at that point, * the configuration option will go away. * * The code in this file uses only JNI 1.1, except for one JNI 1.2 function: * GetEnv, in the JNI Invocation API. (There seems to be no way around using * GetEnv). * * ACKNOWLEDGEMENT: * * I would like to thank Mark Wielaard for his kindness in spending at least * six hours of his own time in reviewing this code and correcting my GNU * coding and commenting style. --Steve Augart * * * NOTES: * * This code has been tested with Jikes RVM and with Kaffe. * * This code should have proper automated unit tests. I manually tested it * by running an application that uses AWT. --Steven Augart * * MINOR NIT: * * - Using a jboolean in the arglist to "throw()" and "rethrow()" * triggers many warnings from GCC's -Wconversion operation, because that * is not the same as the conversion (upcast to an int) that would occur in * the absence of a prototype. * * It would be very slightly more efficient to just pass the jboolean, but * is not worth the clutter of messages. The right solution would be to * turn off the -Wconversion warning for just this file, *except* that * -Wconversion also warns you against constructs such as: * unsigned u = -1; * and that is a useful warning. So I went from a "jboolean" to a * "gboolean" (-Wconversion is not enabled by default for GNU Classpath, * but it is in my own CFLAGS, which, for gcc 3.3.3, read: -pipe -ggdb3 -W * -Wall -Wbad-function-cast -Wcast-align -Wpointer-arith -Wcast-qual * -Wshadow -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations * -fkeep-static-consts -fkeep-inline-functions -Wundef -Wwrite-strings * -Wno-aggregate-return -Wmissing-noreturn -Wnested-externs -Wtrigraphs * -Wconversion -Wsign-compare -Wno-float-equal -Wmissing-format-attribute * -Wno-unreachable-code -Wdisabled-optimization ) */ #include /************************************************************************/ /* Configuration */ /************************************************************************/ /** Tracing and Reporting **/ #define TRACE_API_CALLS 0 /* announce entry and exit into each method, by printing to stderr. */ #define TRACE_MONITORS 0 /* Every enterMonitor() and exitMonitor() goes to stderr. */ /** Trouble handling. There is a discussion below of this. **/ #define EXPLAIN_TROUBLE 1 /* Describe any unexpected trouble that happens. This is a superset of EXPLAIN_BROKEN, and if set trumps an unset EXPLAIN_BROKEN. It is not a strict superset, since at the moment there is no TROUBLE that is not also BROKEN. Use criticalMsg() to describe the problem. */ #define EXPLAIN_BROKEN 1 /* Describe trouble that is serious enough to be BROKEN. (Right now all trouble is at least BROKEN.) */ /* There is no EXPLAIN_BADLY_BROKEN definition. We always explain BADLY_BROKEN trouble, since there is no other way to report it. */ /** Error Handling **/ #define DIE_IF_BROKEN 1 /* Dies if serious trouble happens. There is really no non-serious trouble, except possibly problems that arise during pthread_create, which are reported by a GError. If you do not set DIE_IF_BROKEN, then trouble will raise a Java RuntimeException. We probably do want to die right away, since anything that's BROKEN really indicates a programming error or a system-wide error, and that's what the glib documentation says you should do in case of that kind of error in a glib-style function. But it does work to turn this off. */ #if DIE_IF_BROKEN #define DIE_IF_BADLY_BROKEN 1 /* DIE_IF_BROKEN implies DIE_IF_BADLY_BROKEN */ #else #define DIE_IF_BADLY_BROKEN 1 /* Die if the system is badly broken -- that is, if we have further trouble while attempting to throw an exception upwards, or if we are unable to generate one of the classes we'll need in order to throw wrapped exceptions upward. If unset, we will print a warning message, and limp along anyway. Not that the system is likely to work. */ #endif /** Performance tuning parameters **/ #define ENABLE_EXPENSIVE_ASSERTIONS 0 /* Enable expensive assertions? */ #define DELETE_LOCAL_REFS 1 /* Whether to delete local references. JNI only guarantees that there wil be 16 available. (Jikes RVM provides an number only limited by VM memory.) Jikes RVM will probably perform faster if this is turned off, but other VMs may need this to be turned on in order to perform at all, or might need it if things change. Remember, we don't know how many of those local refs might have already been used up by higher layers of JNI code that end up calling g_thread_self(), g_thread_set_private(), and so on. We set this to 1 for GNU Classpath, since one of our principles is "always go for the most robust implementation" */ #define HAVE_JNI_VERSION_1_2 0 /* Assume we don't. We could dynamically check for this. We will assume JNI 1.2 in later versions of Classpath. As it stands, the code in this file already needs one JNI 1.2 function: GetEnv, in the JNI Invocation API. TODO This code hasn't been tested yet. And really hasn't been implemented yet. */ /************************************************************************/ /* Global data */ /************************************************************************/ #if defined HAVE_STDINT_H #include /* provides intptr_t */ #elif defined HAVE_INTTYPES_H #include #endif #include /* va_list */ #include #include "gthread-jni.h" #include /* assert() */ /* For Java thread priority constants. */ #include /* Since not all JNI header generators actually define constants we define them here explicitly. */ #ifndef gnu_java_awt_peer_gtk_GThreadNativeMethodRunner_MIN_PRIORITY #define gnu_java_awt_peer_gtk_GThreadNativeMethodRunner_MIN_PRIORITY 1 #endif #ifndef gnu_java_awt_peer_gtk_GThreadNativeMethodRunner_NORM_PRIORITY #define gnu_java_awt_peer_gtk_GThreadNativeMethodRunner_NORM_PRIORITY 5 #endif #ifndef gnu_java_awt_peer_gtk_GThreadNativeMethodRunner_MAX_PRIORITY #define gnu_java_awt_peer_gtk_GThreadNativeMethodRunner_MAX_PRIORITY 10 #endif /* The VM handle. This is set in Java_gnu_java_awt_peer_gtk_GtkMainThread_gtkInit */ JavaVM *cp_gtk_the_vm; /* Unions used for type punning. */ union env_union { void **void_env; JNIEnv **jni_env; }; union func_union { void *void_func; GThreadFunc g_func; }; /* Forward Declarations for Functions */ static int threadObj_set_priority (JNIEnv * env, jobject threadObj, GThreadPriority gpriority); static void fatalMsg (const char fmt[], ...) __attribute__ ((format (printf, 1, 2))) __attribute__ ((noreturn)); static void criticalMsg (const char fmt[], ...) __attribute__ ((format (printf, 1, 2))); static void tracing (const char fmt[], ...) __attribute__ ((format (printf, 1, 2))); static jint javaPriorityLevel (GThreadPriority priority) __attribute__ ((const)); /************************************************************************/ /* Trouble-handling, including utilities to reflect exceptions */ /* back to the VM. Also some status reporting. */ /************************************************************************/ /* How are we going to handle problems? There are several approaches: 1) Report them with the GError mechanism. (*thread_create)() is the only one of these functions that takes a GError pointer. And the only G_THREAD error defined maps onto EAGAIN. We don't have any errors in our (*thread_create)() implementation that can be mapped to EAGAIN. So this idea is a non-starter. 2) Reflect the exception back to the VM, wrapped in a RuntimeException. This will fail sometimes, if we're so broken (BADLY_BROKEN) that we fail to throw the exception. 3) Abort execution. This is what the glib functions themselves do for errors that they can't report via GError. Enable DIE_IF_BROKEN and/or DIE_IF_BADLY_BROKEN to make this the default for BROKEN and/or BADLY_BROKEN trouble. 4) Display messages to stderr. We always do this for BADLY_BROKEN trouble. The glib functions do that for errors they can't report via GError. There are some complications. When I attempted to report a problem in g_thread_self() using g_critical (a macro around g_log(), I found that g_log in turn looks for thread-private data and calls g_thread_self() again. We got a segfault, probably due to stack overflow. So, this code doesn't use the g_critical() and g_error() functions any more. Nor do we use g_assert(); we use the C library's assert() instead. */ #define WHERE __FILE__ ":" G_STRINGIFY(__LINE__) ": " /* This is portable to older compilers that lack variable-argument macros. This used to be just g_critical(), but then we ran into the error reporting problem discussed above. */ static void fatalMsg (const char fmt[], ...) { va_list ap; va_start (ap, fmt); vfprintf (stderr, fmt, ap); va_end (ap); fputs ("\nAborting execution\n", stderr); abort (); } static void criticalMsg (const char fmt[], ...) { va_list ap; va_start (ap, fmt); vfprintf (stderr, fmt, ap); va_end (ap); putc ('\n', stderr); } /* Unlike the other two, this one does not append a newline. This is only used if one of the TRACE_ macros is defined. */ static void tracing (const char fmt[], ...) { va_list ap; va_start (ap, fmt); vfprintf (stderr, fmt, ap); va_end (ap); } #define assert_not_reached() \ do \ { \ fputs(WHERE "You should never get here. Aborting execution.\n", \ stderr); \ abort(); \ } \ while(0) #if DIE_IF_BADLY_BROKEN #define BADLY_BROKEN fatalMsg #else #define BADLY_BROKEN criticalMsg /* So, the user may still attempt to recover, even though we do not advise this. */ #endif /* I find it so depressing to have to use C without varargs macros. */ #define BADLY_BROKEN_MSG WHERE "Something fundamental" \ " to GNU Classpath's AWT JNI broke while we were trying to pass up a Java error message" #define BADLY_BROKEN0() \ BADLY_BROKEN(BADLY_BROKEN_MSG); #define BADLY_BROKEN1(msg) \ BADLY_BROKEN(BADLY_BROKEN_MSG ": " msg) #define BADLY_BROKEN2(msg, arg) \ BADLY_BROKEN(BADLY_BROKEN_MSG ": " msg, arg) #define BADLY_BROKEN3(msg, arg, arg2) \ BADLY_BROKEN(BADLY_BROKEN_MSG ": " msg, arg1, arg2) #define BADLY_BROKEN4(msg, arg, arg2, arg3) \ BADLY_BROKEN(BADLY_BROKEN_MSG ": " msg, arg1, arg2, arg3) #define DELETE_LOCAL_REF(env, ref) \ do \ { \ if ( DELETE_LOCAL_REFS ) \ { \ (*env)->DeleteLocalRef (env, ref); \ (ref) = NULL; \ } \ } \ while(0) /* Cached info for Exception-wrapping */ static jclass runtimeException_class; /* java.lang.RuntimeException */ static jmethodID runtimeException_ctor; /* constructor for it */ /* Throw a new RuntimeException. It may wrap around an existing exception. 1 if we did rethrow, -1 if we had trouble while rethrowing. isBroken is always true in this case. */ static int throw (JNIEnv * env, jthrowable cause, const char *message, gboolean isBroken, const char *file, int line) { jstring jmessage; gboolean describedException = FALSE; /* Did we already describe the exception to stderr or the equivalent? */ jthrowable wrapper; /* allocate local message in Java */ const char fmt[] = "In AWT JNI, %s (at %s:%d)"; size_t len = strlen (message) + strlen (file) + sizeof fmt + 25; char *buf; if (EXPLAIN_TROUBLE || (isBroken && EXPLAIN_BROKEN)) { criticalMsg ("%s:%d: AWT JNI failure%s: %s\n", file, line, isBroken ? " (BROKEN)" : "", message); if (cause) { jthrowable currentException = (*env)->ExceptionOccurred (env); if (cause == currentException) { criticalMsg ("Description follows to System.err:"); (*env)->ExceptionDescribe (env); /* ExceptionDescribe has the side-effect of clearing the pending exception; relaunch it. */ describedException = TRUE; if ((*env)->Throw (env, cause)) { BADLY_BROKEN1 ("Relaunching an exception with Throw failed."); return -1; } } else { DELETE_LOCAL_REF (env, currentException); criticalMsg (WHERE "currentException != cause; something else happened" " while handling an exception."); } } } /* if (EXPLAIN_TROUBLE) */ if (isBroken && DIE_IF_BROKEN) fatalMsg ("%s:%d: Aborting execution; BROKEN: %s\n", file, line, message); if ((buf = malloc (len))) { memset (buf, 0, len); g_snprintf (buf, len, fmt, message, file, line); jmessage = (*env)->NewStringUTF (env, buf); free (buf); } else { jmessage = NULL; } /* Create the RuntimeException wrapper object and throw it. It is OK for CAUSE to be NULL. */ wrapper = (jthrowable) (*env)->NewObject (env, runtimeException_class, runtimeException_ctor, jmessage, cause); DELETE_LOCAL_REF (env, jmessage); if (!wrapper) { /* I think this should only happen: - if there are bugs in my JNI code, or - if the VM is broken, or - if we run out of memory. */ if (EXPLAIN_TROUBLE) { criticalMsg (WHERE "GNU Classpath: JNI NewObject() could not create" " a new java.lang.RuntimeException."); criticalMsg ("We were trying to warn about the following" " previous failure:"); criticalMsg ("%s:%d: %s", file, line, message); criticalMsg ("The latest (NewObject()) exception's description" " follows, to System.err:"); (*env)->ExceptionDescribe (env); } BADLY_BROKEN1 ("Failure of JNI NewObject()" " to make a java.lang.RuntimeException"); return -1; } /* throw it */ if ((*env)->Throw (env, wrapper)) { /* Throw() should just never fail, unless we're in such severe trouble that we might as well die. */ BADLY_BROKEN1 ("GNU Classpath: Failure of JNI Throw to report an Exception"); return -1; } DELETE_LOCAL_REF (env, wrapper); return 1; } /* Rethrow an exception we received, wrapping it with a RuntimeException. 1 if we did rethrow, -1 if we had trouble while rethrowing. CAUSE should be identical to the most recent exception that happened, so that ExceptionDescribe will work. (Otherwise nix.) */ static int rethrow (JNIEnv * env, jthrowable cause, const char *message, gboolean isBroken, const char *file, int line) { assert (cause); return throw (env, cause, message, isBroken, file, line); } /* This function checks for a pending exception, and rethrows it with * a wrapper RuntimeException to deal with possible type problems (in * case some calling piece of code does not expect the exception being * thrown) and to include the given extra message. * * Returns 0 if no problems found (so no exception thrown), 1 if we rethrew an * exception. Returns -1 on failure. */ static int maybe_rethrow (JNIEnv * env, const char *message, gboolean isBroken, const char *file, int line) { jthrowable cause = (*env)->ExceptionOccurred (env); int ret = 0; /* rethrow if an exception happened */ if (cause) { ret = rethrow (env, cause, message, isBroken, file, line); DELETE_LOCAL_REF (env, cause); } return 0; } /* MAYBE_TROUBLE() is used to include a source location in the exception message. Once we have run maybe_rethrow, if there WAS trouble, return TRUE, else FALSE. MAYBE_TROUBLE() is actually never used; all problems that throw exceptions are BROKEN, at least. Nothing is recoverable :(. See the discussion of possible errors at thread_create_jni_impl(). */ #define MAYBE_TROUBLE(_env, _message) \ maybe_rethrow(_env, _message, FALSE, __FILE__, __LINE__) /* MAYBE_TROUBLE(), but something would be BROKEN if it were true. */ #define MAYBE_BROKEN(_env, _message) \ maybe_rethrow(_env, _message, TRUE, __FILE__, __LINE__) /* Like MAYBE_TROUBLE(), TROUBLE() is never used. */ #define TROUBLE(_env, _message) \ rethrow(_env, (*env)->ExceptionOccurred (env), _message, FALSE, \ __FILE__, __LINE__) #define BROKEN(_env, _message) \ rethrow (_env, (*env)->ExceptionOccurred (env), _message, TRUE, \ __FILE__, __LINE__) /* Like MAYBE_TROUBLE(), NEW_TROUBLE() is never used. */ #define NEW_TROUBLE(_env, _message) \ throw (_env, NULL, _message, FALSE, __FILE__, __LINE__) #define NEW_BROKEN(_env, _message) \ throw (_env, NULL, _message, TRUE, __FILE__, __LINE__) /* Like MAYBE_TROUBLE(), RETHROW_CAUSE() is never used. */ #define RETHROW_CAUSE(_env, _cause, _message) \ rethrow (_env, _cause, _message, FALSE, __FILE__, __LINE__) #define BROKEN_CAUSE(_env, _cause, _message) \ rethrow (_env, _cause, _message, TRUE, __FILE__, __LINE__) /* Macros to handle the possibility that someone might have called one of the GThreadFunctions API functions with a Java exception pending. It is generally discouraged to continue to use JNI after a Java exception has been raised. Sun's JNI book advises that one trap JNI errors immediately and not continue with an exception pending. These are #if'd out for these reasons: 1) They do not work in the C '89 subset that Classpath is currently (2004 May 10) sticking to; HIDE_OLD_TROUBLE() includes a declaration that should be in scope for the rest of the function, so it needs a language version that lets you mix declarations and statements. (This could be worked around if it were important.) 2) They chew up more time and resources. 3) There does not ever seem to be old trouble -- the assertion in HIDE_OLD_TROUBLE never goes off. You will want to re-enable them if this code needs to be used in a context where old exceptions might be pending when the GThread functions are called. The implementations in this file are responsible for skipping around calls to SHOW_OLD_TROUBLE() if they've raised exceptions during the call. So, if we reach SHOW_OLD_TROUBLE, we are guaranteed that there are no exceptions pending. */ #if 1 #define HIDE_OLD_TROUBLE(env) \ assert ( NULL == (*env)->ExceptionOccurred (env) ) #define SHOW_OLD_TROUBLE() \ assert ( NULL == (*env)->ExceptionOccurred (env) ) #else /* 0 */ #define HIDE_OLD_TROUBLE(env) \ jthrowable savedTrouble = (*env)->ExceptionOccurred (env); \ (*env)->ExceptionClear (env); #define SHOW_OLD_TROUBLE() do \ { \ assert ( NULL == (*env)->ExceptionOccurred (env) ) \ if (savedTrouble) \ { \ if ((*env)->Throw (env, savedTrouble)) \ BADLY_BROKEN ("ReThrowing the savedTrouble failed"); \ } \ DELETE_LOCAL_REF (env, savedTrouble); \ } while(0) #endif /* 0 */ /* Set up the cache of jclass and jmethodID primitives we need in order to throw new exceptions and rethrow exceptions. We do this independently of the other caching. We need to have this cache set up first, so that we can then report errors properly. If any errors while setting up the error cache, the world is BADLY_BROKEN. May be called more than once. Returns -1 if the cache was not initialized properly, 1 if it was. */ static int setup_exception_cache (JNIEnv * env) { static int exception_cache_initialized = 0; /* -1 for trouble, 1 for proper init. */ jclass lcl_class; /* a class used for local refs */ if (exception_cache_initialized) return exception_cache_initialized; lcl_class = (*env)->FindClass (env, "java/lang/RuntimeException"); if ( ! lcl_class ) { BADLY_BROKEN1 ("Broken Class library or VM?" " Couldn't find java/lang/RuntimeException"); return exception_cache_initialized = -1; } /* Pin it down. */ runtimeException_class = (jclass) (*env)->NewGlobalRef (env, lcl_class); DELETE_LOCAL_REF (env, lcl_class); if (!runtimeException_class) { BADLY_BROKEN1 ("Serious trouble: could not turn" " java.lang.RuntimeException into a global reference"); return exception_cache_initialized = -1; } runtimeException_ctor = (*env)->GetMethodID (env, runtimeException_class, "", "(Ljava/lang/String;Ljava/lang/Throwable;)V"); if ( ! runtimeException_ctor ) { BADLY_BROKEN1 ("Serious trouble: classpath couldn't find a" " two-arg constructor for java/lang/RuntimeException"); return exception_cache_initialized = -1; } return exception_cache_initialized = 1; } /**********************************************************/ /***** The main cache *************************************/ /**********************************************************/ /** This is a cache of all classes, methods, and field IDs that we use during the run. We maintain a permanent global reference to each of the classes we cache, since otherwise the (local) jclass that refers to that class would go out of scope and possibly be reused in further calls. The permanent global reference also achieves the secondary goal of protecting the validity of the methods and field IDs in case the classes were otherwise unloaded and then later loaded again. Obviously, this will never happen to classes such as java.lang.Thread and java.lang.Object, but the primary reason for maintaining permanent global refs is sitll valid. The code in jnilink.c has a similar objective. TODO: Consider using that code instead. --Steven Augart */ /* All of these are cached classes and method IDs: */ /* java.lang.Object */ static jclass obj_class; /* java.lang.Object */ static jmethodID obj_ctor; /* no-arg Constructor for java.lang.Object */ static jmethodID obj_notify_mth; /* java.lang.Object.notify() */ static jmethodID obj_notifyall_mth; /* java.lang.Object.notifyall() */ static jmethodID obj_wait_mth; /* java.lang.Object.wait() */ static jmethodID obj_wait_nanotime_mth; /* java.lang.Object.wait(JI) */ /* GThreadMutex and its methods */ static jclass mutex_class; static jmethodID mutex_ctor; static jfieldID mutex_lockForPotentialLockers_fld; static jfieldID mutex_potentialLockers_fld; /* java.lang.Thread and its methods*/ static jclass thread_class; /* java.lang.Thread */ static jmethodID thread_current_mth; /* Thread.currentThread() */ static jmethodID thread_equals_mth; /* Thread.equals() */ static jmethodID thread_join_mth; /* Thread.join() */ static jmethodID thread_setPriority_mth; /* Thread.setPriority() */ static jmethodID thread_stop_mth; /* Thread.stop() */ static jmethodID thread_yield_mth; /* Thread.yield() */ /* java.lang.ThreadLocal and its methods */ static jclass threadlocal_class; /* java.lang.ThreadLocal */ static jmethodID threadlocal_ctor; /* Its constructor */ static jmethodID threadlocal_set_mth; /* ThreadLocal.set() */ static jmethodID threadlocal_get_mth; /* ThreadLocal.get() */ /* java.lang.Long and its methods */ static jclass long_class; /* java.lang.Long */ static jmethodID long_ctor; /* constructor for it: (J) */ static jmethodID long_longValue_mth; /* longValue()J */ /* GThreadNativeMethodRunner */ static jclass runner_class; static jmethodID runner_ctor; static jmethodID runner_threadToThreadID_mth; static jmethodID runner_threadIDToThread_mth; static jmethodID runner_deRegisterJoinable_mth; static jmethodID runner_start_mth; /* Inherited Thread.start() */ /* java.lang.InterruptedException */ static jclass interrupted_exception_class; /* Returns a negative value if there was trouble during initialization. Returns a positive value of the cache was initialized correctly. Never returns zero. */ static int setup_cache (JNIEnv * env) { jclass lcl_class; static int initialized = 0; /* 1 means initialized, 0 means uninitialized, -1 means mis-initialized */ if (initialized) return initialized; /* make sure we can report on trouble */ if (setup_exception_cache (env) < 0) return initialized = -1; #ifdef JNI_VERSION_1_2 if (HAVE_JNI_VERSION_1_2) assert ( ! (*env)->ExceptionCheck (env)); else #endif assert ( ! (*env)->ExceptionOccurred (env)); /* java.lang.Object and its methods */ lcl_class = (*env)->FindClass (env, "java/lang/Object"); if (!lcl_class) { BROKEN (env, "cannot find java.lang.Object"); return initialized = -1; } /* Pin it down. */ obj_class = (jclass) (*env)->NewGlobalRef (env, lcl_class); DELETE_LOCAL_REF (env, lcl_class); if (!obj_class) { BROKEN (env, "Cannot get a global reference to java.lang.Object"); return initialized = -1; } obj_ctor = (*env)->GetMethodID (env, obj_class, "", "()V"); if (!obj_ctor) { BROKEN (env, "cannot find constructor for java.lang.Object"); return initialized = -1; } obj_notify_mth = (*env)->GetMethodID (env, obj_class, "notify", "()V"); if ( ! obj_notify_mth ) { BROKEN (env, "cannot find java.lang.Object.notify()V"); return initialized = -1; } obj_notifyall_mth = (*env)->GetMethodID (env, obj_class, "notifyAll", "()V"); if ( ! obj_notifyall_mth) { BROKEN (env, "cannot find java.lang.Object.notifyall()V"); return initialized = -1; } obj_wait_mth = (*env)->GetMethodID (env, obj_class, "wait", "()V"); if ( ! obj_wait_mth ) { BROKEN (env, "cannot find Object."); return initialized = -1; } obj_wait_nanotime_mth = (*env)->GetMethodID (env, obj_class, "wait", "(JI)V"); if ( ! obj_wait_nanotime_mth ) { BROKEN (env, "cannot find Object."); return initialized = -1; } /* GThreadMutex and its methods */ lcl_class = (*env)->FindClass (env, "gnu/java/awt/peer/gtk/GThreadMutex"); if ( ! lcl_class) { BROKEN (env, "cannot find gnu.java.awt.peer.gtk.GThreadMutex"); return initialized = -1; } /* Pin it down. */ mutex_class = (jclass) (*env)->NewGlobalRef (env, lcl_class); DELETE_LOCAL_REF (env, lcl_class); if ( ! mutex_class) { BROKEN (env, "Cannot get a global reference to GThreadMutex"); return initialized = -1; } mutex_ctor = (*env)->GetMethodID (env, mutex_class, "", "()V"); if ( ! mutex_ctor) { BROKEN (env, "cannot find zero-arg constructor for GThreadMutex"); return initialized = -1; } mutex_potentialLockers_fld = (*env)->GetFieldID (env, mutex_class, "potentialLockers", "I"); if ( ! mutex_class ) { BROKEN (env, "cannot find GThreadMutex.potentialLockers"); return initialized = -1; } if (! (mutex_lockForPotentialLockers_fld = (*env)->GetFieldID (env, mutex_class, "lockForPotentialLockers", "Ljava/lang/Object;"))) { BROKEN (env, "cannot find GThreadMutex.lockForPotentialLockers"); return initialized = -1; } /* java.lang.Thread */ if (! (lcl_class = (*env)->FindClass (env, "java/lang/Thread"))) { BROKEN (env, "cannot find java.lang.Thread"); return initialized = -1; } /* Pin it down. */ thread_class = (jclass) (*env)->NewGlobalRef (env, lcl_class); DELETE_LOCAL_REF (env, lcl_class); if (!thread_class) { BROKEN (env, "Cannot get a global reference to java.lang.Thread"); return initialized = -1; } thread_current_mth = (*env)->GetStaticMethodID (env, thread_class, "currentThread", "()Ljava/lang/Thread;"); if (!thread_current_mth) { BROKEN (env, "cannot find Thread.currentThread() method"); return initialized = -1; } thread_equals_mth = (*env)->GetMethodID (env, thread_class, "equals", "(Ljava/lang/Object;)Z"); if (!thread_equals_mth) { BROKEN (env, "cannot find Thread.equals() method"); return initialized = -1; } thread_join_mth = (*env)->GetMethodID (env, thread_class, "join", "()V"); if (!thread_join_mth) { BROKEN (env, "cannot find Thread.join() method"); return initialized = -1; } thread_stop_mth = (*env)->GetMethodID (env, thread_class, "stop", "()V"); if ( ! thread_stop_mth ) { BROKEN (env, "cannot find Thread.stop() method"); return initialized = -1; } thread_setPriority_mth = (*env)->GetMethodID (env, thread_class, "setPriority", "(I)V"); if ( ! thread_setPriority_mth ) { BROKEN (env, "cannot find Thread.setPriority() method"); return initialized = -1; } thread_yield_mth = (*env)->GetStaticMethodID (env, thread_class, "yield", "()V"); if ( ! thread_yield_mth ) { BROKEN (env, "cannot find Thread.yield() method"); return initialized = -1; } /* java.lang.ThreadLocal */ lcl_class = (*env)->FindClass (env, "java/lang/ThreadLocal"); if ( ! lcl_class ) { BROKEN (env, "cannot find class java.lang.ThreadLocal"); return initialized = -1; } /* Pin it down. */ threadlocal_class = (jclass) (*env)->NewGlobalRef (env, lcl_class); DELETE_LOCAL_REF (env, lcl_class); if ( ! threadlocal_class ) { BROKEN (env, "Cannot get a global reference to java.lang.ThreadLocal"); return initialized = -1; } threadlocal_ctor = (*env)->GetMethodID (env, threadlocal_class, "", "()V"); if ( ! threadlocal_ctor ) { BROKEN (env, "cannot find ThreadLocal.()V"); return initialized = -1; } threadlocal_get_mth = (*env)->GetMethodID (env, threadlocal_class, "get", "()Ljava/lang/Object;"); if ( ! threadlocal_get_mth ) { BROKEN (env, "cannot find java.lang.ThreadLocal.get()Object"); return initialized = -1; } threadlocal_set_mth = (*env)->GetMethodID (env, threadlocal_class, "set", "(Ljava/lang/Object;)V"); if ( ! threadlocal_set_mth ) { BROKEN (env, "cannot find ThreadLocal.set(Object)V"); return initialized = -1; } /* java.lang.Long */ lcl_class = (*env)->FindClass (env, "java/lang/Long"); if ( ! lcl_class ) { BROKEN (env, "cannot find class java.lang.Long"); return initialized = -1; } /* Pin it down. */ long_class = (jclass) (*env)->NewGlobalRef (env, lcl_class); DELETE_LOCAL_REF (env, lcl_class); if (!long_class) { BROKEN (env, "Cannot get a global reference to java.lang.Long"); return initialized = -1; } long_ctor = (*env)->GetMethodID (env, long_class, "", "(J)V"); if (!long_ctor) { BROKEN (env, "cannot find method java.lang.Long.(J)V"); return initialized = -1; } long_longValue_mth = (*env)->GetMethodID (env, long_class, "longValue", "()J"); if (!long_longValue_mth) { BROKEN (env, "cannot find method java.lang.Long.longValue()J"); return initialized = -1; } /* GThreadNativeMethodRunner */ lcl_class = (*env)->FindClass (env, "gnu/java/awt/peer/gtk/GThreadNativeMethodRunner"); if ( ! lcl_class ) { BROKEN (env, "cannot find gnu.java.awt.peer.gtk.GThreadNativeMethodRunner"); return initialized = -1; } /* Pin it down. */ runner_class = (jclass) (*env)->NewGlobalRef (env, lcl_class); DELETE_LOCAL_REF (env, lcl_class); if (!runner_class) { BROKEN (env, "Cannot get a global reference to the class GThreadNativeMethodRunner"); return initialized = -1; } runner_ctor = (*env)->GetMethodID (env, runner_class, "", "(JJZ)V"); if ( ! runner_ctor ) { BROKEN (env, "cannot find method GThreadNativeMethodRunner.(JJZ)"); return initialized = -1; } runner_start_mth = (*env)->GetMethodID (env, runner_class, "start", "()V"); if ( ! runner_start_mth ) { BROKEN (env, "cannot find method GThreadNativeMethodRunner.start()V"); return initialized = -1; } runner_threadToThreadID_mth = (*env)->GetStaticMethodID (env, runner_class, "threadToThreadID", "(Ljava/lang/Thread;)I"); if ( ! runner_threadToThreadID_mth ) { BROKEN (env, "cannot find method GThreadNativeMethodRunner.threadToThreadID(java.lang.Thread)I"); return initialized = -1; } runner_threadIDToThread_mth = (*env)->GetStaticMethodID (env, runner_class, "threadIDToThread", "(I)Ljava/lang/Thread;"); if ( ! runner_threadIDToThread_mth ) { BROKEN (env, "cannot find method GThreadNativeMethodRunner.threadIDToThread(I)java.lang.Thread"); return initialized = -1; } runner_deRegisterJoinable_mth = (*env)->GetStaticMethodID (env, runner_class, "deRegisterJoinable", "(Ljava/lang/Thread;)V"); if (!runner_deRegisterJoinable_mth) { BROKEN (env, "cannot find method GThreadNativeMethodRunner.deRegisterJoinable(java.lang.Thread)V"); return initialized = -1; } /* java.lang.InterruptedException */ lcl_class = (*env)->FindClass (env, "java/lang/InterruptedException"); if ( ! lcl_class ) { BROKEN (env, "cannot find class java.lang.InterruptedException"); return initialized = -1; } /* Pin it down. */ interrupted_exception_class = (jclass) (*env)->NewGlobalRef (env, lcl_class); DELETE_LOCAL_REF (env, lcl_class); if (!interrupted_exception_class) { BROKEN (env, "Cannot make a global reference" " to java.lang.InterruptedException"); return initialized = -1; } #ifdef JNI_VERSION_1_2 if (HAVE_JNI_VERSION_1_2) assert ( ! (*env)->ExceptionCheck (env)); else #endif assert ( ! (*env)->ExceptionOccurred (env)); return initialized = 1; } /************************************************************************/ /* Utilities to allocate and free java.lang.Objects */ /************************************************************************/ /* The condition variables are java.lang.Object objects, * which this method allocates and returns a global ref. Note that global * refs must be explicitly freed (isn't C fun?). */ static jobject allocatePlainObject (JNIEnv * env) { jobject lcl_obj, global_obj; lcl_obj = (*env)->NewObject (env, obj_class, obj_ctor); if (!lcl_obj) { BROKEN (env, "cannot allocate object"); return NULL; } global_obj = (*env)->NewGlobalRef (env, lcl_obj); DELETE_LOCAL_REF (env, lcl_obj); if (!global_obj) { NEW_BROKEN (env, "cannot make global ref for a new plain Java object"); /* Deliberate fall-through */ } return global_obj; } /* Frees any Java object given a global ref (isn't C fun?) */ static void freeObject (JNIEnv * env, jobject obj) { if (obj) { (*env)->DeleteGlobalRef (env, obj); /* DeleteGlobalRef can never fail */ } } /************************************************************************/ /* Utilities to allocate and free Java mutexes */ /************************************************************************/ /* The mutexes are gnu.java.awt.peer.gtk.GThreadMutex objects, * which this method allocates and returns a global ref. Note that global * refs must be explicitly freed (isn't C fun?). * * Free this with freeObject() */ static jobject allocateMutexObject (JNIEnv * env) { jobject lcl_obj, global_obj; lcl_obj = (*env)->NewObject (env, mutex_class, mutex_ctor); if (!lcl_obj) { BROKEN (env, "cannot allocate a GThreadMutex"); return NULL; } global_obj = (*env)->NewGlobalRef (env, lcl_obj); DELETE_LOCAL_REF (env, lcl_obj); if (!global_obj) { NEW_BROKEN (env, "cannot make global ref"); /* Deliberate fallthrough */ } return global_obj; } /************************************************************************/ /* Locking code */ /************************************************************************/ /* Lock a Java object */ #define ENTER_MONITOR(env, m) \ enterMonitor(env, m, G_STRINGIFY(m)) /* Return -1 on failure, 0 on success. */ static int enterMonitor (JNIEnv * env, jobject monitorObj, const char monName[]) { if (TRACE_MONITORS) tracing (" ", monName); assert (monitorObj); if ((*env)->MonitorEnter (env, monitorObj) < 0) { BROKEN (env, "cannot enter monitor"); return -1; } return 0; } /* Unlock a Java object */ #define EXIT_MONITOR(env, m) \ exitMonitor(env, m, G_STRINGIFY(m)) static int exitMonitor (JNIEnv * env, jobject mutexObj, const char monName[]) { if (TRACE_MONITORS) tracing (" ", monName); assert (mutexObj); if ((*env)->MonitorExit (env, mutexObj) < 0) { BROKEN (env, "cannot exit monitor "); return -1; } return 0; } /************************************************************************/ /* Miscellaneous utilities */ /************************************************************************/ /* Get the Java Thread object that corresponds to a particular thread ID. A negative thread Id gives us a null object. Returns a local reference. */ static jobject getThreadFromThreadID (JNIEnv * env, gpointer gThreadID) { jint threadNum = GPOINTER_TO_INT(gThreadID); jobject thread; if (threadNum < 0) { NEW_BROKEN (env, "getThreadFromThreadID asked to look up" " a negative thread index"); return NULL; } thread = (*env)->CallStaticObjectMethod (env, runner_class, runner_threadIDToThread_mth, threadNum); if (MAYBE_BROKEN (env, "cannot get Thread for threadID ")) return NULL; return thread; } /** Return the unique threadID of THREAD. Error handling: Return (gpointer) -1 on all failures, and propagate an exception. */ static gpointer getThreadIDFromThread (JNIEnv * env, jobject thread) { jint threadNum; if (ENABLE_EXPENSIVE_ASSERTIONS) assert ((*env)->IsInstanceOf (env, thread, thread_class)); HIDE_OLD_TROUBLE (env); threadNum = (*env)->CallStaticIntMethod (env, runner_class, runner_threadToThreadID_mth, thread); if (MAYBE_BROKEN (env, "cannot get ThreadID for a Thread ")) { threadNum = -1; goto done; } SHOW_OLD_TROUBLE (); done: return GINT_TO_POINTER(threadNum); } /************************************************************************/ /* The Actual JNI functions that we pass to the function vector. */ /************************************************************************/ /************************************************************************/ /* Mutex Functions */ /************************************************************************/ /*** Mutex Utilities ****/ struct mutexObj_cache { jobject lockForPotentialLockersObj; /* Lock for the potentialLockers field. Local reference. */ jobject lockObj; /* The real lock we use. This is a GLOBAL reference and must not be freed. */ }; /* Initialize the cache of sub-locks for a particular mutex object. -1 on error, 0 on success. The caller is not responsible for freeing the partially-populated cache in case of failure (but in practice does anyway) (This actually never fails, though, since GetObjectField allegedly never fails.) Guaranteed to leave all fields of the cache initialized, even if only to zero. */ static int populate_mutexObj_cache (JNIEnv * env, jobject mutexObj, struct mutexObj_cache *mcache) { mcache->lockObj = mutexObj; /* the mutexObj is its own lock. */ assert (mcache->lockObj); mcache->lockForPotentialLockersObj = (*env)->GetObjectField (env, mutexObj, mutex_lockForPotentialLockers_fld); /* GetObjectField can never fail. */ /* Retrieving a NULL object could only happen if we somehow got a a mutex object that was not properly intialized. */ assert (mcache->lockForPotentialLockersObj); return 0; } /* Clean out the mutexObj_cache, even if it was never populated. */ static void clean_mutexObj_cache (JNIEnv * env, struct mutexObj_cache *mcache) { /* OK to pass NULL refs to DELETE_LOCAL_REF */ DELETE_LOCAL_REF (env, mcache->lockForPotentialLockersObj); /* mcache->lockObj is a GLOBAL reference. */ mcache->lockObj = NULL; } /* -1 on failure, 0 on success. The mutexObj_cache is already populated for this particular object. */ static int mutexObj_lock (JNIEnv * env, jobject mutexObj, struct mutexObj_cache *mcache) { jint potentialLockers; if (ENTER_MONITOR (env, mcache->lockForPotentialLockersObj)) return -1; assert(mutexObj); potentialLockers = (*env)->GetIntField (env, mutexObj, mutex_potentialLockers_fld); /* GetIntField() never fails. */ ++potentialLockers; (*env)->SetIntField (env, mutexObj, mutex_potentialLockers_fld, potentialLockers); if (EXIT_MONITOR (env, mcache->lockForPotentialLockersObj)) return -1; if (ENTER_MONITOR (env, mcache->lockObj)) return -1; SHOW_OLD_TROUBLE (); return 0; } /* Unlock a GMutex, once we're already in JNI and have already gotten the mutexObj for it. This skips the messages that TRACE_API_CALLS would print. Returns -1 on error, 0 on success. */ static int mutexObj_unlock (JNIEnv * env, jobject mutexObj, struct mutexObj_cache *mcache) { jint potentialLockers; int ret = -1; /* assume failure until we suceed. */ /* Free the lock first, so that someone waiting for the lock can get it ASAP. */ /* This is guaranteed not to block. */ if (EXIT_MONITOR (env, mcache->lockObj) < 0) goto done; /* Kick down potentialLockers by one. We do this AFTER we free the lock, so that we hold it no longer than necessary. */ if (ENTER_MONITOR (env, mcache->lockForPotentialLockersObj) < 0) goto done; potentialLockers = (*env)->GetIntField (env, mutexObj, mutex_potentialLockers_fld); /* GetIntField never fails */ assert (potentialLockers >= 1); --potentialLockers; (*env)->SetIntField (env, mutexObj, mutex_potentialLockers_fld, potentialLockers); /* Never fails, so the JNI book says. */ /* Clean up. */ if (EXIT_MONITOR (env, mcache->lockForPotentialLockersObj) < 0) goto done; ret = 0; done: return ret; } /*** Mutex Implementations ****/ /* Create a mutex, which is a java.lang.Object for us. In case of failure, we'll return NULL. Which will implicitly cause future calls to fail. */ static GMutex * mutex_new_jni_impl (void) { jobject mutexObj; JNIEnv *env; union env_union e; if (TRACE_API_CALLS) tracing ("mutex_new_jni_impl()"); e.jni_env = &env; (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); if (setup_cache (env) < 0) { mutexObj = NULL; goto done; } mutexObj = allocateMutexObject (env); done: if (TRACE_API_CALLS) tracing (" ==> %p \n", mutexObj); return (GMutex *) mutexObj; } /* Lock a mutex. */ static void mutex_lock_jni_impl (GMutex * mutex) { struct mutexObj_cache mcache; jobject mutexObj = (jobject) mutex; JNIEnv *env; union env_union e; if (TRACE_API_CALLS) tracing ("mutex_lock_jni_impl( mutexObj = %p )", mutexObj); assert (mutexObj); e.jni_env = &env; (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); if (setup_cache (env) < 0) goto done; HIDE_OLD_TROUBLE (env); if (populate_mutexObj_cache (env, mutexObj, &mcache) < 0) goto done; mutexObj_lock (env, mutexObj, &mcache); /* No need to error check; we've already reported it in any case. */ done: clean_mutexObj_cache (env, &mcache); if (TRACE_API_CALLS) tracing (" ==> VOID \n"); } /* Try to lock a mutex. Return TRUE if we succeed, FALSE if we fail. FALSE on error. */ static gboolean mutex_trylock_jni_impl (GMutex * gmutex) { jobject mutexObj = (jobject) gmutex; jint potentialLockers; gboolean ret = FALSE; JNIEnv *env; union env_union e; struct mutexObj_cache mcache; if (TRACE_API_CALLS) tracing ("mutex_trylock_jni_impl(mutexObj=%p)", mutexObj); assert (mutexObj); e.jni_env = &env; (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); if (setup_cache (env) < 0) goto done; HIDE_OLD_TROUBLE (env); if (populate_mutexObj_cache (env, mutexObj, &mcache) < 0) goto done; if (ENTER_MONITOR (env, mcache.lockForPotentialLockersObj)) goto done; potentialLockers = (*env)->GetIntField (env, mutexObj, mutex_potentialLockers_fld); assert (potentialLockers >= 0); if (potentialLockers) { /* Already locked. Clean up and leave. */ EXIT_MONITOR (env, mcache.lockForPotentialLockersObj); /* Ignore any error code from EXIT_MONITOR; there's nothing we could do at this level, in any case. */ goto done; } /* Guaranteed not to block. */ if (ENTER_MONITOR (env, mcache.lockObj)) { /* Clean up the existing lock. */ EXIT_MONITOR (env, mcache.lockForPotentialLockersObj); /* Ignore any error code from EXIT_MONITOR; there's nothing we could do at this level, in any case. */ goto done; } /* We have the monitor. Record that fact. */ potentialLockers = 1; (*env)->SetIntField (env, mutexObj, mutex_potentialLockers_fld, potentialLockers); /* Set*Field() never fails */ ret = TRUE; /* We have the lock. */ /* Clean up. */ if (EXIT_MONITOR (env, mcache.lockForPotentialLockersObj)) goto done; /* If we fail at this point, still keep the main lock. */ SHOW_OLD_TROUBLE (); done: clean_mutexObj_cache (env, &mcache); if (TRACE_API_CALLS) tracing (" ==> %s\n", ret ? "TRUE" : "FALSE"); return ret; } /* Unlock a mutex. */ static void mutex_unlock_jni_impl (GMutex * gmutex) { jobject mutexObj = (jobject) gmutex; struct mutexObj_cache mcache; JNIEnv *env; union env_union e; if (TRACE_API_CALLS) tracing ("mutex_unlock_jni_impl(mutexObj=%p)", mutexObj); e.jni_env = &env; (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); if (setup_cache (env) < 0) goto done; HIDE_OLD_TROUBLE (env); assert (mutexObj); if ( populate_mutexObj_cache (env, mutexObj, &mcache) < 0) goto done; (void) mutexObj_unlock (env, mutexObj, &mcache); SHOW_OLD_TROUBLE (); done: clean_mutexObj_cache (env, &mcache); if (TRACE_API_CALLS) tracing (" ==> VOID\n"); } /* Free a mutex (isn't C fun?). OK this time for it to be NULL. No failure conditions, for a change. */ static void mutex_free_jni_impl (GMutex * mutex) { jobject mutexObj = (jobject) mutex; JNIEnv *env; union env_union e; e.jni_env = &env; (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); if (TRACE_API_CALLS) tracing ("mutex_free_jni_impl(%p)", mutexObj); freeObject (env, mutexObj); if (TRACE_API_CALLS) tracing (" ==> VOID\n"); } /************************************************************************/ /* Condition variable code */ /************************************************************************/ /* Create a new condition variable. This is a java.lang.Object for us. */ static GCond * cond_new_jni_impl (void) { jobject condObj; JNIEnv *env; union env_union e; if (TRACE_API_CALLS) tracing ("mutex_free_jni_impl()"); e.jni_env = &env; (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); condObj = allocatePlainObject (env); if (TRACE_API_CALLS) tracing (" ==> %p\n", condObj); return (GCond *) condObj; } /* Signal on a condition variable. This is simply calling Object.notify * for us. */ static void cond_signal_jni_impl (GCond * gcond) { JNIEnv *env; union env_union e; jobject condObj = (jobject) gcond; if (TRACE_API_CALLS) tracing ("cond_signal_jni_impl(condObj = %p)", condObj); e.jni_env = &env; (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); if (setup_cache (env) < 0) goto done; HIDE_OLD_TROUBLE (env); assert (condObj); /* Must have locked an object to call notify */ if (ENTER_MONITOR (env, condObj)) goto done; (*env)->CallVoidMethod (env, condObj, obj_notify_mth); if (MAYBE_BROKEN (env, "cannot signal mutex with Object.notify()")) { if (EXIT_MONITOR (env, condObj)) BADLY_BROKEN1 ("Failed to unlock a monitor; the VM may deadlock."); goto done; } EXIT_MONITOR (env, condObj); SHOW_OLD_TROUBLE (); done: if (TRACE_API_CALLS) tracing (" ==> VOID\n"); } /* Broadcast to all waiting on a condition variable. This is simply * calling Object.notifyAll for us. */ static void cond_broadcast_jni_impl (GCond * gcond) { jobject condObj = (jobject) gcond; JNIEnv *env; union env_union e; if (TRACE_API_CALLS) tracing ("cond_broadcast_jni_impl(condObj=%p)", condObj); e.jni_env = &env; (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); if (setup_cache (env) < 0) goto done; HIDE_OLD_TROUBLE (env); assert (condObj); /* Must have locked an object to call notifyAll */ if (ENTER_MONITOR (env, condObj)) goto done; (*env)->CallVoidMethod (env, condObj, obj_notifyall_mth); if (MAYBE_BROKEN (env, "cannot broadcast to mutex with Object.notify()")) { EXIT_MONITOR (env, condObj); goto done; } EXIT_MONITOR (env, condObj); SHOW_OLD_TROUBLE (); done: if (TRACE_API_CALLS) tracing (" ==> VOID\n"); } /* Wait on a condition variable. For us, this simply means calling * Object.wait. * * Throws a Java exception on trouble; may leave the mutexes set arbitrarily. * XXX TODO: Further improve error recovery. */ static void cond_wait_jni_impl (GCond * gcond, GMutex * gmutex) { struct mutexObj_cache cache; jobject condObj = (jobject) gcond; jobject mutexObj = (jobject) gmutex; JNIEnv *env; union env_union e; if (TRACE_API_CALLS) tracing ("cond_wait_jni_impl(condObj=%p, mutexObj=%p)", condObj, mutexObj); e.jni_env = &env; (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); if (setup_cache (env) < 0) goto done; HIDE_OLD_TROUBLE (env); assert (condObj); assert (mutexObj); /* Must have locked a Java object to call wait on it */ if (ENTER_MONITOR (env, condObj) < 0) goto done; /* Our atomicity is now guaranteed; we're protected by the Java monitor on condObj. Unlock the GMutex. */ if (mutexObj_unlock (env, mutexObj, &cache)) goto done; (*env)->CallVoidMethod (env, condObj, obj_wait_mth); if (MAYBE_BROKEN (env, "cannot wait on condObj")) { EXIT_MONITOR (env, condObj); /* ignore err checking */ goto done; } /* Re-acquire the lock on the GMutex. Do this while we're protected by the Java monitor on condObj. */ if (mutexObj_lock (env, mutexObj, &cache)) goto done; EXIT_MONITOR (env, condObj); SHOW_OLD_TROUBLE (); done: if (TRACE_API_CALLS) tracing (" ==> VOID\n"); } /** Wait on a condition variable until a timeout. This is a little tricky * for us. We first call Object.wait(J) giving it the appropriate timeout * value. On return, we check whether an InterruptedException happened. If * so, that is Java-speak for wait timing out. * * We return FALSE if we timed out. Return TRUE if the condition was * signalled first, before we timed out. * * In case of trouble we throw a Java exception. Whether we return FALSE or * TRUE depends upon whether the condition was raised before the trouble * happened. * * I believe that this function goes to the proper lengths to try to unlock * all of the locked mutexes and monitors, as appropriate, and that it further * tries to make sure that the thrown exception is the current one, not any * future cascaded one from something like a failure to unlock the monitors. */ static gboolean cond_timed_wait_jni_impl (GCond * gcond, GMutex * gmutex, GTimeVal * end_time) { JNIEnv *env; union env_union e; jlong time_millisec; jint time_nanosec; jthrowable cause; jobject condObj = (jobject) gcond; jobject mutexObj = (jobject) gmutex; gboolean condRaised = FALSE; /* Condition has not been raised yet. */ struct mutexObj_cache cache; gboolean interrupted; if (TRACE_API_CALLS) { tracing ("cond_timed_wait_jni_impl(cond=%p, mutex=%p," " end_time=< sec=%lu, usec=%lu >)", condObj, mutexObj, (unsigned long) end_time->tv_sec, (unsigned long) end_time->tv_usec); } e.jni_env = &env; (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); if (setup_cache (env) < 0) goto done; HIDE_OLD_TROUBLE (env); time_millisec = end_time->tv_sec * 1000 + end_time->tv_usec / 1000; time_nanosec = 1000 * (end_time->tv_usec % 1000); /* Must have locked an object to call wait */ if (ENTER_MONITOR (env, condObj) < 0) goto done; if (mutexObj_unlock (env, mutexObj, &cache) < 0) { if (EXIT_MONITOR (env, condObj) < 0) criticalMsg ("Unable to unlock an existing lock on a condition; your proram may deadlock"); goto done; } (*env)->CallVoidMethod (env, condObj, obj_wait_nanotime_mth, time_millisec, time_nanosec); /* If there was trouble, save that fact, and the reason for the trouble. We want to respond to this condition as fast as possible. */ cause = (*env)->ExceptionOccurred (env); if ( ! cause ) { condRaised = TRUE; /* condition was signalled */ } else if ((*env)->IsInstanceOf (env, cause, interrupted_exception_class)) { condRaised = FALSE; /* Condition was not raised before timeout. (This is redundant with the initialization of condRaised above) */ (*env)->ExceptionClear (env); /* Clear the InterruptedException. */ cause = NULL; /* no pending cause now. */ } else { interrupted = FALSE; /* Trouble, but not because of InterruptedException. Assume the condition was not raised. */ /* Leave condRaised set to FALSE */ } /* Irrespective of whether there is a pending problem to report, go ahead and try to clean up. This may end up throwing an exception that is different from the one that was thrown by the call to Object.wait(). So we will override it with the first exception (don't want to have cascading problems). */ if (mutexObj_lock (env, mutexObj, &cache) && !cause) { cause = (*env)->ExceptionOccurred (env); assert (cause); } if (EXIT_MONITOR (env, condObj) && !cause) { cause = (*env)->ExceptionOccurred (env); assert (cause); } if (cause) /* Raise the first cause. */ { BROKEN_CAUSE (env, cause, "error in timed wait or during its cleanup"); goto done; } SHOW_OLD_TROUBLE (); done: if (TRACE_API_CALLS) tracing (" ==> condRaised = %s\n", condRaised ? "TRUE" : "FALSE"); return condRaised; } /* Free a condition variable. (isn't C fun?). Can not fail. */ static void cond_free_jni_impl (GCond * cond) { jobject condObj = (jobject) cond; JNIEnv *env; union env_union e; if (TRACE_API_CALLS) tracing ("cond_free_jni_impl(condObj = %p)", condObj); e.jni_env = &env; (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); freeObject (env, condObj); if (TRACE_API_CALLS) tracing (" ==> VOID\n"); } /************************************************************************/ /* Thread-local data code */ /************************************************************************/ /* Create a new thread-local key. We use java.lang.ThreadLocal objects * for this. This returns the pointer representation of a Java global * reference. * * We will throw a Java exception and return NULL in case of failure. */ static GPrivate * private_new_jni_impl (GDestroyNotify notify __attribute__ ((unused))) { JNIEnv *env; union env_union e; jobject lcl_key; jobject global_key; GPrivate *gkey = NULL; /* Error return code */ if (TRACE_API_CALLS) tracing ("private_new_jni_impl()"); e.jni_env = &env; (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); if (setup_cache (env) < 0) goto done; HIDE_OLD_TROUBLE (env); lcl_key = (*env)->NewObject (env, threadlocal_class, threadlocal_ctor); if ( ! lcl_key ) { BROKEN (env, "cannot allocate a ThreadLocal"); goto done; } global_key = ((*env)->NewGlobalRef (env, lcl_key)); DELETE_LOCAL_REF (env, lcl_key); if ( ! global_key) { NEW_BROKEN (env, "cannot create a GlobalRef to a new ThreadLocal"); goto done; } gkey = (GPrivate *) global_key; SHOW_OLD_TROUBLE (); done: if (TRACE_API_CALLS) tracing (" ==> %p\n", (void *) gkey); return gkey; } /* Get this thread's value for a thread-local key. This is simply * ThreadLocal.get for us. Return NULL if no value. (I can't think of * anything else to do.) */ static gpointer private_get_jni_impl (GPrivate * gkey) { JNIEnv *env; union env_union e; jobject val_wrapper; jobject keyObj = (jobject) gkey; gpointer thread_specific_data = NULL; /* Init to the error-return value */ jlong val; if (TRACE_API_CALLS) tracing ("private_get_jni_impl(keyObj=%p)", keyObj); e.jni_env = &env; (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); if (setup_cache (env) < 0) goto done; HIDE_OLD_TROUBLE (env); val_wrapper = (*env)->CallObjectMethod (env, keyObj, threadlocal_get_mth); if (MAYBE_BROKEN (env, "cannot find thread-local object")) goto done; if (! val_wrapper ) { /* It's Java's "null" object. No ref found. This is OK; we must never have set a value in this thread. Note that this next statement is not necessary, strictly speaking, since we're already initialized to NULL. A good optimizing C compiler will detect that and optimize out this statement. */ thread_specific_data = NULL; goto done; } val = (*env)->CallLongMethod (env, val_wrapper, long_longValue_mth); if (MAYBE_BROKEN (env, "cannot get thread local value")) goto done; thread_specific_data = (gpointer) (intptr_t) val; /* Only re-raise the old pending exception if a new one hasn't come along to supersede it. */ SHOW_OLD_TROUBLE (); done: if (TRACE_API_CALLS) tracing (" ==> %p\n", thread_specific_data); return thread_specific_data; } /* Set this thread's value for a thread-local key. This is simply * ThreadLocal.set() for us. */ static void private_set_jni_impl (GPrivate * gkey, gpointer thread_specific_data) { JNIEnv *env; union env_union e; jobject val_wrapper; jobject keyObj = (jobject) gkey; if (TRACE_API_CALLS) tracing ("private_set_jni_impl(keyObj=%p, thread_specific_data=%p)", keyObj, thread_specific_data); e.jni_env = &env; (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); if (setup_cache (env) < 0) goto done; HIDE_OLD_TROUBLE (env); /* We are just going to always use a Java long to represent a C pointer. Otherwise all of the code would end up being conditionalized for various pointer sizes, and that seems like too much of a hassle, in order to save a paltry few bytes, especially given the horrendous overhead of JNI in any case. */ val_wrapper = (*env)->NewObject (env, long_class, long_ctor, (jlong) (intptr_t) thread_specific_data); if ( ! val_wrapper ) { BROKEN (env, "cannot create a java.lang.Long"); goto done; } /* At this point, we now have set lcl_obj as a numeric class that wraps around the thread-specific data we were given. */ (*env)->CallVoidMethod (env, keyObj, threadlocal_set_mth, val_wrapper); if (MAYBE_BROKEN (env, "cannot set thread local value")) goto done; SHOW_OLD_TROUBLE (); done: if (TRACE_API_CALLS) tracing (" ==> VOID\n"); } /** Create an object of type gnu.java.awt.peer.gtk.GThreadNativeMethodRunner. Run it. We need to create joinable threads. We handle the notion of a joinable thread by determining whether or not we are going to maintain a permanent hard reference to it until it croaks. Posix does not appear to have a Java-like concept of daemon threads, where the JVM will exit when there are only daemon threads running. Error handling: To quote from the glib guide: "GError should only be used to report recoverable runtime errors, never to report programming errors." So how do we consider the failure to create a thread? Well, each of the failure cases in this function are discussed, and none of them are really recoverable. The glib library is really designed so that you should fail catastrophically in case of "programming errors". The only error defined for the GThread functions is G_THREAD_ERROR_AGAIN, and that for thread_create. Most of these GThread functions could fail if we run out of memory, for example, but the only one capable of reporting that fact is thread_create. */ static void thread_create_jni_impl (GThreadFunc func, gpointer data, gulong stack_size __attribute__((unused)), gboolean joinable, gboolean bound __attribute__((unused)), GThreadPriority gpriority, /* This prototype is horrible. threadIDp is actually a gpointer to the thread's thread-ID. Which is, of course, itself a gpointer-typed value. Ouch. */ gpointer threadIDp, /* Do not touch the GError stuff unless you have RECOVERABLE trouble. There is no recoverable trouble in this implementation. */ GError **errorp __attribute__((unused))) { JNIEnv *env; union env_union e; union func_union f; jboolean jjoinable = joinable; jobject newThreadObj; gpointer threadID; /* to be filled in */ if (TRACE_API_CALLS) { f.g_func = func; tracing ("thread_create_jni_impl(func=%p, data=%p, joinable=%s," " threadIDp=%p, *(int *) threadIDp = %d)", f.void_func, data, joinable ? "TRUE" : "FALSE", threadIDp, *(int *) threadIDp); } e.jni_env = &env; (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); if (setup_cache (env) < 0) { /* The failed call to setup the cache is certainly not recoverable; not appropriate for G_THREAD_ERROR_AGAIN. */ *(gpointer *) threadIDp = NULL; goto done; } HIDE_OLD_TROUBLE (env); /* If a thread is joinable, then notify its constructor. The constructor will enter a hard reference for it, and the hard ref. won't go away until the thread has been joined. */ newThreadObj = (*env)->NewObject (env, runner_class, runner_ctor, (jlong) (intptr_t) func, (jlong) (intptr_t) data, jjoinable); if ( ! newThreadObj ) { BROKEN (env, "creating a new thread failed in the constructor"); *(gpointer *) threadIDp = NULL; /* The failed call to the constructor does not throw any errors such that G_THREAD_ERROR_AGAIN is appropriate. No other recoverable errors defined. Once again, we go back to the VM. */ goto done; } if (threadObj_set_priority (env, newThreadObj, gpriority) < 0) { *(gpointer *) threadIDp = NULL; /* None of these possible exceptions from Thread.setPriority() are recoverable, so they are not appropriate for EAGAIN. So we should fail. */ goto done; } (*env)->CallVoidMethod (env, runner_class, runner_start_mth); if (MAYBE_BROKEN (env, "starting a new thread failed")) { *(gpointer *) threadIDp = NULL; /* The only exception Thread.start() throws is IllegalStateException. And that would indicate a programming error. So there are no situations such that G_THREAD_ERROR_AGAIN would be OK. So, we don't use g_set_error() here to perform any error reporting. */ goto done; } threadID = getThreadIDFromThread (env, newThreadObj); *(gpointer *) threadIDp = threadID; SHOW_OLD_TROUBLE (); done: if (TRACE_API_CALLS) tracing (" ==> (threadID = %p) \n", threadID); } /* Wraps a call to g_thread_yield. */ static void thread_yield_jni_impl (void) { JNIEnv *env; union env_union e; if (TRACE_API_CALLS) tracing ("thread_yield_jni_impl()"); e.jni_env = &env; (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); if (setup_cache (env) < 0) goto done; HIDE_OLD_TROUBLE (env); (*env)->CallStaticVoidMethod (env, thread_class, thread_yield_mth); if (MAYBE_BROKEN (env, "Thread.yield() failed")) goto done; SHOW_OLD_TROUBLE (); done: if (TRACE_API_CALLS) tracing (" ==> VOID\n"); } static void thread_join_jni_impl (gpointer threadID) { JNIEnv *env; union env_union e; jobject threadObj = NULL; if ( TRACE_API_CALLS ) tracing ("thread_join_jni_impl(threadID=%p) ", threadID); e.jni_env = &env; (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); if (setup_cache (env) < 0) goto done; HIDE_OLD_TROUBLE (env); threadObj = getThreadFromThreadID (env, threadID); if ( ! threadObj ) /* Already reported with BROKEN */ goto done; (*env)->CallVoidMethod (env, threadObj, thread_join_mth); if (MAYBE_BROKEN (env, "Thread.join() failed")) goto done; (*env)->CallStaticVoidMethod (env, runner_class, runner_deRegisterJoinable_mth, threadObj); if (MAYBE_BROKEN (env, "Thread.deRegisterJoinableThread() failed")) goto done; SHOW_OLD_TROUBLE (); done: DELETE_LOCAL_REF (env, threadObj); if (TRACE_API_CALLS) tracing (" ==> VOID \n"); } /* Terminate the current thread. Unlike pthread_exit(), here we do not need to bother with a return value or exit value for the thread which is about to croak. (The gthreads abstraction doesn't use it.) However, we *do* need to bail immediately. We handle this with Thread.stop(), which is a deprecated method. It's deprecated since we might leave objects protected by monitors in half-constructed states on the way out -- Thread.stop() throws a ThreadDeath exception, which is usually unchecked. There is no good solution that I can see. */ static void thread_exit_jni_impl (void) { JNIEnv *env; union env_union e; jobject this_thread; if (TRACE_API_CALLS) tracing ("thread_exit_jni_impl() "); e.jni_env = &env; (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); if (setup_cache (env) < 0) goto done; HIDE_OLD_TROUBLE (env); this_thread = (*env)-> CallStaticObjectMethod (env, thread_class, thread_current_mth); if ( ! this_thread ) { BROKEN (env, "cannot get current thread"); goto done; } (*env)->CallVoidMethod (env, this_thread, thread_stop_mth); if (MAYBE_BROKEN (env, "cannot call Thread.stop() on current thread")) goto done; SHOW_OLD_TROUBLE (); done: if (TRACE_API_CALLS) tracing (" ==> VOID \n"); } /* Translate a GThreadPriority to a Java priority level. */ static jint javaPriorityLevel (GThreadPriority priority) { /* We have these fields in java.lang.Thread to play with: static int MIN_PRIORITY The minimum priority that a thread can have. static int NORM_PRIORITY The default priority that is assigned to a thread. static int MAX_PRIORITY The maximum priority that a thread can have. We get these from the header file generated by javah, even though they're documented as being 1, 5, and 10. */ static const jint minJPri = gnu_java_awt_peer_gtk_GThreadNativeMethodRunner_MIN_PRIORITY; static const jint normJPri = gnu_java_awt_peer_gtk_GThreadNativeMethodRunner_NORM_PRIORITY; static const jint maxJPri = gnu_java_awt_peer_gtk_GThreadNativeMethodRunner_MAX_PRIORITY; switch (priority) { case G_THREAD_PRIORITY_LOW: return minJPri; break; default: assert_not_reached (); /* Deliberate fall-through if assertions are turned off; also shuts up GCC warnings if they're turned on. */ case G_THREAD_PRIORITY_NORMAL: return normJPri; break; case G_THREAD_PRIORITY_HIGH: return (normJPri + maxJPri) / 2; break; case G_THREAD_PRIORITY_URGENT: return maxJPri; break; } } /** It would be safe not to implement this, according to the JNI docs, since not all platforms do thread priorities. However, we might as well provide the hint for those who want it. */ static void thread_set_priority_jni_impl (gpointer gThreadID, GThreadPriority gpriority) { jobject threadObj = NULL; JNIEnv *env; union env_union e; if (TRACE_API_CALLS) tracing ("thread_set_priority_jni_impl(gThreadID=%p, gpriority = %u) ", gThreadID, gpriority); e.jni_env = &env; (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); if (setup_cache (env) < 0) goto done; HIDE_OLD_TROUBLE (env); threadObj = getThreadFromThreadID (env, gThreadID); if ( ! threadObj) /* Reported with BROKEN already. */ goto done; if (threadObj_set_priority (env, threadObj, gpriority)) goto done; SHOW_OLD_TROUBLE (); done: DELETE_LOCAL_REF (env, threadObj); if (TRACE_API_CALLS) tracing (" ==> VOID\n"); } /** It would be safe not to implement this, according to the JNI docs, since not all platforms do thread priorities. However, we might as well provide the hint for those who want it. -1 on failure, 0 on success. */ static int threadObj_set_priority (JNIEnv * env, jobject threadObj, GThreadPriority gpriority) { jint javaPriority = javaPriorityLevel (gpriority); (*env)->CallVoidMethod (env, threadObj, thread_setPriority_mth, javaPriority); return MAYBE_BROKEN (env, "Thread.setPriority() failed"); } /** Return the result of Thread.currentThread(), a static method. */ static void thread_self_jni_impl (/* Another confusing glib prototype. This is actually a gpointer to the thread's thread-ID. Which is, of course, a gpointer. */ gpointer my_thread_IDp) { JNIEnv *env; union env_union e; jobject this_thread; gpointer my_threadID; if (TRACE_API_CALLS) tracing ("thread_self_jni_impl(my_thread_IDp=%p)", my_thread_IDp); e.jni_env = &env; (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); if (setup_cache (env) < 0) return; HIDE_OLD_TROUBLE (env); this_thread = (*env)-> CallStaticObjectMethod (env, thread_class, thread_current_mth); if (! this_thread ) { BROKEN (env, "cannot get current thread"); my_threadID = NULL; goto done; } my_threadID = getThreadIDFromThread (env, this_thread); SHOW_OLD_TROUBLE (); done: if (TRACE_API_CALLS) tracing (" ==> (my_threadID = %p) \n", my_threadID); *(gpointer *) my_thread_IDp = my_threadID; } static gboolean thread_equal_jni_impl (gpointer thread1, gpointer thread2) { JNIEnv *env; union env_union e; gpointer threadID1 = *(gpointer *) thread1; gpointer threadID2 = *(gpointer *) thread2; jobject thread1_obj = NULL; jobject thread2_obj = NULL; gboolean ret; if (TRACE_API_CALLS) tracing ("thread_equal_jni_impl(threadID1=%p, threadID2=%p)", threadID1, threadID2); e.jni_env = &env; (*cp_gtk_the_vm)->GetEnv (cp_gtk_the_vm, e.void_env, JNI_VERSION_1_1); if (setup_cache (env) < 0) { ret = FALSE; /* what is safer? We really don't ever want to return from here. */ goto done; } HIDE_OLD_TROUBLE (env); thread1_obj = getThreadFromThreadID (env, threadID1); thread2_obj = getThreadFromThreadID (env, threadID2); ret = (*env)->CallBooleanMethod (env, thread1_obj, thread_equals_mth, thread2_obj); if (MAYBE_BROKEN (env, "Thread.equals() failed")) { ret = FALSE; goto done; } SHOW_OLD_TROUBLE (); done: DELETE_LOCAL_REF (env, thread1_obj); DELETE_LOCAL_REF (env, thread2_obj); if (TRACE_API_CALLS) tracing (" ==> %s\n", ret ? "TRUE" : "FALSE"); return ret; } /************************************************************************/ /* GLIB interface */ /************************************************************************/ /* set of function pointers to give to glib. */ GThreadFunctions cp_gtk_portable_native_sync_jni_functions = { mutex_new_jni_impl, /* mutex_new */ mutex_lock_jni_impl, /* mutex_lock */ mutex_trylock_jni_impl, /* mutex_trylock */ mutex_unlock_jni_impl, /* mutex_unlock */ mutex_free_jni_impl, /* mutex_free */ cond_new_jni_impl, /* cond_new */ cond_signal_jni_impl, /* cond_signal */ cond_broadcast_jni_impl, /* cond_broadcast */ cond_wait_jni_impl, /* cond_wait */ cond_timed_wait_jni_impl, /* cond_timed_wait */ cond_free_jni_impl, /* cond_free */ private_new_jni_impl, /* private_new */ private_get_jni_impl, /* private_get */ private_set_jni_impl, /* private_set */ thread_create_jni_impl, /* thread_create */ thread_yield_jni_impl, /* thread_yield */ thread_join_jni_impl, /* thread_join */ thread_exit_jni_impl, /* thread_exit */ thread_set_priority_jni_impl, /* thread_set_priority */ thread_self_jni_impl, /* thread_self */ thread_equal_jni_impl, /* thread_equal */ }; /* Keep c-font-lock-extra-types in alphabetical order. */ /* Local Variables: */ /* c-file-style: "gnu" */ /* c-font-lock-extra-types: ("\\sw+_t" "gboolean" "GError" "gpointer" "GPrivate" "GThreadFunc" "GThreadFunctions" "GThreadPriority" "gulong" "JNIEnv" "jboolean" "jclass" "jfieldID" "jint" "jlong" "jmethodID" "jobject" "jstring" "jthrowable" ) */ /* End: */