Commit | Line | Data |
---|---|---|
c9837b5e FT |
1 | package dolda.jsvc; |
2 | ||
5cdd61df | 3 | import dolda.jsvc.util.Misc; |
c9837b5e FT |
4 | import java.util.logging.*; |
5 | import java.lang.reflect.*; | |
5cdd61df | 6 | import java.util.*; |
c9837b5e FT |
7 | |
8 | public class ThreadContext extends ThreadGroup { | |
9 | private Logger logger = Logger.getLogger("dolda.jsvc.context"); | |
10 | private ThreadGroup workers; | |
11 | private long reqs = 0; | |
4b8346e1 | 12 | private final ServerContext ctx; |
c9837b5e | 13 | public final Responder root; |
5cdd61df FT |
14 | private int timelimit = 0; |
15 | private boolean forcelimit = false; | |
c9837b5e | 16 | |
4b8346e1 | 17 | public ThreadContext(ThreadGroup parent, String name, ServerContext ctx, Class<?> bootclass) { |
c9837b5e | 18 | super((parent == null)?(Thread.currentThread().getThreadGroup()):parent, name); |
4b8346e1 | 19 | this.ctx = ctx; |
c9837b5e FT |
20 | workers = new ThreadGroup(this, "Worker threads") { |
21 | public void uncaughtException(Thread t, Throwable e) { | |
22 | logger.log(Level.SEVERE, "Worker thread terminated with an uncaught exception", e); | |
23 | } | |
24 | }; | |
5cdd61df FT |
25 | |
26 | int tl; | |
27 | tl = Integer.parseInt(ctx.sysconfig("jsvc.timelimit", "0")); | |
28 | if((tl > 0) && ((timelimit == 0) || (tl < timelimit))) | |
29 | timelimit = tl; | |
30 | tl = Integer.parseInt(ctx.libconfig("jsvc.timelimit", "0")); | |
31 | if((tl > 0) && ((timelimit == 0) || (tl < timelimit))) | |
32 | timelimit = tl; | |
33 | forcelimit |= Misc.boolval(ctx.sysconfig("jsvc.forcelimit", "0")); | |
34 | forcelimit |= Misc.boolval(ctx.libconfig("jsvc.forcelimit", "0")); | |
35 | ||
c9837b5e | 36 | root = bootstrap(bootclass); |
5cdd61df FT |
37 | |
38 | if(timelimit > 0) | |
39 | (new WatchDog()).start(); | |
40 | } | |
41 | ||
42 | private class WatchDog extends Thread { | |
43 | private Map<RequestThread, State> state = new WeakHashMap<RequestThread, State>(); | |
44 | ||
45 | private class State { | |
46 | String st = "running"; | |
47 | long lastkill; | |
48 | } | |
49 | ||
50 | private WatchDog() { | |
51 | super(ThreadContext.this, "Worker watchdog"); | |
52 | setDaemon(true); | |
53 | } | |
54 | ||
55 | @SuppressWarnings("deprecation") | |
56 | private long ckthread(long now, RequestThread rt) { | |
57 | State st = state.get(rt); | |
58 | if(st == null) { | |
59 | st = new State(); | |
60 | state.put(rt, st); | |
61 | } | |
62 | if(st.st == "running") { | |
63 | if(now - rt.stime() > timelimit) { | |
64 | rt.interrupt(); | |
65 | st.st = "interrupted"; | |
66 | st.lastkill = now; | |
67 | return(5000); | |
68 | } else { | |
69 | return(timelimit - (now - rt.stime())); | |
70 | } | |
71 | } else if((st.st == "interrupted") || (st.st == "killed")) { | |
72 | if(st.st == "killed") | |
73 | logger.log(Level.WARNING, "Thread " + rt + " refused to die; killing again"); | |
74 | if(now - st.lastkill > 5000) { | |
b9089d95 FT |
75 | if(forcelimit) |
76 | rt.stop(); | |
77 | else | |
78 | rt.interrupt(); | |
5cdd61df FT |
79 | st.st = "killed"; |
80 | st.lastkill = now; | |
81 | } else { | |
82 | return(5000 - (now - st.lastkill)); | |
83 | } | |
84 | } | |
85 | return(timelimit); | |
86 | } | |
87 | ||
88 | public void run() { | |
89 | try { | |
90 | while(true) { | |
91 | long next = timelimit; | |
92 | long now = System.currentTimeMillis(); | |
93 | Thread[] w = new Thread[workers.activeCount() + 5]; | |
94 | int num = workers.enumerate(w); | |
95 | for(int i = 0; i < num; i++) { | |
96 | if(w[i] instanceof RequestThread){ | |
97 | RequestThread rt = (RequestThread)w[i]; | |
98 | if(rt.stime() > 0) { | |
99 | long n = ckthread(now, rt); | |
100 | if(n < next) | |
101 | next = n; | |
102 | } | |
103 | } | |
104 | } | |
105 | Thread.sleep(next); | |
106 | } | |
107 | } catch(InterruptedException e) { | |
108 | } | |
109 | } | |
c9837b5e FT |
110 | } |
111 | ||
112 | public void uncaughtException(Thread t, Throwable e) { | |
113 | logger.log(Level.SEVERE, "Service thread " + t.toString() + " terminated with an uncaught exception", e); | |
114 | } | |
115 | ||
4b8346e1 FT |
116 | public ServerContext server() { |
117 | return(ctx); | |
118 | } | |
119 | ||
c9837b5e | 120 | public void shutdown() { |
c9837b5e FT |
121 | if(root instanceof ContextResponder) |
122 | ((ContextResponder)root).destroy(); | |
a0b186f8 FT |
123 | try { |
124 | long last = 0; | |
125 | while(true) { | |
126 | long now = System.currentTimeMillis(); | |
127 | if(now - last > 10000) { | |
128 | interrupt(); | |
129 | last = now; | |
130 | } | |
131 | Thread[] th = new Thread[1]; | |
132 | if(enumerate(th) < 1) | |
133 | break; | |
134 | th[0].join(10000); | |
135 | } | |
136 | } catch(InterruptedException e) { | |
137 | logger.log(Level.WARNING, "Interrupted while trying to shut down all service threads. Some may remain.", e); | |
138 | } | |
139 | destroy(); | |
c9837b5e FT |
140 | } |
141 | ||
142 | public RequestThread respond(Request req) { | |
13e578b1 | 143 | return(ctx.worker(root, req, workers, "Worker thread " + reqs++)); |
c9837b5e FT |
144 | } |
145 | ||
32994173 FT |
146 | public long requests() { |
147 | return(reqs); | |
148 | } | |
149 | ||
c9837b5e FT |
150 | private Responder bootstrap(final Class<?> bootclass) { |
151 | final Throwable[] err = new Throwable[1]; | |
152 | final Responder[] res = new Responder[1]; | |
153 | Thread boot = new Thread(this, "JSvc boot thread") { | |
154 | public void run() { | |
155 | try { | |
156 | Method cm = bootclass.getMethod("responder"); | |
157 | Object resp = cm.invoke(null); | |
158 | if(!(resp instanceof Responder)) | |
159 | throw(new ClassCastException("JSvc bootstrapper did not return a responder")); | |
160 | res[0] = (Responder)resp; | |
161 | } catch(NoSuchMethodException e) { | |
162 | logger.log(Level.SEVERE, "Invalid JSvc bootstrapper specified", e); | |
163 | err[0] = e; | |
164 | } catch(IllegalAccessException e) { | |
165 | logger.log(Level.SEVERE, "Invalid JSvc bootstrapper specified", e); | |
166 | err[0] = e; | |
167 | } catch(InvocationTargetException e) { | |
168 | logger.log(Level.SEVERE, "JSvc bootstrapper failed", e); | |
169 | err[0] = e; | |
170 | } | |
171 | } | |
172 | }; | |
173 | boot.start(); | |
174 | try { | |
175 | boot.join(); | |
176 | } catch(InterruptedException e) { | |
177 | logger.log(Level.WARNING, "Interrupted during bootstrapping", e); | |
178 | boot.interrupt(); | |
179 | Thread.currentThread().interrupt(); | |
180 | } | |
83f55da4 FT |
181 | if(err[0] != null) { |
182 | destroy(); | |
c9837b5e | 183 | throw(new RuntimeException(err[0])); |
83f55da4 | 184 | } |
c9837b5e | 185 | if(res[0] == null) { |
83f55da4 | 186 | destroy(); |
c9837b5e FT |
187 | logger.log(Level.SEVERE, "No responder returned in spite of no error having happened."); |
188 | throw(new NullPointerException("No responder returned in spite of no error having happened.")); | |
189 | } | |
190 | return(res[0]); | |
191 | } | |
4b8346e1 FT |
192 | |
193 | public static ThreadContext current() { | |
194 | for(ThreadGroup tg = Thread.currentThread().getThreadGroup(); tg != null; tg = tg.getParent()) { | |
195 | if(tg instanceof ThreadContext) | |
196 | return((ThreadContext)tg); | |
197 | } | |
198 | return(null); | |
199 | } | |
13e578b1 FT |
200 | |
201 | public static class CreateException extends Exception { | |
202 | public CreateException(String message) { | |
203 | super(message); | |
204 | } | |
205 | ||
206 | public CreateException(String message, Throwable cause) { | |
207 | super(message, cause); | |
208 | } | |
209 | } | |
210 | ||
211 | public static ThreadContext create(ServerContext ctx, ClassLoader cl) throws CreateException { | |
212 | String nm = "JSvc Service"; | |
213 | if(ctx.name() != null) | |
214 | nm = "JSvc Service for " + ctx.name(); | |
215 | ||
216 | String clnm = ctx.libconfig("jsvc.bootstrap", null); | |
217 | if(clnm == null) | |
218 | throw(new CreateException("No JSvc bootstrapper specified")); | |
219 | Class<?> bc; | |
220 | try { | |
221 | bc = cl.loadClass(clnm); | |
222 | } catch(ClassNotFoundException e) { | |
223 | throw(new CreateException("Invalid JSvc bootstrapper specified", e)); | |
224 | } | |
225 | return(new ThreadContext(null, nm, ctx, bc)); | |
226 | } | |
c9837b5e | 227 | } |