--- /dev/null
+package dolda.jsvc;
+
+import java.util.*;
+
+public class ContextParam<T> {
+ private boolean bound;
+ private T value;
+ private final Object id = new Object();
+ private Map<ThreadContext, T> perctx = new WeakHashMap<ThreadContext, T>();
+ private Map<Thread, T> perthr = new WeakHashMap<Thread, T>();
+
+ public ContextParam(T def) {
+ this.value = def;
+ this.bound = true;
+ }
+
+ public ContextParam() {
+ this.bound = false;
+ }
+
+ public synchronized T get() {
+ Thread th = Thread.currentThread();
+ if(perthr.containsKey(th))
+ return(perthr.get(th));
+ ThreadContext ctx = getctx();
+ if(perctx.containsKey(ctx))
+ return(perctx.get(ctx));
+ if(!bound)
+ throw(new IllegalStateException("No value is bound to this parameter."));
+ return(value);
+ }
+
+ public synchronized T ctxset(T val) {
+ ThreadContext ctx = getctx();
+ return(perctx.put(ctx, val));
+ }
+
+ private static ThreadContext getctx() {
+ for(ThreadGroup tg = Thread.currentThread().getThreadGroup(); tg != null; tg = tg.getParent()) {
+ if(tg instanceof ThreadContext)
+ return((ThreadContext)tg);
+ }
+ return(null);
+ }
+
+ public static Responder let(final Responder next, Object... params) {
+ final Map<ContextParam, Object> values = new HashMap<ContextParam, Object>();
+ if((params.length % 2) != 0)
+ throw(new IllegalArgumentException("SvcConfig.let takes only an even number of parameters"));
+ for(int i = 0; i < params.length; i += 2)
+ values.put((ContextParam)params[i], params[i + 1]);
+
+ return(new Responder() {
+ /* This can very well actually be set to something
+ * of the wrong type, but since the result would,
+ * obviously, be a ClassCastException either way,
+ * this way is at least the more convenient. */
+ @SuppressWarnings("unchecked")
+ public void respond(Request req) {
+ final Map<ContextParam, Object> old = new HashMap<ContextParam, Object>();
+ Thread th = Thread.currentThread();
+ for(Map.Entry<ContextParam, Object> val : values.entrySet()) {
+ ContextParam p = val.getKey();
+ synchronized(p) {
+ if(p.perthr.containsKey(th))
+ old.put(p, p.perthr.get(th));
+ p.perthr.put(th, val.getValue());
+ }
+ }
+ try {
+ next.respond(req);
+ } finally {
+ for(Map.Entry<ContextParam, Object> val : values.entrySet()) {
+ ContextParam p = val.getKey();
+ synchronized(p) {
+ if(old.containsKey(p)) {
+ p.perthr.put(th, old.get(p));
+ } else {
+ p.perthr.remove(th);
+ }
+ }
+ }
+ }
+ }
+ });
+ }
+}
+++ /dev/null
-package dolda.jsvc;
-
-import java.util.*;
-
-public class SvcConfig {
- public static class Param<T> {
- private T value;
- private final Object id = new Object();
-
- public Param(T def) {
- this.value = def;
- }
-
- @SuppressWarnings("unchecked")
- public T get() {
- if(Thread.currentThread() instanceof RequestThread) {
- Map<Object, Object> props = RequestThread.request().props();
- if(props.containsKey(id)) {
- /* This can very well actually be set to something
- * of the wrong type, but since the result would,
- * obviously, be a ClassCastException either way,
- * this way is at least the more convenient. */
- return((T)props.get(id));
- }
- }
- return(value);
- }
- }
-
- public static Responder let(final Responder next, Object... params) {
- final Map<Param, Object> values = new HashMap<Param, Object>();
- if((params.length % 2) != 0)
- throw(new IllegalArgumentException("SvcConfig.let takes only an even number of parameters"));
- for(int i = 0; i < params.length; i += 2)
- values.put((Param)params[i], params[i + 1]);
- return(new Responder() {
- public void respond(Request req) {
- final Map<Param, Object> old = new HashMap<Param, Object>();
- {
- Map<Object, Object> props = req.props();
- for(Map.Entry<Param, Object> val : values.entrySet()) {
- Param p = val.getKey();
- if(props.containsKey(p.id))
- old.put(p, props.get(p.id));
- props.put(p.id, val.getValue());
- }
- }
- try {
- next.respond(req);
- } finally {
- Map<Object, Object> props = req.props();
- for(Map.Entry<Param, Object> val : values.entrySet()) {
- Param p = val.getKey();
- if(old.containsKey(p)) {
- props.put(p.id, old.get(p));
- } else {
- props.remove(p.id);
- }
- }
- }
- }
- });
- }
-}
--- /dev/null
+package dolda.jsvc;
+
+import java.util.logging.*;
+import java.lang.reflect.*;
+
+public class ThreadContext extends ThreadGroup {
+ private Logger logger = Logger.getLogger("dolda.jsvc.context");
+ private ThreadGroup workers;
+ private long reqs = 0;
+ public final Responder root;
+
+ public ThreadContext(ThreadGroup parent, String name, Class<?> bootclass) {
+ super((parent == null)?(Thread.currentThread().getThreadGroup()):parent, name);
+ workers = new ThreadGroup(this, "Worker threads") {
+ public void uncaughtException(Thread t, Throwable e) {
+ logger.log(Level.SEVERE, "Worker thread terminated with an uncaught exception", e);
+ }
+ };
+ root = bootstrap(bootclass);
+ }
+
+ public void uncaughtException(Thread t, Throwable e) {
+ logger.log(Level.SEVERE, "Service thread " + t.toString() + " terminated with an uncaught exception", e);
+ }
+
+ public void shutdown() {
+ if(root instanceof ContextResponder)
+ ((ContextResponder)root).destroy();
+ try {
+ long last = 0;
+ while(true) {
+ long now = System.currentTimeMillis();
+ if(now - last > 10000) {
+ interrupt();
+ last = now;
+ }
+ Thread[] th = new Thread[1];
+ if(enumerate(th) < 1)
+ break;
+ th[0].join(10000);
+ }
+ } catch(InterruptedException e) {
+ logger.log(Level.WARNING, "Interrupted while trying to shut down all service threads. Some may remain.", e);
+ }
+ destroy();
+ }
+
+ public RequestThread respond(Request req) {
+ return(new RequestThread(root, req, workers, "Worker thread " + reqs++));
+ }
+
+ private Responder bootstrap(final Class<?> bootclass) {
+ final Throwable[] err = new Throwable[1];
+ final Responder[] res = new Responder[1];
+ Thread boot = new Thread(this, "JSvc boot thread") {
+ public void run() {
+ try {
+ Method cm = bootclass.getMethod("responder");
+ Object resp = cm.invoke(null);
+ if(!(resp instanceof Responder))
+ throw(new ClassCastException("JSvc bootstrapper did not return a responder"));
+ res[0] = (Responder)resp;
+ } catch(NoSuchMethodException e) {
+ logger.log(Level.SEVERE, "Invalid JSvc bootstrapper specified", e);
+ err[0] = e;
+ } catch(IllegalAccessException e) {
+ logger.log(Level.SEVERE, "Invalid JSvc bootstrapper specified", e);
+ err[0] = e;
+ } catch(InvocationTargetException e) {
+ logger.log(Level.SEVERE, "JSvc bootstrapper failed", e);
+ err[0] = e;
+ }
+ }
+ };
+ boot.start();
+ try {
+ boot.join();
+ } catch(InterruptedException e) {
+ logger.log(Level.WARNING, "Interrupted during bootstrapping", e);
+ boot.interrupt();
+ Thread.currentThread().interrupt();
+ }
+ if(err[0] != null)
+ throw(new RuntimeException(err[0]));
+ if(res[0] == null) {
+ logger.log(Level.SEVERE, "No responder returned in spite of no error having happened.");
+ throw(new NullPointerException("No responder returned in spite of no error having happened."));
+ }
+ return(res[0]);
+ }
+}
import javax.servlet.*;
public class Servlet extends HttpServlet {
- private Responder root;
- private ThreadGroup workers;
- private long reqs = 0;
+ private ThreadContext tg;
public void init() throws ServletException {
- workers = new ThreadGroup("JSvc worker threads") {
- public void uncaughtException(Thread t, Throwable e) {
- log("Worker thread terminated with an uncaught exception", e);
- }
- };
Properties sprop = new Properties();
try {
InputStream pi = Servlet.class.getClassLoader().getResourceAsStream("jsvc.properties");
String clnm = (String)sprop.get("jsvc.bootstrap");
if(clnm == null)
throw(new ServletException("No JSvc bootstrapper specified"));
+ Class<?> bc;
try {
- Class<?> rc = Class.forName(clnm);
- Method cm = rc.getMethod("responder");
- Object resp = cm.invoke(null);
- if(!(resp instanceof Responder))
- throw(new ServletException("JSvc bootstrapper did not return a responder"));
- root = (Responder)resp;
+ bc = Class.forName(clnm);
} catch(ClassNotFoundException e) {
throw(new ServletException("Invalid JSvc bootstrapper specified", e));
- } catch(NoSuchMethodException e) {
- throw(new ServletException("Invalid JSvc bootstrapper specified", e));
- } catch(IllegalAccessException e) {
- throw(new ServletException("Invalid JSvc bootstrapper specified", e));
- } catch(InvocationTargetException e) {
- throw(new ServletException("JSvc bootstrapper failed", e));
}
+ tg = new ThreadContext(null, "JSvc service", bc);
ServletContext ctx = getServletContext();
ctx.setAttribute("jsvc.starttime", System.currentTimeMillis());
}
public void destroy() {
- workers.interrupt();
- if(root instanceof ContextResponder)
- ((ContextResponder)root).destroy();
+ tg.shutdown();
}
public void service(HttpServletRequest req, HttpServletResponse resp) {
} catch(UnsupportedEncodingException e) {
throw(new Error(e));
}
- long mynum = reqs++;
Request rr = new J2eeRequest(getServletConfig(), req, resp);
- RequestThread w = new RequestThread(root, rr, workers, "Worker thread " + mynum);
+ RequestThread w = tg.respond(rr);
w.start();
try {
w.join();
public class ErrorHandler implements Responder {
private Responder next;
- private static Logger logger = Logger.getLogger("jsvc.error");
+ private static Logger logger = Logger.getLogger("dolda.jsvc.context");
public ErrorHandler(Responder next) {
this.next = next;