[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
portable-native-sync-4.patch; ready to commit?
From: |
Steven Augart |
Subject: |
portable-native-sync-4.patch; ready to commit? |
Date: |
Wed, 12 May 2004 22:30:19 -0400 |
User-agent: |
Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8a) Gecko/20040504 |
I believe this is ready to commit. I would prefer to have somebody
else review it and point out any problems you see.
--Steve A
--
Steven Augart
Jikes RVM, a free, open source, Virtual Machine:
http://oss.software.ibm.com/jikesrvm
This patch fixes the --portable-native-sync support in GNU Classpath.
I have tested it with Jikes RVM.
Please commit it with the following ChangeLog entry:
2004-05-10 Steven Augart <address@hidden>
--portable-native-sync implemented for GTK2, by adding the missing
old implementations to the GThreadFunctions structure. Fully
implemented mutex_trylock(), which had been a stub before. Some
refactoring and cleanup of previous implementation.
* native/jni/gtk-peer/gnu_java_awt_peer_gtk_GtkMainThread.c,
native/jni/gtk-peer/gthread-jni.c, native/jni/gtk-peer/gthread-jni.h:
Renamed g_thread_jni_functions to
portable_native_sync_jni_functions, in the interest of not
polluting the g_ namespace.
Rename gdk_vm to the_vm, so we do not appropriate portions of the
gdk_ namespace (or at least, so we don't do it in code that I've
taken responsibility for!)
* native/jni/gtk-peer/gnu_java_awt_peer_gtk_GtkMainThread.c:
Removed redundant definition of gdk_vm (now the_vm).
(gtkInit) Got rid of a progress message to stdout that had crept
in somehow; probably old debugging code.
Use g_malloc() instead of a (buggy; incorrect) malloc() call
without a check for NULL! Similar substitution with g_free().
Fix a const assignment bug where we had been assigning a const string
to a regular "char *" pointer.
It's OK to give g_free() a NULL pointer. Exploit this fact.
* native/jni/gtk-peer/gthread-jni.c: Major refactoring.
Implemented missing functions for GTK2.
Added debugging #defines so we can get progress reports while we run.
Added an #ifdef surrounding the whole file so we don't build it
and include it in the library on configurations that will never
use it.
Removed the g_ prefix from all of our own functions, to avoid
namespace pollution and possible confusion.
Added TRACE_API_CALLS option.
Added the EXPLAIN_TROUBLE option, to generate messages to stderr
in addition to throwing or rethrowing an exception. Also added
EXPLAIN_BROKEN and EXPLAIN_BADLY_BROKEN, for progressively worse
cases.
Added the DIE_IF_BADLY_BROKEN option, enabled by default. Die if
we can't throw an exception. The weaker new DIE_IF_BROKEN option
is not enabled by default.
Added DELETE_LOCAL_REFS option and code that polices the quantity
of local references. It is not enabled by default -- Jikes RVM
does not have a fixed limit on the number of local refs -- but it
might need to be for some other VM's JNI implementation.
Reformatted the code largely to GNU standards. I probably have
not gotten every spot.
Rewrote all of the global-reference code so that we never
call malloc or g_malloc(). Eliminated a layer of indirection
where we were returning a reference to a global reference; that
extra layer would only be necessary if a jobject did not fit into
a machine pointer (void *). But it is guaranteed to always do so.
If any of the functions encounter errors, they now detect that
fact and clean up and exit, rather than throwing a Java exception,
but then continuing to run and causing further errors to cascade.
Well, that isn't perfect yet, but it *is* a lot better than it
was.
(cond_timed_wait_jni_impl) Went from millisecond to microsecond
resolution.
(setup_cache) Cache all methods and classes we will use throughout
the code.
(mutex_trylock_jni_impl) Used to be a stub; now properly
implemented.
(mutex_cond_wait_jni_impl, mutex_cond_timed_wait_jni_impl) Fixed
bug where they were not unlocking the GMutex associated with the
condition variable during the wait on that condition variable.
That could have lead to deadlock.
* gnu/java/awt/peer/gtk/GThreadMutex.java: New file. Implements a
mutex object that supports glib's GThreadFunctions.mutex_trylock
operation.
* gnu/java/awt/peer/gtk/GThreadNativeMethodRunner.java,
native/jni/gtk-peer/gnu_java_awt_peer_gtk_GThreadNativeMethodRunner.c,
native/jni/gtk-peer/gnu_java_awt_peer_gtk_GThreadNativeMethodRunner.h:
New files.
Implement the Java portions of glib's
GThreadFunctions.thread_create operation.
Provide the utility methods threadToThreadID and threadIDToThread,
which are used in several places in the GThreadFunctions code.
--- /dev/null 2003-01-30 10:24:37.000000000 +0000
+++ gnu/java/awt/peer/gtk/GThreadMutex.java 2004-05-12 19:46:06.000000000
+0000
@@ -0,0 +1,105 @@
+/* GThreadMutex.java -- Implements a mutex object for glib's gthread
+ abstraction, for use with GNU Classpath's --portable-native-sync option.
+ This is used in gthread-jni.c
+
+ Copyright (C) 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., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 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. */
+
+package gnu.java.awt.peer.gtk;
+
+/** Implements a mutex object for glib's gthread
+ abstraction, for use with GNU Classpath's --portable-native-sync option.
+ This is used in gthread-jni.c.
+
+ We use it to implement the POSIX semantics for Mutexes. They are needed
+ are needed for the function vector that is passed to glib's g_thread
+ subpackage's initialization function.
+
+ The GThreadMutex object itself serves as the Real Lock; if code has
+ entered the monitor for this GThreadMutex object (in Java language, if
+ it's synchronized on this object) then it holds the lock that this object
+ represents.
+
+ @author Steven Augart
+ May, 2004
+
+
+*/
+
+public class GThreadMutex {
+ /** Might "lock" be locked? Is anyone waiting
+ to get that lock? How long is the queue?
+
+ If zero, nobody holds a lock on this GThreadMutex object, and nobody is
+ trying to get one. Before someone attempts to acquire a lock on this
+ object, they must increment potentialLockers. After they release their
+ lock on this object, they must decrement potentialLockers.
+
+ Access to this field is guarded by synchronizing on the object
+ <code>lockForPotentialLockers</code>.
+
+ We only access this field from C via JNI.
+ */
+ int potentialLockers;
+
+ /* An object to synchronize to if you want to examine or modify the
+ <code>potentialLockers</code> field. Only hold this lock for brief
+ moments, just long enough to check or set the value of
+ <code>lockForPotentialLockers</code>.
+
+ We use this representation so that g_thread_mutex_trylock() will work
+ with the POSIX semantics. This is the only case in which you ever hold a
+ lock on <code>lockForPotentialLockers</code> while trying to get another
+ lock -- if you are the mutex_trylock() implementation, and you have just
+ checked that <code>potentialLockers</code> has the value zero. In that
+ case, mutex_trylock() holds the lock on lockForPotentialLockers so that
+ another thread calling mutex_trylock() or mutex_lock() won't increment
+ potentialLockers after we've checked it and before we've gained the lock
+ on the POSIX mutex. Of course, in that case the operation of gaining
+ the POSIX lock itself will succeed immediately, and once it has
+ succeeded, trylock releases lockForPotentialLockers right away,
+ incremented to 1 (one).
+ */
+ Object lockForPotentialLockers;
+
+ GThreadMutex() {
+ potentialLockers = 0;
+ lockForPotentialLockers = new Object();
+ }
+}
+// Local Variables:
+// c-file-style: "gnu"
+// End:
--- /dev/null 2003-01-30 10:24:37.000000000 +0000
+++ gnu/java/awt/peer/gtk/GThreadNativeMethodRunner.java 2004-05-12
20:24:15.000000000 +0000
@@ -0,0 +1,219 @@
+/* GThreadNativeMethodRunner.java -- Implements pthread_create(), under
+ glib's gthread abstraction, for use with GNU Classpath's
+ --portable-native-sync option.
+ This is used by gthread-jni.c
+
+ Copyright (C) 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., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 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. */
+
+package gnu.java.awt.peer.gtk;
+import java.lang.ref.WeakReference;
+import java.util.Set;
+import java.util.Collections;
+import java.util.HashSet;
+
+/** Implements pthread_create(), under
+ glib's gthread abstraction, for use with GNU Classpath's
+ --portable-native-sync option.
+ This is used in gthread-jni.c
+
+ Also implements a registry for threads, mapping Thread objects to small
+ integers. We use weak references for threads that aren't joinable, so
+ that they will be garbage collected. Otherwise, Eclipse and other systems
+ that generate more than 2^16 threads would place a burden on the VM.
+*/
+
+public class GThreadNativeMethodRunner extends Thread {
+ /** The C function pointer that was passed to g_thread_create().
+ Specifically, this the numeric address of an object of
+ C type "void *(*funcPtr)(void *funcArg)".
+ */
+ private final long funcPtr;
+
+ /** The argument for the function "funcPtr(funcArg)". */
+ private final long funcArg;
+
+// /** A numeric code. We could use this to help along the system by
deleting
+// weak references as soon as we will no longer need them. We don't
+// actually use this at this time, though, since we would need to store
+// additional information (whether a thread was joinable or not) in order
+// to make this work. And there is not a clear efficiency gain. */
+// private final int myThreadID;
+
+ GThreadNativeMethodRunner(long funcPtr, long funcArg, boolean joinable) {
+ this.funcPtr = funcPtr;
+ this.funcArg = funcArg;
+// this.myThreadID = registerThread(self); // register self numerically
+ if (joinable)
+ registerSelfJoinable();
+ }
+
+ public void run() {
+ nativeRun(funcPtr, funcArg);
+ }
+
+ private native void nativeRun(long funcPtr, long funcArg);
+
+ /** THREADS is an array of threads, indexed by thread ID codes. Not sure
+ whether this is the "best" approach but it does make it O(1) to look up a
+ thread by its ID.
+
+ Zero is a valid thread ID code. Any negative number is invalid. */
+ private static WeakReference[] threads
+ = new WeakReference[17]; /* 17 is just a starting point. Any number will
+ do, including zero. */
+
+ /** This is a private method, only used by threadToThreadID, below.
+
+ Returns the registration number of the newly-registered thread.
+ */
+ private static synchronized int registerThread(Thread t) {
+ int i;
+ for (i = 0; i < threads.length; ++i) {
+ WeakReference ref = threads[i];
+ if (ref == null)
+ break; // found an empty spot.
+ }
+ if (i == threads.length) {
+ /* expand the array */
+ WeakReference[] nxt = new WeakReference[threads.length * 2];
+ for (int j = 0; j < threads.length; ++j) {
+ nxt[j] = threads[j];
+ }
+ // NXT is now the expanded array. It is the new THREADS.
+ threads = nxt;
+ }
+ threads[i] = new WeakReference(t);
+ return i;
+ }
+
+ /** This is O(n/2) lookup of threads by thread ID.
+
+ TODO We could use a WeakHashMap to store this info instead; the current
+ implementation is grossly inefficient if we have a huge number of
+ threads. I don't think the AWT code generates a huge number of them,
+ though, so we are probably safe.
+
+ Consider that not all threads will have a threadID, because not all
+ threads were launched by GThreadNativeMethodRunner.
+ This is a general routine for handling all threads, including the VM's
+ main thread, if appropriate.
+ */
+ static synchronized int threadToThreadID(Thread t) {
+ for (int i = 0; i < threads.length; ++i ) {
+ if (threads[i] == null)
+ continue;
+ Thread referent = (Thread) threads[i].get();
+ if (referent == null) {
+ threads[i] = null; // Purge the dead WeakReference.
+ continue;
+ }
+ if (referent.equals(t))
+ return i;
+ } // for()
+ /* No match found. */
+ return registerThread(t);
+ }
+
+ /** @param threadID Must be a non-negative integer.
+
+ Used to return null if the thread number was out of range or if
+ the thread was unregistered. Now we bail. We could go back to
+ returning null in some sort of check-free mode, so code that calls this
+ function must be prepared to get null. */
+ static Thread threadIDToThread(int threadID)
+ throws IllegalArgumentException
+ {
+ if (threadID < 0)
+ throw new IllegalArgumentException("Received a negative threadID, "
+ + threadID);
+ if (threadID >= threads.length)
+ throw new IllegalArgumentException
+ ("Received a threadID (" + threadID + ") higher than was"
+ + " ever issued");
+
+ /* Note: if the user is using a stale reference, things will just
+ break. We might end up getting a different thread than the one
+ expected.
+
+ TODO: Add an error-checking mode where the user's problems
+ with threads are announced. Giving a value higher than the top
+ issued one, for instance, would be a good use of this.
+
+ TODO: Consider disabling all of this error-checking; it probably
+ slows down the implementation. We could just return NULL*/
+ WeakReference threadRef = threads[threadID];
+ if (threadRef == null)
+ throw new IllegalArgumentException
+ ("Asked to look up a stale or unissued threadID (" + threadID + ")" );
+
+
+ Thread referent = (Thread) threadRef.get();
+ if (referent == null)
+ throw new IllegalArgumentException
+ ("Asked to look up a stale threadID (" + threadID + ")");
+
+ return referent;
+ }
+
+ /** Joinable threads need a permanent reference, so that they won't go away
+ when they die. That is because their thread IDs need to stay valid
+ until the thread is joined via g_thread_join(threadID). Joinable
+ threads have to be explicitly joined before they are allowed to go away
+ completely.
+ */
+ static final Set joinable = Collections.synchronizedSet(new HashSet());
+
+ private void registerSelfJoinable() {
+ joinable.add(this);
+ }
+
+ /** This method is only called from JNI, and only after we have succeeded in
+ a thread_join() operation. */
+ static void deRegisterJoinable(Thread thread) {
+ joinable.remove(thread);
+ }
+
+// private int registerSelf() {
+// return registerThread(self);
+// }
+
+
+}
+
+// Local Variables:
+// c-file-style: "gnu"
+// End:
--- /dev/null 2003-01-30 10:24:37.000000000 +0000
+++ native/jni/gtk-peer/gnu_java_awt_peer_gtk_GThreadNativeMethodRunner.c
2004-05-08 06:12:47.000000000 +0000
@@ -0,0 +1,68 @@
+/* Native implementation of functions in GThreadNativeMethodRunner
+ Copyright (C) 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., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 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. */
+
+
+#include "gnu_java_awt_peer_gtk_GThreadNativeMethodRunner.h"
+#include "gthread-jni.h"
+
+/*
+ * Class: GThreadNativeMethodRunner
+ * Method: nativeRun
+ * Signature: (J)V
+ *
+ * Purpose: Run the C function whose function pointer is
+ *
+ */
+JNIEXPORT void JNICALL
+Java_gnu_java_awt_peer_gtk_GThreadNativeMethodRunner_nativeRun(JNIEnv
*gdk_env, jobject lcl_obj,
+ jlong funcAddr, jlong funcArg)
+{
+ /* Convert the function's address back into a pointer to a C function. */
+ void *(*funcPtr)(void *) = (void *(*)(void *)) funcAddr;
+
+ /* We do not need to worry about the return value from funcPtr(); it's
+ just thrown away. That is part of the g_threads spec, so no reason
+ to worry about returning it. */
+ (void) funcPtr((void *) funcArg);
+ /* Fall off the end and terminate the thread of control. */
+}
+
+/* Local Variables: */
+/* c-file-style: "gnu" */
+/* End: */
+
+
--- /dev/null 2003-01-30 10:24:37.000000000 +0000
+++ native/jni/gtk-peer/gnu_java_awt_peer_gtk_GThreadNativeMethodRunner.h
2004-05-12 07:02:30.000000000 +0000
@@ -0,0 +1,27 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class GThreadNativeMethodRunner */
+
+#ifndef _Included_GThreadNativeMethodRunner
+#define _Included_GThreadNativeMethodRunner
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef GThreadNativeMethodRunner_MIN_PRIORITY
+#define GThreadNativeMethodRunner_MIN_PRIORITY 1L
+#undef GThreadNativeMethodRunner_NORM_PRIORITY
+#define GThreadNativeMethodRunner_NORM_PRIORITY 5L
+#undef GThreadNativeMethodRunner_MAX_PRIORITY
+#define GThreadNativeMethodRunner_MAX_PRIORITY 10L
+/*
+ * Class: GThreadNativeMethodRunner
+ * Method: nativeRun
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL
Java_gnu_java_awt_peer_gtk_GThreadNativeMethodRunner_nativeRun (JNIEnv *,
jobject, jlong funcAddr, jlong funcArg);
+
+
+#ifdef __cplusplus
+}
+#endif
+#endif
Index: native/jni/gtk-peer/gnu_java_awt_peer_gtk_GtkMainThread.c
===================================================================
RCS file:
/cvsroot/classpath/classpath/native/jni/gtk-peer/gnu_java_awt_peer_gtk_GtkMainThread.c,v
retrieving revision 1.17
diff -I*.class -u -r1.17 gnu_java_awt_peer_gtk_GtkMainThread.c
--- native/jni/gtk-peer/gnu_java_awt_peer_gtk_GtkMainThread.c 29 Mar 2004
07:07:27 -0000 1.17
+++ native/jni/gtk-peer/gnu_java_awt_peer_gtk_GtkMainThread.c 13 May 2004
02:24:14 -0000
@@ -63,10 +63,6 @@
JNIEnv *gdk_env;
-#ifdef PORTABLE_NATIVE_SYNC
-JavaVM *gdk_vm;
-#endif
-
GtkWindowGroup *global_gtk_window_group;
/*
@@ -87,18 +83,25 @@
NSA_INIT (env, clazz);
/* GTK requires a program's argc and argv variables, and requires that they
- be valid. */
-
- argv = (char **) malloc (sizeof (char *) * 2);
- argv[0] = "";
+ be valid. Set it up. */
+ argv = (char **) g_malloc (sizeof (char *) * 2);
+ argv[0] = (char *) g_malloc(1);
+#if 1
+ strcpy(argv[0], "");
+#else /* The following is a more efficient alternative, but less intuitively
+ * expresses what we are trying to do. This code is only run once, so
+ * I'm going for intuitive. */
+ argv[0][0] = '\0';
+#endif
argv[1] = NULL;
/* until we have JDK 1.2 JNI, assume we have a VM with threads that
match what GLIB was compiled for */
#ifdef PORTABLE_NATIVE_SYNC
- (*env)->GetJavaVM( env, &gdk_vm );
- g_thread_init ( &g_thread_jni_functions );
- printf("called gthread init\n");
+ (*env)->GetJavaVM( env, &the_vm );
+ g_thread_init ( &portable_native_sync_jni_functions );
+ /* Debugging progress message; uncomment if needed: */
+ /* printf("called gthread init\n"); */
#else
g_thread_init ( NULL );
#endif
@@ -121,16 +124,15 @@
if ((homedir = getenv ("HOME")))
{
- rcpath = (char *) malloc (strlen (homedir) + strlen (RC_FILE) + 2);
+ rcpath = (char *) g_malloc (strlen (homedir) + strlen (RC_FILE) + 2);
sprintf (rcpath, "%s/%s", homedir, RC_FILE);
}
gtk_rc_parse ((rcpath) ? rcpath : RC_FILE);
- if (rcpath)
- free (rcpath);
-
- free (argv);
+ g_free (rcpath);
+ g_free (argv[0]);
+ g_free (argv);
/* setup cached IDs for posting GTK events to Java */
/* gtkgenericpeer = (*env)->FindClass (env, */
Index: native/jni/gtk-peer/gthread-jni.c
===================================================================
RCS file: /cvsroot/classpath/classpath/native/jni/gtk-peer/gthread-jni.c,v
retrieving revision 1.11
diff -I*.class -u -r1.11 gthread-jni.c
--- native/jni/gtk-peer/gthread-jni.c 30 Apr 2004 11:05:17 -0000 1.11
+++ native/jni/gtk-peer/gthread-jni.c 13 May 2004 02:24:15 -0000
@@ -40,142 +40,1049 @@
/************************************************************************/
/*
- * Julian Dolby (address@hidden)
- * February 7, 2003
+ * @author Julian Dolby (address@hidden)
+ * @date February 7, 2003
+ *
+ * @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 2.0,
+ * 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 fucntionality uses the java.lang.ThreadLocal class.
+ * local functionality uses the java.lang.ThreadLocal class.
*
- * This code is designed to be portable in that it makes no assumptions
+ * 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.
*
- * The one piece that does not really work is trylock for mutexes. The
- * Java locking model does not include such functionality, and I do not
- * see how to implement it without knowing something about how the VM
- * implements locking.
+ * Currently, use of this code is governed by the configuration option
+ * --enable-portable-native-sync
*
* NOTES:
*
- * I have tested it only on JikesRVM---the CVS head as of early February
- * 2003.
+ * We have tested this only on Jikes RVM---the CVS head as of
+ * early February 2003 and the CVS head as of May, 2004.
*
- * Currently, use of this code is governed by the configuration option
- * --enable-portable-native-sync
*
+ * MINOR NITS:
+ *
+ * - This file is only needed for --portable-native-sync. We
+ * conditionally compile it against the PORTABLE_NATIVE_SYNC macro. Should
+ * we manipulate Makefile.am instead? Or should we go ahead and compile
+ * it so that something which breaks this code will not be used?
+ *
+ * - 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>
+#if PORTABLE_NATIVE_SYNC /* This is not needed otherwise. */
+
+/************************************************************************/
+/* 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. */
+
+#define EXPLAIN_TROUBLE 0 /* Describe any unexpected trouble that
+ happens. This includes running out of Java
+ memory. Use criticalMsg() to describe it.
+ */
+
+#define EXPLAIN_BROKEN 0 /* Describe trouble that is serious enough to
+ be BROKEN. Most trouble is at least
+ BROKEN. */
+
+/* We will always explain BADLY_BROKEN trouble, since there is no other way to
+ report it. */
+
+/** Error Handling **/
+#define DIE_IF_BROKEN 0 /* 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. */
+
+#if DIE_IF_BROKEN
+#define DIE_IF_BADLY_BROKEN 1
+#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 0 /* 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. */
+
+#define HAVE_JNI_VERSION_1_2 0 /* Assume we don't. We could dynamically
+ check for this.
+
+ TODO This code hasn't been tested yet. */
/************************************************************************/
/* Global data */
/************************************************************************/
+#include <stdint.h> /* provides intptr_t */
+#include <stdarg.h> /* va_list */
#include "gthread-jni.h"
+#include <assert.h> /* assert() */
+
+/* For Java thread priority constants. */
+#include "gnu_java_awt_peer_gtk_GThreadNativeMethodRunner.h"
+
+/* The VM handle. This is set in
+ Java_gnu_java_awt_peer_gtk_GtkMainThread_gtkInit */
+JavaVM *the_vm;
+
+/* 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)));
-/* The VM handle. This is set in GtkToolkitMain.gtkInit */
-JavaVM *gdk_vm;
+static void tracing(const char fmt[], ...)
+ __attribute__ ((format(printf, 1, 2)));
+static jint javaPriorityLevel(GThreadPriority priority)
+ __attribute__((const));
/************************************************************************/
-/* Utilities to reflect exceptions back to the VM */
+/* Trouble-handling, including utilities to reflect exceptions */
+/* back to the VM. Also some status reporting.
*/
/************************************************************************/
-/* This function checks for a pending exception, and rethrows it with
+/* How are we going to handle problems?
+
+ There are several approaches:
+
+ 1) Report them with the GError mechanism (this only works for
+ thread_create())
+
+ 2) Reflect the exception back to the VM, wrapped in a RuntimeException.
+ This will fail sometimes -- but you can enable DIE_IF_BADLY_BROKEN to
+ croak in such a situation.
+
+ 3) Abort execution. Enable DIE_IF_BROKEN and/or DIE_IF_BADLY_BROKEN to
+ make this the default for BROKEN or BADLY_BROKEN trouble.
+
+ 4) Display messages to stderr. We always do this for BADLY_BROKEN
+ trouble.
+
+ 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 -- exactly the sort of error this attempted to avoid.
+ So I don't use the g_critical() and g_error() functions any more from this
+ code. Nor do I use g_assert(); I use old-fashioned 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 */
+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)
+
+/* Needed for exception-wrapping */
+#define DELETE_LOCAL_REF(env, ref) \
+ do \
+ { \
+ if ( DELETE_LOCAL_REFS ) \
+ { \
+ (*env)->DeleteLocalRef(env, ref); \
+ (ref) = NULL; \
+ } \
+ } \
+ while(0)
+
+/* Cached info for Exception-wrapping */
+
+jclass runtimeException_class; /* java.lang.RuntimeException */
+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)))
+ {
+ bzero(buf, len);
+ 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 void maybe_rethrow(JNIEnv *gdk_env, char *message, char *file, int
line) {
- jthrowable cause;
+static int
+maybe_rethrow(JNIEnv *env, const char *message, gboolean isBroken,
+ const char *file, int line)
+{
+ jthrowable cause = (*env)->ExceptionOccurred(env);
+ int ret = 0;
- jstring jmessage;
- jclass obj_class;
- jobject obj;
- jmethodID ctor;
- int len;
- char *buf;
-
- /* rethrow if an exception happened */
- if ((cause = (*gdk_env)->ExceptionOccurred(gdk_env)) != NULL)
- {
-
- /* allocate local message in Java */
- len = strlen(message) + strlen(file) + 25;
- buf = (char *) malloc(len);
- if (buf != NULL)
- {
- bzero(buf, len);
- sprintf(buf, "%s (at %s:%d)", message, file, line);
- jmessage = (*gdk_env)->NewStringUTF(gdk_env, buf);
- free(buf);
- }
- else
- jmessage = NULL;
+ /* rethrow if an exception happened */
+ if (cause)
+ {
+ ret = rethrow(env, cause, message, isBroken, file, line);
+ DELETE_LOCAL_REF(env, cause);
+ }
+
+ return 0;
+}
+
+/* This macro 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 */
+#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__)
+
+#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__)
+
+#define NEW_TROUBLE(_env, _message) \
+ throw(_env, NULL, _message, FALSE, __FILE__, __LINE__)
+
+#define NEW_BROKEN(_env, _message) \
+ throw(_env, NULL, _message, TRUE, __FILE__, __LINE__)
+
+#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. That is
+ *highly* unlikely, but perhaps not absolutely impossible, since we have no
+ way of knowing what is really going on.
+
+ 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, since HIDE_OLD_TROUBLE includes declarations.
+
+ 2) They chew up more time and resources.
+
+ 3) There does not ever seem to be old trouble -- the assertion that I
+ replaced HIDE_OLD_TROUBLE with never goes off.
+
+ 4) I'd put them in because I hadn't wanted to confuse the error reporting
+ while debugging, by possibly showing old exceptions. That is no longer
+ a problem.
+
+ HIDE_OLD_TROUBLE has to be called after the local variable (env) is
+ already set.
+
+*/
+#if 1
+#define HIDE_OLD_TROUBLE(env) \
+ assert ( NULL == (*env)->ExceptionOccurred(env) )
+
+#define SHOW_OLD_TROUBLE() do {} while(0) /* no-op */
+#else
+/* These only work with C compilers that let you mix declarations and
+ statements, to wit, gcc and C '99. Further, they assume that these
+ functions might be called with an exception pending. That should never be
+ the case ... but it could be! */
+#define HIDE_OLD_TROUBLE(env) \
+ jthrowable savedTrouble = (*env)->ExceptionOccurred(env); \
+ (*env)->ExceptionClear(env);
+
+/* The implementations in this file are responsible for skipping around calls
+ to SHOW_OLD_TROUBLE() if they've raiseed exceptions during the call. */
+#define SHOW_OLD_TROUBLE() do { \
+ if (savedTrouble) { \
+ if ((*env)->Throw(env, savedTrouble)) { \
+ criticalMsg(WHERE "ReThrowing the savedTrouble failed"); \
+ assert_not_reached(); \
+ } \
+ } \
+ DELETE_LOCAL_REF(env, savedTrouble) \
+} while(0)
+
+#endif /* 0 */
+
+/* Set up the cache of jclass and jmethodID primitives we need
+ for throw(). We do this independently of the others, since we need
+ to have this cache set up first, so that we can then report errors
+ properly.
+
+*/
+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;
+ }
+
+ assert(runtimeException_class);
+
+ 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.
+
+ --Steven Augart
+*/
+
+/* All of these are cached classes and method IDs: */
+/* java.lang.Object */
+jclass obj_class; /* java.lang.Object */
+jmethodID obj_ctor; /* no-arg Constructor for java.lang.Object */
+jmethodID obj_notify_mth; /* java.lang.Object.notify() */
+jmethodID obj_notifyall_mth; /* java.lang.Object.notifyall() */
+jmethodID obj_wait_mth; /* java.lang.Object.wait() */
+jmethodID obj_wait_nanotime_mth; /* java.lang.Object.wait(JJ) */
+
+/* GThreadMutex and its methods */
+jclass mutex_class;
+jmethodID mutex_ctor;
+jfieldID mutex_lock_fld;
+jfieldID mutex_lockForPotentialLockers_fld;
+jfieldID mutex_potentialLockers_fld;
+
+/* java.lang.Thread and its methods*/
+jclass thread_class; /* java.lang.Thread */
+jmethodID thread_current_mth; /* Thread.currentThread() */
+jmethodID thread_equals_mth; /* Thread.equals() */
+jmethodID thread_join_mth; /* Thread.join() */
+jmethodID thread_setPriority_mth; /* Thread.setPriority() */
+jmethodID thread_stop_mth; /* Thread.stop() */
+jmethodID thread_yield_mth; /* Thread.yield() */
+
+/* java.lang.ThreadLocal and its methods */
+jclass threadlocal_class; /* java.lang.ThreadLocal */
+jmethodID threadlocal_ctor; /* Its constructor */
+jmethodID threadlocal_set_mth; /* ThreadLocal.set() */
+jmethodID threadlocal_get_mth; /* ThreadLocal.get() */
+
+/* java.lang.Long and its methods */
+jclass long_class; /* java.lang.Long */
+jmethodID long_ctor; /* constructor for it: (J) */
+jmethodID long_longValue_mth; /* longValue()J */
+
+
+/* GThreadNativeMethodRunner */
+jclass runner_class;
+jmethodID runner_ctor;
+jmethodID runner_threadToThreadID_mth;
+jmethodID runner_threadIDToThread_mth;
+jmethodID runner_deRegisterJoinable_mth;
+jmethodID runner_start_mth; /* Inherited Thread.start() */
+
+
+/* java.lang.InterruptedException */
+jclass interrupted_exception_class;
+
+
+
+
+/* Return a negative value if there was trouble during initialization. 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;
+
+ assert(! (*env)->ExceptionCheck(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;
+ }
+
+
+ if (! (mutex_lock_fld = (*env)->GetFieldID(env, mutex_class,
+ "lock", "Ljava/lang/Object;")))
+ {
+ BROKEN(env, "cannot find GThreadMutex.lock");
+ 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;
+ }
+ assert(thread_class);
+
+ 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;
+ }
+ assert(threadlocal_class);
+
+
+ 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;
+ }
+ assert(long_class);
+
+
+ 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 */
+ runner_class = (*env)->FindClass (env,
"gnu/java/awt/peer/gtk/GThreadNativeMethodRunner");
+ if ( ! runner_class )
+ {
+ BROKEN(env, "cannot find
gnu.java.awt.peer.gtk.GThreadNativeMethodRunner");
+ return initialized = -1;
+ }
+
+ /* Pin it down. */
+ runner_class = (jclass) (*env)->NewGlobalRef(env, runner_class);
+ if (!runner_class)
+ {
+ BROKEN(env, "Cannot get a global reference to the class
GThreadNativeMethodRunner");
+ return initialized = -1;
+ }
+ assert(runner_class);
+
+ 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;
+ }
+ assert(interrupted_exception_class);
+
+#ifdef JNI_VERSION_1_2
+ if (HAVE_JNI_VERSION_1_2)
+ assert(! (*env)->ExceptionCheck(env));
+ else
+#endif
+ assert(! (*env)->ExceptionOccurred(env));
- /* create RuntimeException wrapper object */
- obj_class = (*gdk_env)->FindClass (gdk_env,
- "java/lang/RuntimeException");
- ctor = (*gdk_env)->GetMethodID(gdk_env, obj_class, "<init>",
- "(Ljava/langString;Ljava/lang/Throwable)V");
- obj = (*gdk_env)->NewObject (gdk_env, obj_class, ctor, jmessage, cause);
- /* throw it */
- (*gdk_env)->Throw(gdk_env, (jthrowable)obj);
- }
+ return initialized = 1;
}
-/* This macro is used to include a source location in the exception message */
-#define MAYBE_RETHROW(_class, _message) \
-maybe_rethrow(_class, _message, __FILE__, __LINE__)
+
+
/************************************************************************/
/* Utilities to allocate and free java.lang.Objects */
/************************************************************************/
-/* Both the mutexes and the condition variables are java.lang.Object 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() {
- jclass obj_class;
- jobject *obj;
- JNIEnv *gdk_env;
- jmethodID ctor;
-
- (*gdk_vm)->GetEnv(gdk_vm, (void **)&gdk_env, JNI_VERSION_1_1);
-
- obj_class = (*gdk_env)->FindClass (gdk_env, "java/lang/Object");
- MAYBE_RETHROW(gdk_env, "cannot find Object");
-
- ctor = (*gdk_env)->GetMethodID(gdk_env, obj_class, "<init>", "()V");
- MAYBE_RETHROW(gdk_env, "cannot find constructor");
+static jobject
+allocatePlainObject(JNIEnv *env)
+{
+ jobject lcl_obj, global_obj;
- obj = (jobject *) g_malloc (sizeof (jobject));
- *obj = (*gdk_env)->NewObject (gdk_env, obj_class, ctor);
- MAYBE_RETHROW(gdk_env, "cannot allocate object");
+ lcl_obj = (*env)->NewObject (env, obj_class, obj_ctor);
+ if (! lcl_obj )
+ {
+ BROKEN(env, "cannot allocate object");
+ return NULL;
+ }
- *obj = (*gdk_env)->NewGlobalRef (gdk_env, *obj);
- MAYBE_RETHROW(gdk_env, "cannot make global ref");
+ 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 obj;
+ return global_obj;
}
-/* Frees a Java object given a global ref (isn't C fun?) */
-static void freePlainObject(jobject *obj) {
- JNIEnv *gdk_env;
-
+/* Frees any Java object given a global ref (isn't C fun?) */
+static void
+freeObject(JNIEnv *env, jobject obj)
+{
if (obj) {
- (*gdk_vm)->GetEnv(gdk_vm, (void **)&gdk_env, JNI_VERSION_1_1);
+ (*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;
- (*gdk_env)->DeleteGlobalRef (gdk_env, *obj);
- MAYBE_RETHROW(gdk_env, "cannot delete global ref");
+ lcl_obj = (*env)->NewObject (env, mutex_class, mutex_ctor);
+ if (! lcl_obj )
+ {
+ BROKEN(env, "cannot allocate a GThreadMutex");
+ return NULL;
+ }
- g_free (obj);
- }
+ 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;
}
@@ -184,195 +1091,723 @@
/************************************************************************/
/* Lock a Java object */
-static void takeLock(JNIEnv *gdk_env, void *mutex) {
- (*gdk_env)->MonitorEnter (gdk_env, *((jobject *)mutex));
- MAYBE_RETHROW(gdk_env, "cannot get lock");
+#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 */
-static void releaseLock(JNIEnv *gdk_env, void *mutex) {
- (*gdk_env)->MonitorExit (gdk_env, *((jobject *)mutex));
- MAYBE_RETHROW(gdk_env, "cannot release lock");
+#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 = (jint) 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 the pointer equivalent -1 on all failures.
+
+ A null thread means we call NEW_BROKEN().
+ For other errors, return -1 on error and call BROKEN. */
+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 (gpointer) 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. */
+ jobject lockObj; /* The real lock we use. */
+};
+
+/* 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)
+{
+#if 0 /* New implementation. XXX This should work. */
+ mcache->lockObj = mutexObj; /* the mutexObj is its own lock. */
+#else /* Old implementation. */
+ /* So, get the lock itself now. */
+ mcache->lockObj = (*env)->GetObjectField (env, mutexObj, mutex_lock_fld);
+ /* Can never fail. Or so the JNI book says.
+ I personally fail to see how it can never fail -- one might give it a
+ NULL object, for instance. That would throw a NullPointerException in
+ Java, so why not here?
+
+ A NULL would mean that we had an internal error with a mutex object that
+ had never gotten initialized properly. */
+#endif
+ assert (mcache->lockObj);
+
+ mcache->lockForPotentialLockersObj = (*env)->GetObjectField
+ (env, mutexObj, mutex_lockForPotentialLockers_fld);
+ /* Can never fail. Or so the JNI book says.
+ A NULL would mean that we had an internal error with a mutex object that
+ had never gotten initialized properly. */
+ assert(mcache->lockForPotentialLockersObj);
+
+ return 0;
}
-/* Create a mutex, which is a java.lang.Object for us */
-static GMutex *g_mutex_new_jni_impl (void) {
- return (GMutex*) allocatePlainObject();
+
+/* 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);
+ DELETE_LOCAL_REF(env, mcache->lockObj);
+}
+
+/* -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;
+
+ potentialLockers = (*env)->GetIntField
+ (env, mutexObj, mutex_potentialLockers_fld);
+ if (MAYBE_BROKEN(env, "cannot read GThreadMutex.potentialLockers field's
value"))
+ return -1;
+
+ ++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);
+ /* Never fails, so the JNI book says. */
+
+ 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;
+
+ if (TRACE_API_CALLS)
+ tracing ("mutex_new_jni_impl()");
+
+ (*the_vm)->GetEnv(the_vm, (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 g_mutex_lock_jni_impl (GMutex *mutex __attribute__((unused))) {
- JNIEnv *gdk_env;
+static void
+mutex_lock_jni_impl (GMutex *mutex)
+{
+ struct mutexObj_cache mcache;
+ jobject mutexObj = (jobject) mutex;
+ JNIEnv *env;
+
+ if (TRACE_API_CALLS)
+ tracing ("mutex_lock_jni_impl( mutexObj = %p )", mutexObj);
- (*gdk_vm)->GetEnv(gdk_vm, (void **)&gdk_env, JNI_VERSION_1_1);
+ assert(mutexObj);
+ (*the_vm)->GetEnv(the_vm, (void **)&env, JNI_VERSION_1_1);
- takeLock(gdk_env, mutex);
+ 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. Actually, do not try because Java objects
- * do not provide such an interface. To be at least minimally correct,
- * pretend we tried and failed.
- */
-static gboolean g_mutex_trylock_jni_impl
- (GMutex *mutex __attribute__((unused)))
+
+/* 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)
{
- /* XXX Shall we implement this in a VM-specific way under a flag? */
- return FALSE;
+ jobject mutexObj = (jobject) gmutex;
+ jint potentialLockers;
+ gboolean ret = FALSE;
+ JNIEnv *env;
+ struct mutexObj_cache mcache;
+
+ if (TRACE_API_CALLS)
+ tracing ("mutex_trylock_jni_impl(mutexObj=%p)", mutexObj);
+
+ assert(mutexObj);
+
+ (*the_vm)->GetEnv(the_vm, (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 error
code;
+ nothing to be done.
+ */
+ goto done;
+ }
+
+ assert(potentialLockers == 0);
+ /* This is guaranteed not to block, since we've done our testing */
+ if (ENTER_MONITOR(env, mcache.lockObj))
+ goto done;
+
+
+ /* We have the monitor. Record that fact. */
+ potentialLockers = 1; /* Was zero. */
+ (*env)->SetIntField
+ (env, mutexObj, mutex_potentialLockers_fld, potentialLockers);
+ /* Set*Field() never fails */
+
+ /* Clean up. */
+ if (EXIT_MONITOR(env, mcache.lockForPotentialLockersObj))
+ goto done;
+
+ SHOW_OLD_TROUBLE();
+
+ ret = TRUE; /* We have it! */
+ done:
+ clean_mutexObj_cache(env, &mcache);
+ if (TRACE_API_CALLS)
+ tracing (" ==> %s\n", ret ? "TRUE" : "FALSE");
+ return ret;
}
+
/* Unlock a mutex. */
-static void g_mutex_unlock_jni_impl (GMutex *mutex) {
- JNIEnv *gdk_env;
+static void
+mutex_unlock_jni_impl (GMutex *gmutex)
+{
+ jobject mutexObj = (jobject) gmutex;
+ struct mutexObj_cache mcache;
+ JNIEnv *env;
+
+ if (TRACE_API_CALLS)
+ tracing ("mutex_unlock_jni_impl(mutexObj=%p)", mutexObj);
+
+ (*the_vm)->GetEnv(the_vm, (void **)&env, JNI_VERSION_1_1);
+ if (setup_cache(env) < 0)
+ goto done;
+ HIDE_OLD_TROUBLE(env);
+
+ assert(mutexObj);
- (*gdk_vm)->GetEnv(gdk_vm, (void **)&gdk_env, JNI_VERSION_1_1);
+ if (populate_mutexObj_cache(env, mutexObj, &mcache) < 0)
+ goto done;
- releaseLock(gdk_env, mutex);
+ (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?) */
-static void g_mutex_free_jni_impl (GMutex *mutex)
+
+
+/* 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)
{
- freePlainObject( (jobject*)mutex );
+ jobject mutexObj = (jobject) mutex;
+ JNIEnv *env;
+
+ (*the_vm)->GetEnv (the_vm, (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 *g_cond_new_jni_impl () {
- return (GCond*)allocatePlainObject();
+static GCond *
+cond_new_jni_impl (void)
+{
+ jobject condObj;
+ JNIEnv *env;
+
+ if (TRACE_API_CALLS)
+ tracing ("mutex_free_jni_impl()");
+
+ (*the_vm)->GetEnv (the_vm, (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 g_cond_signal_jni_impl (GCond *cond) {
- jclass lcl_class;
- jmethodID signal_mth;
- JNIEnv *gdk_env;
+static void
+cond_signal_jni_impl (GCond *gcond)
+{
+ JNIEnv *env;
+ jobject condObj = (jobject) gcond;
- (*gdk_vm)->GetEnv(gdk_vm, (void **)&gdk_env, JNI_VERSION_1_1);
+ if (TRACE_API_CALLS)
+ tracing ("cond_signal_jni_impl(condObj = %p)", condObj);
- lcl_class = (*gdk_env)->FindClass (gdk_env, "java.lang.Object");
- MAYBE_RETHROW(gdk_env, "cannot find Object");
-
- signal_mth = (*gdk_env)->GetMethodID(gdk_env, lcl_class, "notify", "()V");
- MAYBE_RETHROW(gdk_env, "cannot find Object.<notify>");
+ (*the_vm)->GetEnv(the_vm, (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 */
- takeLock(gdk_env, cond);
+ if (ENTER_MONITOR(env, condObj))
+ goto done;
- (*gdk_env)->CallVoidMethod(gdk_env, *(jobject*)cond, signal_mth);
- MAYBE_RETHROW(gdk_env, "cannot signal mutex");
+ (*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;
+ }
- releaseLock(gdk_env, cond);
+ 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 g_cond_broadcast_jni_impl (GCond *cond) {
- jclass lcl_class;
- jmethodID bcast_mth;
- JNIEnv *gdk_env;
+static void
+cond_broadcast_jni_impl (GCond *gcond)
+{
+ jobject condObj = (jobject) gcond;
+ JNIEnv *env;
- (*gdk_vm)->GetEnv(gdk_vm, (void **)&gdk_env, JNI_VERSION_1_1);
+ if (TRACE_API_CALLS)
+ tracing ("cond_broadcast_jni_impl(condObj=%p)", condObj);
- lcl_class = (*gdk_env)->FindClass (gdk_env, "java.lang.Object");
- MAYBE_RETHROW(gdk_env, "cannot find Object");
-
- bcast_mth = (*gdk_env)->GetMethodID(gdk_env, lcl_class, "notifyAll", "()V");
- MAYBE_RETHROW(gdk_env, "cannot find Object.<notifyAll>");
+ (*the_vm)->GetEnv(the_vm, (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 */
- takeLock(gdk_env, cond);
+ 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;
+ }
- (*gdk_env)->CallVoidMethod(gdk_env, *(jobject*)cond, bcast_mth);
- MAYBE_RETHROW(gdk_env, "cannot broadcast to mutex");
+ EXIT_MONITOR(env, condObj);
- releaseLock(gdk_env, cond);
+ SHOW_OLD_TROUBLE();
+
+ done:
+ if (TRACE_API_CALLS)
+ tracing (" ==> VOID\n");
}
-/* Wait on a condition variable. For us, this simply means call
+/* 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 g_cond_wait_jni_impl
- (GCond *cond, GMutex *mutex __attribute__((unused)))
+static void
+cond_wait_jni_impl (GCond *gcond, GMutex *gmutex)
{
- jclass lcl_class;
- jmethodID wait_mth;
- JNIEnv *gdk_env;
-
- (*gdk_vm)->GetEnv(gdk_vm, (void **)&gdk_env, JNI_VERSION_1_1);
-
- lcl_class = (*gdk_env)->FindClass (gdk_env, "java.lang.Object");
- MAYBE_RETHROW(gdk_env, "cannot find Object");
+ struct mutexObj_cache cache;
+ jobject condObj = (jobject) gcond;
+ jobject mutexObj = (jobject) gmutex;
+ JNIEnv *env;
+
+ if (TRACE_API_CALLS)
+ tracing ("cond_wait_jni_impl(condObj=%p, mutexObj=%p)",
+ condObj, mutexObj);
+
+ (*the_vm)->GetEnv(the_vm, (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;
- wait_mth = (*gdk_env)->GetMethodID(gdk_env, lcl_class, "wait", "()V");
- MAYBE_RETHROW(gdk_env, "cannot find Object.<wait>");
+ (*env)->CallVoidMethod(env, condObj, obj_wait_mth);
+ if (MAYBE_BROKEN(env, "cannot wait on condObj"))
+ {
+ EXIT_MONITOR(env, condObj); /* ignore err checking */
+ goto done;
+ }
- /* Must have locked an object to call wait */
- takeLock(gdk_env, cond);
+ /* 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);
- (*gdk_env)->CallVoidMethod(gdk_env, *(jobject*)cond, wait_mth);
- MAYBE_RETHROW(gdk_env, "cannot wait on mutex");
+ SHOW_OLD_TROUBLE();
- releaseLock(gdk_env, cond);
+ done:
+ if (TRACE_API_CALLS)
+ tracing (" ==> VOID\n");
}
-/* Wait on a condition vairable until a timeout. This is a little tricky
+
+/** 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.
+ * 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
-g_cond_timed_wait_jni_impl
- (GCond *cond, GMutex *mutex __attribute__((unused)),
- GTimeVal *end_time)
+cond_timed_wait_jni_impl(GCond *gcond, GMutex *gmutex, GTimeVal *end_time)
{
- jclass lcl_class;
- jmethodID wait_mth;
- JNIEnv *gdk_env;
- jlong time;
+ JNIEnv *env;
+ 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);
+ }
- (*gdk_vm)->GetEnv(gdk_vm, (void **)&gdk_env, JNI_VERSION_1_1);
-
- lcl_class = (*gdk_env)->FindClass (gdk_env, "java.lang.Object");
- MAYBE_RETHROW(gdk_env, "cannot find Object");
- wait_mth = (*gdk_env)->GetMethodID(gdk_env, lcl_class, "wait", "(J)V");
- MAYBE_RETHROW(gdk_env, "cannot find Object.<wait(J)>");
-
- time = end_time->tv_sec*1000;
- time += end_time->tv_usec/1000;
+ (*the_vm)->GetEnv(the_vm, (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 */
- takeLock(gdk_env, cond);
+ if (ENTER_MONITOR(env, condObj) < 0)
+ goto done;
- (*gdk_env)->CallVoidMethod(gdk_env, *(jobject*)cond, wait_mth, time);
+ 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;
+ }
+
- if ((cause = (*gdk_env)->ExceptionOccurred(gdk_env)) != NULL) {
- jclass intr = (*gdk_env)->FindClass (gdk_env,
"java.lang.InterruptedException");
- if ( (*gdk_env)->IsInstanceOf(gdk_env, cause, intr) ) {
- releaseLock(gdk_env, cond);
- return FALSE;
- } else {
- MAYBE_RETHROW(gdk_env, "error in timed wait");
+ (*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;
}
- }
- releaseLock(gdk_env, cond);
+ SHOW_OLD_TROUBLE();
- return TRUE;
+ done:
+ if (TRACE_API_CALLS)
+ tracing (" ==> condRaised = %s\n", condRaised ? "TRUE" : "FALSE");
+ return condRaised;
}
-/* Free a condition variable. (isn't C fun?) */
-static void g_cond_free_jni_impl (GCond *cond) {
- freePlainObject( (jobject*)cond );
+
+/* 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;
+
+ if (TRACE_API_CALLS)
+ tracing("cond_free_jni_impl(condObj = %p)", condObj);
+ (*the_vm)->GetEnv(the_vm, (void **)&env, JNI_VERSION_1_1);
+
+ freeObject (env, condObj );
+
+ if (TRACE_API_CALLS)
+ tracing (" ==> VOID\n");
}
@@ -380,128 +1815,580 @@
/* Thread-local data code */
/************************************************************************/
-/* Create a new thread-local key. We use java.lang.ThreadLocal objects
- * for this.
+/* 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 *g_private_new_jni_impl
- (GDestroyNotify notify __attribute__((unused)))
+static GPrivate *
+private_new_jni_impl (GDestroyNotify notify __attribute__((unused)))
{
- jclass lcl_class;
- jobject *local;
- JNIEnv *gdk_env;
- jmethodID ctor;
+ JNIEnv *env;
+ jobject lcl_key;
+ jobject global_key;
+ GPrivate *gkey = NULL; /* Error return code */
+
+ if (TRACE_API_CALLS)
+ tracing ("private_new_jni_impl()");
+
+ (*the_vm)->GetEnv(the_vm, (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);
- (*gdk_vm)->GetEnv(gdk_vm, (void **)&gdk_env, JNI_VERSION_1_1);
+ 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;
+ 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);
+
+ (*the_vm)->GetEnv(the_vm, (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;
+ }
+
- lcl_class = (*gdk_env)->FindClass (gdk_env, "java.lang.ThreadLocal");
- MAYBE_RETHROW(gdk_env, "cannot find ThreadLocal");
+
+ val = (*env)->CallLongMethod (env, val_wrapper, long_longValue_mth);
- ctor = (*gdk_env)->GetMethodID(gdk_env, lcl_class, "<init>", "()V");
- MAYBE_RETHROW(gdk_env, "cannot find ThreadLocal.<init>");
+ if (MAYBE_BROKEN(env, "cannot get thread local value"))
+ goto done;
- local = (jobject *) g_malloc (sizeof (jobject));
- *local = (*gdk_env)->NewObject(gdk_env, lcl_class, ctor);
- MAYBE_RETHROW(gdk_env, "cannot allocate a ThreadLocal");
+ thread_specific_data = (gpointer) (intptr_t) val;
- *local = ((*gdk_env)->NewGlobalRef (gdk_env, *local));
- MAYBE_RETHROW(gdk_env, "cannot create a GlobalRef");
- return (GPrivate*) local;
+ /* 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;
}
-/* Get this thread's value for a thread-local key. This is simply
- * ThreadLocal.get for us.
+/* Set this thread's value for a thread-local key. This is simply
+ * ThreadLocal.set() for us.
*/
-static gpointer g_private_get_jni_impl (GPrivate *private) {
- jclass lcl_class;
- jobject lcl_obj;
- JNIEnv *gdk_env;
- jmethodID get_mth;
- jclass int_class;
- jmethodID val_mth;
- jint int_val;
+static void
+private_set_jni_impl (GPrivate *gkey, gpointer thread_specific_data )
+{
+ JNIEnv *env;
+ 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);
+
+ (*the_vm)->GetEnv(the_vm, (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. */
+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
+ trouble. */
+ GError **errorp)
+{
+ JNIEnv *env;
+ jboolean jjoinable = joinable;
+ jobject newThreadObj;
+ gpointer threadID; /* to be filled in */
+
+ if (TRACE_API_CALLS)
+ tracing ("thread_create_jni_impl(func=%p, data=%p, joinable=%s,"
+ " threadIDp=%p, *(int *) threadIDp = %d)",
+ (void *) func, data, joinable ? "TRUE" : "FALSE",
+ threadIDp, *(int *) threadIDp);
+
+ (*the_vm)->GetEnv (the_vm, (void **)&env, JNI_VERSION_1_1);
+ if (setup_cache (env) < 0)
+ {
+ *(gpointer *) threadIDp = NULL;
+ g_set_error (errorp, 0xffff, 0xffff, "Initializing thread cache failed");
+ goto done;
+ }
+ HIDE_OLD_TROUBLE(env);
- (*gdk_vm)->GetEnv(gdk_vm, (void **)&gdk_env, JNI_VERSION_1_1);
+ /* 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 )
+ {
+ TROUBLE(env, "creating a new thread failed in the constructor");
+ *(gpointer *) threadIDp = NULL;
+ /* g_set_error(errorp, G_THREAD_ERROR, G_THREAD_ERROR_FAILED,
+ "creating a new thread failed in the constructor"); */
+ g_set_error(errorp, 0xffff, 0xffff, "creating a new thread failed in the
constructor");
+ goto done;
+ }
- lcl_class = (*gdk_env)->FindClass (gdk_env, "java.lang.ThreadLocal");
- MAYBE_RETHROW(gdk_env, "cannot find ThreadLocal");
+ if (threadObj_set_priority (env, newThreadObj, gpriority) < 0)
+ {
+ *(gpointer *) threadIDp = NULL;
+ g_set_error(errorp, 0xffff, 0xffff, "Setting thread priority failed");
+ goto done;
+ }
+
+ (*env)->CallVoidMethod(env, runner_class, runner_start_mth);
+
+ if (MAYBE_TROUBLE(env, "starting a new thread failed"))
+ {
+ *(gpointer *) threadIDp = NULL;
+ /* TODO: Replace this with a proper G_ error type. */
+ g_set_error(errorp, 0xffff, 0xffff, "starting the new thread failed");
+ /* g_set_error(errorp, G_THREAD_ERROR, G_THREAD_ERROR_FAILED, "starting
+ the new thread failed"); */
+ goto done;
+ }
+
+ threadID = getThreadIDFromThread(env, newThreadObj);
- get_mth = (*gdk_env)->GetMethodID(gdk_env, lcl_class, "get",
"()Ljava/lang/Object;");
- MAYBE_RETHROW(gdk_env, "cannot find ThreadLocal.<get>");
+ *(gpointer *)threadIDp = threadID;
+ SHOW_OLD_TROUBLE();
+
+ done:
+ if (TRACE_API_CALLS)
+ tracing (" ==> (threadID = %p) \n", threadID);
+}
- lcl_obj = (*gdk_env)->CallObjectMethod(gdk_env, *(jobject*)private, get_mth);
- MAYBE_RETHROW(gdk_env, "cannot find thread-local object");
- int_class = (*gdk_env)->FindClass (gdk_env, "java.lang.Integer");
- MAYBE_RETHROW(gdk_env, "cannot find Integer");
+/* Wraps a call to g_thread_yield. */
+static void
+thread_yield_jni_impl (void)
+{
+ JNIEnv *env;
- val_mth = (*gdk_env)->GetMethodID(gdk_env, int_class, "intValue", "()I");
- MAYBE_RETHROW(gdk_env, "cannot find Integer.<intValue>");
+ if (TRACE_API_CALLS)
+ tracing ("thread_yield_jni_impl()");
- int_val = (*gdk_env)->CallIntMethod(gdk_env, lcl_obj, val_mth);
- MAYBE_RETHROW(gdk_env, "cannot get thread local value");
+ (*the_vm)->GetEnv(the_vm, (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();
- return (gpointer) int_val;
+ done:
+ if (TRACE_API_CALLS)
+ tracing (" ==> VOID\n");
}
-/* Set this thread's value for a thread-local key. This is simply
- * ThreadLocal.set for us.
- */
-static void g_private_set_jni_impl (GPrivate *private, gpointer data) {
- jclass lcl_class, int_class;
- jobject lcl_obj;
- JNIEnv *gdk_env;
- jmethodID new_int, set_mth;
- (*gdk_vm)->GetEnv(gdk_vm, (void **)&gdk_env, JNI_VERSION_1_1);
+static void
+thread_join_jni_impl (gpointer threadID)
+{
+ JNIEnv *env;
+ jobject threadObj;
- int_class = (*gdk_env)->FindClass (gdk_env, "java.lang.Integer");
- MAYBE_RETHROW(gdk_env, "cannot find Integer");
+ if ( TRACE_API_CALLS )
+ tracing ("thread_join_jni_impl(threadID=%p) ", threadID);
- new_int = (*gdk_env)->GetMethodID(gdk_env, int_class, "<init>", "(I)V");
- MAYBE_RETHROW(gdk_env, "cannot find Integer.<init>");
+ (*the_vm)->GetEnv(the_vm, (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;
+
- lcl_obj = (*gdk_env)->NewObject(gdk_env, int_class, new_int, (jint)data);
- MAYBE_RETHROW(gdk_env, "cannot create an Integer");
+ (*env)->CallStaticVoidMethod
+ (env, runner_class, runner_deRegisterJoinable_mth, threadObj);
+ if (MAYBE_BROKEN(env, "Thread.deRegisterJoinableThread() failed"))
+ goto done;
+
+ SHOW_OLD_TROUBLE();
+
+done:
+ 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
+ normally deprecated.
+
+ It's supposedly normally deprecated so that we can unlock monitors on the
+ way out -- Thread.stop() throws a ThreadDeath exception, which is usually
+ unchecked. Well, if we're in JNI code, we will die. */
+static void
+thread_exit_jni_impl (void)
+{
+ JNIEnv *env;
+ jobject this_thread;
- lcl_class = (*gdk_env)->FindClass (gdk_env, "java.lang.ThreadLocal");
- MAYBE_RETHROW(gdk_env, "cannot find ThreadLocal");
+ if (TRACE_API_CALLS)
+ tracing ("thread_exit_jni_impl() ");
- set_mth = (*gdk_env)->GetMethodID(gdk_env, lcl_class, "set",
"(Ljava/lang/Object;)V");
- MAYBE_RETHROW(gdk_env, "cannot find ThreadLocal.<set>");
+ (*the_vm)->GetEnv(the_vm, (void **)&env, JNI_VERSION_1_1);
+ if (setup_cache(env) < 0)
+ goto done;
- (*gdk_env)->CallVoidMethod(gdk_env, *(jobject*)private, set_mth, lcl_obj);
- MAYBE_RETHROW(gdk_env, "cannot set thread local value");
+ 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;
+ }
+ assert(this_thread);
+
+ (*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.
+ */
+ static const jint
+ minJPri = GThreadNativeMethodRunner_MIN_PRIORITY,
+ normJPri = GThreadNativeMethodRunner_NORM_PRIORITY,
+ maxJPri = GThreadNativeMethodRunner_MAX_PRIORITY;
+
+ switch ( priority )
+ {
+ case G_THREAD_PRIORITY_LOW:
+ return minJPri;
+ break;
+
+ default:
+ assert_not_reached();
+ /* Deliberate fall-through; shut up GCC warnings. Apparently glib (at
+ least the installation on my machine) doesn't tell GNU C that
+ assert_not_reached() will never return. */
+ 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;
+ JNIEnv *env;
+
+ if (TRACE_API_CALLS)
+ tracing ("thread_set_priority_jni_impl(gThreadID=%p, gpriority = %u) ",
+ gThreadID, gpriority);
+
+ (*the_vm)->GetEnv(the_vm, (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:
+ 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;
+ jobject this_thread;
+ gpointer my_threadID;
+
+ if (TRACE_API_CALLS)
+ tracing ("thread_self_jni_impl(my_thread_IDp=%p)", my_thread_IDp);
+
+ (*the_vm)->GetEnv(the_vm, (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;
+
+ gpointer threadID1 = *(gpointer *) thread1;
+ gpointer threadID2 = *(gpointer *) thread2;
+
+ jobject thread1_obj, thread2_obj;
+ gboolean ret;
+
+ if (TRACE_API_CALLS)
+ tracing ("thread_equal_jni_impl(threadID1=%p, threadID2=%p)",
+ threadID1, threadID2);
+
+ (*the_vm)->GetEnv (the_vm, (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:
+
+ if (TRACE_API_CALLS)
+ tracing (" ==> %s\n", ret ? "TRUE" : "FALSE");
+
+ return ret;
}
+
+
/************************************************************************/
/* GLIB interface */
/************************************************************************/
/* set of function pointers to give to glib. */
-GThreadFunctions g_thread_jni_functions =
+GThreadFunctions portable_native_sync_jni_functions =
{
- g_mutex_new_jni_impl, /* mutex_new */
- g_mutex_lock_jni_impl, /* mutex_lock */
- g_mutex_trylock_jni_impl, /* mutex_try_lock */
- g_mutex_unlock_jni_impl, /* mutex_unlock */
- g_mutex_free_jni_impl, /* mutex_free */
- g_cond_new_jni_impl, /* cond_new */
- g_cond_signal_jni_impl, /* cond_signal */
- g_cond_broadcast_jni_impl, /* cond_broadcast */
- g_cond_wait_jni_impl, /* cond_wait */
- g_cond_timed_wait_jni_impl, /* cond_timed_wait */
- g_cond_free_jni_impl, /* cond_free */
- g_private_new_jni_impl, /* private_new */
- g_private_get_jni_impl, /* private_get */
- g_private_set_jni_impl, /* private_set */
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL
+ 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 */
};
+#endif /* PORTABLE_NATIVE_SYNC */
+
+/* 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" "jint" "jlong" "jobject" "jstring" "jthrowable" ) */
+/* End: */
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- portable-native-sync-4.patch; ready to commit?,
Steven Augart <=