| /* 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 |
| * <steve+classpath at augart dot com>, <augart at watson dot ibm dot com> |
| * @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 <config.h> |
| |
| /************************************************************************/ |
| /* 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 <stdint.h> /* provides intptr_t */ |
| #elif defined HAVE_INTTYPES_H |
| #include <inttypes.h> |
| #endif |
| #include <stdarg.h> /* va_list */ |
| #include <glib.h> |
| #include "gthread-jni.h" |
| #include <assert.h> /* assert() */ |
| |
| /* For Java thread priority constants. */ |
| #include <gnu_java_awt_peer_gtk_GThreadNativeMethodRunner.h> |
| |
| /* 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, "<init>", |
| "(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, "<init>", "()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.<wait()V>"); |
| 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.<wait(JI)V>"); |
| 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, "<init>", "()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, |
| "<init>", "()V"); |
| if ( ! threadlocal_ctor ) |
| { |
| BROKEN (env, "cannot find ThreadLocal.<init>()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, "<init>", "(J)V"); |
| if (!long_ctor) |
| { |
| BROKEN (env, "cannot find method java.lang.Long.<init>(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, "<init>", "(JJZ)V"); |
| if ( ! runner_ctor ) |
| { |
| BROKEN (env, |
| "cannot find method GThreadNativeMethodRunner.<init>(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 (" <MonitorEnter(%s)>", 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 (" <MonitorExit(%s)>", 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: */ |