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