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