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