From: Fredrik Tolf Date: Tue, 13 Oct 2009 18:07:58 +0000 (+0200) Subject: Merge branch 'master' of git.dolda2000.com:/srv/git/r/jsvc X-Git-Url: http://www.dolda2000.com/gitweb/?a=commitdiff_plain;h=a7c50bd905f146c3a28c991e72f43bc9079f3ed8;hp=18a289941f029666d490c4045ed89dccf64b8a1b;p=jsvc.git Merge branch 'master' of git.dolda2000.com:/srv/git/r/jsvc --- diff --git a/src/dolda/jsvc/ContextParam.java b/src/dolda/jsvc/ContextParam.java new file mode 100644 index 0000000..f146dc3 --- /dev/null +++ b/src/dolda/jsvc/ContextParam.java @@ -0,0 +1,87 @@ +package dolda.jsvc; + +import java.util.*; + +public class ContextParam { + private boolean bound; + private T value; + private final Object id = new Object(); + private Map perctx = new WeakHashMap(); + private Map perthr = new WeakHashMap(); + + 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 values = new HashMap(); + 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 old = new HashMap(); + Thread th = Thread.currentThread(); + for(Map.Entry 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 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 index d200aa3..0000000 --- a/src/dolda/jsvc/SvcConfig.java +++ /dev/null @@ -1,64 +0,0 @@ -package dolda.jsvc; - -import java.util.*; - -public class SvcConfig { - public static class Param { - 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 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 values = new HashMap(); - 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 old = new HashMap(); - { - Map props = req.props(); - for(Map.Entry 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 props = req.props(); - for(Map.Entry 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 index 0000000..78a5d4e --- /dev/null +++ b/src/dolda/jsvc/ThreadContext.java @@ -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]); + } +} diff --git a/src/dolda/jsvc/j2ee/Servlet.java b/src/dolda/jsvc/j2ee/Servlet.java index 165b0f2..4372652 100644 --- a/src/dolda/jsvc/j2ee/Servlet.java +++ b/src/dolda/jsvc/j2ee/Servlet.java @@ -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(); diff --git a/src/dolda/jsvc/util/ErrorHandler.java b/src/dolda/jsvc/util/ErrorHandler.java index f1149e8..d840ee7 100644 --- a/src/dolda/jsvc/util/ErrorHandler.java +++ b/src/dolda/jsvc/util/ErrorHandler.java @@ -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;