Merge branch 'master' of git.dolda2000.com:/srv/git/r/jsvc
authorFredrik Tolf <fredrik@dolda2000.com>
Tue, 13 Oct 2009 18:07:58 +0000 (20:07 +0200)
committerFredrik Tolf <fredrik@dolda2000.com>
Tue, 13 Oct 2009 18:07:58 +0000 (20:07 +0200)
src/dolda/jsvc/ContextParam.java [new file with mode: 0644]
src/dolda/jsvc/SvcConfig.java [deleted file]
src/dolda/jsvc/ThreadContext.java [new file with mode: 0644]
src/dolda/jsvc/j2ee/Servlet.java
src/dolda/jsvc/util/ErrorHandler.java

diff --git a/src/dolda/jsvc/ContextParam.java b/src/dolda/jsvc/ContextParam.java
new file mode 100644 (file)
index 0000000..f146dc3
--- /dev/null
@@ -0,0 +1,87 @@
+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);
+                               }
+                           }
+                       }
+                   }
+               }
+           });
+    }
+}
diff --git a/src/dolda/jsvc/SvcConfig.java b/src/dolda/jsvc/SvcConfig.java
deleted file mode 100644 (file)
index d200aa3..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-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);
-                           }
-                       }
-                   }
-               }
-           });
-    }
-}
diff --git a/src/dolda/jsvc/ThreadContext.java b/src/dolda/jsvc/ThreadContext.java
new file mode 100644 (file)
index 0000000..78a5d4e
--- /dev/null
@@ -0,0 +1,91 @@
+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]);
+    }
+}
index 165b0f2..4372652 100644 (file)
@@ -8,16 +8,9 @@ import javax.servlet.http.*;
 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");
@@ -32,30 +25,19 @@ public class Servlet extends HttpServlet {
        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) {
@@ -65,9 +47,8 @@ public class Servlet extends HttpServlet {
        } 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();
index f1149e8..d840ee7 100644 (file)
@@ -7,7 +7,7 @@ import java.util.logging.*;
 
 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;