public class RequestThread extends Thread {
private Request req;
private Responder resp;
+ private long stime = 0;
public RequestThread(Responder resp, Request req, ThreadGroup th, String name) {
super(th, name);
}
public void run() {
+ stime = System.currentTimeMillis();
resp.respond(req);
try {
req.output().close();
public static Request request() {
return(((RequestThread)Thread.currentThread()).req);
}
+
+ public long stime() {
+ return(stime);
+ }
}
public interface ServerContext {
public long starttime();
- public String config(String key);
+ public String sysconfig(String key, String def);
+ public String libconfig(String key, String def);
public String name();
}
package dolda.jsvc;
+import dolda.jsvc.util.Misc;
import java.util.logging.*;
import java.lang.reflect.*;
+import java.util.*;
public class ThreadContext extends ThreadGroup {
private Logger logger = Logger.getLogger("dolda.jsvc.context");
private long reqs = 0;
private final ServerContext ctx;
public final Responder root;
+ private int timelimit = 0;
+ private boolean forcelimit = false;
public ThreadContext(ThreadGroup parent, String name, ServerContext ctx, Class<?> bootclass) {
super((parent == null)?(Thread.currentThread().getThreadGroup()):parent, name);
logger.log(Level.SEVERE, "Worker thread terminated with an uncaught exception", e);
}
};
+
+ int tl;
+ tl = Integer.parseInt(ctx.sysconfig("jsvc.timelimit", "0"));
+ if((tl > 0) && ((timelimit == 0) || (tl < timelimit)))
+ timelimit = tl;
+ tl = Integer.parseInt(ctx.libconfig("jsvc.timelimit", "0"));
+ if((tl > 0) && ((timelimit == 0) || (tl < timelimit)))
+ timelimit = tl;
+ forcelimit |= Misc.boolval(ctx.sysconfig("jsvc.forcelimit", "0"));
+ forcelimit |= Misc.boolval(ctx.libconfig("jsvc.forcelimit", "0"));
+
root = bootstrap(bootclass);
+
+ if(timelimit > 0)
+ (new WatchDog()).start();
+ }
+
+ private class WatchDog extends Thread {
+ private Map<RequestThread, State> state = new WeakHashMap<RequestThread, State>();
+
+ private class State {
+ String st = "running";
+ long lastkill;
+ }
+
+ private WatchDog() {
+ super(ThreadContext.this, "Worker watchdog");
+ setDaemon(true);
+ }
+
+ @SuppressWarnings("deprecation")
+ private long ckthread(long now, RequestThread rt) {
+ State st = state.get(rt);
+ if(st == null) {
+ st = new State();
+ state.put(rt, st);
+ }
+ if(st.st == "running") {
+ if(now - rt.stime() > timelimit) {
+ rt.interrupt();
+ st.st = "interrupted";
+ st.lastkill = now;
+ return(5000);
+ } else {
+ return(timelimit - (now - rt.stime()));
+ }
+ } else if((st.st == "interrupted") || (st.st == "killed")) {
+ if(st.st == "killed")
+ logger.log(Level.WARNING, "Thread " + rt + " refused to die; killing again");
+ if(now - st.lastkill > 5000) {
+ rt.stop();
+ st.st = "killed";
+ st.lastkill = now;
+ } else {
+ return(5000 - (now - st.lastkill));
+ }
+ }
+ return(timelimit);
+ }
+
+ public void run() {
+ try {
+ while(true) {
+ long next = timelimit;
+ long now = System.currentTimeMillis();
+ Thread[] w = new Thread[workers.activeCount() + 5];
+ int num = workers.enumerate(w);
+ for(int i = 0; i < num; i++) {
+ if(w[i] instanceof RequestThread){
+ RequestThread rt = (RequestThread)w[i];
+ if(rt.stime() > 0) {
+ long n = ckthread(now, rt);
+ if(n < next)
+ next = n;
+ }
+ }
+ }
+ Thread.sleep(next);
+ }
+ } catch(InterruptedException e) {
+ }
+ }
}
public void uncaughtException(Thread t, Throwable e) {
public abstract class J2eeContext implements ServerContext {
private final ServletConfig sc;
private final long ctime;
- protected final Properties config;
+ protected final Properties sysconfig, libconfig;
protected J2eeContext(ServletConfig sc) {
this.sc = sc;
this.ctime = System.currentTimeMillis();
- config = new Properties();
+ sysconfig = new Properties();
+ libconfig = new Properties();
}
static J2eeContext create(ServletConfig sc) {
return(ctime);
}
- public String config(String key) {
- return((String)config.get(key));
+ public String sysconfig(String key, String def) {
+ return(sysconfig.getProperty(key, def));
+ }
+
+ public String libconfig(String key, String def) {
+ return(libconfig.getProperty(key, def));
+ }
+
+ void loadconfig(InputStream in) throws IOException {
+ libconfig.load(in);
}
public ServletConfig j2eeconfig() {
private ThreadContext tg;
public void init(ServletConfig cfg) throws ServletException {
- Properties sprop = new Properties();
+ J2eeContext ctx = J2eeContext.create(cfg);
try {
InputStream pi = Servlet.class.getClassLoader().getResourceAsStream("jsvc.properties");
try {
- sprop.load(pi);
+ ctx.loadconfig(pi);
} finally {
pi.close();
}
} catch(IOException e) {
throw(new Error(e));
}
- String clnm = (String)sprop.get("jsvc.bootstrap");
+ String clnm = ctx.libconfig("jsvc.bootstrap", null);
if(clnm == null)
throw(new ServletException("No JSvc bootstrapper specified"));
Class<?> bc;
} catch(ClassNotFoundException e) {
throw(new ServletException("Invalid JSvc bootstrapper specified", e));
}
- ServerContext ctx = J2eeContext.create(cfg);
String tgn;
if(ctx.name() != null)
tgn = "JSvc service for " + ctx.name();
logger.log(Level.WARNING, "no permissions to fetch Tomcat base directory while reading configuration", e);
return;
}
- config.put("jsvc.storage", "file:" + new File(new File(base, "work"), "jsvc").getPath());
+ sysconfig.put("jsvc.storage", "file:" + new File(new File(base, "work"), "jsvc").getPath());
File cdir = new File(base, "conf");
try {
- loadprops(config, new File(cdir, "jsvc.properties"));
+ loadprops(sysconfig, new File(cdir, "jsvc.properties"));
} catch(SecurityException e) {
logger.log(Level.WARNING, "no permssions to read from Tomcat conf directory while reading configuration", e);
}
ThreadContext ctx = ThreadContext.current();
if(ctx == null)
throw(new RuntimeException("Not running in jsvc context"));
- String bn = ctx.server().config("jsvc.storage");
+ String bn = ctx.server().sysconfig("jsvc.storage", null);
if(bn == null)
throw(new RuntimeException("No storage root has been configured"));
return(bn);
}
return(buf.toString());
}
+
+ public static boolean boolval(String val) {
+ val = val.trim().toLowerCase();
+ if(val.equals("1") || val.equals("on") || val.equals("true") || val.equals("yes") || val.equals("\u22a4"))
+ return(true);
+ if(val.equals("0") || val.equals("off") || val.equals("false") || val.equals("no") || val.equals("\u22a5"))
+ return(false);
+ throw(new IllegalArgumentException("value not recognized as boolean: " + val));
+ }
}