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