001: /*
002: JSPWiki - a JSP-based WikiWiki clone.
003:
004: Copyright (C) 2005 Janne Jalkanen (Janne.Jalkanen@iki.fi)
005:
006: This program is free software; you can redistribute it and/or modify
007: it under the terms of the GNU Lesser General Public License as published by
008: the Free Software Foundation; either version 2.1 of the License, or
009: (at your option) any later version.
010:
011: This program is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU Lesser General Public License for more details.
015:
016: You should have received a copy of the GNU Lesser General Public License
017: along with this program; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package com.ecyrd.jspwiki.util;
021:
022: import java.lang.ref.WeakReference;
023: import java.util.*;
024:
025: import org.apache.log4j.Logger;
026:
027: import com.ecyrd.jspwiki.WikiEngine;
028:
029: /**
030: * WatchDog is a general system watchdog. You can attach any Watchable
031: * or a Thread object to it, and it will notify you if a timeout has been
032: * exceeded.
033: * <p>
034: * The notification of the timeouts is done from a separate WatchDog thread,
035: * of which there is one per watched thread. This Thread is named 'WatchDog for
036: * XXX', where XXX is your Thread name.
037: * <p>
038: * The suggested method of obtaining a WatchDog is via the static factory
039: * method, since it will return you the correct watchdog for the current
040: * thread. However, we do not prevent you from creating your own watchdogs
041: * either.
042: * <p>
043: * If you create a WatchDog for a Thread, the WatchDog will figure out when
044: * the Thread is dead, and will stop itself accordingly. However, this object
045: * is not automatically released, so you might want to check it out after a while.
046: *
047: * @author Janne Jalkanen
048: * @since 2.4.92
049: */
050: public final class WatchDog {
051: private Watchable m_watchable;
052: private Stack m_stateStack = new Stack();
053: private boolean m_enabled = true;
054: private WikiEngine m_engine;
055:
056: private static Logger log = Logger.getLogger(WatchDog.class
057: .getName());
058:
059: private static HashMap c_kennel = new HashMap();
060: private static WikiBackgroundThread c_watcherThread;
061:
062: /**
063: * Returns the current watchdog for the current thread. This
064: * is the preferred method of getting you a Watchdog, since it
065: * keeps an internal list of Watchdogs for you so that there
066: * won't be more than one watchdog per thread.
067: *
068: * @param engine The WikiEngine to which the Watchdog should
069: * be bonded to.
070: * @return A usable WatchDog object.
071: */
072: public static WatchDog getCurrentWatchDog(WikiEngine engine) {
073: Thread t = Thread.currentThread();
074: WatchDog wd = null;
075:
076: WeakReference w = (WeakReference) c_kennel.get(new Integer(t
077: .hashCode()));
078:
079: if (w != null)
080: wd = (WatchDog) w.get();
081:
082: if (w == null || wd == null) {
083: wd = new WatchDog(engine, t);
084: w = new WeakReference(wd);
085:
086: synchronized (c_kennel) {
087: c_kennel.put(new Integer(t.hashCode()), w);
088: }
089: }
090:
091: return wd;
092: }
093:
094: /**
095: * Creates a new WatchDog for a Watchable.
096: *
097: * @param engine The WikiEngine.
098: * @param watch A Watchable object.
099: */
100: public WatchDog(WikiEngine engine, Watchable watch) {
101: m_engine = engine;
102: m_watchable = watch;
103:
104: synchronized (this .getClass()) {
105: if (c_watcherThread == null) {
106: c_watcherThread = new WatchDogThread(engine);
107:
108: c_watcherThread.start();
109: }
110: }
111: }
112:
113: /**
114: * Creates a new WatchDog for a Thread. The Thread is wrapped
115: * in a Watchable wrapper for this purpose.
116: *
117: * @param engine The WikiEngine
118: * @param thread A Thread for watching.
119: */
120: public WatchDog(WikiEngine engine, Thread thread) {
121: this (engine, new ThreadWrapper(thread));
122: }
123:
124: /**
125: * Hopefully finalizes this properly. This is rather untested
126: * for now...
127: */
128: private static void scrub() {
129: //
130: // During finalization, the object may already be cleared (depending
131: // on the finalization order). Therefore, it's possible that this
132: // method is called from another thread after the WatchDog itself
133: // has been cleared.
134: //
135: if (c_kennel == null)
136: return;
137:
138: synchronized (c_kennel) {
139: for (Iterator i = c_kennel.entrySet().iterator(); i
140: .hasNext();) {
141: Map.Entry e = (Map.Entry) i.next();
142:
143: WeakReference w = (WeakReference) e.getValue();
144:
145: //
146: // Remove expired as well
147: //
148: if (w.get() == null) {
149: c_kennel.remove(e.getKey());
150: scrub();
151: break;
152: }
153: }
154: }
155: }
156:
157: /**
158: * Can be used to enable the WatchDog. Will cause a new
159: * Thread to be created, if none was existing previously.
160: *
161: */
162: public void enable() {
163: synchronized (this .getClass()) {
164: if (!m_enabled) {
165: m_enabled = true;
166: c_watcherThread = new WatchDogThread(m_engine);
167: c_watcherThread.start();
168: }
169: }
170: }
171:
172: /**
173: * Is used to disable a WatchDog. The watchdog thread is
174: * shut down and resources released.
175: *
176: */
177: public void disable() {
178: synchronized (this .getClass()) {
179: if (m_enabled) {
180: m_enabled = false;
181: c_watcherThread.shutdown();
182: c_watcherThread = null;
183: }
184: }
185: }
186:
187: /**
188: * Enters a watched state with no expectation of the expected completion time.
189: * In practice this method is used when you have no idea, but would like to figure
190: * out, e.g. via debugging, where exactly your Watchable is.
191: *
192: * @param state A free-form string description of your state.
193: */
194: public void enterState(String state) {
195: enterState(state, Integer.MAX_VALUE);
196: }
197:
198: /**
199: * Enters a watched state which has an expected completion time. This is the
200: * main method for using the WatchDog. For example:
201: *
202: * <code>
203: * WatchDog w = m_engine.getCurrentWatchDog();
204: * w.enterState("Processing Foobar", 60);
205: * foobar();
206: * w.exitState();
207: * </code>
208: *
209: * If the call to foobar() takes more than 60 seconds, you will receive an
210: * ERROR in the log stream.
211: *
212: * @param state A free-form string description of the state
213: * @param expectedCompletionTime The timeout in seconds.
214: */
215: public void enterState(String state, int expectedCompletionTime) {
216: if (log.isDebugEnabled())
217: log.debug(m_watchable.getName() + ": Entering state "
218: + state + ", expected completion in "
219: + expectedCompletionTime + " s");
220:
221: synchronized (m_stateStack) {
222: State st = new State(state, expectedCompletionTime);
223:
224: m_stateStack.push(st);
225: }
226: }
227:
228: /**
229: * Exits a state entered with enterState(). This method does not check
230: * that the state is correct, it'll just pop out whatever is on the top
231: * of the state stack.
232: *
233: */
234: public void exitState() {
235: exitState(null);
236: }
237:
238: /**
239: * Exits a particular state entered with enterState(). The state is
240: * checked against the current state, and if they do not match, an error
241: * is flagged.
242: *
243: * @param state The state you wish to exit.
244: */
245: public void exitState(String state) {
246: try {
247: synchronized (m_stateStack) {
248: State st = (State) m_stateStack.peek();
249:
250: if (state == null || st.getState().equals(state)) {
251: m_stateStack.pop();
252:
253: if (log.isDebugEnabled())
254: log.debug(m_watchable.getName()
255: + ": Exiting state " + st.getState());
256: } else {
257: // FIXME: should actually go and fix things for that
258: log.error("exitState() called before enterState()");
259: }
260: }
261: } catch (EmptyStackException e) {
262: log.error("Stack is empty!", e);
263: }
264:
265: }
266:
267: private void check() {
268: if (log.isDebugEnabled())
269: log.debug("Checking watchdog '" + m_watchable.getName()
270: + "'");
271:
272: synchronized (m_stateStack) {
273: try {
274: WatchDog.State st = (WatchDog.State) m_stateStack
275: .peek();
276:
277: long now = System.currentTimeMillis();
278:
279: if (now > st.getExpiryTime()) {
280: log.info("Watchable '" + m_watchable.getName()
281: + "' exceeded timeout in state '"
282: + st.getState() + "' by "
283: + (now - st.getExpiryTime()) / 1000
284: + " seconds");
285:
286: m_watchable.timeoutExceeded(st.getState());
287: }
288: } catch (EmptyStackException e) {
289: // FIXME: Do something?
290: }
291: }
292: }
293:
294: /**
295: * Strictly for debugging/informative purposes.
296: *
297: * @return Random ramblings.
298: */
299: public String toString() {
300: synchronized (m_stateStack) {
301: String state = "Idle";
302:
303: try {
304: State st = (State) m_stateStack.peek();
305: state = st.getState();
306: } catch (EmptyStackException e) {
307: }
308: return "WatchDog state=" + state;
309: }
310: }
311:
312: /**
313: * This is the chief watchdog thread.
314: *
315: * @author jalkanen
316: *
317: */
318: private static class WatchDogThread extends WikiBackgroundThread {
319: /** How often the watchdog thread should wake up (in seconds) */
320: private static final int CHECK_INTERVAL = 30;
321:
322: public WatchDogThread(WikiEngine engine) {
323: super (engine, CHECK_INTERVAL);
324: setName("WatchDog for '" + engine.getApplicationName()
325: + "'");
326: }
327:
328: public void startupTask() {
329: }
330:
331: public void shutdownTask() {
332: WatchDog.scrub();
333: }
334:
335: /**
336: * Checks if the watchable is alive, and if it is, checks if
337: * the stack is finished.
338: *
339: * If the watchable has been deleted in the mean time, will
340: * simply shut down itself.
341: */
342: public void backgroundTask() throws Exception {
343: synchronized (c_kennel) {
344: for (Iterator i = c_kennel.entrySet().iterator(); i
345: .hasNext();) {
346: Map.Entry entry = (Map.Entry) i.next();
347:
348: WeakReference wr = (WeakReference) entry.getValue();
349:
350: WatchDog w = (WatchDog) wr.get();
351:
352: if (w != null) {
353: if (w.m_watchable != null
354: && w.m_watchable.isAlive()) {
355: w.check();
356: } else {
357: c_kennel.remove(entry.getKey());
358: break;
359: }
360: }
361: } // for
362: } // synchronized
363:
364: WatchDog.scrub();
365: }
366: }
367:
368: /**
369: * A class which just stores the state in our State stack.
370: *
371: * @author Janne Jalkanen
372: */
373: private static class State {
374: protected String m_state;
375: protected long m_enterTime;
376: protected long m_expiryTime;
377:
378: protected State(String state, int expiry) {
379: m_state = state;
380: m_enterTime = System.currentTimeMillis();
381: m_expiryTime = m_enterTime + (expiry * 1000L);
382: }
383:
384: protected String getState() {
385: return m_state;
386: }
387:
388: protected long getExpiryTime() {
389: return m_expiryTime;
390: }
391: }
392:
393: /**
394: * This class wraps a Thread so that it can become Watchable.
395: *
396: * @author Janne Jalkanen
397: *
398: */
399: private static class ThreadWrapper implements Watchable {
400: private Thread m_thread;
401:
402: public ThreadWrapper(Thread thread) {
403: m_thread = thread;
404: }
405:
406: public void timeoutExceeded(String state) {
407: // TODO: Figure out something sane to do here.
408: }
409:
410: public String getName() {
411: return m_thread.getName();
412: }
413:
414: public boolean isAlive() {
415: return m_thread.isAlive();
416: }
417: }
418: }
|