Enable for async requests.
authormarkt <markt@13f79535-47bb-0310-9956-ffa450edef68>
Tue, 7 Jun 2011 23:47:16 +0000 (23:47 +0000)
committermarkt <markt@13f79535-47bb-0310-9956-ffa450edef68>
Tue, 7 Jun 2011 23:47:16 +0000 (23:47 +0000)
Looking for stuck threads - no special async support required here.
When the asycn timeout is infinite requests may get stuck but that'll need a different detection mechanism.

git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@1133200 13f79535-47bb-0310-9956-ffa450edef68

java/org/apache/catalina/valves/StuckThreadDetectionValve.java

index d79be4b..0015f96 100644 (file)
-/*\r
- * Licensed to the Apache Software Foundation (ASF) under one or more\r
- * contributor license agreements.  See the NOTICE file distributed with\r
- * this work for additional information regarding copyright ownership.\r
- * The ASF licenses this file to You under the Apache License, Version 2.0\r
- * (the "License"); you may not use this file except in compliance with\r
- * the License.  You may obtain a copy of the License at\r
- *\r
- *      http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-package org.apache.catalina.valves;\r
-\r
-import java.io.IOException;\r
-import java.util.ArrayList;\r
-import java.util.Date;\r
-import java.util.List;\r
-import java.util.Queue;\r
-import java.util.concurrent.ConcurrentHashMap;\r
-import java.util.concurrent.ConcurrentLinkedQueue;\r
-import java.util.concurrent.atomic.AtomicInteger;\r
-\r
-import javax.servlet.ServletException;\r
-\r
-import org.apache.catalina.LifecycleException;\r
-import org.apache.catalina.connector.Request;\r
-import org.apache.catalina.connector.Response;\r
-import org.apache.juli.logging.Log;\r
-import org.apache.juli.logging.LogFactory;\r
-import org.apache.tomcat.util.res.StringManager;\r
-\r
-/**\r
- * This valve allows to detect requests that take a long time to process, which might\r
- * indicate that the thread that is processing it is stuck.\r
- * Based on code proposed by TomLu in Bugzilla entry #50306\r
- * \r
- * @author slaurent\r
- *\r
- */\r
-public class StuckThreadDetectionValve extends ValveBase {\r
-\r
-    /**\r
-     * The descriptive information related to this implementation.\r
-     */\r
-    private static final String info =\r
-            "org.apache.catalina.valves.StuckThreadDetectionValve/1.0";\r
-    /**\r
-     * Logger\r
-     */\r
-    private static final Log log = LogFactory.getLog(StuckThreadDetectionValve.class);\r
-    \r
-    /**\r
-     * The string manager for this package.\r
-     */\r
-    private static final StringManager sm =\r
-        StringManager.getManager(Constants.Package);\r
-\r
-    /**\r
-     * Keeps count of the number of stuck threads detected\r
-     */\r
-    private final AtomicInteger stuckCount = new AtomicInteger(0);\r
-    \r
-    /**\r
-     * In seconds. Default 600 (10 minutes).\r
-     */\r
-    private int threshold = 600;\r
-    \r
-    /**\r
-     * The only references we keep to actual running Thread objects are in\r
-     * this Map (which is automatically cleaned in invoke()s finally clause).\r
-     * That way, Threads can be GC'ed, eventhough the Valve still thinks they\r
-     * are stuck (caused by a long monitor interval)\r
-     */\r
-    private ConcurrentHashMap<Long, MonitoredThread> activeThreads =\r
-            new ConcurrentHashMap<Long, MonitoredThread>();\r
-    /**\r
-     *\r
-     */\r
-    private Queue<CompletedStuckThread> completedStuckThreadsQueue =\r
-            new ConcurrentLinkedQueue<CompletedStuckThread>();\r
-\r
-    /**\r
-     * Specify the threshold (in seconds) used when checking for stuck threads.\r
-     * If &lt;=0, the detection is disabled. The default is 600 seconds.\r
-     * \r
-     * @param threshold\r
-     *            The new threshold in seconds\r
-     */\r
-    public void setThreshold(int threshold) {\r
-        this.threshold = threshold;\r
-    }\r
-\r
-    /**\r
-     * @see #setThreshold(int)\r
-     * @return The current threshold in seconds\r
-     */\r
-    public int getThreshold() {\r
-        return threshold;\r
-    }\r
-\r
-    @Override\r
-    protected void initInternal() throws LifecycleException {\r
-        super.initInternal();\r
-\r
-        if (log.isDebugEnabled()) {\r
-            log.debug("Monitoring stuck threads with threshold = "\r
-                    + threshold\r
-                    + " sec");\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Return descriptive information about this Valve implementation.\r
-     */\r
-    @Override\r
-    public String getInfo() {\r
-        return info;\r
-    }\r
-\r
-    private void notifyStuckThreadDetected(MonitoredThread monitoredThread,\r
-        long activeTime, int numStuckThreads) {\r
-        if (log.isWarnEnabled()) {\r
-            String msg = sm.getString(\r
-                "stuckThreadDetectionValve.notifyStuckThreadDetected",\r
-                monitoredThread.getThread().getName(), activeTime,\r
-                monitoredThread.getStartTime(), numStuckThreads,\r
-                monitoredThread.getRequestUri(), threshold);\r
-            // msg += "\n" + getStackTraceAsString(trace);\r
-            Throwable th = new Throwable();\r
-            th.setStackTrace(monitoredThread.getThread().getStackTrace());\r
-            log.warn(msg, th);\r
-        }\r
-    }\r
-\r
-    private void notifyStuckThreadCompleted(String threadName,\r
-            long activeTime, int numStuckThreads) {\r
-        if (log.isWarnEnabled()) {\r
-            String msg = sm.getString(\r
-                "stuckThreadDetectionValve.notifyStuckThreadCompleted",\r
-                threadName, activeTime, numStuckThreads);\r
-            // Since the "stuck thread notification" is warn, this should also\r
-            // be warn\r
-            log.warn(msg);\r
-        }\r
-    }\r
-\r
-    /**\r
-     * {@inheritDoc}\r
-     */\r
-    @Override\r
-    public void invoke(Request request, Response response)\r
-            throws IOException, ServletException {\r
-        \r
-        if (threshold <= 0) {\r
-            // short-circuit if not monitoring stuck threads\r
-            getNext().invoke(request, response);\r
-            return;\r
-        }\r
-\r
-        // Save the thread/runnable\r
-        // Keeping a reference to the thread object here does not prevent\r
-        // GC'ing, as the reference is removed from the Map in the finally clause\r
-\r
-        Long key = new Long(Thread.currentThread().getId());\r
-        StringBuffer requestUrl = request.getRequestURL();\r
-        if(request.getQueryString()!=null) {\r
-            requestUrl.append("?");\r
-            requestUrl.append(request.getQueryString());\r
-        }\r
-        MonitoredThread monitoredThread = new MonitoredThread(Thread.currentThread(), \r
-            requestUrl.toString());\r
-        activeThreads.put(key, monitoredThread);\r
-\r
-        try {\r
-            getNext().invoke(request, response);\r
-        } finally {\r
-            activeThreads.remove(key);\r
-            if (monitoredThread.markAsDone() == MonitoredThreadState.STUCK) {\r
-                completedStuckThreadsQueue.add(\r
-                        new CompletedStuckThread(monitoredThread.getThread().getName(),\r
-                            monitoredThread.getActiveTimeInMillis()));\r
-            }\r
-        }\r
-    }\r
-\r
-    @Override\r
-    public void backgroundProcess() {\r
-        super.backgroundProcess();\r
-\r
-        long thresholdInMillis = threshold * 1000;\r
-\r
-        // Check monitored threads, being careful that the request might have\r
-        // completed by the time we examine it\r
-        for (MonitoredThread monitoredThread : activeThreads.values()) {\r
-            long activeTime = monitoredThread.getActiveTimeInMillis();\r
-\r
-            if (activeTime >= thresholdInMillis && monitoredThread.markAsStuckIfStillRunning()) {\r
-                int numStuckThreads = stuckCount.incrementAndGet();\r
-                notifyStuckThreadDetected(monitoredThread, activeTime, numStuckThreads);\r
-            }\r
-        }\r
-        // Check if any threads previously reported as stuck, have finished.\r
-        for (CompletedStuckThread completedStuckThread = completedStuckThreadsQueue.poll(); \r
-            completedStuckThread != null; completedStuckThread = completedStuckThreadsQueue.poll()) {\r
-\r
-            int numStuckThreads = stuckCount.decrementAndGet();\r
-            notifyStuckThreadCompleted(completedStuckThread.getName(),\r
-                    completedStuckThread.getTotalActiveTime(), numStuckThreads);\r
-        }\r
-    }\r
-    \r
-    public long[] getStuckThreadIds() {\r
-        List<Long> idList = new ArrayList<Long>();\r
-        for (MonitoredThread monitoredThread : activeThreads.values()) {\r
-            if (monitoredThread.isMarkedAsStuck()) {\r
-                idList.add(monitoredThread.getThread().getId());\r
-            }\r
-        }\r
-\r
-        long[] result = new long[idList.size()];\r
-        for (int i = 0; i < result.length; i++) {\r
-            result[i] = idList.get(i);\r
-        }\r
-        return result;\r
-    }\r
-\r
-    private class MonitoredThread {\r
-\r
-        /**\r
-         * Reference to the thread to get a stack trace from background task\r
-         */\r
-        private final Thread thread;\r
-        private final String requestUri;\r
-        private final long start;\r
-        private final AtomicInteger state = new AtomicInteger(\r
-            MonitoredThreadState.RUNNING.ordinal());\r
-\r
-        public MonitoredThread(Thread thread, String requestUri) {\r
-            this.thread = thread;\r
-            this.requestUri = requestUri;\r
-            this.start = System.currentTimeMillis();\r
-        }\r
-\r
-        public Thread getThread() {\r
-            return this.thread;\r
-        }\r
-\r
-        public String getRequestUri() {\r
-            return requestUri;\r
-        }\r
-\r
-        public long getActiveTimeInMillis() {\r
-            return System.currentTimeMillis() - start;\r
-        }\r
-\r
-        public Date getStartTime() {\r
-            return new Date(start);\r
-        }\r
-\r
-        public boolean markAsStuckIfStillRunning() {\r
-            return this.state.compareAndSet(MonitoredThreadState.RUNNING.ordinal(),\r
-                MonitoredThreadState.STUCK.ordinal());\r
-        }\r
-\r
-        public MonitoredThreadState markAsDone() {\r
-            int val = this.state.getAndSet(MonitoredThreadState.DONE.ordinal());\r
-            return MonitoredThreadState.values()[val];\r
-        }\r
-        \r
-        boolean isMarkedAsStuck() {\r
-            return this.state.get() == MonitoredThreadState.STUCK.ordinal();\r
-        }\r
-    }\r
-\r
-    private class CompletedStuckThread {\r
-\r
-        private String threadName;\r
-        private long totalActiveTime;\r
-\r
-        public CompletedStuckThread(String threadName, long totalActiveTime) {\r
-            this.threadName = threadName;\r
-            this.totalActiveTime = totalActiveTime;\r
-        }\r
-\r
-        public String getName() {\r
-            return this.threadName;\r
-        }\r
-\r
-        public long getTotalActiveTime() {\r
-            return this.totalActiveTime;\r
-        }\r
-    }\r
-\r
-    private enum MonitoredThreadState {\r
-        RUNNING, STUCK, DONE;\r
-    }\r
-}\r
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.valves;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.ServletException;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * This valve allows to detect requests that take a long time to process, which might
+ * indicate that the thread that is processing it is stuck.
+ * Based on code proposed by TomLu in Bugzilla entry #50306
+ * 
+ * @author slaurent
+ *
+ */
+public class StuckThreadDetectionValve extends ValveBase {
+
+    /**
+     * The descriptive information related to this implementation.
+     */
+    private static final String info =
+            "org.apache.catalina.valves.StuckThreadDetectionValve/1.0";
+    /**
+     * Logger
+     */
+    private static final Log log = LogFactory.getLog(StuckThreadDetectionValve.class);
+    
+    /**
+     * The string manager for this package.
+     */
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+    /**
+     * Keeps count of the number of stuck threads detected
+     */
+    private final AtomicInteger stuckCount = new AtomicInteger(0);
+    
+    /**
+     * In seconds. Default 600 (10 minutes).
+     */
+    private int threshold = 600;
+    
+    /**
+     * The only references we keep to actual running Thread objects are in
+     * this Map (which is automatically cleaned in invoke()s finally clause).
+     * That way, Threads can be GC'ed, eventhough the Valve still thinks they
+     * are stuck (caused by a long monitor interval)
+     */
+    private ConcurrentHashMap<Long, MonitoredThread> activeThreads =
+            new ConcurrentHashMap<Long, MonitoredThread>();
+    /**
+     *
+     */
+    private Queue<CompletedStuckThread> completedStuckThreadsQueue =
+            new ConcurrentLinkedQueue<CompletedStuckThread>();
+
+    /**
+     * Specify the threshold (in seconds) used when checking for stuck threads.
+     * If &lt;=0, the detection is disabled. The default is 600 seconds.
+     * 
+     * @param threshold
+     *            The new threshold in seconds
+     */
+    public void setThreshold(int threshold) {
+        this.threshold = threshold;
+    }
+
+    /**
+     * @see #setThreshold(int)
+     * @return The current threshold in seconds
+     */
+    public int getThreshold() {
+        return threshold;
+    }
+
+    
+    /**
+     * Required to enable async support.
+     */
+    public StuckThreadDetectionValve() {
+        super(true);
+    }
+
+
+    @Override
+    protected void initInternal() throws LifecycleException {
+        super.initInternal();
+
+        if (log.isDebugEnabled()) {
+            log.debug("Monitoring stuck threads with threshold = "
+                    + threshold
+                    + " sec");
+        }
+    }
+
+    /**
+     * Return descriptive information about this Valve implementation.
+     */
+    @Override
+    public String getInfo() {
+        return info;
+    }
+
+    private void notifyStuckThreadDetected(MonitoredThread monitoredThread,
+        long activeTime, int numStuckThreads) {
+        if (log.isWarnEnabled()) {
+            String msg = sm.getString(
+                "stuckThreadDetectionValve.notifyStuckThreadDetected",
+                monitoredThread.getThread().getName(), activeTime,
+                monitoredThread.getStartTime(), numStuckThreads,
+                monitoredThread.getRequestUri(), threshold);
+            // msg += "\n" + getStackTraceAsString(trace);
+            Throwable th = new Throwable();
+            th.setStackTrace(monitoredThread.getThread().getStackTrace());
+            log.warn(msg, th);
+        }
+    }
+
+    private void notifyStuckThreadCompleted(String threadName,
+            long activeTime, int numStuckThreads) {
+        if (log.isWarnEnabled()) {
+            String msg = sm.getString(
+                "stuckThreadDetectionValve.notifyStuckThreadCompleted",
+                threadName, activeTime, numStuckThreads);
+            // Since the "stuck thread notification" is warn, this should also
+            // be warn
+            log.warn(msg);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void invoke(Request request, Response response)
+            throws IOException, ServletException {
+        
+        if (threshold <= 0) {
+            // short-circuit if not monitoring stuck threads
+            getNext().invoke(request, response);
+            return;
+        }
+
+        // Save the thread/runnable
+        // Keeping a reference to the thread object here does not prevent
+        // GC'ing, as the reference is removed from the Map in the finally clause
+
+        Long key = new Long(Thread.currentThread().getId());
+        StringBuffer requestUrl = request.getRequestURL();
+        if(request.getQueryString()!=null) {
+            requestUrl.append("?");
+            requestUrl.append(request.getQueryString());
+        }
+        MonitoredThread monitoredThread = new MonitoredThread(Thread.currentThread(), 
+            requestUrl.toString());
+        activeThreads.put(key, monitoredThread);
+
+        try {
+            getNext().invoke(request, response);
+        } finally {
+            activeThreads.remove(key);
+            if (monitoredThread.markAsDone() == MonitoredThreadState.STUCK) {
+                completedStuckThreadsQueue.add(
+                        new CompletedStuckThread(monitoredThread.getThread().getName(),
+                            monitoredThread.getActiveTimeInMillis()));
+            }
+        }
+    }
+
+    @Override
+    public void backgroundProcess() {
+        super.backgroundProcess();
+
+        long thresholdInMillis = threshold * 1000;
+
+        // Check monitored threads, being careful that the request might have
+        // completed by the time we examine it
+        for (MonitoredThread monitoredThread : activeThreads.values()) {
+            long activeTime = monitoredThread.getActiveTimeInMillis();
+
+            if (activeTime >= thresholdInMillis && monitoredThread.markAsStuckIfStillRunning()) {
+                int numStuckThreads = stuckCount.incrementAndGet();
+                notifyStuckThreadDetected(monitoredThread, activeTime, numStuckThreads);
+            }
+        }
+        // Check if any threads previously reported as stuck, have finished.
+        for (CompletedStuckThread completedStuckThread = completedStuckThreadsQueue.poll(); 
+            completedStuckThread != null; completedStuckThread = completedStuckThreadsQueue.poll()) {
+
+            int numStuckThreads = stuckCount.decrementAndGet();
+            notifyStuckThreadCompleted(completedStuckThread.getName(),
+                    completedStuckThread.getTotalActiveTime(), numStuckThreads);
+        }
+    }
+    
+    public long[] getStuckThreadIds() {
+        List<Long> idList = new ArrayList<Long>();
+        for (MonitoredThread monitoredThread : activeThreads.values()) {
+            if (monitoredThread.isMarkedAsStuck()) {
+                idList.add(monitoredThread.getThread().getId());
+            }
+        }
+
+        long[] result = new long[idList.size()];
+        for (int i = 0; i < result.length; i++) {
+            result[i] = idList.get(i);
+        }
+        return result;
+    }
+
+    private class MonitoredThread {
+
+        /**
+         * Reference to the thread to get a stack trace from background task
+         */
+        private final Thread thread;
+        private final String requestUri;
+        private final long start;
+        private final AtomicInteger state = new AtomicInteger(
+            MonitoredThreadState.RUNNING.ordinal());
+
+        public MonitoredThread(Thread thread, String requestUri) {
+            this.thread = thread;
+            this.requestUri = requestUri;
+            this.start = System.currentTimeMillis();
+        }
+
+        public Thread getThread() {
+            return this.thread;
+        }
+
+        public String getRequestUri() {
+            return requestUri;
+        }
+
+        public long getActiveTimeInMillis() {
+            return System.currentTimeMillis() - start;
+        }
+
+        public Date getStartTime() {
+            return new Date(start);
+        }
+
+        public boolean markAsStuckIfStillRunning() {
+            return this.state.compareAndSet(MonitoredThreadState.RUNNING.ordinal(),
+                MonitoredThreadState.STUCK.ordinal());
+        }
+
+        public MonitoredThreadState markAsDone() {
+            int val = this.state.getAndSet(MonitoredThreadState.DONE.ordinal());
+            return MonitoredThreadState.values()[val];
+        }
+        
+        boolean isMarkedAsStuck() {
+            return this.state.get() == MonitoredThreadState.STUCK.ordinal();
+        }
+    }
+
+    private class CompletedStuckThread {
+
+        private String threadName;
+        private long totalActiveTime;
+
+        public CompletedStuckThread(String threadName, long totalActiveTime) {
+            this.threadName = threadName;
+            this.totalActiveTime = totalActiveTime;
+        }
+
+        public String getName() {
+            return this.threadName;
+        }
+
+        public long getTotalActiveTime() {
+            return this.totalActiveTime;
+        }
+    }
+
+    private enum MonitoredThreadState {
+        RUNNING, STUCK, DONE;
+    }
+}