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.isEmpty() ) { 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 LOG.debug( "{}: Entering state {}, expected completion in {} s", m_watchable.getName(), state, expectedCompletionTime ); 189 synchronized( m_stateStack ) { 190 final State st = new State( state, expectedCompletionTime ); 191 m_stateStack.push( st ); 192 } 193 } 194 195 /** 196 * Exits a state entered with enterState(). This method does not check that the state is correct, it'll just 197 * pop out whatever is on the top of the state stack. 198 */ 199 public void exitState() { 200 exitState( null ); 201 } 202 203 /** 204 * Exits a particular state entered with enterState(). The state is checked against the current state, and if 205 * they do not match, an error is flagged. 206 * 207 * @param state The state you wish to exit. 208 */ 209 public void exitState( final String state ) { 210 if( !m_stateStack.empty() ) { 211 synchronized( m_stateStack ) { 212 final State st = m_stateStack.peek(); 213 if( state == null || st.getState().equals( state ) ) { 214 m_stateStack.pop(); 215 216 LOG.debug( "{}: Exiting state {}", m_watchable.getName(), st.getState() ); 217 } else { 218 // FIXME: should actually go and fix things for that 219 LOG.error( "exitState() called before enterState()" ); 220 } 221 } 222 } else { 223 LOG.warn( "Stack for " + m_watchable.getName() + " is empty!" ); 224 } 225 } 226 227 /** 228 * helper to see if the associated stateStack is not empty. 229 * 230 * @return {@code true} if not empty, {@code false} otherwise. 231 */ 232 public boolean isStateStackNotEmpty() { 233 return !m_stateStack.isEmpty(); 234 } 235 236 /** 237 * helper to see if the associated watchable is alive. 238 * 239 * @return {@code true} if it's alive, {@code false} otherwise. 240 */ 241 public boolean isWatchableAlive() { 242 return m_watchable != null && m_watchable.isAlive(); 243 } 244 245 private void check() { 246 LOG.debug( "Checking watchdog '{}'", m_watchable.getName() ); 247 248 synchronized( m_stateStack ) { 249 if( !m_stateStack.empty() ) { 250 final State st = m_stateStack.peek(); 251 final long now = System.currentTimeMillis(); 252 253 if( now > st.getExpiryTime() ) { 254 LOG.info( "Watchable '" + m_watchable.getName() + "' exceeded timeout in state '" + st.getState() + 255 "' by " + (now - st.getExpiryTime()) / 1000 + " seconds" + 256 ( LOG.isDebugEnabled() ? "" : "Enable DEBUG-level logging to see stack traces." ) ); 257 dumpStackTraceForWatchable(); 258 259 m_watchable.timeoutExceeded( st.getState() ); 260 } 261 } else { 262 LOG.warn( "Stack for " + m_watchable.getName() + " is empty!" ); 263 } 264 } 265 } 266 267 /** 268 * Dumps the stack traces as DEBUG level events. 269 */ 270 private void dumpStackTraceForWatchable() { 271 if( !LOG.isDebugEnabled() ) { 272 return; 273 } 274 275 final Map< Thread, StackTraceElement[] > stackTraces = Thread.getAllStackTraces(); 276 final Set< Thread > threads = stackTraces.keySet(); 277 final Iterator< Thread > threadIterator = threads.iterator(); 278 final StringBuilder stacktrace = new StringBuilder(); 279 280 while ( threadIterator.hasNext() ) { 281 final Thread t = threadIterator.next(); 282 if( t.getName().equals( m_watchable.getName() ) ) { 283 if( t.getName().equals( m_watchable.getName() ) ) { 284 stacktrace.append( "dumping stacktrace for too long running thread : " ).append( t ); 285 } else { 286 stacktrace.append( "dumping stacktrace for other running thread : " ).append( t ); 287 } 288 final StackTraceElement[] ste = stackTraces.get( t ); 289 for( final StackTraceElement stackTraceElement : ste ) { 290 stacktrace.append( "\n" ).append( stackTraceElement ); 291 } 292 } 293 } 294 295 LOG.debug( stacktrace.toString() ); 296 } 297 298 /** 299 * Strictly for debugging/informative purposes. 300 * 301 * @return Random ramblings. 302 */ 303 @Override 304 public String toString() { 305 synchronized( m_stateStack ) { 306 String state = "Idle"; 307 308 if( !m_stateStack.empty() ) { 309 final State st = m_stateStack.peek(); 310 state = st.getState(); 311 } 312 return "WatchDog state=" + state; 313 } 314 } 315 316 /** 317 * This is the chief watchdog thread. 318 */ 319 private static class WatchDogThread extends WikiBackgroundThread { 320 /** How often the watchdog thread should wake up (in seconds) */ 321 private static final int CHECK_INTERVAL = 30; 322 323 public WatchDogThread( final Engine engine ) { 324 super( engine, CHECK_INTERVAL ); 325 setName( "WatchDog for '" + engine.getApplicationName() + "'" ); 326 } 327 328 @Override 329 public void startupTask() { 330 } 331 332 @Override 333 public void shutdownTask() { 334 WatchDog.scrub(); 335 } 336 337 /** 338 * Checks if the watchable is alive, and if it is, checks if the stack is finished. 339 * 340 * If the watchable has been deleted in the meantime, will simply shut down itself. 341 */ 342 @Override 343 public void backgroundTask() { 344 if( c_kennel.isEmpty() ) { 345 return; 346 } 347 348 for( final Map.Entry< Integer, WeakReference< WatchDog > > entry : c_kennel.entrySet() ) { 349 final WeakReference< WatchDog > wr = entry.getValue(); 350 final WatchDog w = wr.get(); 351 if( w != null ) { 352 if( w.isWatchableAlive() && w.isStateStackNotEmpty() ) { 353 w.check(); 354 } else { 355 c_kennel.remove( entry.getKey() ); 356 break; 357 } 358 } 359 } 360 361 WatchDog.scrub(); 362 } 363 364 } 365 366 /** 367 * A class which just stores the state in our State stack. 368 */ 369 private static class State { 370 371 protected final String m_state; 372 protected final long m_enterTime; 373 protected final long m_expiryTime; 374 375 protected State( final String state, final int expiry ) { 376 m_state = state; 377 m_enterTime = System.currentTimeMillis(); 378 m_expiryTime = m_enterTime + ( expiry * 1_000L ); 379 } 380 381 protected String getState() { 382 return m_state; 383 } 384 385 protected long getExpiryTime() { 386 return m_expiryTime; 387 } 388 389 } 390 391 /** 392 * This class wraps a Thread so that it can become Watchable. 393 */ 394 private static class ThreadWrapper implements Watchable { 395 private final Thread m_thread; 396 397 public ThreadWrapper( final Thread thread ) { 398 m_thread = thread; 399 } 400 401 @Override 402 public void timeoutExceeded( final String state ) { 403 // TODO: Figure out something sane to do here. 404 } 405 406 @Override 407 public String getName() { 408 return m_thread.getName(); 409 } 410 411 @Override 412 public boolean isAlive() { 413 return m_thread.isAlive(); 414 } 415 } 416 417}