2002/07/15 23:17:09
[org.ibex.core.git] / src / org / xwt / ThreadMessage.java
1 // Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL]
2 package org.xwt;
3
4 import org.xwt.util.*;
5 import java.util.*;
6 import org.mozilla.javascript.*;
7
8 /**
9  *  A background thread. All threads created with <tt>xwt.thread</tt>
10  *  are instances of this class. ThreadMessage objects can be enqueued
11  *  in MessageQueue.
12  *
13  *  INVARIANT: only one thread can be executing JavaScript scripts or
14  *             accessing Box instances at any given time. For
15  *             performance reasons, no locks are employed to enforce
16  *             this invariant.
17  */
18 public class ThreadMessage extends Thread implements Message {
19     
20     private volatile static int threadcount = 0;
21
22     /** the JavaScript function that we are executing */
23     volatile Function f;
24
25     /** the ThreadMessage thread blocks on this before executing any JavaScript */
26     Semaphore go = new Semaphore();
27
28     /** The MessageQueue (main) thread blocks on this while the ThreadMessage thread is running JavaScript code */
29     Semaphore done = new Semaphore();
30
31     /** used to pool ThreadMessages that are not in use */
32     private static Queue spare = new Queue(50);
33
34     /** queue of functions waiting to be spawned; used if threadcount == Platform.maxThreads() */
35     private static Queue waiting = new Queue(50);
36
37     private ThreadMessage() { start(); }
38     private static Object[] emptyobj = new Object[] { };
39
40     /** creates a new thread to execute function <tt>f</tt> */
41     public static synchronized void newthread(Function f) {
42         ThreadMessage ret = (ThreadMessage)spare.remove(false);
43         if (ret == null) {
44             if (threadcount < Platform.maxThreads()) ret = new ThreadMessage();
45             else {
46                 waiting.append(f);
47                 return;
48             }
49         }
50         ret.f = f;
51         MessageQueue.add(ret);
52     }
53
54     /** attempts to put this thread into the background to perform a blocking operation; returns false if unable to do so */
55     public static boolean suspendThread() {
56         // put ourselves in the background
57         Thread thread = Thread.currentThread();
58         if (!(thread instanceof ThreadMessage)) {
59             Context cx = Context.enter();
60             if (Log.on) Log.log(ThreadMessage.class, "attempt to perform background-only operation in a foreground thread at " +
61                                 cx.interpreterSourceFile + ":" + cx.interpreterLine);
62             return false;
63         }
64         ThreadMessage mythread = (ThreadMessage)thread;
65         mythread.setPriority(Thread.MIN_PRIORITY);
66         mythread.done.release();
67         return true;
68     }
69
70     /** re-enqueues this thread */
71     public static void resumeThread() {
72         ThreadMessage mythread = (ThreadMessage)Thread.currentThread();
73         MessageQueue.add(mythread);
74         mythread.setPriority(Thread.NORM_PRIORITY);
75         mythread.go.block();        
76     }
77     
78     public void run() {
79         try {
80             threadcount++;
81             Context cx = Context.enter();
82             while (true) {
83                 try {
84                     go.block();
85                     f.call(cx, f.getParentScope(), f.getParentScope(), emptyobj);
86                 } catch (EcmaError e) {
87                     if (Log.on) Log.log(this, "WARNING: uncaught interpreter exception: " + e.getMessage());
88                     if (Log.on) Log.log(this, "         thrown from background thread at " + e.getSourceName() + ":" + e.getLineNumber());
89                 } catch (JavaScriptException e) {
90                     if (Log.on) Log.log(this, "WARNING: uncaught ecmascript exception: " + e.getMessage());
91                     if (Log.on) Log.log(this, "         thrown from background thread at " + e.sourceFile + ":" + e.line);
92                 }
93                 done.release();
94                 synchronized(waiting) {
95                     if (waiting.size() > 0) {
96                         f = (Function)waiting.remove(false);
97                         MessageQueue.add(this);
98                     } else if (spare.size() < 10) {
99                         spare.append(this);
100                     } else {
101                         threadcount--;
102                         return;
103                     }
104                 }
105             }
106         } catch (Throwable t) {
107             if (Log.on) Log.log(this, "caught throwable at thread entry point; this should never happen");
108             if (Log.on) Log.log(this, t);
109             if (Log.on) Log.log(this, "reaping thread");
110             return;
111         }
112     }
113
114     /** this is invoked in the MessageQueue thread */
115     public void perform() {
116         go.release();
117         done.block();
118     }
119
120 }