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