diff options
-rw-r--r-- | OpenSim/Framework/Util.cs | 280 |
1 files changed, 269 insertions, 11 deletions
diff --git a/OpenSim/Framework/Util.cs b/OpenSim/Framework/Util.cs index 6be1722..2de596e 100644 --- a/OpenSim/Framework/Util.cs +++ b/OpenSim/Framework/Util.cs | |||
@@ -51,6 +51,7 @@ using Nwc.XmlRpc; | |||
51 | using OpenMetaverse; | 51 | using OpenMetaverse; |
52 | using OpenMetaverse.StructuredData; | 52 | using OpenMetaverse.StructuredData; |
53 | using Amib.Threading; | 53 | using Amib.Threading; |
54 | using System.Collections.Concurrent; | ||
54 | 55 | ||
55 | namespace OpenSim.Framework | 56 | namespace OpenSim.Framework |
56 | { | 57 | { |
@@ -148,6 +149,9 @@ namespace OpenSim.Framework | |||
148 | /// </summary> | 149 | /// </summary> |
149 | private static SmartThreadPool m_ThreadPool; | 150 | private static SmartThreadPool m_ThreadPool; |
150 | 151 | ||
152 | // Watchdog timer that aborts threads that have timed-out | ||
153 | private static Timer m_threadPoolWatchdog; | ||
154 | |||
151 | // Unix-epoch starts at January 1st 1970, 00:00:00 UTC. And all our times in the server are (or at least should be) in UTC. | 155 | // Unix-epoch starts at January 1st 1970, 00:00:00 UTC. And all our times in the server are (or at least should be) in UTC. |
152 | public static readonly DateTime UnixEpoch = | 156 | public static readonly DateTime UnixEpoch = |
153 | DateTime.ParseExact("1970-01-01 00:00:00 +0", "yyyy-MM-dd hh:mm:ss z", DateTimeFormatInfo.InvariantInfo).ToUniversalTime(); | 157 | DateTime.ParseExact("1970-01-01 00:00:00 +0", "yyyy-MM-dd hh:mm:ss z", DateTimeFormatInfo.InvariantInfo).ToUniversalTime(); |
@@ -1883,6 +1887,7 @@ namespace OpenSim.Framework | |||
1883 | startInfo.MinWorkerThreads = minThreads; | 1887 | startInfo.MinWorkerThreads = minThreads; |
1884 | 1888 | ||
1885 | m_ThreadPool = new SmartThreadPool(startInfo); | 1889 | m_ThreadPool = new SmartThreadPool(startInfo); |
1890 | m_threadPoolWatchdog = new Timer(ThreadPoolWatchdog, null, 0, 1000); | ||
1886 | } | 1891 | } |
1887 | 1892 | ||
1888 | public static int FireAndForgetCount() | 1893 | public static int FireAndForgetCount() |
@@ -1907,16 +1912,117 @@ namespace OpenSim.Framework | |||
1907 | } | 1912 | } |
1908 | 1913 | ||
1909 | 1914 | ||
1915 | /// <summary> | ||
1916 | /// Additional information about threads in the main thread pool. Used to time how long the | ||
1917 | /// thread has been running, and abort it if it has timed-out. | ||
1918 | /// </summary> | ||
1919 | private class ThreadInfo | ||
1920 | { | ||
1921 | public long ThreadFuncNum { get; set; } | ||
1922 | public string StackTrace { get; set; } | ||
1923 | private string context; | ||
1924 | |||
1925 | public IWorkItemResult WorkItem { get; set; } | ||
1926 | public Thread Thread { get; set; } | ||
1927 | public bool Running { get; set; } | ||
1928 | public bool Aborted { get; set; } | ||
1929 | private int started; | ||
1930 | |||
1931 | public ThreadInfo(long threadFuncNum, string context) | ||
1932 | { | ||
1933 | ThreadFuncNum = threadFuncNum; | ||
1934 | this.context = context; | ||
1935 | Thread = null; | ||
1936 | Running = false; | ||
1937 | Aborted = false; | ||
1938 | } | ||
1939 | |||
1940 | public void Started() | ||
1941 | { | ||
1942 | Thread = Thread.CurrentThread; | ||
1943 | started = EnvironmentTickCount(); | ||
1944 | Running = true; | ||
1945 | } | ||
1946 | |||
1947 | public void Ended() | ||
1948 | { | ||
1949 | Running = false; | ||
1950 | } | ||
1951 | |||
1952 | public int Elapsed() | ||
1953 | { | ||
1954 | return EnvironmentTickCountSubtract(started); | ||
1955 | } | ||
1956 | |||
1957 | public void Abort() | ||
1958 | { | ||
1959 | Aborted = true; | ||
1960 | WorkItem.Cancel(true); | ||
1961 | } | ||
1962 | |||
1963 | /// <summary> | ||
1964 | /// Returns the thread's stack trace. | ||
1965 | /// </summary> | ||
1966 | /// <remarks> | ||
1967 | /// May return one of two stack traces. First, tries to get the thread's active stack | ||
1968 | /// trace. But this can fail, so as a fallback this method will return the stack | ||
1969 | /// trace that was active when the task was queued. | ||
1970 | /// </remarks> | ||
1971 | public string GetStackTrace() | ||
1972 | { | ||
1973 | string ret = (context == null) ? "" : ("(" + context + ") "); | ||
1974 | |||
1975 | StackTrace activeStackTrace = Util.GetStackTrace(Thread); | ||
1976 | if (activeStackTrace != null) | ||
1977 | ret += activeStackTrace.ToString(); | ||
1978 | else if (StackTrace != null) | ||
1979 | ret += "(Stack trace when queued) " + StackTrace; | ||
1980 | // else, no stack trace available | ||
1981 | |||
1982 | return ret; | ||
1983 | } | ||
1984 | } | ||
1985 | |||
1986 | |||
1910 | private static long nextThreadFuncNum = 0; | 1987 | private static long nextThreadFuncNum = 0; |
1911 | private static long numQueuedThreadFuncs = 0; | 1988 | private static long numQueuedThreadFuncs = 0; |
1912 | private static long numRunningThreadFuncs = 0; | 1989 | private static long numRunningThreadFuncs = 0; |
1913 | private static Int32 threadFuncOverloadMode = 0; | 1990 | private static Int32 threadFuncOverloadMode = 0; |
1914 | 1991 | ||
1992 | // Maps (ThreadFunc number -> Thread) | ||
1993 | private static ConcurrentDictionary<long, ThreadInfo> activeThreads = new ConcurrentDictionary<long, ThreadInfo>(); | ||
1994 | |||
1995 | private static readonly int THREAD_TIMEOUT = 60 * 1000; | ||
1996 | |||
1997 | /// <summary> | ||
1998 | /// Finds threads in the main thread pool that have timed-out, and aborts them. | ||
1999 | /// </summary> | ||
2000 | private static void ThreadPoolWatchdog(object state) | ||
2001 | { | ||
2002 | foreach (KeyValuePair<long, ThreadInfo> entry in activeThreads) | ||
2003 | { | ||
2004 | ThreadInfo t = entry.Value; | ||
2005 | if (t.Running && !t.Aborted && (t.Elapsed() >= THREAD_TIMEOUT)) | ||
2006 | { | ||
2007 | m_log.WarnFormat("Timeout in threadfunc {0} ({1}) {2}", t.ThreadFuncNum, t.Thread.Name, t.GetStackTrace()); | ||
2008 | t.Abort(); | ||
2009 | |||
2010 | ThreadInfo dummy; | ||
2011 | activeThreads.TryRemove(entry.Key, out dummy); | ||
2012 | |||
2013 | // It's possible that the thread won't abort. To make sure the thread pool isn't | ||
2014 | // depleted, increase the pool size. | ||
2015 | m_ThreadPool.MaxThreads++; | ||
2016 | } | ||
2017 | } | ||
2018 | } | ||
2019 | |||
2020 | |||
1915 | public static void FireAndForget(System.Threading.WaitCallback callback, object obj) | 2021 | public static void FireAndForget(System.Threading.WaitCallback callback, object obj) |
1916 | { | 2022 | { |
1917 | FireAndForget(callback, obj, null); | 2023 | FireAndForget(callback, obj, null); |
1918 | } | 2024 | } |
1919 | 2025 | ||
1920 | public static void FireAndForget(System.Threading.WaitCallback callback, object obj, string context) | 2026 | public static void FireAndForget(System.Threading.WaitCallback callback, object obj, string context) |
1921 | { | 2027 | { |
1922 | WaitCallback realCallback; | 2028 | WaitCallback realCallback; |
@@ -1924,6 +2030,7 @@ namespace OpenSim.Framework | |||
1924 | bool loggingEnabled = (DebugFlags & DebugFlagsEnum.LogThreadPool) != 0; | 2030 | bool loggingEnabled = (DebugFlags & DebugFlagsEnum.LogThreadPool) != 0; |
1925 | 2031 | ||
1926 | long threadFuncNum = Interlocked.Increment(ref nextThreadFuncNum); | 2032 | long threadFuncNum = Interlocked.Increment(ref nextThreadFuncNum); |
2033 | ThreadInfo threadInfo = new ThreadInfo(threadFuncNum, context); | ||
1927 | 2034 | ||
1928 | if (FireAndForgetMethod == FireAndForgetMethod.RegressionTest) | 2035 | if (FireAndForgetMethod == FireAndForgetMethod.RegressionTest) |
1929 | { | 2036 | { |
@@ -1939,6 +2046,8 @@ namespace OpenSim.Framework | |||
1939 | { | 2046 | { |
1940 | long numQueued1 = Interlocked.Decrement(ref numQueuedThreadFuncs); | 2047 | long numQueued1 = Interlocked.Decrement(ref numQueuedThreadFuncs); |
1941 | long numRunning1 = Interlocked.Increment(ref numRunningThreadFuncs); | 2048 | long numRunning1 = Interlocked.Increment(ref numRunningThreadFuncs); |
2049 | threadInfo.Started(); | ||
2050 | activeThreads[threadFuncNum] = threadInfo; | ||
1942 | 2051 | ||
1943 | try | 2052 | try |
1944 | { | 2053 | { |
@@ -1949,15 +2058,22 @@ namespace OpenSim.Framework | |||
1949 | 2058 | ||
1950 | callback(o); | 2059 | callback(o); |
1951 | } | 2060 | } |
2061 | catch (ThreadAbortException e) | ||
2062 | { | ||
2063 | m_log.Error(string.Format("Aborted threadfunc {0} ", threadFuncNum), e); | ||
2064 | } | ||
1952 | catch (Exception e) | 2065 | catch (Exception e) |
1953 | { | 2066 | { |
1954 | m_log.Error("[UTIL]: FireAndForget thread terminated with error ", e); | 2067 | m_log.Error(string.Format("[UTIL]: Util STP threadfunc {0} terminated with error ", threadFuncNum), e); |
1955 | } | 2068 | } |
1956 | finally | 2069 | finally |
1957 | { | 2070 | { |
1958 | Interlocked.Decrement(ref numRunningThreadFuncs); | 2071 | Interlocked.Decrement(ref numRunningThreadFuncs); |
2072 | threadInfo.Ended(); | ||
2073 | ThreadInfo dummy; | ||
2074 | activeThreads.TryRemove(threadFuncNum, out dummy); | ||
1959 | if (loggingEnabled || (threadFuncOverloadMode == 1)) | 2075 | if (loggingEnabled || (threadFuncOverloadMode == 1)) |
1960 | m_log.Debug("Exit threadfunc " + threadFuncNum); | 2076 | m_log.DebugFormat("Exit threadfunc {0} ({1})", threadFuncNum, FormatDuration(threadInfo.Elapsed())); |
1961 | } | 2077 | } |
1962 | }; | 2078 | }; |
1963 | } | 2079 | } |
@@ -1979,10 +2095,13 @@ namespace OpenSim.Framework | |||
1979 | 2095 | ||
1980 | if (loggingEnabled || (threadFuncOverloadMode == 1)) | 2096 | if (loggingEnabled || (threadFuncOverloadMode == 1)) |
1981 | { | 2097 | { |
2098 | string full, partial; | ||
2099 | GetFireAndForgetStackTrace(out full, out partial); | ||
1982 | m_log.DebugFormat("Queue threadfunc {0} (Queued {1}, Running {2}) {3}{4}", | 2100 | m_log.DebugFormat("Queue threadfunc {0} (Queued {1}, Running {2}) {3}{4}", |
1983 | threadFuncNum, numQueued, numRunningThreadFuncs, | 2101 | threadFuncNum, numQueued, numRunningThreadFuncs, |
1984 | (context == null) ? "" : ("(" + context + ") "), | 2102 | (context == null) ? "" : ("(" + context + ") "), |
1985 | GetFireAndForgetStackTrace(loggingEnabled)); | 2103 | loggingEnabled ? full : partial); |
2104 | threadInfo.StackTrace = full; | ||
1986 | } | 2105 | } |
1987 | 2106 | ||
1988 | switch (FireAndForgetMethod) | 2107 | switch (FireAndForgetMethod) |
@@ -2004,7 +2123,7 @@ namespace OpenSim.Framework | |||
2004 | case FireAndForgetMethod.SmartThreadPool: | 2123 | case FireAndForgetMethod.SmartThreadPool: |
2005 | if (m_ThreadPool == null) | 2124 | if (m_ThreadPool == null) |
2006 | InitThreadPool(2, 15); | 2125 | InitThreadPool(2, 15); |
2007 | m_ThreadPool.QueueWorkItem((cb, o) => cb(o), realCallback, obj); | 2126 | threadInfo.WorkItem = m_ThreadPool.QueueWorkItem((cb, o) => cb(o), realCallback, obj); |
2008 | break; | 2127 | break; |
2009 | case FireAndForgetMethod.Thread: | 2128 | case FireAndForgetMethod.Thread: |
2010 | Thread thread = new Thread(delegate(object o) { realCallback(o); }); | 2129 | Thread thread = new Thread(delegate(object o) { realCallback(o); }); |
@@ -2017,6 +2136,8 @@ namespace OpenSim.Framework | |||
2017 | catch (Exception) | 2136 | catch (Exception) |
2018 | { | 2137 | { |
2019 | Interlocked.Decrement(ref numQueuedThreadFuncs); | 2138 | Interlocked.Decrement(ref numQueuedThreadFuncs); |
2139 | ThreadInfo dummy; | ||
2140 | activeThreads.TryRemove(threadFuncNum, out dummy); | ||
2020 | throw; | 2141 | throw; |
2021 | } | 2142 | } |
2022 | } | 2143 | } |
@@ -2024,8 +2145,9 @@ namespace OpenSim.Framework | |||
2024 | /// <summary> | 2145 | /// <summary> |
2025 | /// Returns a stack trace for a thread added using FireAndForget(). | 2146 | /// Returns a stack trace for a thread added using FireAndForget(). |
2026 | /// </summary> | 2147 | /// </summary> |
2027 | /// <param name="full">True: return full stack trace; False: return only the first frame</param> | 2148 | /// <param name="full">Will contain the full stack trace</param> |
2028 | private static string GetFireAndForgetStackTrace(bool full) | 2149 | /// <param name="partial">Will contain only the first frame of the stack trace</param> |
2150 | private static void GetFireAndForgetStackTrace(out string full, out string partial) | ||
2029 | { | 2151 | { |
2030 | string src = Environment.StackTrace; | 2152 | string src = Environment.StackTrace; |
2031 | string[] lines = src.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); | 2153 | string[] lines = src.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); |
@@ -2034,6 +2156,7 @@ namespace OpenSim.Framework | |||
2034 | 2156 | ||
2035 | bool started = false; | 2157 | bool started = false; |
2036 | bool first = true; | 2158 | bool first = true; |
2159 | partial = ""; | ||
2037 | 2160 | ||
2038 | for (int i = 0; i < lines.Length; i++) | 2161 | for (int i = 0; i < lines.Length; i++) |
2039 | { | 2162 | { |
@@ -2051,20 +2174,101 @@ namespace OpenSim.Framework | |||
2051 | { | 2174 | { |
2052 | line = line.TrimStart(); | 2175 | line = line.TrimStart(); |
2053 | first = false; | 2176 | first = false; |
2177 | partial = line; | ||
2054 | } | 2178 | } |
2055 | 2179 | ||
2056 | bool last = (i == lines.Length - 1) || !full; | 2180 | bool last = (i == lines.Length - 1); |
2057 | if (last) | 2181 | if (last) |
2058 | dest.Append(line); | 2182 | dest.Append(line); |
2059 | else | 2183 | else |
2060 | dest.AppendLine(line); | 2184 | dest.AppendLine(line); |
2185 | } | ||
2061 | 2186 | ||
2062 | if (!full) | 2187 | full = dest.ToString(); |
2063 | break; | 2188 | } |
2189 | |||
2190 | #pragma warning disable 0618 | ||
2191 | /// <summary> | ||
2192 | /// Return the stack trace of a different thread. | ||
2193 | /// </summary> | ||
2194 | /// <remarks> | ||
2195 | /// This is complicated because the thread needs to be paused in order to get its stack | ||
2196 | /// trace. And pausing another thread can cause a deadlock. This method attempts to | ||
2197 | /// avoid deadlock by using a short timeout (200ms), after which it gives up and | ||
2198 | /// returns 'null' instead of the stack trace. | ||
2199 | /// | ||
2200 | /// Take from: http://stackoverflow.com/a/14935378 | ||
2201 | /// | ||
2202 | /// WARNING: this doesn't work in Mono. See https://bugzilla.novell.com/show_bug.cgi?id=571691 | ||
2203 | /// | ||
2204 | /// </remarks> | ||
2205 | /// <returns>The stack trace, or null if failed to get it</returns> | ||
2206 | private static StackTrace GetStackTrace(Thread targetThread) | ||
2207 | { | ||
2208 | if (IsPlatformMono) | ||
2209 | { | ||
2210 | // This doesn't work in Mono | ||
2211 | return null; | ||
2064 | } | 2212 | } |
2065 | 2213 | ||
2066 | return dest.ToString(); | 2214 | ManualResetEventSlim fallbackThreadReady = new ManualResetEventSlim(); |
2215 | ManualResetEventSlim exitedSafely = new ManualResetEventSlim(); | ||
2216 | |||
2217 | try | ||
2218 | { | ||
2219 | new Thread(delegate() | ||
2220 | { | ||
2221 | fallbackThreadReady.Set(); | ||
2222 | while (!exitedSafely.Wait(200)) | ||
2223 | { | ||
2224 | try | ||
2225 | { | ||
2226 | targetThread.Resume(); | ||
2227 | } | ||
2228 | catch (Exception) | ||
2229 | { | ||
2230 | // Whatever happens, do never stop to resume the main-thread regularly until the main-thread has exited safely. | ||
2231 | } | ||
2232 | } | ||
2233 | }).Start(); | ||
2234 | |||
2235 | fallbackThreadReady.Wait(); | ||
2236 | // From here, you have about 200ms to get the stack-trace | ||
2237 | |||
2238 | targetThread.Suspend(); | ||
2239 | |||
2240 | StackTrace trace = null; | ||
2241 | try | ||
2242 | { | ||
2243 | trace = new StackTrace(targetThread, true); | ||
2244 | } | ||
2245 | catch (ThreadStateException) | ||
2246 | { | ||
2247 | //failed to get stack trace, since the fallback-thread resumed the thread | ||
2248 | //possible reasons: | ||
2249 | //1.) This thread was just too slow | ||
2250 | //2.) A deadlock ocurred | ||
2251 | //Automatic retry seems too risky here, so just return null. | ||
2252 | } | ||
2253 | |||
2254 | try | ||
2255 | { | ||
2256 | targetThread.Resume(); | ||
2257 | } | ||
2258 | catch (ThreadStateException) | ||
2259 | { | ||
2260 | // Thread is running again already | ||
2261 | } | ||
2262 | |||
2263 | return trace; | ||
2264 | } | ||
2265 | finally | ||
2266 | { | ||
2267 | // Signal the fallack-thread to stop | ||
2268 | exitedSafely.Set(); | ||
2269 | } | ||
2067 | } | 2270 | } |
2271 | #pragma warning restore 0618 | ||
2068 | 2272 | ||
2069 | /// <summary> | 2273 | /// <summary> |
2070 | /// Get information about the current state of the smart thread pool. | 2274 | /// Get information about the current state of the smart thread pool. |
@@ -2150,6 +2354,60 @@ namespace OpenSim.Framework | |||
2150 | } | 2354 | } |
2151 | 2355 | ||
2152 | /// <summary> | 2356 | /// <summary> |
2357 | /// Formats a duration (given in milliseconds). | ||
2358 | /// </summary> | ||
2359 | public static string FormatDuration(int ms) | ||
2360 | { | ||
2361 | TimeSpan span = new TimeSpan(ms * TimeSpan.TicksPerMillisecond); | ||
2362 | |||
2363 | string str = ""; | ||
2364 | string suffix = null; | ||
2365 | |||
2366 | int hours = (int)span.TotalHours; | ||
2367 | if (hours > 0) | ||
2368 | { | ||
2369 | str += hours.ToString(str.Length == 0 ? "0" : "00"); | ||
2370 | suffix = "hours"; | ||
2371 | } | ||
2372 | |||
2373 | if ((hours > 0) || (span.Minutes > 0)) | ||
2374 | { | ||
2375 | if (str.Length > 0) | ||
2376 | str += ":"; | ||
2377 | str += span.Minutes.ToString(str.Length == 0 ? "0" : "00"); | ||
2378 | if (suffix == null) | ||
2379 | suffix = "min"; | ||
2380 | } | ||
2381 | |||
2382 | if ((hours > 0) || (span.Minutes > 0) || (span.Seconds > 0)) | ||
2383 | { | ||
2384 | if (str.Length > 0) | ||
2385 | str += ":"; | ||
2386 | str += span.Seconds.ToString(str.Length == 0 ? "0" : "00"); | ||
2387 | if (suffix == null) | ||
2388 | suffix = "sec"; | ||
2389 | } | ||
2390 | |||
2391 | if (suffix == null) | ||
2392 | suffix = "ms"; | ||
2393 | |||
2394 | if (span.TotalMinutes < 1) | ||
2395 | { | ||
2396 | int ms1 = span.Milliseconds; | ||
2397 | if (str.Length > 0) | ||
2398 | { | ||
2399 | ms1 /= 100; | ||
2400 | str += "."; | ||
2401 | } | ||
2402 | str += ms1.ToString("0"); | ||
2403 | } | ||
2404 | |||
2405 | str += " " + suffix; | ||
2406 | |||
2407 | return str; | ||
2408 | } | ||
2409 | |||
2410 | /// <summary> | ||
2153 | /// Prints the call stack at any given point. Useful for debugging. | 2411 | /// Prints the call stack at any given point. Useful for debugging. |
2154 | /// </summary> | 2412 | /// </summary> |
2155 | public static void PrintCallStack() | 2413 | public static void PrintCallStack() |