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    }