001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
003: * notice. All rights reserved.
004: */
005: package com.terracotta.session;
006:
007: import java.io.File;
008: import java.io.IOException;
009: import java.io.InputStream;
010: import java.util.ArrayList;
011: import java.util.Collections;
012: import java.util.IdentityHashMap;
013: import java.util.List;
014: import java.util.Map;
015: import java.util.TreeMap;
016: import java.util.Map.Entry;
017:
018: import javax.servlet.http.HttpServletRequest;
019: import javax.servlet.http.HttpSession;
020:
021: public class StuckRequestTracker implements RequestTracker {
022:
023: private final Map requests = Collections
024: .synchronizedMap(new IdentityHashMap());
025: private final Monitor monitor;
026:
027: StuckRequestTracker(long sleepTime, long stuckThreshold,
028: boolean dump) {
029: monitor = new Monitor(sleepTime, stuckThreshold, dump);
030: }
031:
032: void start() {
033: monitor.start();
034: }
035:
036: public void begin(HttpServletRequest request) {
037: getRequestDetail(true).begin(request);
038: }
039:
040: public void recordSessionId(TerracottaRequest tr) {
041: getRequestDetail(false).recordSessionId(tr);
042: }
043:
044: public boolean end() {
045: boolean done = getRequestDetail(false).end();
046: if (done) {
047: requests.remove(Thread.currentThread());
048: }
049: return done;
050: }
051:
052: private RequestDetail getRequestDetail(boolean create) {
053: Thread t = Thread.currentThread();
054: RequestDetail rd = (RequestDetail) requests.get(t);
055: if (rd == null) {
056: if (!create) {
057: throw new AssertionError("missing request detail");
058: }
059: rd = new RequestDetail(t);
060: requests.put(t, rd);
061: }
062:
063: return rd;
064: }
065:
066: private static class RequestDetail implements RequestTracker {
067: private final Thread thread;
068: private final List requests = new ArrayList();
069: private final long start = System.currentTimeMillis();
070: private String sid;
071: private int count;
072:
073: RequestDetail(Thread thread) {
074: this .thread = thread;
075: }
076:
077: public synchronized String toString() {
078: return "[" + thread.getName() + "], session " + sid
079: + ", request(s) " + requests;
080: }
081:
082: public synchronized void begin(HttpServletRequest req) {
083: count++;
084:
085: StringBuffer buf = new StringBuffer(req.getRequestURI());
086:
087: String query = req.getQueryString();
088: if (query != null && query.length() > 0) {
089: buf.append('?').append(query);
090: }
091:
092: requests.add(buf.toString());
093: }
094:
095: public synchronized boolean end() {
096: count--;
097: return count == 0;
098: }
099:
100: public synchronized void recordSessionId(TerracottaRequest tr) {
101: if (sid == null) {
102: HttpSession s = tr.getSession(false);
103: if (s != null) {
104: sid = s.getId();
105: }
106: }
107: }
108: }
109:
110: private static class ThreadDump {
111: private static final String[] CMD;
112: private static final boolean hasKillAll;
113:
114: static {
115: String killall = findKillAll();
116:
117: if (killall == null) {
118: hasKillAll = false;
119: CMD = null;
120: } else {
121: hasKillAll = true;
122: CMD = new String[] { killall, "-3", "java" };
123: }
124: }
125:
126: private static String findKillAll() {
127: String[] variants = new String[] { "/usr/bin/killall",
128: "/usr/sbin/killall", "/sbin/killall",
129: "/bin/killall" };
130: for (int i = 0; i < variants.length; i++) {
131: String path = variants[i];
132: File f = new File(path);
133: if (f.exists()) {
134: return path;
135: }
136: }
137: return null;
138: }
139:
140: static void dumpThreads() {
141: if (hasKillAll) {
142: try {
143: Process proc = Runtime.getRuntime().exec(CMD);
144: proc.getOutputStream().close();
145: consume(proc.getInputStream());
146: consume(proc.getErrorStream());
147: proc.waitFor();
148: } catch (Exception e) {
149: e.printStackTrace();
150: }
151: }
152: }
153:
154: private static final byte[] buf = new byte[128];
155:
156: private static void consume(InputStream is) throws IOException {
157: while (is.read(buf, 0, 128) >= 0) {
158: //
159: }
160: }
161:
162: }
163:
164: private class Monitor extends Thread {
165:
166: private final long stuckThreshold;
167: private final long sleepTime;
168: private final boolean dump;
169:
170: Monitor(long sleepTime, long stuckThreshold, boolean dump) {
171: this .sleepTime = sleepTime;
172: this .stuckThreshold = stuckThreshold;
173: this .dump = dump;
174: setDaemon(true);
175: setName("Session Stuck Thread Monitor");
176: }
177:
178: public void run() {
179: while (true) {
180: try {
181: sleep(sleepTime);
182: } catch (InterruptedException e) {
183: continue;
184: }
185:
186: long now = System.currentTimeMillis();
187: Map stuck = new TreeMap();
188: Object[] currentRequests = StuckRequestTracker.this .requests
189: .values().toArray();
190: for (int i = 0, n = currentRequests.length; i < n; i++) {
191: RequestDetail rd = (RequestDetail) currentRequests[i];
192:
193: long time = now - rd.start;
194: if (time > stuckThreshold) {
195: stuck.put(new Long(time), rd);
196: }
197: }
198:
199: if (stuck.size() > 0) {
200: StringBuffer message = new StringBuffer(
201: "Stuck Threads (").append(stuck.size())
202: .append(")\n");
203: Object[] stuckRequests = stuck.entrySet().toArray();
204: for (int i = stuckRequests.length - 1; i >= 0; i--) {
205: Map.Entry entry = (Entry) stuckRequests[i];
206: RequestDetail t = (RequestDetail) entry
207: .getValue();
208: long time = ((Long) entry.getKey()).longValue();
209: message.append(" ").append(time).append(" ")
210: .append(t).append("\n");
211: }
212:
213: System.err.println(message);
214: System.err.flush();
215: if (dump) {
216: ThreadDump.dumpThreads();
217: }
218: }
219: }
220: }
221: }
222:
223: }
|