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