    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}