Line 0
Link Here
|
|
|
1 |
/* |
2 |
* Licensed to the Apache Software Foundation (ASF) under one or more |
3 |
* contributor license agreements. See the NOTICE file distributed with |
4 |
* this work for additional information regarding copyright ownership. |
5 |
* The ASF licenses this file to You under the Apache License, Version 2.0 |
6 |
* (the "License"); you may not use this file except in compliance with |
7 |
* the License. You may obtain a copy of the License at |
8 |
* |
9 |
* http://www.apache.org/licenses/LICENSE-2.0 |
10 |
* |
11 |
* Unless required by applicable law or agreed to in writing, software |
12 |
* distributed under the License is distributed on an "AS IS" BASIS, |
13 |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14 |
* See the License for the specific language governing permissions and |
15 |
* limitations under the License. |
16 |
*/ |
17 |
package org.apache.catalina.valves; |
18 |
|
19 |
import java.io.IOException; |
20 |
import java.util.ArrayList; |
21 |
import java.util.Date; |
22 |
import java.util.List; |
23 |
import java.util.Queue; |
24 |
import java.util.concurrent.ConcurrentHashMap; |
25 |
import java.util.concurrent.ConcurrentLinkedQueue; |
26 |
import java.util.concurrent.atomic.AtomicInteger; |
27 |
|
28 |
import javax.servlet.ServletException; |
29 |
|
30 |
import org.apache.catalina.connector.Request; |
31 |
import org.apache.catalina.connector.Response; |
32 |
import org.apache.juli.logging.Log; |
33 |
import org.apache.juli.logging.LogFactory; |
34 |
import org.apache.tomcat.util.res.StringManager; |
35 |
|
36 |
/** |
37 |
* This valve allows to detect requests that take a long time to process, which |
38 |
* might indicate that the thread that is processing it is stuck. |
39 |
*/ |
40 |
public class StuckThreadDetectionValve extends ValveBase { |
41 |
|
42 |
/** |
43 |
* Logger |
44 |
*/ |
45 |
private static final Log log = LogFactory.getLog(StuckThreadDetectionValve.class); |
46 |
|
47 |
/** |
48 |
* The string manager for this package. |
49 |
*/ |
50 |
private static final StringManager sm = |
51 |
StringManager.getManager(Constants.Package); |
52 |
|
53 |
/** |
54 |
* Keeps count of the number of stuck threads detected |
55 |
*/ |
56 |
private final AtomicInteger stuckCount = new AtomicInteger(0); |
57 |
|
58 |
/** |
59 |
* In seconds. Default 600 (10 minutes). |
60 |
*/ |
61 |
private int threshold = 600; |
62 |
|
63 |
/** |
64 |
* The only references we keep to actual running Thread objects are in |
65 |
* this Map (which is automatically cleaned in invoke()s finally clause). |
66 |
* That way, Threads can be GC'ed, eventhough the Valve still thinks they |
67 |
* are stuck (caused by a long monitor interval) |
68 |
*/ |
69 |
private final ConcurrentHashMap<Long, MonitoredThread> activeThreads = |
70 |
new ConcurrentHashMap<Long, MonitoredThread>(); |
71 |
/** |
72 |
* |
73 |
*/ |
74 |
private final Queue<CompletedStuckThread> completedStuckThreadsQueue = |
75 |
new ConcurrentLinkedQueue<CompletedStuckThread>(); |
76 |
|
77 |
/** |
78 |
* Specify the threshold (in seconds) used when checking for stuck threads. |
79 |
* If <=0, the detection is disabled. The default is 600 seconds. |
80 |
* |
81 |
* @param threshold |
82 |
* The new threshold in seconds |
83 |
*/ |
84 |
public void setThreshold(int threshold) { |
85 |
this.threshold = threshold; |
86 |
} |
87 |
|
88 |
/** |
89 |
* @see #setThreshold(int) |
90 |
* @return The current threshold in seconds |
91 |
*/ |
92 |
public int getThreshold() { |
93 |
return threshold; |
94 |
} |
95 |
|
96 |
|
97 |
private void notifyStuckThreadDetected(MonitoredThread monitoredThread, |
98 |
long activeTime, int numStuckThreads) { |
99 |
if (log.isWarnEnabled()) { |
100 |
String msg = sm.getString( |
101 |
"stuckThreadDetectionValve.notifyStuckThreadDetected", |
102 |
monitoredThread.getThread().getName(), |
103 |
Long.valueOf(activeTime), |
104 |
monitoredThread.getStartTime(), |
105 |
Integer.valueOf(numStuckThreads), |
106 |
monitoredThread.getRequestUri(), |
107 |
Integer.valueOf(threshold), |
108 |
String.valueOf(monitoredThread.getThread().getId()) |
109 |
); |
110 |
// msg += "\n" + getStackTraceAsString(trace); |
111 |
Throwable th = new Throwable(); |
112 |
th.setStackTrace(monitoredThread.getThread().getStackTrace()); |
113 |
log.warn(msg, th); |
114 |
} |
115 |
} |
116 |
|
117 |
private void notifyStuckThreadCompleted(CompletedStuckThread thread, |
118 |
int numStuckThreads) { |
119 |
if (log.isWarnEnabled()) { |
120 |
String msg = sm.getString( |
121 |
"stuckThreadDetectionValve.notifyStuckThreadCompleted", |
122 |
thread.getName(), |
123 |
Long.valueOf(thread.getTotalActiveTime()), |
124 |
Integer.valueOf(numStuckThreads), |
125 |
String.valueOf(thread.getId())); |
126 |
// Since the "stuck thread notification" is warn, this should also |
127 |
// be warn |
128 |
log.warn(msg); |
129 |
} |
130 |
} |
131 |
|
132 |
/** |
133 |
* {@inheritDoc} |
134 |
*/ |
135 |
@Override |
136 |
public void invoke(Request request, Response response) |
137 |
throws IOException, ServletException { |
138 |
|
139 |
if (threshold <= 0) { |
140 |
// short-circuit if not monitoring stuck threads |
141 |
getNext().invoke(request, response); |
142 |
return; |
143 |
} |
144 |
|
145 |
// Save the thread/runnable |
146 |
// Keeping a reference to the thread object here does not prevent |
147 |
// GC'ing, as the reference is removed from the Map in the finally clause |
148 |
|
149 |
Long key = Long.valueOf(Thread.currentThread().getId()); |
150 |
StringBuffer requestUrl = request.getRequestURL(); |
151 |
if(request.getQueryString()!=null) { |
152 |
requestUrl.append("?"); |
153 |
requestUrl.append(request.getQueryString()); |
154 |
} |
155 |
MonitoredThread monitoredThread = new MonitoredThread(Thread.currentThread(), |
156 |
requestUrl.toString()); |
157 |
activeThreads.put(key, monitoredThread); |
158 |
|
159 |
try { |
160 |
getNext().invoke(request, response); |
161 |
} finally { |
162 |
activeThreads.remove(key); |
163 |
if (monitoredThread.markAsDone() == MonitoredThreadState.STUCK) { |
164 |
completedStuckThreadsQueue.add( |
165 |
new CompletedStuckThread(monitoredThread.getThread(), |
166 |
monitoredThread.getActiveTimeInMillis())); |
167 |
} |
168 |
} |
169 |
} |
170 |
|
171 |
@Override |
172 |
public void backgroundProcess() { |
173 |
super.backgroundProcess(); |
174 |
|
175 |
long thresholdInMillis = threshold * 1000; |
176 |
|
177 |
// Check monitored threads, being careful that the request might have |
178 |
// completed by the time we examine it |
179 |
for (MonitoredThread monitoredThread : activeThreads.values()) { |
180 |
long activeTime = monitoredThread.getActiveTimeInMillis(); |
181 |
|
182 |
if (activeTime >= thresholdInMillis && monitoredThread.markAsStuckIfStillRunning()) { |
183 |
int numStuckThreads = stuckCount.incrementAndGet(); |
184 |
notifyStuckThreadDetected(monitoredThread, activeTime, numStuckThreads); |
185 |
} |
186 |
} |
187 |
// Check if any threads previously reported as stuck, have finished. |
188 |
for (CompletedStuckThread completedStuckThread = completedStuckThreadsQueue.poll(); |
189 |
completedStuckThread != null; completedStuckThread = completedStuckThreadsQueue.poll()) { |
190 |
|
191 |
int numStuckThreads = stuckCount.decrementAndGet(); |
192 |
notifyStuckThreadCompleted(completedStuckThread, numStuckThreads); |
193 |
} |
194 |
} |
195 |
|
196 |
public long[] getStuckThreadIds() { |
197 |
List<Long> idList = new ArrayList<Long>(); |
198 |
for (MonitoredThread monitoredThread : activeThreads.values()) { |
199 |
if (monitoredThread.isMarkedAsStuck()) { |
200 |
idList.add(Long.valueOf(monitoredThread.getThread().getId())); |
201 |
} |
202 |
} |
203 |
|
204 |
long[] result = new long[idList.size()]; |
205 |
for (int i = 0; i < result.length; i++) { |
206 |
result[i] = idList.get(i).longValue(); |
207 |
} |
208 |
return result; |
209 |
} |
210 |
|
211 |
public String[] getStuckThreadNames() { |
212 |
List<String> nameList = new ArrayList<String>(); |
213 |
for (MonitoredThread monitoredThread : activeThreads.values()) { |
214 |
if (monitoredThread.isMarkedAsStuck()) { |
215 |
nameList.add(monitoredThread.getThread().getName()); |
216 |
} |
217 |
} |
218 |
return nameList.toArray(new String[nameList.size()]); |
219 |
} |
220 |
|
221 |
private static class MonitoredThread { |
222 |
|
223 |
/** |
224 |
* Reference to the thread to get a stack trace from background task |
225 |
*/ |
226 |
private final Thread thread; |
227 |
private final String requestUri; |
228 |
private final long start; |
229 |
private final AtomicInteger state = new AtomicInteger( |
230 |
MonitoredThreadState.RUNNING.ordinal()); |
231 |
|
232 |
public MonitoredThread(Thread thread, String requestUri) { |
233 |
this.thread = thread; |
234 |
this.requestUri = requestUri; |
235 |
this.start = System.currentTimeMillis(); |
236 |
} |
237 |
|
238 |
public Thread getThread() { |
239 |
return this.thread; |
240 |
} |
241 |
|
242 |
public String getRequestUri() { |
243 |
return requestUri; |
244 |
} |
245 |
|
246 |
public long getActiveTimeInMillis() { |
247 |
return System.currentTimeMillis() - start; |
248 |
} |
249 |
|
250 |
public Date getStartTime() { |
251 |
return new Date(start); |
252 |
} |
253 |
|
254 |
public boolean markAsStuckIfStillRunning() { |
255 |
return this.state.compareAndSet(MonitoredThreadState.RUNNING.ordinal(), |
256 |
MonitoredThreadState.STUCK.ordinal()); |
257 |
} |
258 |
|
259 |
public MonitoredThreadState markAsDone() { |
260 |
int val = this.state.getAndSet(MonitoredThreadState.DONE.ordinal()); |
261 |
return MonitoredThreadState.values()[val]; |
262 |
} |
263 |
|
264 |
boolean isMarkedAsStuck() { |
265 |
return this.state.get() == MonitoredThreadState.STUCK.ordinal(); |
266 |
} |
267 |
} |
268 |
|
269 |
private static class CompletedStuckThread { |
270 |
|
271 |
private final String threadName; |
272 |
private final long threadId; |
273 |
private final long totalActiveTime; |
274 |
|
275 |
public CompletedStuckThread(Thread thread, |
276 |
long totalActiveTime) { |
277 |
this.threadName = thread.getName(); |
278 |
this.threadId = thread.getId(); |
279 |
this.totalActiveTime = totalActiveTime; |
280 |
} |
281 |
|
282 |
public String getName() { |
283 |
return this.threadName; |
284 |
} |
285 |
|
286 |
public long getId() { |
287 |
return this.threadId; |
288 |
} |
289 |
|
290 |
public long getTotalActiveTime() { |
291 |
return this.totalActiveTime; |
292 |
} |
293 |
} |
294 |
|
295 |
private enum MonitoredThreadState { |
296 |
RUNNING, STUCK, DONE; |
297 |
} |
298 |
} |