changeset 925:66f1ba72d07b

added timeout-parameter and timeout-handler to AsyncServletWorker. added stopNow abort method to ImageWorker.
author robcast
date Mon, 19 Dec 2011 21:39:17 +0100
parents 6853c02b238b
children 2c70e595e4d7
files .hgignore common/src/main/java/digilib/image/ImageWorker.java servlet3/src/main/java/digilib/servlet/AsyncServletWorker.java servlet3/src/main/java/digilib/servlet/DigilibServletConfiguration.java servlet3/src/main/java/digilib/servlet/Initialiser.java servlet3/src/main/java/digilib/servlet/Scaler.java webapp/src/main/webapp/WEB-INF/digilib-config.xml
diffstat 7 files changed, 180 insertions(+), 44 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Fri Dec 16 14:34:04 2011 +0100
+++ b/.hgignore	Mon Dec 19 21:39:17 2011 +0100
@@ -61,4 +61,8 @@
 syntax: regexp
 ^digilib-log\.txt$
 syntax: regexp
-^dl-access-log\.txt$
\ No newline at end of file
+^dl-access-log\.txt$
+syntax: regexp
+^webapp/digilib-log\.txt$
+syntax: regexp
+^webapp/dl-access-log\.txt$
\ No newline at end of file
--- a/common/src/main/java/digilib/image/ImageWorker.java	Fri Dec 16 14:34:04 2011 +0100
+++ b/common/src/main/java/digilib/image/ImageWorker.java	Mon Dec 19 21:39:17 2011 +0100
@@ -12,10 +12,11 @@
 import digilib.io.FileOpException;
 import digilib.servlet.DigilibConfiguration;
 
