aboutsummaryrefslogtreecommitdiff
path: root/libjava/gnu/java/awt/peer/gtk/GThreadNativeMethodRunner.java
blob: 753c7cd46f1263479d4f3feb9da88e34333e070c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
/* 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, 2005  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.Collections;
import java.util.HashSet;
import java.util.Set;

/** 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.  The registry uses weak references for threads that aren't
    joinable, so that they will be garbage collected.

    There are a number of possible alternative implementations.
    
    
    The rest of this comment consists of an answer to a question that was
    raised on the commit-classpath mailing list:

    Mark Wielaard wrote:

    > Can't we assume that jobject and gpointer are both (void *) so we don't
    > need the int <-> Thread (global jobject ref) mapping?
    > Maybe there are platforms where jobject and gpointer aren't the same,
    > but I guess that is pretty unlikely.


    I agree with you on the pointer size issues.  A gpointer is a void *, so
    it's certainly guaranteed to be at least as large as any other
    pointer. And a jobject is implicitly an opaque pointer (in Jikes RVM, we
    use small integers, but we coerce them into the representation of a
    pointer).

    The int <==> Thread mapping addresses a different issue.  I realize that I
    did not document this properly (two and a half lines in thread_create),
    and the point is subtle (at least to me; took me a while to figure out).

    The int => Thread mapping always returns jobjects that are local
    references, not global ones.  This is because Thread objects need to be
    able to go away and be garbage collected after the thread they refer to
    has died.

    If we keep a global object reference to a thread, then when do we delete
    that global object reference?  We have an answer in the case of GThread
    objects that were explicitly created with the joinable attribute.  It is
    safe for us to maintain a global reference to any joinable thread, since
    the joinable thread must linger (even if only in a zombie state)
    until it's explicitly joined via a g_thread_join() call.  The global ref
    could be cleaned up at that point too.

    However, in the case of GThreads that were created non-joinable by
    g_thread_create(), and in the case of Java threads that were created
    within pure Java code (not via g_thread_create()), we don't want them to
    linger forever, and there is no way to tell when the last reference
    to such threads needs to expire.  In the case of this application -- AWT
    with GTK peers -- it would probably be safe anyway, since there are not
    very many threads we create, but I was going for correctness even in the
    case of long-running programs that might set up and tear down AWT
    interfaces many times.

    So, I duplicated the POSIX thread-ID semantics.  The thread ID of a
    non-joinable thread remains valid as long as that thread is still alive.
    Once that thread dies, the old thread ID may be reused at any moment.  And
    that's why the array indexed by thread ID numbers is an array of weak
    references.

    That's also why the int => Thread jobject mapping function always returns
    local references, since global references would lock the Thread in memory
    forever.

    I would dearly love there to be a cleaner solution.  I dislike the
    repeated dips from C code into Java that are necessary to look up thread
    ID numbers.  If anyone can think of one, I'm all ears.
*/

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;
  
  GThreadNativeMethodRunner(long funcPtr, long funcArg, boolean joinable) 
  {
    this.funcPtr = funcPtr;
    this.funcArg = funcArg;

    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.

      Possible future fixes (TODO?)

     - The THREADS array will only grow. probably not a problem.
        But we could keep count when nulling entries and shrink when we have
        lots of nulls at the end. Probably not worth it. --mjw

     - Could make this a set of Object; see the comment on "joinable" below.

     The initial size of 17 is just a starting point.  Any number will do,
     including zero.
  */ 
  private static WeakReference[] threads = new WeakReference[17]; 

  /**  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[] bigger = new WeakReference[threads.length * 2];
        System.arraycopy(threads, 0, bigger, 0, threads.length);
	threads = bigger;
      }

    threads[i] = new WeakReference(t);

    return i;
  }
  
  /**  Look up the Thread ID # for a Thread.  Assign a Thread ID # if none
       exists.  This is a general routine for handling all threads, including
       the VM's main thread, if appropriate.


       Runs in O(n/2) time.

       We can't just issue a threadID upon thread creation.  If we were to do
       that, not all threads would have a threadID, because not all threads
       are launched by GThreadNativeMethodRunner.
  */ 
  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 throw an exception.

      Possible Alternative Interface:  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.  For instance, if the user asks for the thread
          associated with a threadID that was never issued, we could print a
          warning or even abort.
       
       TODO: Consider optionally disabling all of the error-checking we
          already have; it probably slows down the implementation.  We could
          just return NULL.  This is just the reverse of the above TODO item.
    */ 

    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 hard 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 thread_join(threadID).  Joinable threads have to be
      explicitly joined before they are allowed to go away completely.

      Possible Alternative Implementation: Eliminate the Joinable set.  When
          calling getThreadIDFromThread() you know whether or not the thread
          is joinable.  So just store the Thread itself in the threads array?
          Make that array an Object array and check with instanceof.  This
          looks cleaner and more robust to me and it saves a native -> Java
          call. But instanceof might be expensive.  --mjw
  */
  private static final Set joinable = 
       Collections.synchronizedSet(new HashSet()); 
  
  /** Only called from the constructor. */
  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);
  }
}

// Local Variables:
// c-file-style: "gnu"
// End: