* 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); | void SetCancelToken(CancellationToken cancelToken); | ||||
Task ConnectAsync(string host); | Task ConnectAsync(string host); | ||||
Task DisconnectAsync(); | |||||
Task DisconnectAsync(int closeCode = 1000); | |||||
Task SendAsync(byte[] data, int index, int count, bool isText); | Task SendAsync(byte[] data, int index, int count, bool isText); | ||||
} | } | ||||
@@ -40,7 +40,7 @@ namespace Discord.Net.Providers.WS4Net | |||||
{ | { | ||||
if (disposing) | if (disposing) | ||||
{ | { | ||||
DisconnectInternalAsync(true).GetAwaiter().GetResult(); | |||||
DisconnectInternalAsync(isDisposing: true).GetAwaiter().GetResult(); | |||||
_lock?.Dispose(); | _lock?.Dispose(); | ||||
_cancelTokenSource?.Dispose(); | _cancelTokenSource?.Dispose(); | ||||
} | } | ||||
@@ -92,19 +92,19 @@ namespace Discord.Net.Providers.WS4Net | |||||
_waitUntilConnect.Wait(_cancelToken); | _waitUntilConnect.Wait(_cancelToken); | ||||
} | } | ||||
public async Task DisconnectAsync() | |||||
public async Task DisconnectAsync(int closeCode = 1000) | |||||
{ | { | ||||
await _lock.WaitAsync().ConfigureAwait(false); | await _lock.WaitAsync().ConfigureAwait(false); | ||||
try | try | ||||
{ | { | ||||
await DisconnectInternalAsync().ConfigureAwait(false); | |||||
await DisconnectInternalAsync(closeCode: closeCode).ConfigureAwait(false); | |||||
} | } | ||||
finally | finally | ||||
{ | { | ||||
_lock.Release(); | _lock.Release(); | ||||
} | } | ||||
} | } | ||||
private Task DisconnectInternalAsync(bool isDisposing = false) | |||||
private Task DisconnectInternalAsync(int closeCode = 1000, bool isDisposing = false) | |||||
{ | { | ||||
_disconnectCancelTokenSource.Cancel(); | _disconnectCancelTokenSource.Cancel(); | ||||
if (_client == null) | if (_client == null) | ||||
@@ -112,7 +112,7 @@ namespace Discord.Net.Providers.WS4Net | |||||
if (_client.State == WebSocketState.Open) | if (_client.State == WebSocketState.Open) | ||||
{ | { | ||||
try { _client.Close(1000, ""); } | |||||
try { _client.Close(closeCode, ""); } | |||||
catch { } | catch { } | ||||
} | } | ||||
@@ -47,7 +47,7 @@ namespace Discord.API | |||||
internal ulong? CurrentUserId { get; set; } | internal ulong? CurrentUserId { get; set; } | ||||
public RateLimitPrecision RateLimitPrecision { get; private set; } | public RateLimitPrecision RateLimitPrecision { get; private set; } | ||||
internal bool UseSystemClock { get; set; } | internal bool UseSystemClock { get; set; } | ||||
internal JsonSerializer Serializer => _serializer; | internal JsonSerializer Serializer => _serializer; | ||||
/// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | /// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | ||||
@@ -164,7 +164,7 @@ namespace Discord.API | |||||
try { _loginCancelToken?.Cancel(false); } | try { _loginCancelToken?.Cancel(false); } | ||||
catch { } | catch { } | ||||
await DisconnectInternalAsync().ConfigureAwait(false); | |||||
await DisconnectInternalAsync(null).ConfigureAwait(false); | |||||
await RequestQueue.ClearAsync().ConfigureAwait(false); | await RequestQueue.ClearAsync().ConfigureAwait(false); | ||||
await RequestQueue.SetCancelTokenAsync(CancellationToken.None).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 ConnectInternalAsync() => Task.Delay(0); | ||||
internal virtual Task DisconnectInternalAsync() => Task.Delay(0); | |||||
internal virtual Task DisconnectInternalAsync(Exception ex = null) => Task.Delay(0); | |||||
//Core | //Core | ||||
internal Task SendAsync(string method, Expression<Func<string>> endpointExpr, BucketIds ids, | 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) | foreach (var roleId in args.RoleIds.Value) | ||||
Preconditions.NotEqual(roleId, 0, nameof(roleId)); | Preconditions.NotEqual(roleId, 0, nameof(roleId)); | ||||
} | |||||
} | |||||
options = RequestOptions.CreateOrClone(options); | 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); | await _stateLock.WaitAsync().ConfigureAwait(false); | ||||
try | 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(); } | finally { _stateLock.Release(); } | ||||
} | } | ||||
/// <exception cref="NotSupportedException">This client is not configured with WebSocket support.</exception> | /// <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) | if (WebSocketClient == null) | ||||
throw new NotSupportedException("This client is not configured with WebSocket support."); | throw new NotSupportedException("This client is not configured with WebSocket support."); | ||||
@@ -194,6 +185,9 @@ namespace Discord.API | |||||
try { _connectCancelToken?.Cancel(false); } | try { _connectCancelToken?.Cancel(false); } | ||||
catch { } | catch { } | ||||
if (ex is GatewayReconnectException) | |||||
await WebSocketClient.DisconnectAsync(4000); | |||||
else | |||||
await WebSocketClient.DisconnectAsync().ConfigureAwait(false); | await WebSocketClient.DisconnectAsync().ConfigureAwait(false); | ||||
ConnectionState = ConnectionState.Disconnected; | ConnectionState = ConnectionState.Disconnected; | ||||
@@ -264,7 +264,7 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
await _gatewayLogger.DebugAsync("Disconnecting ApiClient").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Disconnecting ApiClient").ConfigureAwait(false); | ||||
await ApiClient.DisconnectAsync().ConfigureAwait(false); | |||||
await ApiClient.DisconnectAsync(ex).ConfigureAwait(false); | |||||
//Wait for tasks to complete | //Wait for tasks to complete | ||||
await _gatewayLogger.DebugAsync("Waiting for heartbeater").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Waiting for heartbeater").ConfigureAwait(false); | ||||
@@ -511,7 +511,7 @@ namespace Discord.WebSocket | |||||
case GatewayOpCode.Reconnect: | case GatewayOpCode.Reconnect: | ||||
{ | { | ||||
await _gatewayLogger.DebugAsync("Received Reconnect").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Received Reconnect").ConfigureAwait(false); | ||||
_connection.Error(new Exception("Server requested a reconnect")); | |||||
_connection.Error(new GatewayReconnectException("Server requested a reconnect")); | |||||
} | } | ||||
break; | break; | ||||
case GatewayOpCode.Dispatch: | case GatewayOpCode.Dispatch: | ||||
@@ -1689,7 +1689,7 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
if (ConnectionState == ConnectionState.Connected && (_guildDownloadTask?.IsCompleted ?? true)) | if (ConnectionState == ConnectionState.Connected && (_guildDownloadTask?.IsCompleted ?? true)) | ||||
{ | { | ||||
_connection.Error(new Exception("Server missed last heartbeat")); | |||||
_connection.Error(new GatewayReconnectException("Server missed last heartbeat")); | |||||
return; | 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) | if (disposing) | ||||
{ | { | ||||
DisconnectInternalAsync(true).GetAwaiter().GetResult(); | |||||
DisconnectInternalAsync(isDisposing: true).GetAwaiter().GetResult(); | |||||
_disconnectTokenSource?.Dispose(); | _disconnectTokenSource?.Dispose(); | ||||
_cancelTokenSource?.Dispose(); | _cancelTokenSource?.Dispose(); | ||||
_lock?.Dispose(); | _lock?.Dispose(); | ||||
@@ -94,19 +94,19 @@ namespace Discord.Net.WebSockets | |||||
_task = RunAsync(_cancelToken); | _task = RunAsync(_cancelToken); | ||||
} | } | ||||
public async Task DisconnectAsync() | |||||
public async Task DisconnectAsync(int closeCode = 1000) | |||||
{ | { | ||||
await _lock.WaitAsync().ConfigureAwait(false); | await _lock.WaitAsync().ConfigureAwait(false); | ||||
try | try | ||||
{ | { | ||||
await DisconnectInternalAsync().ConfigureAwait(false); | |||||
await DisconnectInternalAsync(closeCode: closeCode).ConfigureAwait(false); | |||||
} | } | ||||
finally | finally | ||||
{ | { | ||||
_lock.Release(); | _lock.Release(); | ||||
} | } | ||||
} | } | ||||
private async Task DisconnectInternalAsync(bool isDisposing = false) | |||||
private async Task DisconnectInternalAsync(int closeCode = 1000, bool isDisposing = false) | |||||
{ | { | ||||
try { _disconnectTokenSource.Cancel(false); } | try { _disconnectTokenSource.Cancel(false); } | ||||
catch { } | catch { } | ||||
@@ -117,7 +117,8 @@ namespace Discord.Net.WebSockets | |||||
{ | { | ||||
if (!isDisposing) | if (!isDisposing) | ||||
{ | { | ||||
try { await _client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", new CancellationToken()); } | |||||
var status = (WebSocketCloseStatus)closeCode; | |||||
try { await _client.CloseOutputAsync(status, "", new CancellationToken()); } | |||||
catch { } | catch { } | ||||
} | } | ||||
try { _client.Dispose(); } | try { _client.Dispose(); } | ||||
@@ -141,7 +142,7 @@ namespace Discord.Net.WebSockets | |||||
await _lock.WaitAsync().ConfigureAwait(false); | await _lock.WaitAsync().ConfigureAwait(false); | ||||
try | try | ||||
{ | { | ||||
await DisconnectInternalAsync(false); | |||||
await DisconnectInternalAsync(isDisposing: false); | |||||
} | } | ||||
finally | finally | ||||
{ | { | ||||