/* Copyright 1996-2008 Ariba, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. $Id: //ariba/platform/util/core/ariba/util/core/ThreadDebugState.java#10 $ */ package ariba.util.core; /** This class maintains debug state for the current thread. The class is intended to allow callers to add and remove arbitrary debug information as processing of a transation continues. This way when there is an error or a hang this information can be retrieved. It is very cheap to add and remove items as there is no synchronization. All static methods are safe to call. <p> Non static calls should be made with extreme caution - as that code will have to be able to handle all types of exceptions due to possible synchronization issues. <p> The only non static methods are for reading data exclusivly. This way a thread trying to look at the debug state of another thread will not be able to, under any circumstances, damage the thread it is looking at. Although it certainly may trigger exceptions in its own thread if race conditions are hit. @aribaapi documented */ public class ThreadDebugState { public static boolean threadStateEnabled = true; private static final ThreadDebugKey ThreadNameKey = new ThreadDebugKey("ThreadName"); /* private static int threadLocalAccessCount = 0; private static final GrowOnlyHashtable statAggrigation = new GrowOnlyHashtable(); private static void updateStats (ThreadDebugKey key) { Integer i = (Integer)statAggrigation.get(key); if (i == null) { i = Util.getInteger(0); } statAggrigation.put(key, Util.getInteger(i.intValue()+1)); } */ /** The key specified will overwrite any other date stored with a pointer equal key. The value reference will be saved, but not used, until needed. This is to allow operations to be significantly faster. Callers of this method should not do much computation or memory allocation to create the value object. It is ok if the value object's toString() is expensive as that will be called rarely. It is also ok if the value object changes internal contents during operation. <p> If <code>value</code> implements DebugState the debugState() method will be called instead of toString() on <code>value</code> when it is needed. This should happen rarely, so this method implementation may also be expensive without hurting performance of the overall application. <p> The toString() method of the value object must be be made safe for other threads to call, even if the value is being modified at the time. Throwing exceptions from the toString if the thread gets unlucky is acceptable, but data corruption is not. @param key the ThreadDebugKey to use for this object when printed. <code>key</code> may not be null. @param value the object to print out as part of the state. <code>value</code> may be null. @aribaapi documented @see DebugState */ public static void set (ThreadDebugKey key, Object value) { if (!threadStateEnabled) { return; } //updateStats(key); StateMap htable = getThisThreadHashtable(); // make work for the caller be as little as possible and // handle null values - since those are determined at // runtime unlike the key... if (value == null) { value = NullObject; } htable.put(key, value); } /** Retrieve a value for a specified key. The key must be pointer equal to the key used to set the value. @aribaapi ariba */ public static Object get (ThreadDebugKey key) { if (!threadStateEnabled) { return null; } //updateStats(key); StateMap htable = getThisThreadHashtable(); return internalGet(key, htable); } /** Remove a value for a specified key. The key must be pointer equal to the key used to set the value. The old value will be returned. @param key the ThreadDebugKey to delete from the current application state. @return the old value that was saved. @aribaapi documented */ public static Object remove (ThreadDebugKey key) { if (!threadStateEnabled) { return null; } //updateStats(key); Hashtable htable = getThisThreadHashtable(); Object oldValue = htable.remove(key); if (oldValue == NullObject) { oldValue = null; } return oldValue; } /** Remove all values that are currently stored. @aribaapi ariba */ public static void clear () { ProgressMonitor.internalClear(); if (!threadStateEnabled) { return; } StateMap state = getThisThreadHashtable(); PerformanceState.internalClear(state, true); Object threadName = state.get(ThreadNameKey); // clear the hashtable, but restore the thread name key state.clear(); state.put(ThreadNameKey, threadName); // we force clear of the PerformanceState as well } private static Boolean localTrue = Boolean.TRUE; private static Boolean localFalse = Boolean.FALSE; private static final State reentryCheck = StateFactory.createState(); /** Return a string representing the contents of the state. @return The current state of this thread as a string. If there are errors in creating the string, the returned string will contain the text of the exception. @aribaapi documented */ public static String makeString () { if (!threadStateEnabled) { return ""; } if (localTrue == reentryCheck.get()) { // Add a check here so that if the protectedToString // call causes an assert to fail we do not end up with // a stack overflow. return "do not recursivly call makeString"; } try { reentryCheck.set(localTrue); return protectedToString(getThisThreadHashtable()); } finally { reentryCheck.set(localFalse); } } /** This is the method to call to print state from a different thread. To print your own thread's state, call the static makeString() method instead. This method will catch any exceptions that are thrown during processing and return them as the string. @aribaapi ariba */ public String toString () { return protectedToString(someRandomThreadState); } /** This is an unsafe method. The caller is required to protect against exceptions that occur as a result of synchronization issues. It will attempt to perform an equivilent of a get. @aribaapi private */ public Object unsafeGet (ThreadDebugKey key) { if (!threadStateEnabled) { return null; } Assert.that(someRandomThreadState != null, "Do not call unsafe methods unless you know what " + "you are doing and deeply understand this code"); return internalGet(key, someRandomThreadState); } private static final ThreadDebugState EmptyThreadDebugState = new ThreadDebugState(new StateMap()); /** This method is thread safe, but is the first step towards getting non safe data. It will return a new ThreadDebugState object that will allow unsafe calls on it from a different thread. That object will, from then on, be able to see state that is set on this thread - even state set after this call. @aribaapi ariba */ public static ThreadDebugState getUnsafeThreadDebugState () { if (!threadStateEnabled) { return EmptyThreadDebugState; } return new ThreadDebugState(getThisThreadHashtable()); } private static Object internalGet (ThreadDebugKey key, StateMap htable) { Object value = htable.get(key); if (value == NullObject) { value = null; } return value; } protected static String protectedToString (StateMap someRandomThreadState) { try { return unprotectedToString(someRandomThreadState); } // don't want debugging code to bring down the system. catch (RuntimeException ex) { return Fmt.S("Unable to print state: %s", SystemUtil.stackTrace(ex)); } } private static String unprotectedToString (StateMap stateInfo) { if (!threadStateEnabled) { return ""; } // Note: We MUST call toString() explicitly here to work around a glitch // where FormattingSerializer skips calling toString() on subclasses of Map. if (stateInfo._performanceState != null) { String perfString = stateInfo._performanceState.toString(); return Fmt.S("%s, PerformanceState: %s", stateInfo, perfString); } return stateInfo.toString(); } private static final State state = StateFactory.createState(); private static final Object NullObject = new String("NullValue"); // OK private final StateMap someRandomThreadState; private ThreadDebugState (StateMap someRandomThreadState) { this.someRandomThreadState = someRandomThreadState; } protected static StateMap getThisThreadHashtable () { StateMap htable = (StateMap)state.get(); if (htable == null) { htable = new StateMap(); state.set(htable); // make sure this is after the state is set so there // are no recursion problems. set(ThreadNameKey, Thread.currentThread().toString()); } /* threadLocalAccessCount++; if (threadLocalAccessCount % 100 == 0) { Util.out().println("At " + new java.util.Date() + " there are " + threadLocalAccessCount + " calls to thread local"); Util.out().println("access pattern: " + statAggrigation); } */ return htable; } /** This is just a convinence for callers who wish to create Hashtables or Lists that contain objects that may be null. ThreadDebugState itself can handle null values */ public static Object nullSafeObject (Object o) { if (o == null) { return "null"; } return o; } /** * @aribaapi ariba */ public static class StateMap extends EqHashtable { protected PerformanceState.Stats _performanceState; } }