1package gov.nist.core;
2
3import java.util.*;
4
5/**
6 * Thread Auditor class:
7 *   - Provides a mechanism for applications to check the health of internal threads
8 *   - The mechanism is fairly simple:
9 *   - Threads register with the auditor at startup and "ping" the auditor every so often.
10 *   - The application queries the auditor about the health of the system periodically. The
11 *     auditor reports if the threads are healthy or if any of them failed to ping and are
12 *     considered dead or stuck.
13 *   - The main implication for the monitored threads is that they can no longer block
14 *     waiting for an event forever. Any wait() must be implemented with a timeout so that
15 *     the thread can periodically ping the auditor.
16 *
17 * This code is in the public domain.
18 *
19 * @author R. Borba (Natural Convergence)   <br/>
20 * @version 1.2
21 */
22
23public class ThreadAuditor {
24    /// Threads being monitored
25    private Map<Thread,ThreadHandle> threadHandles = new HashMap<Thread,ThreadHandle>();
26
27    /// How often are threads supposed to ping
28    private long pingIntervalInMillisecs = 0;
29
30    /// Internal class, used as a handle by the monitored threads
31    public class ThreadHandle {
32        /// Set to true when the thread pings, periodically reset to false by the auditor
33        private boolean isThreadActive;
34
35        /// Thread being monitored
36        private Thread thread;
37
38        /// Thread auditor monitoring this thread
39        private ThreadAuditor threadAuditor;
40
41        /// Constructor
42        public ThreadHandle(ThreadAuditor aThreadAuditor) {
43            isThreadActive = false;
44            thread = Thread.currentThread();
45            threadAuditor = aThreadAuditor;
46        }
47
48        /// Called by the auditor thread to check the ping status of the thread
49        public boolean isThreadActive() {
50            return isThreadActive;
51        }
52
53        /// Called by the auditor thread to reset the ping status of the thread
54        protected void setThreadActive(boolean value) {
55            isThreadActive = value;
56        }
57
58        /// Return the thread being monitored
59        public Thread getThread() {
60            return thread;
61        }
62
63        // Helper function to allow threads to ping using this handle
64        public void ping() {
65            threadAuditor.ping(this);
66        }
67
68        // Helper function to allow threads to get the ping interval directly from this handle
69        public long getPingIntervalInMillisecs() {
70            return threadAuditor.getPingIntervalInMillisecs();
71        }
72
73        /**
74         * Returns a string representation of the object
75         *
76         * @return a string representation of the object
77         */
78        public String toString() {
79            StringBuffer toString = new StringBuffer()
80                    .append("Thread Name: ").append(thread.getName())
81                    .append(", Alive: ").append(thread.isAlive());
82            return toString.toString();
83        }
84    }
85
86    /// Indicates how often monitored threads are supposed to ping (0 = no thread monitoring)
87    public long getPingIntervalInMillisecs() {
88        return pingIntervalInMillisecs;
89    }
90
91    /// Defines how often monitored threads are supposed to ping
92    public void setPingIntervalInMillisecs(long value) {
93        pingIntervalInMillisecs = value;
94    }
95
96    /// Indicates if the auditing of threads is enabled
97    public boolean isEnabled() {
98        return (pingIntervalInMillisecs > 0);
99    }
100
101    /// Called by a thread that wants to be monitored
102    public synchronized ThreadHandle addCurrentThread() {
103        // Create and return a thread handle but only add it
104        // to the list of monitored threads if the auditor is enabled
105        ThreadHandle threadHandle = new ThreadHandle(this);
106        if (isEnabled()) {
107            threadHandles.put(Thread.currentThread(), threadHandle);
108        }
109        return threadHandle;
110    }
111
112    /// Stops monitoring a given thread
113    public synchronized void removeThread(Thread thread) {
114        threadHandles.remove(thread);
115    }
116
117    /// Called by a monitored thread reporting that it's alive and well
118    public synchronized void ping(ThreadHandle threadHandle) {
119        threadHandle.setThreadActive(true);
120    }
121
122    /// Resets the auditor
123    public synchronized void reset() {
124        threadHandles.clear();
125    }
126
127    /**
128     * Audits the sanity of all threads
129     *
130     * @return An audit report string (multiple lines), or null if all is well
131     */
132    public synchronized String auditThreads() {
133        String auditReport = null;
134        // Map stackTraces = null;
135
136        // Scan all monitored threads looking for non-responsive ones
137        Iterator<ThreadHandle> it = threadHandles.values().iterator();
138        while (it.hasNext()) {
139            ThreadHandle threadHandle = (ThreadHandle) it.next();
140            if (!threadHandle.isThreadActive()) {
141                // Get the non-responsive thread
142                Thread thread = threadHandle.getThread();
143
144                // Update the audit report
145                if (auditReport == null) {
146                    auditReport = "Thread Auditor Report:\n";
147                }
148                auditReport += "   Thread [" + thread.getName() + "] has failed to respond to an audit request.\n";
149
150                /*
151                 * Stack traces are not available with JDK 1.4.
152                 * Feel free to uncomment this block to get a better report if you're using JDK 1.5.
153                 */
154                //  // Get stack traces for all live threads (do this only once per audit)
155                //  if (stackTraces == null) {
156                //      stackTraces = Thread.getAllStackTraces();
157                //  }
158                //
159                //  // Get the stack trace for the non-responsive thread
160                //  StackTraceElement[] stackTraceElements = (StackTraceElement[])stackTraces.get(thread);
161                //  if (stackTraceElements != null && stackTraceElements.length > 0) {
162                //      auditReport += "      Stack trace:\n";
163                //
164                //      for (int i = 0; i < stackTraceElements.length ; i ++ ) {
165                //          StackTraceElement stackTraceElement = stackTraceElements[i];
166                //          auditReport += "         " + stackTraceElement.toString() + "\n";
167                //      }
168                //  } else {
169                //      auditReport += "      Stack trace is not available.\n";
170                //  }
171            }
172
173            // Reset the ping status of the thread
174            threadHandle.setThreadActive(false);
175        }
176        return auditReport;
177    }
178
179    /**
180     * Returns a string representation of the object
181     *
182     * @return a string representation of the object
183     */
184    public synchronized String toString() {
185        String toString = "Thread Auditor - List of monitored threads:\n";
186        Iterator<ThreadHandle> it = threadHandles.values().iterator();
187        while ( it.hasNext()) {
188            ThreadHandle threadHandle = (ThreadHandle)it.next();
189            toString += "   " + threadHandle.toString() + "\n";
190        }
191        return toString;
192    }
193}
194