PageRenderTime 42ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/src/PluginPlatform.Shell/Services/PluginDispatcher.cs

https://gitlab.com/RakotVT/PluginPlatform
C# | 295 lines | 216 code | 52 blank | 27 comment | 33 complexity | ff7b2245a84133bd2a8568d09004eeb5 MD5 | raw file
  1. using System;
  2. using System.Diagnostics;
  3. using System.Linq;
  4. using System.Runtime.InteropServices;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using PluginPlatform.Core.Util;
  8. using PluginPlatform.Core.WinFunctions;
  9. using PluginPlatform.Shell.Model;
  10. namespace PluginPlatform.Shell.Services
  11. {
  12. /// <summary>
  13. /// Диспетчер плагинов, контролирующий их работу. Позволяет отслеживать "зависшие" плагины, приостанавливать/перезапускать их.
  14. /// </summary>
  15. class PluginDispatcher
  16. {
  17. #region Variables
  18. private ThreadSafeCollection<PluginProcess> _listPluginProcesses;
  19. Timer _timer;
  20. private static object locker = new object();
  21. #endregion
  22. #region Конструктор Singleton PluginDispatcher
  23. private static volatile PluginDispatcher _instance;
  24. private static object _syncRoot = new Object();
  25. private PluginDispatcher()
  26. {
  27. _listPluginProcesses = new ThreadSafeCollection<PluginProcess>();
  28. _timer = new Timer(new TimerCallback(TrackPlugins), null, 0, 5000);
  29. }
  30. /// <summary>
  31. /// Экземпляр <see cref="PluginDispatcher"/>.
  32. /// </summary>
  33. public static PluginDispatcher Instance
  34. {
  35. get
  36. {
  37. if (_instance == null)
  38. {
  39. lock (_syncRoot)
  40. {
  41. if (_instance == null)
  42. _instance = new PluginDispatcher();
  43. }
  44. }
  45. return _instance;
  46. }
  47. }
  48. internal ThreadSafeCollection<PluginProcess> ListPluginProcesses
  49. {
  50. get
  51. {
  52. return _listPluginProcesses;
  53. }
  54. set
  55. {
  56. _listPluginProcesses = value;
  57. }
  58. }
  59. #endregion
  60. #region Public Methods
  61. public void RegisterPlugin(PluginDescription pluginDesc, Plugin plugin, int processID)
  62. {
  63. if (pluginDesc == null)
  64. throw new ArgumentNullException("pluginDesc");
  65. if (plugin == null)
  66. throw new ArgumentNullException("plugin");
  67. PluginProcess process = new PluginProcess(pluginDesc, plugin, processID);
  68. _listPluginProcesses.Add(process);
  69. }
  70. public void ExitPluginProcess(Guid pluginID)
  71. {
  72. PluginProcess pluginProc = _listPluginProcesses.FirstOrDefault(item => item.UID == pluginID);
  73. if (pluginProc == null)
  74. return;
  75. pluginProc.IsTracking = false;
  76. Process process = Process.GetProcessById(pluginProc.ProcessID);
  77. process.Kill();
  78. _listPluginProcesses.Remove(pluginProc);
  79. pluginProc = null;
  80. }
  81. #endregion
  82. #region Private Methods
  83. private void TrackPlugins(object state)
  84. {
  85. lock (_listPluginProcesses)
  86. {
  87. Parallel.ForEach(_listPluginProcesses, item =>
  88. {
  89. if (item.IsTracking)
  90. {
  91. try
  92. {
  93. Process process = Process.GetProcessById(item.ProcessID);
  94. bool checkResult = IsResponding(process, item.PluginViewHandle);
  95. PluginProcessStatuses status = checkResult ? PluginProcessStatuses.Stable : PluginProcessStatuses.NotResponding;
  96. if (checkResult != item.IsResponding)
  97. {
  98. OnPluginProcessStatusChanged(new PluginRespondingEventArgs(item.UID, status));
  99. item.IsResponding = checkResult;
  100. }
  101. }
  102. catch (ArgumentException)
  103. {
  104. item.IsTracking = false;
  105. _listPluginProcesses.Remove(item);
  106. OnPluginProcessStatusChanged(new PluginRespondingEventArgs(item.UID, PluginProcessStatuses.Exited));
  107. }
  108. }
  109. });
  110. }
  111. }
  112. private void TrackPlugin(Guid pluginID, bool track)
  113. {
  114. PluginProcess pluginProc = _listPluginProcesses.FirstOrDefault(item => item.UID == pluginID);
  115. pluginProc.IsTracking = track;
  116. if (pluginProc.IsTracking)
  117. {
  118. try
  119. {
  120. Process process = Process.GetProcessById(pluginProc.ProcessID);
  121. bool checkResponding = IsResponding(process, pluginProc.PluginViewHandle);
  122. PluginProcessStatuses status = checkResponding ? PluginProcessStatuses.Stable : PluginProcessStatuses.NotResponding;
  123. if (checkResponding != pluginProc.IsResponding)
  124. OnPluginProcessStatusChanged(new PluginRespondingEventArgs(pluginID, status));
  125. pluginProc.IsResponding = checkResponding;
  126. }
  127. catch (ArgumentException)
  128. {
  129. pluginProc.IsTracking = false;
  130. _listPluginProcesses.Remove(pluginProc);
  131. OnPluginProcessStatusChanged(new PluginRespondingEventArgs(pluginID, PluginProcessStatuses.Exited));
  132. }
  133. }
  134. }
  135. private bool IsResponding(Process process, IntPtr pluginViewHandle)
  136. {
  137. lock (locker)
  138. {
  139. if (pluginViewHandle == IntPtr.Zero)
  140. return true;
  141. HandleRef handleRef = new HandleRef(process, pluginViewHandle);
  142. int timeout = 5000;
  143. IntPtr lpdwResult;
  144. IntPtr lResult = WindowsFunctions.SendMessageTimeout(
  145. handleRef,
  146. 0,
  147. IntPtr.Zero,
  148. IntPtr.Zero,
  149. SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
  150. timeout,
  151. out lpdwResult);
  152. return lResult != IntPtr.Zero;
  153. }
  154. }
  155. private void SuspendProcess(Guid pluginID)
  156. {
  157. PluginProcess pluginProc = _listPluginProcesses.FirstOrDefault(item => item.UID == pluginID);
  158. Process proc = Process.GetProcessById(pluginProc.ProcessID);
  159. if (proc.ProcessName == string.Empty)
  160. return;
  161. foreach (ProcessThread pT in proc.Threads)
  162. {
  163. IntPtr pOpenThread = WindowsFunctions.OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)pT.Id);
  164. if (pOpenThread == IntPtr.Zero)
  165. {
  166. break;
  167. }
  168. WindowsFunctions.SuspendThread(pOpenThread);
  169. }
  170. }
  171. private void ResumeProcess(Guid pluginID)
  172. {
  173. PluginProcess pluginProc = _listPluginProcesses.FirstOrDefault(item => item.UID == pluginID);
  174. Process proc = Process.GetProcessById(pluginProc.ProcessID);
  175. if (proc.ProcessName == string.Empty)
  176. return;
  177. foreach (ProcessThread pT in proc.Threads)
  178. {
  179. IntPtr pOpenThread = WindowsFunctions.OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)pT.Id);
  180. if (pOpenThread == IntPtr.Zero)
  181. {
  182. break;
  183. }
  184. WindowsFunctions.ResumeThread(pOpenThread);
  185. }
  186. }
  187. #endregion
  188. #region Events
  189. /// <summary>
  190. /// Событие, сигнализирущее об отсутствии отклика интрефейса плагина.
  191. /// </summary>
  192. public event EventHandler<PluginRespondingEventArgs> PluginProcessStatusChanged;
  193. /// <summary>
  194. /// Вызывается при отсутствии отклика интрефейса плагина.
  195. /// </summary>
  196. /// <param _name="e">Аргументы для события.</param>
  197. private void OnPluginProcessStatusChanged(PluginRespondingEventArgs e)
  198. {
  199. EventHandler<PluginRespondingEventArgs> temp = Interlocked.CompareExchange(ref PluginProcessStatusChanged, null, null);
  200. if (temp != null)
  201. temp(this, e);
  202. }
  203. /// <summary>
  204. /// Предоставляет данные для события <see cref="PluginProcessStatusChanged"/>
  205. /// </summary>
  206. public class PluginRespondingEventArgs : EventArgs
  207. {
  208. private readonly Guid _pluginID;
  209. /// <summary>
  210. /// Guid идентификатор плагина.
  211. /// </summary>
  212. public Guid PluginID
  213. {
  214. get { return _pluginID; }
  215. }
  216. private readonly PluginProcessStatuses _pluginProcessStatus;
  217. /// <summary>
  218. /// Статус плагина.
  219. /// </summary>
  220. public PluginProcessStatuses IsResponding
  221. {
  222. get { return _pluginProcessStatus; }
  223. }
  224. /// <summary>
  225. /// Инициализирует новый экземпляр класса <see cref="PluginRespondingEventArgs"/>.
  226. /// </summary>
  227. /// <param _name="pluginID">Guid идентификатор плагина.</param>
  228. /// <param _name="checkResponding">Статус плагина.</param>
  229. public PluginRespondingEventArgs(Guid pluginID, PluginProcessStatuses isResponding)
  230. {
  231. _pluginID = pluginID;
  232. _pluginProcessStatus = isResponding;
  233. }
  234. }
  235. #endregion
  236. }
  237. public enum PluginProcessStatuses
  238. {
  239. Stable,
  240. NotResponding,
  241. Exited
  242. }
  243. }