-/** Worker that renders an image.
+/**
+ * Worker that renders an image.
  * 
  * @author casties
- *
+ * 
  */
 public class ImageWorker implements Callable<DocuImage> {
 
@@ -23,7 +24,11 @@
     private DigilibConfiguration dlConfig;
     private ImageJobDescription jobinfo;
 
-    public ImageWorker(DigilibConfiguration dlConfig, ImageJobDescription jobinfo) {
+    /** flag for stopping the thread */
+    private boolean stopNow = false;
+
+    public ImageWorker(DigilibConfiguration dlConfig,
+            ImageJobDescription jobinfo) {
         super();
         this.dlConfig = dlConfig;
         this.jobinfo = jobinfo;
@@ -32,10 +37,15 @@
     /**
      * render and return the image
      */
-    public DocuImage call() throws FileOpException, IOException, ImageOpException {
-        
-        logger.debug("image worker starting");
+    public DocuImage call() throws FileOpException, IOException,
+            ImageOpException {
+
+        logger.debug("ImageWorker starting");
         long startTime = System.currentTimeMillis();
+        if (stopNow) {
+            logger.debug("ImageWorker stopping (at the start)");
+            return null;
+        }
 
         DocuImage docuImage = jobinfo.getDocuImage();
         if (docuImage == null) {
@@ -50,7 +60,11 @@
 
         Rectangle loadRect = jobinfo.getOuterUserImgArea().getBounds();
         float scaleXY = jobinfo.getScaleXY();
-        
+
+        if (stopNow) {
+            logger.debug("ImageWorker stopping (after setup)");
+            return null;
+        }
         // use subimage loading if possible
         if (docuImage.isSubimageSupported()) {
             logger.debug("Subimage: scale " + scaleXY + " = " + (1 / scaleXY));
@@ -60,7 +74,10 @@
                 subf = 1 / scaleXY;
                 // for higher quality reduce subsample factor by minSubsample
                 if (jobinfo.getScaleQual() > 0) {
-                    subsamp = (float) Math.max(Math.floor(subf / dlConfig.getAsFloat("subsample-minimum")), 1d);
+                    subsamp = (float) Math
+                            .max(Math.floor(subf
+                                    / dlConfig.getAsFloat("subsample-minimum")),
+                                    1d);
                 } else {
                     subsamp = (float) Math.floor(subf);
                 }
@@ -70,15 +87,31 @@
             }
             docuImage.loadSubimage(jobinfo.getInput(), loadRect, (int) subsamp);
             logger.debug("SUBSAMP: " + subsamp + " -> " + docuImage.getSize());
+            if (stopNow) {
+                logger.debug("ImageWorker stopping (after loading and cropping)");
+                return null;
+            }
             docuImage.scale(scaleXY, scaleXY);
         } else {
             // else load and crop the whole file
             docuImage.loadImage(jobinfo.getInput());
+            if (stopNow) {
+                logger.debug("ImageWorker stopping (after loading)");
+                return null;
+            }
             docuImage.crop((int) loadRect.getX(), (int) loadRect.getY(),
                     (int) loadRect.getWidth(), (int) loadRect.getHeight());
+            if (stopNow) {
+                logger.debug("ImageWorker stopping (after cropping)");
+                return null;
+            }
             docuImage.scale(scaleXY, scaleXY);
         }
 
+        if (stopNow) {
+            logger.debug("ImageWorker stopping (after scaling)");
+            return null;
+        }
         // mirror image
         // operation mode: "hmir": mirror horizontally, "vmir": mirror
         // vertically
@@ -89,28 +122,32 @@
             docuImage.mirror(90);
         }
 
+        if (stopNow) {
+            logger.debug("ImageWorker stopping (after mirroring)");
+            return null;
+        }
         // rotate image
         if (jobinfo.getAsFloat("rot") != 0d) {
             docuImage.rotate(jobinfo.getAsFloat("rot"));
-            /* if (jobinfo.get_wholeRotArea()) {
-                // crop to the inner bounding box
-                float xcrop = (float) (docuImage.getWidth() - jobinfo.get_innerUserImgArea().getWidth()
-                        * scaleXY);
-                float ycrop = (float) (docuImage.getHeight() - jobinfo.get_innerUserImgArea().getHeight()
-                        * scaleXY);
-                if ((xcrop > 0) || (ycrop > 0)) {
-                    // only crop smaller
-                    xcrop = (xcrop > 0) ? xcrop : 0;
-                    ycrop = (ycrop > 0) ? ycrop : 0;
-                    // crop image
-                    docuImage.crop((int) (xcrop / 2), (int) (ycrop / 2),
-                            (int) (docuImage.getWidth() - xcrop),
-                            (int) (docuImage.getHeight() - ycrop));
-                }
-            } */
+            /*
+             * if (jobinfo.get_wholeRotArea()) { // crop to the inner bounding
+             * box float xcrop = (float) (docuImage.getWidth() -
+             * jobinfo.get_innerUserImgArea().getWidth() scaleXY); float ycrop =
+             * (float) (docuImage.getHeight() -
+             * jobinfo.get_innerUserImgArea().getHeight() scaleXY); if ((xcrop >
+             * 0) || (ycrop > 0)) { // only crop smaller xcrop = (xcrop > 0) ?
+             * xcrop : 0; ycrop = (ycrop > 0) ? ycrop : 0; // crop image
+             * docuImage.crop((int) (xcrop / 2), (int) (ycrop / 2), (int)
+             * (docuImage.getWidth() - xcrop), (int) (docuImage.getHeight() -
+             * ycrop)); } }
+             */
 
         }
 
+        if (stopNow) {
+            logger.debug("ImageWorker stopping (after rotating)");
+            return null;
+        }
         // color modification
         float[] paramRGBM = jobinfo.getRGBM();
         float[] paramRGBA = jobinfo.getRGBA();
@@ -130,6 +167,10 @@
             docuImage.enhanceRGB(mult, paramRGBA);
         }
 
+        if (stopNow) {
+            logger.debug("ImageWorker stopping (after enhanceRGB)");
+            return null;
+        }
         // contrast and brightness enhancement
         float paramCONT = jobinfo.getAsFloat("cont");
         float paramBRGT = jobinfo.getAsFloat("brgt");
@@ -138,15 +179,27 @@
             docuImage.enhance(mult, paramBRGT);
         }
 
+        if (stopNow) {
+            logger.debug("ImageWorker stopping (after enhance)");
+            return null;
+        }
         // color operation
         DocuImage.ColorOp colop = jobinfo.getColOp();
         if (colop != null) {
-        	docuImage.colorOp(colop);
+            docuImage.colorOp(colop);
         }
-        
-        logger.debug("rendered in " + (System.currentTimeMillis() - startTime) + "ms");
+
+        logger.debug("rendered in " + (System.currentTimeMillis() - startTime)
+                + "ms");
 
         return docuImage;
     }
 
+    /**
+     * Set the stopNow flag. Thread stops at the next occasion.
+     */
+    public void stopNow() {
+        this.stopNow = true;
+    }
+    
 }
--- a/servlet3/src/main/java/digilib/servlet/AsyncServletWorker.java	Fri Dec 16 14:34:04 2011 +0100
+++ b/servlet3/src/main/java/digilib/servlet/AsyncServletWorker.java	Mon Dec 19 21:39:17 2011 +0100
@@ -6,6 +6,8 @@
 import java.io.IOException;
 
 import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletResponse;
 
