* API breaking change: Specify WebSocket close code Should fix #1479 and help overall with resuming sessions. * Also try to resume on missed heartbeatstags/2.3.0
| @@ -14,7 +14,7 @@ namespace Discord.Net.WebSockets | |||
| void SetCancelToken(CancellationToken cancelToken); | |||
| Task ConnectAsync(string host); | |||
| Task DisconnectAsync(); | |||
| Task DisconnectAsync(int closeCode = 1000); | |||
| Task SendAsync(byte[] data, int index, int count, bool isText); | |||
| } | |||
| @@ -40,7 +40,7 @@ namespace Discord.Net.Providers.WS4Net | |||
| { | |||
| if (disposing) | |||
| { | |||
| DisconnectInternalAsync(true).GetAwaiter().GetResult(); | |||
| DisconnectInternalAsync(isDisposing: true).GetAwaiter().GetResult(); | |||
| _lock?.Dispose(); | |||
| _cancelTokenSource?.Dispose(); | |||
| } | |||
| @@ -92,19 +92,19 @@ namespace Discord.Net.Providers.WS4Net | |||
| _waitUntilConnect.Wait(_cancelToken); | |||
| } | |||
| public async Task DisconnectAsync() | |||
| public async Task DisconnectAsync(int closeCode = 1000) | |||
| { | |||
| await _lock.WaitAsync().ConfigureAwait(false); | |||
| try | |||
| { | |||
| await DisconnectInternalAsync().ConfigureAwait(false); | |||
| await DisconnectInternalAsync(closeCode: closeCode).ConfigureAwait(false); | |||
| } | |||
| finally | |||
| { | |||
| _lock.Release(); | |||
| } | |||
| } | |||
| private Task DisconnectInternalAsync(bool isDisposing = false) | |||
| private Task DisconnectInternalAsync(int closeCode = 1000, bool isDisposing = false) | |||
| { | |||
| _disconnectCancelTokenSource.Cancel(); | |||
| if (_client == null) | |||
| @@ -112,7 +112,7 @@ namespace Discord.Net.Providers.WS4Net | |||
| if (_client.State == WebSocketState.Open) | |||
| { | |||
| try { _client.Close(1000, ""); } | |||
| try { _client.Close(closeCode, ""); } | |||
| catch { } | |||
| } | |||
| @@ -47,7 +47,7 @@ namespace Discord.API | |||
| internal ulong? CurrentUserId { get; set; } | |||
| public RateLimitPrecision RateLimitPrecision { get; private set; } | |||
| internal bool UseSystemClock { get; set; } | |||
| internal JsonSerializer Serializer => _serializer; | |||
| /// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | |||
| @@ -164,7 +164,7 @@ namespace Discord.API | |||
| try { _loginCancelToken?.Cancel(false); } | |||
| catch { } | |||
| await DisconnectInternalAsync().ConfigureAwait(false); | |||
| await DisconnectInternalAsync(null).ConfigureAwait(false); | |||
| await RequestQueue.ClearAsync().ConfigureAwait(false); | |||
| await RequestQueue.SetCancelTokenAsync(CancellationToken.None).ConfigureAwait(false); | |||
| @@ -175,7 +175,7 @@ namespace Discord.API | |||
| } | |||
| internal virtual Task ConnectInternalAsync() => Task.Delay(0); | |||
| internal virtual Task DisconnectInternalAsync() => Task.Delay(0); | |||
| internal virtual Task DisconnectInternalAsync(Exception ex = null) => Task.Delay(0); | |||
| //Core | |||
| internal Task SendAsync(string method, Expression<Func<string>> endpointExpr, BucketIds ids, | |||
| @@ -1062,7 +1062,7 @@ namespace Discord.API | |||
| { | |||
| foreach (var roleId in args.RoleIds.Value) | |||
| Preconditions.NotEqual(roleId, 0, nameof(roleId)); | |||
| } | |||
| } | |||
| options = RequestOptions.CreateOrClone(options); | |||
| @@ -164,26 +164,17 @@ namespace Discord.API | |||
| } | |||
| } | |||
| public async Task DisconnectAsync() | |||
| public async Task DisconnectAsync(Exception ex = null) | |||
| { | |||
| await _stateLock.WaitAsync().ConfigureAwait(false); | |||
| try | |||
| { | |||
| await DisconnectInternalAsync().ConfigureAwait(false); | |||
| } | |||
| finally { _stateLock.Release(); } | |||
| } | |||
| public async Task DisconnectAsync(Exception ex) | |||
| { | |||
| await _stateLock.WaitAsync().ConfigureAwait(false); | |||
| try | |||
| { | |||
| await DisconnectInternalAsync().ConfigureAwait(false); | |||
| await DisconnectInternalAsync(ex).ConfigureAwait(false); | |||
| } | |||
| finally { _stateLock.Release(); } | |||
| } | |||
| /// <exception cref="NotSupportedException">This client is not configured with WebSocket support.</exception> | |||
| internal override async Task DisconnectInternalAsync() | |||
| internal override async Task DisconnectInternalAsync(Exception ex = null) | |||
| { | |||
| if (WebSocketClient == null) | |||
| throw new NotSupportedException("This client is not configured with WebSocket support."); | |||
| @@ -194,6 +185,9 @@ namespace Discord.API | |||
| try { _connectCancelToken?.Cancel(false); } | |||
| catch { } | |||
| if (ex is GatewayReconnectException) | |||
| await WebSocketClient.DisconnectAsync(4000); | |||
| else | |||
| await WebSocketClient.DisconnectAsync().ConfigureAwait(false); | |||
| ConnectionState = ConnectionState.Disconnected; | |||
| @@ -264,7 +264,7 @@ namespace Discord.WebSocket | |||
| { | |||
| await _gatewayLogger.DebugAsync("Disconnecting ApiClient").ConfigureAwait(false); | |||
| await ApiClient.DisconnectAsync().ConfigureAwait(false); | |||
| await ApiClient.DisconnectAsync(ex).ConfigureAwait(false); | |||
| //Wait for tasks to complete | |||
| await _gatewayLogger.DebugAsync("Waiting for heartbeater").ConfigureAwait(false); | |||
| @@ -511,7 +511,7 @@ namespace Discord.WebSocket | |||
| case GatewayOpCode.Reconnect: | |||
| { | |||
| await _gatewayLogger.DebugAsync("Received Reconnect").ConfigureAwait(false); | |||
| _connection.Error(new Exception("Server requested a reconnect")); | |||
| _connection.Error(new GatewayReconnectException("Server requested a reconnect")); | |||
| } | |||
| break; | |||
| case GatewayOpCode.Dispatch: | |||
| @@ -1689,7 +1689,7 @@ namespace Discord.WebSocket | |||
| { | |||
| if (ConnectionState == ConnectionState.Connected && (_guildDownloadTask?.IsCompleted ?? true)) | |||
| { | |||
| _connection.Error(new Exception("Server missed last heartbeat")); | |||
| _connection.Error(new GatewayReconnectException("Server missed last heartbeat")); | |||
| return; | |||
| } | |||
| } | |||
| @@ -0,0 +1,22 @@ | |||
| using System; | |||
| namespace Discord.WebSocket | |||
| { | |||
| /// <summary> | |||
| /// An exception thrown when the gateway client has been requested to | |||
| /// reconnect. | |||
| /// </summary> | |||
| public class GatewayReconnectException : Exception | |||
| { | |||
| /// <summary> | |||
| /// Creates a new instance of the | |||
| /// <see cref="GatewayReconnectException"/> type. | |||
| /// </summary> | |||
| /// <param name="message"> | |||
| /// The reason why the gateway has been requested to reconnect. | |||
| /// </param> | |||
| public GatewayReconnectException(string message) | |||
| : base(message) | |||
| { } | |||
| } | |||
| } | |||
| @@ -44,7 +44,7 @@ namespace Discord.Net.WebSockets | |||
| { | |||
| if (disposing) | |||
| { | |||
| DisconnectInternalAsync(true).GetAwaiter().GetResult(); | |||
| DisconnectInternalAsync(isDisposing: true).GetAwaiter().GetResult(); | |||
| _disconnectTokenSource?.Dispose(); | |||
| _cancelTokenSource?.Dispose(); | |||
| _lock?.Dispose(); | |||
| @@ -94,19 +94,19 @@ namespace Discord.Net.WebSockets | |||
| _task = RunAsync(_cancelToken); | |||
| } | |||
| public async Task DisconnectAsync() | |||
| public async Task DisconnectAsync(int closeCode = 1000) | |||
| { | |||
| await _lock.WaitAsync().ConfigureAwait(false); | |||
| try | |||
| { | |||
| await DisconnectInternalAsync().ConfigureAwait(false); | |||
| await DisconnectInternalAsync(closeCode: closeCode).ConfigureAwait(false); | |||
| } | |||
| finally | |||
| { | |||
| _lock.Release(); | |||
| } | |||
| } | |||
| private async Task DisconnectInternalAsync(bool isDisposing = false) | |||
| private async Task DisconnectInternalAsync(int closeCode = 1000, bool isDisposing = false) | |||
| { | |||
| try { _disconnectTokenSource.Cancel(false); } | |||
| catch { } | |||
| @@ -117,7 +117,8 @@ namespace Discord.Net.WebSockets | |||
| { | |||
| if (!isDisposing) | |||
| { | |||
| try { await _client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", new CancellationToken()); } | |||
| var status = (WebSocketCloseStatus)closeCode; | |||
| try { await _client.CloseOutputAsync(status, "", new CancellationToken()); } | |||
| catch { } | |||
| } | |||
| try { _client.Dispose(); } | |||
| @@ -141,7 +142,7 @@ namespace Discord.Net.WebSockets | |||
| await _lock.WaitAsync().ConfigureAwait(false); | |||
| try | |||
| { | |||
| await DisconnectInternalAsync(false); | |||
| await DisconnectInternalAsync(isDisposing: false); | |||
| } | |||
| finally | |||
| { | |||