/* Copyright (c) 2002 by Oracle Corporation. All Rights Reserved.*/

package oracle.portal.provider.v1;

import java.util.Hashtable;
import java.util.Enumeration;
import java.io.*;
import oracle.webdb.utils.SimpleStringBuffer;

/**
 *  A monitor class that stops any provider threads that are hanging. An object
 *  of this type is created in the servlet init() method and destroyed in the 
 *  servlet destroy method.  
 *
 *  The monitor will periodically check if any threads in the internal list
 *  have elapsed.
 *
 *  NOTE: This class used the deprecated Thread method, stop(). This is a last 
 *  resort measure and is used to ensure that a hanging portlet does not 
 *  consume server resources.
 */
public class ProviderThreadMonitor implements Runnable
{
    static private final long SLEEP_TIME = 30000;   // 30s
    static private final String KILL_MSG_1 = 
               "The provider thread is being stopped having exceeded the " +
               "portlet kill timeout of ";
    static private final String KILL_MSG_2 = 
               "ms. See the performance log entry for more details.";
    private Object mLock = new Object();

    // A list of provider threads indexed by portlet execution trackers
    private Hashtable mThreadList = new Hashtable();

    // A boolean to indicate that the monitor is running
    private boolean mRunning = true;

    private ProviderLog mLog;
    private int mDebugLevel;

    /**
     * Constructor
     *
     * @param log a <code>ProviderLog</code> object for recording log messages
     * @param debugLevel the servlet debug level
     */
    public ProviderThreadMonitor(ProviderLog log, int debugLevel)
    {
        mLog = log;
        mDebugLevel = debugLevel;
    }

    /**
     *  Add a provider thread to the list of threads to be monitored. Each
     *  thread is indexed by a <code>PortletExecutionTracker</code>.
     *
     *  @param t the provider thread that will be stopped if it hangs
     *  @param pet an execution tracker
     */
    public void addThread(PortletExecutionTracker tracker, Thread t)
    {
        synchronized (mLock)
        {
            try
            {
                if (mDebugLevel > 1)
                {
                    SimpleStringBuffer logMsg = new SimpleStringBuffer(30);
                    logMsg.append("Adding provider thread to monitor.");
                    mLog.log(logMsg.toString());
                }

                mThreadList.put(tracker, t);

                // Wake up the main timer now that there is something to 
                // process.
                mLock.notifyAll();
            }
            catch (IllegalMonitorStateException imse) {}
        }
    }

    /**
     *  Remove a provider thread from the list to be monitored. This method is
     *  called from the client code when a provider thread has successfully 
     *  completed execution.
     *
     *  @param pet tracker object used as the index for the thread in the list.
     */
    public void removeThread(PortletExecutionTracker pet)
    {
        synchronized(mLock)
        {
            mThreadList.remove(pet);
            if (mDebugLevel > 1)
            {
                mLog.log("Removing provider thread from monitor.");
            }
        }
    }

    /**
     *  Stop the thread monitor. This should not be called if there are likely
     *  to be threads still in the internal list. If there are, it will check 
     *  them one more time before exiting.
     */
    public void stopMonitor()
    {
        // Set the flag to stop the monitor.
        synchronized (mLock)
        {
            mRunning = false;

            // Wake up the main thread so that it can gracefully exit.
            mLock.notifyAll();
        }
    }

    /**
     *  The main method for the provider monitor. When there are provider 
     *  threads to be monitored it will check each one in the list every 30 
     *  seconds to see if it needs to be stopped.
     */
    public void run()
    {   
        if (mDebugLevel > 1)
        {
            mLog.log("Starting thread monitor run().");
        }
        while (mRunning)
        {            
            // Wait for a thread to be added to the internal list.
            synchronized (mLock)
            {
                if (mThreadList.isEmpty())
                {
                    try
                    {
                        if (mDebugLevel > 1)
                        {
                            mLog.log("Waiting for provider thread to be " +
                                     " added to the monitor.");
                        }
                        mLock.wait();
                    }                    
                    catch (InterruptedException ie) {}
                }
            }

            // At this stage, either:
            // - there is now at least one provider thread to check on, or
            // - the monitor has been stopped and this method will exit at 
            // the end of this loop. There should not be any threads in the 
            // internal list at this point since the monitor should only be 
            // stopped when the provider is being closed down. If there is, 
            // there is no harm in checking them once more.

            // Sleep for a fixed period of time
            try
            {
                Thread.sleep(SLEEP_TIME);
            }
            catch (InterruptedException ie) {}

            // Check each key in the internal list to see if its associated 
            // thread needs to be stopped. Synchronise on object lock so no
            // provider threads can be added or removed during this check.

            synchronized (mLock)
            {
                if (mDebugLevel > 1)
                {
                    mLog.log("Monitor checking the internal thread list.");
                }

                Enumeration enum = mThreadList.keys();
                while (enum.hasMoreElements())
                {
                    PortletExecutionTracker pet = 
                                   (PortletExecutionTracker)enum.nextElement();
                    if (pet.hasPortletKillTimeoutElapsed())
                    {
                        // Get the associated thread, remove it from the 
                        // internal list and kill it.
                        Thread t = (Thread)mThreadList.get(pet);
                        mLog.log(getLogMsg(pet.getPortletKillTimeout()));
                        mThreadList.remove(pet);
                        t.stop();
                    }
                }
            }
        }
        if (mDebugLevel > 1)
        {
            mLog.log("Thread monitor exiting.");
        }
    }
    
    private String getLogMsg(long timeout)
    {
        SimpleStringBuffer msg = new SimpleStringBuffer(50);
        msg.append(KILL_MSG_1);
        msg.append(Long.toString(timeout));
        msg.append(KILL_MSG_2);
        return msg.toString();
    }
}