@@ -22,7 +24,7 @@
  * @author casties
  * 
  */
-public class AsyncServletWorker implements Runnable {
+public class AsyncServletWorker implements Runnable, AsyncListener {
 
     /** the AsyncServlet context */
     private AsyncContext asyncContext;
@@ -33,7 +35,11 @@
     protected static Logger logger = Logger.getLogger(AsyncServletWorker.class);
     private long startTime;
     private ErrMsg errMsgType = ErrMsg.IMAGE;
-	private ImageJobDescription jobinfo;
+    private ImageJobDescription jobinfo;
+    /** flag to indicate that the response is completed (on abort)*/
+    private boolean completed = false;
+    /** AsyncRequest timeout */
+    protected static long timeout = 60000l;
 
     /**
      * @param dlConfig
@@ -46,6 +52,8 @@
         imageWorker = new ImageWorker(dlConfig, jobinfo);
         // save AsyncContext
         this.asyncContext = asyncContext;
+        asyncContext.setTimeout(AsyncServletWorker.timeout);
+        logger.debug("timeout for worker: " + asyncContext.getTimeout() + "ms");
         this.startTime = startTime;
         this.errMsgType = errMsgType;
         this.jobinfo = jobinfo;
@@ -56,39 +64,98 @@
      */
     public void run() {
         // get fresh response
-        HttpServletResponse response = (HttpServletResponse) asyncContext.getResponse();
-        logger.debug("working on response: (" + ServletOps.headersToString(response) + ")");
+        HttpServletResponse response = (HttpServletResponse) asyncContext
+                .getResponse();
+        logger.debug("working on response: ("
+                + ServletOps.headersToString(response) + ")");
         try {
             // render the image
             DocuImage img = imageWorker.call();
+            if (completed) {
+                logger.debug("AsyncServletWorker already completed (after scaling)!");
+                return;
+            }
             // forced destination image type
             String mt = null;
             if (jobinfo.hasOption("jpg")) {
-            	mt = "image/jpeg";
+                mt = "image/jpeg";
             } else if (jobinfo.hasOption("png")) {
-            	mt = "image/png";
+                mt = "image/png";
             }
             // send image
-            ServletOps.sendImage(img, mt, response, logger);
+            ServletOps.sendImage(img, mt,
+                    (HttpServletResponse) asyncContext.getResponse(), logger);
             logger.debug("Job done in: "
                     + (System.currentTimeMillis() - startTime) + "ms");
         } catch (ImageOpException e) {
             logger.error(e.getClass() + ": " + e.getMessage());
-            Scaler.digilibError(errMsgType, Error.IMAGE, null, response);
+            Scaler.digilibError(errMsgType, Error.IMAGE, null,
+                    (HttpServletResponse) asyncContext.getResponse());
         } catch (IOException e) {
             logger.error(e.getClass() + ": " + e.getMessage());
-            Scaler.digilibError(errMsgType, Error.FILE, null, response);
+            Scaler.digilibError(errMsgType, Error.FILE, null,
+                    (HttpServletResponse) asyncContext.getResponse());
         } catch (ServletException e) {
             logger.error("Servlet error: ", e);
         } catch (Exception e) {
             logger.error("Other error: ", e);
         } finally {
-            // submit response
-            logger.debug("context complete.");
-            logger.debug("response: (" + ServletOps.headersToString(response) + ")");
-            asyncContext.complete();
+            if (completed) {
+                logger.debug("AsyncServletWorker already completed (finally)!");
+            } else {
+                // submit response
+                logger.debug("context complete.");
+                this.completed = true;
+                asyncContext.complete();
+            }
         }
 
     }
 
+    @Override
+    public void onStartAsync(AsyncEvent event) throws IOException {
+        logger.debug("onStartAsync called (why?)");
+    }
+
+    @Override
+    public void onComplete(AsyncEvent event) throws IOException {
+        logger.debug("AsyncServletWorker onComplete");
+        // make sure complete isn't called twice
+        this.completed = true;
+    }
+
+    @Override
+    public void onError(AsyncEvent event) throws IOException {
+        logger.error("AsyncServletWorker onError: " + event.toString());
+        if (completed) {
+            logger.debug("AsyncServletWorker already completed (TimeOut)!");
+            return;
+        }
+        imageWorker.stopNow();
+        this.completed = true;
+        Scaler.digilibError(errMsgType, Error.UNKNOWN, null, (HttpServletResponse) asyncContext.getResponse());
+        asyncContext.complete();
+    }
+
+    @Override
+    public void onTimeout(AsyncEvent event) throws IOException {
+        logger.error("AsyncServletWorker TIMED OUT! (increase worker-timeout?)"+event);
+        if (completed) {
+            logger.debug("AsyncServletWorker already completed (TimeOut)!");
+            return;
+        }
+        imageWorker.stopNow();
+        this.completed = true;
+        Scaler.digilibError(errMsgType, Error.UNKNOWN, null, (HttpServletResponse) asyncContext.getResponse());
+        asyncContext.complete();
+    }
+    
+    public static long getTimeout() {
+        return timeout;
+    }
+
+    public static void setTimeout(long timeout) {
+        AsyncServletWorker.timeout = timeout;
+    }
+
 }
--- a/servlet3/src/main/java/digilib/servlet/DigilibServletConfiguration.java	Fri Dec 16 14:34:04 2011 +0100
+++ b/servlet3/src/main/java/digilib/servlet/DigilibServletConfiguration.java	Mon Dec 19 21:39:17 2011 +0100
@@ -120,6 +120,8 @@
         newParameter("worker-threads", new Integer(1), null, 'f');
         // max number of waiting threads
         newParameter("max-waiting-threads", new Integer(20), null, 'f');
+        // timeout for worker threads (ms)
+        newParameter("worker-timeout", new Integer(60000), null, 'f');
         // number of pdf-generation threads
         newParameter("pdf-worker-threads", new Integer(1), null, 'f');
         // max number of waiting pdf-generation threads
--- a/servlet3/src/main/java/digilib/servlet/Initialiser.java	Fri Dec 16 14:34:04 2011 +0100
+++ b/servlet3/src/main/java/digilib/servlet/Initialiser.java	Mon Dec 19 21:39:17 2011 +0100
@@ -138,7 +138,10 @@
 				int nt = dlConfig.getAsInt("worker-threads");
                 int mt = dlConfig.getAsInt("max-waiting-threads");
 				imageEx = new DigilibJobCenter<DocuImage>(nt, mt, false, "servlet.worker.imageexecutor");
-                dlConfig.setValue("servlet.worker.imageexecutor", imageEx);				
+                dlConfig.setValue("servlet.worker.imageexecutor", imageEx);
+                // digilib worker timeout
+                long to = dlConfig.getAsInt("worker-timeout");
+                AsyncServletWorker.setTimeout(to);
 				// PDF worker threads
 				int pnt = dlConfig.getAsInt("pdf-worker-threads");
                 int pmt = dlConfig.getAsInt("pdf-max-waiting-threads");
--- a/servlet3/src/main/java/digilib/servlet/Scaler.java	Fri Dec 16 14:34:04 2011 +0100
+++ b/servlet3/src/main/java/digilib/servlet/Scaler.java	Mon Dec 19 21:39:17 2011 +0100
@@ -5,6 +5,8 @@
 import java.util.List;
 
 import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
@@ -31,7 +33,7 @@
     private static final long serialVersionUID = 5289386646192471549L;
 
     /** digilib servlet version (for all components) */
-    public static final String version = "2.0b1 async";
+    public static final String version = "2.0b2 async";
 
     /** servlet error codes */
     public static enum Error {UNKNOWN, AUTH, FILE, IMAGE};
@@ -265,9 +267,11 @@
             }
             
             // worker job is done asynchronously
-            AsyncContext asyncCtx = request.startAsync(request, response); 
+            AsyncContext asyncCtx = request.startAsync(request, response);
             // create job
             AsyncServletWorker job = new AsyncServletWorker(dlConfig, jobTicket, asyncCtx, errMsgType, startTime);
+            // AsyncServletWorker is its own AsyncListener
+            asyncCtx.addListener(job);
             // submit job
             imageJobCenter.submit(job);
             // we're done for now
--- a/webapp/src/main/webapp/WEB-INF/digilib-config.xml	Fri Dec 16 14:34:04 2011 +0100
+++ b/webapp/src/main/webapp/WEB-INF/digilib-config.xml	Mon Dec 19 21:39:17 2011 +0100
@@ -37,6 +37,9 @@
   <!-- number of waiting requests in queue -->
   <parameter name="max-waiting-threads" value="20" />
 
+  <!-- timeout for asynchronous servlet worker (ms) -->
+  <parameter name="worker-timeout" value="60000" />
+
   <!-- Restrict access to authorized users.
        User authentication and roles are provided by the servlet container 
        (see tomcat-users.xml).