PageRenderTime 41ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/SignalR/Transports/TransportHeartBeat.cs

https://github.com/kpmrafeeq/SignalR
C# | 305 lines | 213 code | 43 blank | 49 comment | 20 complexity | 527b4fcee19b04515c776cbf5a045fb7 MD5 | raw file
Possible License(s): MIT
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Linq;
  6. using System.Threading;
  7. using SignalR.Infrastructure;
  8. namespace SignalR.Transports
  9. {
  10. /// <summary>
  11. /// Default implementation of <see cref="ITransportHeartBeat"/>.
  12. /// </summary>
  13. public class TransportHeartBeat : ITransportHeartBeat
  14. {
  15. private readonly ConcurrentDictionary<string, ConnectionMetadata> _connections = new ConcurrentDictionary<string, ConnectionMetadata>();
  16. private readonly Timer _timer;
  17. private readonly IConfigurationManager _configurationManager;
  18. private readonly IServerCommandHandler _serverCommandHandler;
  19. private readonly ITraceManager _trace;
  20. private readonly string _serverId;
  21. private int _running;
  22. /// <summary>
  23. /// Initializes and instance of the <see cref="TransportHeartBeat"/> class.
  24. /// </summary>
  25. /// <param name="resolver">The <see cref="IDependencyResolver"/>.</param>
  26. public TransportHeartBeat(IDependencyResolver resolver)
  27. {
  28. _configurationManager = resolver.Resolve<IConfigurationManager>();
  29. _serverCommandHandler = resolver.Resolve<IServerCommandHandler>();
  30. _serverId = resolver.Resolve<IServerIdManager>().ServerId;
  31. _trace = resolver.Resolve<ITraceManager>();
  32. _serverCommandHandler.Command = ProcessServerCommand;
  33. // REVIEW: When to dispose the timer?
  34. _timer = new Timer(Beat,
  35. null,
  36. _configurationManager.HeartBeatInterval,
  37. _configurationManager.HeartBeatInterval);
  38. }
  39. private TraceSource Trace
  40. {
  41. get
  42. {
  43. return _trace["SignalR.Transports.TransportHeartBeat"];
  44. }
  45. }
  46. private void ProcessServerCommand(ServerCommand command)
  47. {
  48. switch (command.Type)
  49. {
  50. case ServerCommandType.RemoveConnection:
  51. // Only remove connections if this command didn't originate from the owner
  52. if (!command.IsFromSelf(_serverId))
  53. {
  54. RemoveConnection((string)command.Value);
  55. }
  56. break;
  57. default:
  58. break;
  59. }
  60. }
  61. /// <summary>
  62. /// Adds a new connection to the list of tracked connections.
  63. /// </summary>
  64. /// <param name="connection">The connection to be added.</param>
  65. public bool AddConnection(ITrackingConnection connection)
  66. {
  67. var newMetadata = new ConnectionMetadata(connection);
  68. ConnectionMetadata oldMetadata = null;
  69. bool isNewConnection = true;
  70. _connections.AddOrUpdate(connection.ConnectionId, newMetadata, (key, old) =>
  71. {
  72. oldMetadata = old;
  73. return newMetadata;
  74. });
  75. if (oldMetadata != null)
  76. {
  77. Trace.TraceInformation("Connection exists. Closing previous connection. Old=({0}, {1}) New=({2})", oldMetadata.Connection.IsAlive, oldMetadata.Connection.Url, connection.Url);
  78. // Kick out the older connection. This should only happen when
  79. // a previous connection attempt fails on the client side (e.g. transport fallback).
  80. oldMetadata.Connection.End();
  81. // If we have old metadata this isn't a new connection
  82. isNewConnection = false;
  83. }
  84. else
  85. {
  86. Trace.TraceInformation("Connection is New=({0}).", connection.Url);
  87. }
  88. // Set the initial connection time
  89. newMetadata.Initial = DateTime.UtcNow;
  90. // Set the keep alive time
  91. newMetadata.UpdateKeepAlive(_configurationManager.KeepAlive);
  92. return isNewConnection;
  93. }
  94. private void RemoveConnection(string connectionId)
  95. {
  96. // Remove the connection
  97. ConnectionMetadata metadata;
  98. if (_connections.TryRemove(connectionId, out metadata))
  99. {
  100. Trace.TraceInformation("Removing connection {0}", connectionId);
  101. }
  102. }
  103. /// <summary>
  104. /// Removes a connection from the list of tracked connections.
  105. /// </summary>
  106. /// <param name="connection">The connection to remove.</param>
  107. public void RemoveConnection(ITrackingConnection connection)
  108. {
  109. // Remove the connection and associated metadata
  110. RemoveConnection(connection.ConnectionId);
  111. }
  112. /// <summary>
  113. /// Marks an existing connection as active.
  114. /// </summary>
  115. /// <param name="connection">The connection to mark.</param>
  116. public void MarkConnection(ITrackingConnection connection)
  117. {
  118. ConnectionMetadata metadata;
  119. if (_connections.TryGetValue(connection.ConnectionId, out metadata))
  120. {
  121. metadata.LastMarked = DateTime.UtcNow;
  122. }
  123. }
  124. public IList<ITrackingConnection> GetConnections()
  125. {
  126. return _connections.Values.Select(metadata => metadata.Connection).ToList();
  127. }
  128. private void Beat(object state)
  129. {
  130. if (Interlocked.Exchange(ref _running, 1) == 1)
  131. {
  132. Trace.TraceInformation("timer handler took longer than current interval");
  133. return;
  134. }
  135. try
  136. {
  137. foreach (var metadata in _connections.Values)
  138. {
  139. if (metadata.Connection.IsAlive)
  140. {
  141. CheckTimeoutAndKeepAlive(metadata);
  142. }
  143. else
  144. {
  145. // Check if we need to disconnect this connection
  146. CheckDisconnect(metadata);
  147. }
  148. }
  149. }
  150. catch (Exception ex)
  151. {
  152. Trace.TraceInformation("SignalR error during transport heart beat on background thread: {0}", ex);
  153. }
  154. finally
  155. {
  156. Interlocked.Exchange(ref _running, 0);
  157. }
  158. }
  159. private void CheckTimeoutAndKeepAlive(ConnectionMetadata metadata)
  160. {
  161. if (RaiseTimeout(metadata))
  162. {
  163. // If we're past the expiration time then just timeout the connection
  164. metadata.Connection.Timeout();
  165. RemoveConnection(metadata.Connection);
  166. }
  167. else
  168. {
  169. // The connection is still alive so we need to keep it alive with a server side "ping".
  170. // This is for scenarios where networing hardware (proxies, loadbalancers) get in the way
  171. // of us handling timeout's or disconnects gracefully
  172. if (RaiseKeepAlive(metadata))
  173. {
  174. metadata.Connection.KeepAlive();
  175. metadata.UpdateKeepAlive(_configurationManager.KeepAlive);
  176. }
  177. MarkConnection(metadata.Connection);
  178. }
  179. }
  180. private void CheckDisconnect(ConnectionMetadata metadata)
  181. {
  182. try
  183. {
  184. if (RaiseDisconnect(metadata))
  185. {
  186. // Remove the connection from the list
  187. RemoveConnection(metadata.Connection);
  188. // Fire disconnect on the connection
  189. metadata.Connection.Disconnect();
  190. }
  191. }
  192. catch (Exception ex)
  193. {
  194. // Swallow exceptions that might happen during disconnect
  195. Trace.TraceInformation("Raising Disconnect failed: {0}", ex);
  196. }
  197. }
  198. private bool RaiseDisconnect(ConnectionMetadata metadata)
  199. {
  200. // The transport is currently dead but it could just be reconnecting
  201. // so we to check it's last active time to see if it's over the disconnect
  202. // threshold
  203. TimeSpan elapsed = DateTime.UtcNow - metadata.LastMarked;
  204. // The threshold for disconnect is the transport threshold + (potential network issues)
  205. var threshold = metadata.Connection.DisconnectThreshold + _configurationManager.DisconnectTimeout;
  206. return elapsed >= threshold;
  207. }
  208. private bool RaiseKeepAlive(ConnectionMetadata metadata)
  209. {
  210. TimeSpan? keepAlive = _configurationManager.KeepAlive;
  211. if (keepAlive == null)
  212. {
  213. return false;
  214. }
  215. // Raise keep alive if the keep alive value has passed
  216. return DateTime.UtcNow >= metadata.KeepAliveTime;
  217. }
  218. private bool RaiseTimeout(ConnectionMetadata metadata)
  219. {
  220. // The connection already timed out so do nothing
  221. if (metadata.Connection.IsTimedOut)
  222. {
  223. return false;
  224. }
  225. TimeSpan? keepAlive = _configurationManager.KeepAlive;
  226. // If keep alive is configured and the connection supports keep alive
  227. // don't ever time out
  228. if (keepAlive != null && metadata.Connection.SupportsKeepAlive)
  229. {
  230. return false;
  231. }
  232. TimeSpan elapsed = DateTime.UtcNow - metadata.Initial;
  233. // Only raise timeout if we're past the configured connection timeout.
  234. return elapsed >= _configurationManager.ConnectionTimeout;
  235. }
  236. private class ConnectionMetadata
  237. {
  238. public ConnectionMetadata(ITrackingConnection connection)
  239. {
  240. Connection = connection;
  241. Initial = DateTime.UtcNow;
  242. LastMarked = DateTime.UtcNow;
  243. }
  244. // The connection instance
  245. public ITrackingConnection Connection { get; set; }
  246. // The last time the connection had any activity
  247. public DateTime LastMarked { get; set; }
  248. // The initial connection time of the connection
  249. public DateTime Initial { get; set; }
  250. // The time to send the keep alive ping
  251. public DateTime KeepAliveTime { get; set; }
  252. public void UpdateKeepAlive(TimeSpan? keepAliveInterval)
  253. {
  254. if (keepAliveInterval == null)
  255. {
  256. return;
  257. }
  258. KeepAliveTime = DateTime.UtcNow + keepAliveInterval.Value;
  259. }
  260. }
  261. }
  262. }