You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

ConnectionManager.cs 9.1 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. using Discord.Logging;
  2. using System;
  3. using System.Threading;
  4. using System.Threading.Tasks;
  5. using Discord.Net;
  6. namespace Discord
  7. {
  8. internal class ConnectionManager : IDisposable
  9. {
  10. public event Func<Task> Connected { add { _connectedEvent.Add(value); } remove { _connectedEvent.Remove(value); } }
  11. private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>();
  12. public event Func<Exception, bool, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } }
  13. private readonly AsyncEvent<Func<Exception, bool, Task>> _disconnectedEvent = new AsyncEvent<Func<Exception, bool, Task>>();
  14. private readonly SemaphoreSlim _stateLock;
  15. private readonly Logger _logger;
  16. private readonly int _connectionTimeout;
  17. private readonly Func<Task> _onConnecting;
  18. private readonly Func<Exception, Task> _onDisconnecting;
  19. private TaskCompletionSource<bool> _connectionPromise, _readyPromise;
  20. private CancellationTokenSource _combinedCancelToken, _reconnectCancelToken, _connectionCancelToken;
  21. private Task _task;
  22. private bool _isDisposed;
  23. public ConnectionState State { get; private set; }
  24. public CancellationToken CancelToken { get; private set; }
  25. internal ConnectionManager(SemaphoreSlim stateLock, Logger logger, int connectionTimeout,
  26. Func<Task> onConnecting, Func<Exception, Task> onDisconnecting, Action<Func<Exception, Task>> clientDisconnectHandler)
  27. {
  28. _stateLock = stateLock;
  29. _logger = logger;
  30. _connectionTimeout = connectionTimeout;
  31. _onConnecting = onConnecting;
  32. _onDisconnecting = onDisconnecting;
  33. clientDisconnectHandler(ex =>
  34. {
  35. if (ex != null)
  36. {
  37. var ex2 = ex as WebSocketClosedException;
  38. if (ex2?.CloseCode == 4006)
  39. CriticalError(new Exception("WebSocket session expired", ex));
  40. else if (ex2?.CloseCode == 4014)
  41. CriticalError(new Exception("WebSocket connection was closed", ex));
  42. else
  43. Error(new Exception("WebSocket connection was closed", ex));
  44. }
  45. else
  46. Error(new Exception("WebSocket connection was closed"));
  47. return Task.Delay(0);
  48. });
  49. }
  50. public virtual async Task StartAsync()
  51. {
  52. await AcquireConnectionLock().ConfigureAwait(false);
  53. var reconnectCancelToken = new CancellationTokenSource();
  54. _reconnectCancelToken?.Dispose();
  55. _reconnectCancelToken = reconnectCancelToken;
  56. _task = Task.Run(async () =>
  57. {
  58. try
  59. {
  60. Random jitter = new Random();
  61. int nextReconnectDelay = 1000;
  62. while (!reconnectCancelToken.IsCancellationRequested)
  63. {
  64. try
  65. {
  66. await ConnectAsync(reconnectCancelToken).ConfigureAwait(false);
  67. nextReconnectDelay = 1000; //Reset delay
  68. await _connectionPromise.Task.ConfigureAwait(false);
  69. }
  70. catch (Exception ex)
  71. {
  72. Error(ex); //In case this exception didn't come from another Error call
  73. if (!reconnectCancelToken.IsCancellationRequested)
  74. {
  75. await _logger.WarningAsync(ex).ConfigureAwait(false);
  76. await DisconnectAsync(ex, true).ConfigureAwait(false);
  77. }
  78. else
  79. {
  80. await _logger.ErrorAsync(ex).ConfigureAwait(false);
  81. await DisconnectAsync(ex, false).ConfigureAwait(false);
  82. }
  83. }
  84. if (!reconnectCancelToken.IsCancellationRequested)
  85. {
  86. //Wait before reconnecting
  87. await Task.Delay(nextReconnectDelay, reconnectCancelToken.Token).ConfigureAwait(false);
  88. nextReconnectDelay = (nextReconnectDelay * 2) + jitter.Next(-250, 250);
  89. if (nextReconnectDelay > 60000)
  90. nextReconnectDelay = 60000;
  91. }
  92. }
  93. }
  94. finally { _stateLock.Release(); }
  95. });
  96. }
  97. public virtual Task StopAsync()
  98. {
  99. Cancel();
  100. return Task.CompletedTask;
  101. }
  102. private async Task ConnectAsync(CancellationTokenSource reconnectCancelToken)
  103. {
  104. _connectionCancelToken?.Dispose();
  105. _combinedCancelToken?.Dispose();
  106. _connectionCancelToken = new CancellationTokenSource();
  107. _combinedCancelToken = CancellationTokenSource.CreateLinkedTokenSource(_connectionCancelToken.Token, reconnectCancelToken.Token);
  108. CancelToken = _combinedCancelToken.Token;
  109. _connectionPromise = new TaskCompletionSource<bool>();
  110. State = ConnectionState.Connecting;
  111. await _logger.InfoAsync("Connecting").ConfigureAwait(false);
  112. try
  113. {
  114. var readyPromise = new TaskCompletionSource<bool>();
  115. _readyPromise = readyPromise;
  116. //Abort connection on timeout
  117. var cancelToken = CancelToken;
  118. var _ = Task.Run(async () =>
  119. {
  120. try
  121. {
  122. await Task.Delay(_connectionTimeout, cancelToken).ConfigureAwait(false);
  123. readyPromise.TrySetException(new TimeoutException());
  124. }
  125. catch (OperationCanceledException) { }
  126. });
  127. await _onConnecting().ConfigureAwait(false);
  128. await _logger.InfoAsync("Connected").ConfigureAwait(false);
  129. State = ConnectionState.Connected;
  130. await _logger.DebugAsync("Raising Event").ConfigureAwait(false);
  131. await _connectedEvent.InvokeAsync().ConfigureAwait(false);
  132. }
  133. catch (Exception ex)
  134. {
  135. Error(ex);
  136. throw;
  137. }
  138. }
  139. private async Task DisconnectAsync(Exception ex, bool isReconnecting)
  140. {
  141. if (State == ConnectionState.Disconnected) return;
  142. State = ConnectionState.Disconnecting;
  143. await _logger.InfoAsync("Disconnecting").ConfigureAwait(false);
  144. await _onDisconnecting(ex).ConfigureAwait(false);
  145. await _disconnectedEvent.InvokeAsync(ex, isReconnecting).ConfigureAwait(false);
  146. State = ConnectionState.Disconnected;
  147. await _logger.InfoAsync("Disconnected").ConfigureAwait(false);
  148. }
  149. public async Task CompleteAsync()
  150. {
  151. await _readyPromise.TrySetResultAsync(true).ConfigureAwait(false);
  152. }
  153. public async Task WaitAsync()
  154. {
  155. await _readyPromise.Task.ConfigureAwait(false);
  156. }
  157. public void Cancel()
  158. {
  159. _readyPromise?.TrySetCanceled();
  160. _connectionPromise?.TrySetCanceled();
  161. _reconnectCancelToken?.Cancel();
  162. _connectionCancelToken?.Cancel();
  163. }
  164. public void Error(Exception ex)
  165. {
  166. _readyPromise.TrySetException(ex);
  167. _connectionPromise.TrySetException(ex);
  168. _connectionCancelToken?.Cancel();
  169. }
  170. public void CriticalError(Exception ex)
  171. {
  172. _reconnectCancelToken?.Cancel();
  173. Error(ex);
  174. }
  175. public void Reconnect()
  176. {
  177. _readyPromise.TrySetCanceled();
  178. _connectionPromise.TrySetCanceled();
  179. _connectionCancelToken?.Cancel();
  180. }
  181. private async Task AcquireConnectionLock()
  182. {
  183. while (true)
  184. {
  185. await StopAsync().ConfigureAwait(false);
  186. if (await _stateLock.WaitAsync(0).ConfigureAwait(false))
  187. break;
  188. }
  189. }
  190. protected virtual void Dispose(bool disposing)
  191. {
  192. if (!_isDisposed)
  193. {
  194. if (disposing)
  195. {
  196. _combinedCancelToken?.Dispose();
  197. _reconnectCancelToken?.Dispose();
  198. _connectionCancelToken?.Dispose();
  199. }
  200. _isDisposed = true;
  201. }
  202. }
  203. public void Dispose()
  204. {
  205. Dispose(true);
  206. }
  207. }
  208. }