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