* API breaking change: Specify WebSocket close code Should fix #1479 and help overall with resuming sessions. * Also try to resume on missed heartbeatspull/1503/head
@@ -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 | |||
{ | |||