Watchdog.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSimulator Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. using System;
  28. using System.Collections.Generic;
  29. using System.Linq;
  30. using System.Threading;
  31. using log4net;
  32. namespace OpenSim.Framework
  33. {
  34. /// <summary>
  35. /// Manages launching threads and keeping watch over them for timeouts
  36. /// </summary>
  37. public static class Watchdog
  38. {
  39. /// <summary>Timer interval in milliseconds for the watchdog timer</summary>
  40. const double WATCHDOG_INTERVAL_MS = 2500.0d;
  41. /// <summary>Maximum timeout in milliseconds before a thread is considered dead</summary>
  42. const int WATCHDOG_TIMEOUT_MS = 5000;
  43. [System.Diagnostics.DebuggerDisplay("{Thread.Name}")]
  44. public class ThreadWatchdogInfo
  45. {
  46. public Thread Thread;
  47. public int LastTick;
  48. public ThreadWatchdogInfo(Thread thread)
  49. {
  50. Thread = thread;
  51. LastTick = Environment.TickCount & Int32.MaxValue;
  52. }
  53. }
  54. /// <summary>
  55. /// This event is called whenever a tracked thread is stopped or
  56. /// has not called UpdateThread() in time
  57. /// </summary>
  58. /// <param name="thread">The thread that has been identified as dead</param>
  59. /// <param name="lastTick">The last time this thread called UpdateThread()</param>
  60. public delegate void WatchdogTimeout(Thread thread, int lastTick);
  61. /// <summary>This event is called whenever a tracked thread is
  62. /// stopped or has not called UpdateThread() in time</summary>
  63. public static event WatchdogTimeout OnWatchdogTimeout;
  64. private static readonly ILog m_log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
  65. private static Dictionary<int, ThreadWatchdogInfo> m_threads;
  66. private static System.Timers.Timer m_watchdogTimer;
  67. static Watchdog()
  68. {
  69. m_threads = new Dictionary<int, ThreadWatchdogInfo>();
  70. m_watchdogTimer = new System.Timers.Timer(WATCHDOG_INTERVAL_MS);
  71. m_watchdogTimer.AutoReset = false;
  72. m_watchdogTimer.Elapsed += WatchdogTimerElapsed;
  73. m_watchdogTimer.Start();
  74. }
  75. /// <summary>
  76. /// Start a new thread that is tracked by the watchdog timer
  77. /// </summary>
  78. /// <param name="start">The method that will be executed in a new thread</param>
  79. /// <param name="name">A name to give to the new thread</param>
  80. /// <param name="priority">Priority to run the thread at</param>
  81. /// <param name="isBackground">True to run this thread as a background
  82. /// thread, otherwise false</param>
  83. /// <returns>The newly created Thread object</returns>
  84. public static Thread StartThread(ThreadStart start, string name, ThreadPriority priority, bool isBackground)
  85. {
  86. Thread thread = new Thread(start);
  87. thread.Name = name;
  88. thread.Priority = priority;
  89. thread.IsBackground = isBackground;
  90. thread.Start();
  91. return thread;
  92. }
  93. /// <summary>
  94. /// Marks the current thread as alive
  95. /// </summary>
  96. public static void UpdateThread()
  97. {
  98. UpdateThread(Thread.CurrentThread.ManagedThreadId);
  99. }
  100. /// <summary>
  101. /// Stops watchdog tracking on the current thread
  102. /// </summary>
  103. /// <returns>True if the thread was removed from the list of tracked
  104. /// threads, otherwise false</returns>
  105. public static bool RemoveThread()
  106. {
  107. return RemoveThread(Thread.CurrentThread.ManagedThreadId);
  108. }
  109. private static void AddThread(ThreadWatchdogInfo threadInfo)
  110. {
  111. m_log.Debug("[WATCHDOG]: Started tracking thread \"" + threadInfo.Thread.Name + "\" (ID " + threadInfo.Thread.ManagedThreadId + ")");
  112. lock (m_threads)
  113. m_threads.Add(threadInfo.Thread.ManagedThreadId, threadInfo);
  114. }
  115. private static bool RemoveThread(int threadID)
  116. {
  117. lock (m_threads)
  118. return m_threads.Remove(threadID);
  119. }
  120. private static void UpdateThread(int threadID)
  121. {
  122. ThreadWatchdogInfo threadInfo;
  123. // Although TryGetValue is not a thread safe operation, we use a try/catch here instead
  124. // of a lock for speed. Adding/removing threads is a very rare operation compared to
  125. // UpdateThread(), and a single UpdateThread() failure here and there won't break
  126. // anything
  127. try
  128. {
  129. if (m_threads.TryGetValue(threadID, out threadInfo))
  130. threadInfo.LastTick = Environment.TickCount & Int32.MaxValue;
  131. else
  132. AddThread(new ThreadWatchdogInfo(Thread.CurrentThread));
  133. }
  134. catch { }
  135. }
  136. /// <summary>
  137. /// Get currently watched threads for diagnostic purposes
  138. /// </summary>
  139. /// <returns></returns>
  140. public static ThreadWatchdogInfo[] GetThreads()
  141. {
  142. return m_threads.Values.ToArray();
  143. }
  144. private static void WatchdogTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
  145. {
  146. WatchdogTimeout callback = OnWatchdogTimeout;
  147. if (callback != null)
  148. {
  149. ThreadWatchdogInfo timedOut = null;
  150. lock (m_threads)
  151. {
  152. int now = Environment.TickCount & Int32.MaxValue;
  153. foreach (ThreadWatchdogInfo threadInfo in m_threads.Values)
  154. {
  155. if (threadInfo.Thread.ThreadState == ThreadState.Stopped || now - threadInfo.LastTick >= WATCHDOG_TIMEOUT_MS)
  156. {
  157. timedOut = threadInfo;
  158. m_threads.Remove(threadInfo.Thread.ManagedThreadId);
  159. break;
  160. }
  161. }
  162. }
  163. if (timedOut != null)
  164. callback(timedOut.Thread, timedOut.LastTick);
  165. }
  166. m_watchdogTimer.Start();
  167. }
  168. }
  169. }