PageRenderTime 60ms CodeModel.GetById 35ms RepoModel.GetById 0ms app.codeStats 0ms

/sipsorcery-core/SIPSorcery.Servers.Cores/SIPWatchTower/SIPAppServerManager.cs

https://github.com/thecc4re/sipsorcery-mono
C# | 397 lines | 341 code | 39 blank | 17 comment | 42 complexity | c24e831cee9001f7b7e6315aa34ff6a9 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Net;
  8. using System.ServiceModel;
  9. using System.ServiceModel.Channels;
  10. using System.Text;
  11. using System.Threading;
  12. using System.Xml;
  13. using SIPSorcery.SIP;
  14. using SIPSorcery.SIP.App;
  15. using SIPSorcery.Sys;
  16. using SIPSorcery.Web.Services;
  17. using log4net;
  18. using Microsoft.Scripting.Hosting;
  19. namespace SIPSorcery.Servers
  20. {
  21. /// <summary>
  22. ///
  23. /// </summary>
  24. /// <remarks>
  25. /// Example call dispatcher workers node:
  26. ///
  27. /// <sipappserverworkers>
  28. /// <sipappserverworker>
  29. /// <workerprocesspath>C:\Temp\sipsorcery-appsvr1\sipsorcery-appsvr.exe</workerprocesspath>
  30. /// <workerprocessargs>-sip:{0} -cms:{1}</workerprocessargs>
  31. /// <sipsocket>127.0.0.1:5070</sipsocket>
  32. /// <callmanageraddress>http://localhost:8081/callmanager</callmanageraddress>
  33. /// </sipappserverworker>
  34. /// </sipappserverworkers>
  35. ///
  36. /// </remarks>
  37. public class SIPAppServerManager : ISIPCallDispatcher
  38. {
  39. private class SIPAppServerWorker
  40. {
  41. private const int START_ATTEMPT_INTERVAL = 30;
  42. public string WorkerProcessPath;
  43. public string WorkerProcessArgs;
  44. public SIPEndPoint AppServerEndpoint;
  45. public EndpointAddress CallManagerAddress;
  46. public Process WorkerProcess;
  47. public DateTime? LastStartAttempt;
  48. public DateTime? RestartTime { get; private set; }
  49. public bool HasBeenKilled;
  50. public bool IsUnHealthy;
  51. public event EventHandler Unhealthy;
  52. public event EventHandler Healthy;
  53. public SIPAppServerWorker(XmlNode xmlConfigNode)
  54. {
  55. WorkerProcessPath = xmlConfigNode.SelectSingleNode("workerprocesspath").InnerText;
  56. WorkerProcessArgs = xmlConfigNode.SelectSingleNode("workerprocessargs").InnerText;
  57. AppServerEndpoint = SIPEndPoint.ParseSIPEndPoint(xmlConfigNode.SelectSingleNode("sipsocket").InnerText);
  58. CallManagerAddress = new EndpointAddress(xmlConfigNode.SelectSingleNode("callmanageraddress").InnerText);
  59. }
  60. public void StartProcess()
  61. {
  62. if (LastStartAttempt == null || DateTime.Now.Subtract(LastStartAttempt.Value).TotalSeconds > START_ATTEMPT_INTERVAL)
  63. {
  64. if (!HasBeenKilled && WorkerProcess != null)
  65. {
  66. Kill();
  67. }
  68. LastStartAttempt = DateTime.Now;
  69. RestartTime = null;
  70. HasBeenKilled = false;
  71. IsUnHealthy = false;
  72. ProcessStartInfo startInfo = new ProcessStartInfo(WorkerProcessPath, String.Format(WorkerProcessArgs, new object[] { AppServerEndpoint.ToString(), CallManagerAddress.ToString(), "false" }));
  73. startInfo.CreateNoWindow = true;
  74. startInfo.UseShellExecute = false;
  75. WorkerProcess = Process.Start(startInfo);
  76. logger.Debug("New call dispatcher worker process on " + AppServerEndpoint.ToString() + " started on pid=" + WorkerProcess.Id + ".");
  77. if (Healthy != null)
  78. {
  79. Healthy(this, null);
  80. }
  81. }
  82. else
  83. {
  84. logger.Debug("Interval of starts for call dispatcher worker process was too short, last restart " + DateTime.Now.Subtract(LastStartAttempt.Value).TotalSeconds.ToString("0.00") + "s ago.");
  85. }
  86. }
  87. public void ScheduleRestart(DateTime restartTime)
  88. {
  89. RestartTime = restartTime;
  90. if (Unhealthy != null)
  91. {
  92. Unhealthy(this, null);
  93. }
  94. }
  95. private void Kill()
  96. {
  97. try
  98. {
  99. logger.Debug("Restarting worker process on pid=" + WorkerProcess.Id + ".");
  100. WorkerProcess.Kill();
  101. HasBeenKilled = true;
  102. }
  103. catch (Exception excp)
  104. {
  105. logger.Error("Exception SIPAppServerWorker Kill. " + excp.Message);
  106. }
  107. }
  108. public bool IsHealthy()
  109. {
  110. try
  111. {
  112. if (WorkerProcess != null && !WorkerProcess.HasExited && RestartTime == null)
  113. {
  114. return true;
  115. }
  116. else
  117. {
  118. return false;
  119. }
  120. }
  121. catch (Exception excp)
  122. {
  123. logger.Error("Exception SIPAppServerWorker IsHealthy. " + excp.Message);
  124. return false;
  125. }
  126. }
  127. }
  128. private const string WORKER_PROCESS_MONITOR_THREAD_NAME = "sipappservermanager-workermonitor";
  129. private const string WORKER_PROCESS_PROBE_THREAD_NAME = "sipappservermanager-probe";
  130. private const int MAX_LIFETIME_SECONDS = 180;
  131. private const long MAX_PHYSICAL_MEMORY = 150000000; // Restart worker processes when they've used up 150MB of physical memory.
  132. private const int PROCESS_RESTART_DELAY = 33;
  133. private const int CHECK_WORKER_MEMORY_PERIOD = 1000;
  134. private const int PROBE_WORKER_CALL_PERIOD = 15000;
  135. private static int m_unhealthyPriority = SIPCallDispatcherFile.DISABLED_APPSERVER_PRIORITY;
  136. private static int m_healthyPriority = SIPCallDispatcherFile.USEALWAYS_APPSERVER_PRIORITY;
  137. private static ILog logger = AppState.logger;
  138. private SIPMonitorLogDelegate SIPMonitorLogEvent_External;
  139. private SIPTransport m_sipTransport;
  140. private XmlNode m_appServerWorkersNode;
  141. private ServiceHost m_callManagerPassThruSvcHost;
  142. private bool m_exit;
  143. private string m_dispatcherUsername = SIPCallManager.DISPATCHER_SIPACCOUNT_NAME;
  144. private string m_appServerEndPointsPath;
  145. private SIPCallDispatcherFile m_sipCallDispatcherFile;
  146. private List<SIPAppServerWorker> m_appServerWorkers = new List<SIPAppServerWorker>();
  147. private List<string> m_workerSIPEndPoints = new List<string>(); // Allow quick lookups to determine whether a remote end point is that of a worker process.
  148. public SIPAppServerManager(
  149. SIPMonitorLogDelegate logDelegate,
  150. SIPTransport sipTransport,
  151. XmlNode appServerWorkersNode,
  152. string appServerEndPointsPath)
  153. {
  154. if (appServerWorkersNode == null || appServerWorkersNode.ChildNodes.Count == 0)
  155. {
  156. throw new ArgumentNullException("A SIPAppServerManager cannot be created with an empty workers node.");
  157. }
  158. SIPMonitorLogEvent_External = logDelegate;
  159. m_sipTransport = sipTransport;
  160. m_appServerWorkersNode = appServerWorkersNode;
  161. m_appServerEndPointsPath = appServerEndPointsPath;
  162. if (!appServerEndPointsPath.IsNullOrBlank() && File.Exists(appServerEndPointsPath))
  163. {
  164. m_sipCallDispatcherFile = new SIPCallDispatcherFile(logDelegate, appServerEndPointsPath);
  165. }
  166. try
  167. {
  168. CallManagerPassThruServiceInstanceProvider callManagerPassThruSvcInstanceProvider = new CallManagerPassThruServiceInstanceProvider(this);
  169. m_callManagerPassThruSvcHost = new ServiceHost(typeof(CallManagerPassThruService));
  170. m_callManagerPassThruSvcHost.Description.Behaviors.Add(callManagerPassThruSvcInstanceProvider);
  171. m_callManagerPassThruSvcHost.Open();
  172. logger.Debug("SIPAppServerManager CallManagerPassThru hosted service successfully started on " + m_callManagerPassThruSvcHost.BaseAddresses[0].AbsoluteUri + ".");
  173. }
  174. catch (Exception excp)
  175. {
  176. logger.Warn("Exception starting SIPAppServerManager CallManagerPassThru hosted service. " + excp.Message);
  177. }
  178. foreach (XmlNode appServerWorkerNode in m_appServerWorkersNode.ChildNodes)
  179. {
  180. SIPAppServerWorker appServerWorker = new SIPAppServerWorker(appServerWorkerNode);
  181. if (m_sipCallDispatcherFile != null)
  182. {
  183. appServerWorker.Healthy += (s, e) => { m_sipCallDispatcherFile.UpdateAppServerPriority(((SIPAppServerWorker)s).AppServerEndpoint, m_healthyPriority); };
  184. appServerWorker.Unhealthy += (s, e) => { m_sipCallDispatcherFile.UpdateAppServerPriority(((SIPAppServerWorker)s).AppServerEndpoint, m_unhealthyPriority); };
  185. }
  186. m_appServerWorkers.Add(appServerWorker);
  187. m_workerSIPEndPoints.Add(appServerWorker.AppServerEndpoint.ToString());
  188. logger.Debug(" SIPAppServerManager worker added for " + appServerWorker.AppServerEndpoint.ToString() + " and " + appServerWorker.CallManagerAddress.ToString() + ".");
  189. }
  190. ThreadPool.QueueUserWorkItem(delegate { SpawnWorkers(); });
  191. ThreadPool.QueueUserWorkItem(delegate { ProbeWorkers(); });
  192. }
  193. private void SpawnWorkers()
  194. {
  195. try
  196. {
  197. Thread.CurrentThread.Name = WORKER_PROCESS_MONITOR_THREAD_NAME;
  198. foreach (SIPAppServerWorker worker in m_appServerWorkers)
  199. {
  200. worker.StartProcess();
  201. }
  202. while (!m_exit)
  203. {
  204. try
  205. {
  206. lock (m_appServerWorkers)
  207. {
  208. foreach (SIPAppServerWorker worker in m_appServerWorkers)
  209. {
  210. if (worker.RestartTime != null)
  211. {
  212. if (worker.RestartTime < DateTime.Now)
  213. {
  214. logger.Debug("Restarting worker process on pid=" + worker.WorkerProcess.Id + ".");
  215. worker.StartProcess();
  216. }
  217. }
  218. else if (!worker.IsHealthy())
  219. {
  220. if (!worker.IsUnHealthy)
  221. {
  222. worker.IsUnHealthy = true;
  223. m_sipCallDispatcherFile.UpdateAppServerPriority(worker.AppServerEndpoint, m_unhealthyPriority);
  224. }
  225. worker.StartProcess();
  226. }
  227. else
  228. {
  229. worker.WorkerProcess.Refresh();
  230. if (worker.WorkerProcess.PrivateMemorySize64 >= MAX_PHYSICAL_MEMORY)
  231. {
  232. logger.Debug("Worker process on pid=" + worker.WorkerProcess.Id + " has reached the memory limit, scheduling a restart.");
  233. worker.ScheduleRestart(DateTime.Now.AddSeconds(PROCESS_RESTART_DELAY));
  234. }
  235. }
  236. }
  237. }
  238. }
  239. catch (Exception checkWorkersExcp)
  240. {
  241. logger.Error("Exception SIPAppServerManager Checking Workers. " + checkWorkersExcp.Message);
  242. }
  243. Thread.Sleep(CHECK_WORKER_MEMORY_PERIOD);
  244. }
  245. }
  246. catch (Exception excp)
  247. {
  248. logger.Error("Exception SIPAppServerManager SpawnWorkers. " + excp.Message);
  249. }
  250. }
  251. private void ProbeWorkers()
  252. {
  253. try
  254. {
  255. while (!m_exit)
  256. {
  257. Thread.Sleep(PROBE_WORKER_CALL_PERIOD);
  258. try
  259. {
  260. SIPEndPoint activeWorkerEndPoint = GetFirstHealthyEndPoint();
  261. if (activeWorkerEndPoint != null)
  262. {
  263. SIPCallDescriptor callDescriptor = new SIPCallDescriptor(m_dispatcherUsername, null, "sip:" + m_dispatcherUsername + "@" + activeWorkerEndPoint.GetIPEndPoint().ToString(),
  264. "sip:" + m_dispatcherUsername + "@sipcalldispatcher", "sip:" + activeWorkerEndPoint.GetIPEndPoint().ToString(), null, null, null, SIPCallDirection.Out, null, null, null);
  265. SIPClientUserAgent uac = new SIPClientUserAgent(m_sipTransport, null, null, null, null);
  266. uac.CallFailed += new SIPCallFailedDelegate(AppServerCallFailed);
  267. uac.CallAnswered += (call, sipResponse) =>
  268. {
  269. if (sipResponse.Status != SIPResponseStatusCodesEnum.BadExtension)
  270. {
  271. logger.Warn("Probe call answered with unexpected response code of " + sipResponse.StatusCode + ".");
  272. AppServerCallFailed(call, "Unexpected response of " + ((int)sipResponse.StatusCode) + " on probe call.");
  273. }
  274. };
  275. uac.Call(callDescriptor);
  276. }
  277. else
  278. {
  279. logger.Warn("SIPAppServerManager was not able to find a healthy app server endpoint.");
  280. }
  281. }
  282. catch (Exception probeExcp)
  283. {
  284. logger.Error("Exception SIPAppServerManager Sending Probe. " + probeExcp.Message);
  285. }
  286. }
  287. }
  288. catch (Exception excp)
  289. {
  290. logger.Error("Exception SIPAppServerManager ProberWorkers. " + excp.Message);
  291. }
  292. }
  293. private void AppServerCallFailed(ISIPClientUserAgent uac, string errorMessage)
  294. {
  295. try
  296. {
  297. string workerSocket = SIPURI.ParseSIPURI(uac.CallDescriptor.Uri).Host;
  298. logger.Warn(" SIPAppServerManager call to " + workerSocket + " failed " + errorMessage + ".");
  299. // Find the worker for the failed end point.
  300. SIPAppServerWorker failedWorker = null;
  301. lock (m_appServerWorkers)
  302. {
  303. foreach (SIPAppServerWorker worker in m_appServerWorkers)
  304. {
  305. if (worker.AppServerEndpoint.GetIPEndPoint().ToString() == workerSocket)
  306. {
  307. failedWorker = worker;
  308. break;
  309. }
  310. }
  311. }
  312. if (failedWorker != null)
  313. {
  314. logger.Debug("Scheduling immediate restart on app server worker process pid=" + failedWorker.WorkerProcess.Id + " due to failed probe.");
  315. failedWorker.ScheduleRestart(DateTime.Now);
  316. }
  317. }
  318. catch (Exception excp)
  319. {
  320. logger.Error("Exception AppServerCallFailed. " + excp.Message);
  321. }
  322. }
  323. public void Stop()
  324. {
  325. m_exit = true;
  326. }
  327. public CallManagerProxy GetCallManagerClient()
  328. {
  329. lock (m_appServerWorkers)
  330. {
  331. foreach (SIPAppServerWorker worker in m_appServerWorkers)
  332. {
  333. if (worker.IsHealthy())
  334. {
  335. return new CallManagerProxy(new BasicHttpBinding(), worker.CallManagerAddress);
  336. }
  337. }
  338. }
  339. logger.Warn("GetCallManagerClient could not find a healthy SIPAppServerWorker.");
  340. return null;
  341. }
  342. public SIPEndPoint GetFirstHealthyEndPoint()
  343. {
  344. lock (m_appServerWorkers)
  345. {
  346. foreach (SIPAppServerWorker worker in m_appServerWorkers)
  347. {
  348. if (worker.IsHealthy())
  349. {
  350. return worker.AppServerEndpoint;
  351. }
  352. }
  353. }
  354. logger.Warn("GetFirstHealthyEndPoint could not find a healthy SIPAppServerWorker.");
  355. return null;
  356. }
  357. }
  358. }