-/*\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 <=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 <=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;
+ }
+}