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