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.EmptyStackException;
023import java.util.HashMap;
024import java.util.Iterator;
025import java.util.Map;
026import java.util.Set;
027import java.util.Stack;
028
029import 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 */
052public 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}