diff --git a/OpenSim/Framework/Util.cs b/OpenSim/Framework/Util.cs
index c2c96981bb..ed94c6f90d 100644
--- a/OpenSim/Framework/Util.cs
+++ b/OpenSim/Framework/Util.cs
@@ -116,6 +116,16 @@ namespace OpenSim.Framework
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+ ///
+ /// Log every invocation of a thread using the threadpool.
+ ///
+ public static bool LogThreadPool { get; set; }
+
+ static Util()
+ {
+ LogThreadPool = false;
+ }
+
private static uint nextXferID = 5000;
private static Random randomClass = new Random();
@@ -1887,10 +1897,17 @@ namespace OpenSim.Framework
}
}
+
+ private static long nextThreadFuncNum = 0;
+ private static long numQueuedThreadFuncs = 0;
+ private static long numRunningThreadFuncs = 0;
+
public static void FireAndForget(System.Threading.WaitCallback callback, object obj)
{
WaitCallback realCallback;
+ long threadFuncNum = Interlocked.Increment(ref nextThreadFuncNum);
+
if (FireAndForgetMethod == FireAndForgetMethod.RegressionTest)
{
// If we're running regression tests, then we want any exceptions to rise up to the test code.
@@ -1903,49 +1920,117 @@ namespace OpenSim.Framework
// for decimals places but is read by a culture that treats commas as number seperators.
realCallback = o =>
{
- Culture.SetCurrentCulture();
+ long numQueued1 = Interlocked.Decrement(ref numQueuedThreadFuncs);
+ long numRunning1 = Interlocked.Increment(ref numRunningThreadFuncs);
try
{
+ if (LogThreadPool)
+ m_log.DebugFormat("Run threadfunc {0} (Queued {1}, Running {2})", threadFuncNum, numQueued1, numRunning1);
+
+ Culture.SetCurrentCulture();
+
callback(o);
}
catch (Exception e)
{
- m_log.ErrorFormat(
- "[UTIL]: Continuing after async_call_method thread terminated with exception {0}{1}",
- e.Message, e.StackTrace);
+ m_log.Error("[UTIL]: FireAndForget thread terminated with error ", e);
+ }
+ finally
+ {
+ Interlocked.Decrement(ref numRunningThreadFuncs);
+ if (LogThreadPool)
+ m_log.Debug("Exit threadfunc " + threadFuncNum);
}
};
}
- switch (FireAndForgetMethod)
+ long numQueued = Interlocked.Increment(ref numQueuedThreadFuncs);
+ try
{
- case FireAndForgetMethod.RegressionTest:
- case FireAndForgetMethod.None:
- realCallback.Invoke(obj);
- break;
- case FireAndForgetMethod.UnsafeQueueUserWorkItem:
- ThreadPool.UnsafeQueueUserWorkItem(realCallback, obj);
- break;
- case FireAndForgetMethod.QueueUserWorkItem:
- ThreadPool.QueueUserWorkItem(realCallback, obj);
- break;
- case FireAndForgetMethod.BeginInvoke:
- FireAndForgetWrapper wrapper = FireAndForgetWrapper.Instance;
- wrapper.FireAndForget(realCallback, obj);
- break;
- case FireAndForgetMethod.SmartThreadPool:
- if (m_ThreadPool == null)
- InitThreadPool(2, 15);
- m_ThreadPool.QueueWorkItem((cb, o) => cb(o), realCallback, obj);
- break;
- case FireAndForgetMethod.Thread:
- Thread thread = new Thread(delegate(object o) { realCallback(o); });
- thread.Start(obj);
- break;
- default:
- throw new NotImplementedException();
+ if (LogThreadPool)
+ m_log.DebugFormat("Queue threadfunc {0} (Queued {1}, Running {2}) {3}",
+ threadFuncNum, numQueued, numRunningThreadFuncs, GetFireAndForgetStackTrace(true));
+
+ switch (FireAndForgetMethod)
+ {
+ case FireAndForgetMethod.RegressionTest:
+ case FireAndForgetMethod.None:
+ realCallback.Invoke(obj);
+ break;
+ case FireAndForgetMethod.UnsafeQueueUserWorkItem:
+ ThreadPool.UnsafeQueueUserWorkItem(realCallback, obj);
+ break;
+ case FireAndForgetMethod.QueueUserWorkItem:
+ ThreadPool.QueueUserWorkItem(realCallback, obj);
+ break;
+ case FireAndForgetMethod.BeginInvoke:
+ FireAndForgetWrapper wrapper = FireAndForgetWrapper.Instance;
+ wrapper.FireAndForget(realCallback, obj);
+ break;
+ case FireAndForgetMethod.SmartThreadPool:
+ if (m_ThreadPool == null)
+ InitThreadPool(2, 15);
+ m_ThreadPool.QueueWorkItem((cb, o) => cb(o), realCallback, obj);
+ break;
+ case FireAndForgetMethod.Thread:
+ Thread thread = new Thread(delegate(object o) { realCallback(o); });
+ thread.Start(obj);
+ break;
+ default:
+ throw new NotImplementedException();
+ }
}
+ catch (Exception)
+ {
+ Interlocked.Decrement(ref numQueuedThreadFuncs);
+ throw;
+ }
+ }
+
+ ///
+ /// Returns a stack trace for a thread added using FireAndForget().
+ ///
+ /// True: return full stack trace; False: return only the first frame
+ private static string GetFireAndForgetStackTrace(bool full)
+ {
+ string src = Environment.StackTrace;
+ string[] lines = src.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
+
+ StringBuilder dest = new StringBuilder(src.Length);
+
+ bool started = false;
+ bool first = true;
+
+ for (int i = 0; i < lines.Length; i++)
+ {
+ string line = lines[i];
+
+ if (!started)
+ {
+ // Skip the initial stack frames, because they're of no interest for debugging
+ if (line.Contains("StackTrace") || line.Contains("FireAndForget"))
+ continue;
+ started = true;
+ }
+
+ if (first)
+ {
+ line = line.TrimStart();
+ first = false;
+ }
+
+ bool last = (i == lines.Length - 1) || !full;
+ if (last)
+ dest.Append(line);
+ else
+ dest.AppendLine(line);
+
+ if (!full)
+ break;
+ }
+
+ return dest.ToString();
}
///