From 375c25c8130ee3b89e015672b23ee2e85292b32f Mon Sep 17 00:00:00 2001 From: RogueException Date: Sat, 26 Dec 2015 03:58:43 -0400 Subject: [PATCH] Improve async and reconnect stability. Added support for websocket 1012 --- src/Discord.Net.Commands/CommandService.cs | 6 +-- src/Discord.Net.Net45/Discord.Net.csproj | 3 ++ .../API/Client/GatewaySocket/OpCodes.cs | 2 +- src/Discord.Net/DiscordClient.Obsolete.cs | 2 +- src/Discord.Net/DiscordClient.cs | 4 +- src/Discord.Net/Models/Channel.cs | 2 +- src/Discord.Net/Net/WebSocketException.cs | 25 +++++++++++++ .../Net/WebSockets/GatewaySocket.cs | 37 ++++++++++++------- .../Net/WebSockets/WS4NetEngine.cs | 8 +--- src/Discord.Net/Net/WebSockets/WebSocket.cs | 2 +- 10 files changed, 62 insertions(+), 29 deletions(-) create mode 100644 src/Discord.Net/Net/WebSocketException.cs diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index c21bd2356..bf1785565 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -51,14 +51,14 @@ namespace Discord.Commands .Description("Returns information about commands.") .Do(async e => { - Channel replyChannel = _config.HelpMode == HelpMode.Public ? e.Channel : await e.User.CreateChannel(); + Channel replyChannel = _config.HelpMode == HelpMode.Public ? e.Channel : await e.User.CreateChannel().ConfigureAwait(false); if (e.Args.Length > 0) //Show command help { var map = _map.GetItem(string.Join(" ", e.Args)); if (map != null) - await ShowCommandHelp(map, e.User, e.Channel, replyChannel); + await ShowCommandHelp(map, e.User, e.Channel, replyChannel).ConfigureAwait(false); else - await replyChannel.SendMessage("Unable to display help: Unknown command."); + await replyChannel.SendMessage("Unable to display help: Unknown command.").ConfigureAwait(false); } else //Show general help await ShowGeneralHelp(e.User, e.Channel, replyChannel); diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj index 3ec6c67e1..21bdc5dd4 100644 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ b/src/Discord.Net.Net45/Discord.Net.csproj @@ -509,6 +509,9 @@ Net\TimeoutException.cs + + Net\WebSockets\WebSocketException.cs + Net\WebSockets\GatewaySocket.cs diff --git a/src/Discord.Net/API/Client/GatewaySocket/OpCodes.cs b/src/Discord.Net/API/Client/GatewaySocket/OpCodes.cs index 9942c670e..3a62aa86d 100644 --- a/src/Discord.Net/API/Client/GatewaySocket/OpCodes.cs +++ b/src/Discord.Net/API/Client/GatewaySocket/OpCodes.cs @@ -19,6 +19,6 @@ /// C←S - Used to notify a client that they must reconnect to another gateway. Redirect = 7, /// C→S - Used to request all members that were withheld by large_threshold - RequestGuildMembers = 8 + RequestGuildMembers = 99 } } diff --git a/src/Discord.Net/DiscordClient.Obsolete.cs b/src/Discord.Net/DiscordClient.Obsolete.cs index 153253d40..0c2f77255 100644 --- a/src/Discord.Net/DiscordClient.Obsolete.cs +++ b/src/Discord.Net/DiscordClient.Obsolete.cs @@ -159,7 +159,7 @@ namespace Discord.Legacy if (messages == null) throw new ArgumentNullException(nameof(messages)); foreach (var message in messages) - await message.Delete(); + await message.Delete().ConfigureAwait(false); } [Obsolete("Use Channel.DownloadMessages")] diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index dac7bcfa9..6063b2271 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -128,7 +128,7 @@ namespace Discord Connected += async (s, e) => { ClientAPI.CancelToken = CancelToken; - await SendStatus(); + await SendStatus().ConfigureAwait(false); }; //Extensibility @@ -250,7 +250,7 @@ namespace Discord } //Cache other stuff - var regionsResponse = (await ClientAPI.Send(new GetVoiceRegionsRequest())); + var regionsResponse = (await ClientAPI.Send(new GetVoiceRegionsRequest()).ConfigureAwait(false)); _regions = regionsResponse.Select(x => new Region(x.Id, x.Name, x.Hostname, x.Port)) .ToDictionary(x => x.Id); break; diff --git a/src/Discord.Net/Models/Channel.cs b/src/Discord.Net/Models/Channel.cs index c178beec2..8bcc307c8 100644 --- a/src/Discord.Net/Models/Channel.cs +++ b/src/Discord.Net/Models/Channel.cs @@ -213,7 +213,7 @@ namespace Discord #region Invites /// Gets all active (non-expired) invites to this server. public async Task> GetInvites() - => (await Server.GetInvites()).Where(x => x.Channel.Id == Id); + => (await Server.GetInvites().ConfigureAwait(false)).Where(x => x.Channel.Id == Id); /// Creates a new invite to this channel. /// Time (in seconds) until the invite expires. Set to null to never expire. diff --git a/src/Discord.Net/Net/WebSocketException.cs b/src/Discord.Net/Net/WebSocketException.cs new file mode 100644 index 000000000..b845d90c4 --- /dev/null +++ b/src/Discord.Net/Net/WebSocketException.cs @@ -0,0 +1,25 @@ +using System; + +namespace Discord.Net +{ + public class WebSocketException : Exception + { + public int Code { get; } + public string Reason { get; } + + public WebSocketException(int code, string reason) + : base(GenerateMessage(code, reason)) + { + Code = code; + Reason = reason; + } + + private static string GenerateMessage(int? code, string reason) + { + if (!String.IsNullOrEmpty(reason)) + return $"Received close code {code}: {reason}"; + else + return $"Received close code {code}"; + } + } +} diff --git a/src/Discord.Net/Net/WebSockets/GatewaySocket.cs b/src/Discord.Net/Net/WebSockets/GatewaySocket.cs index 14da0fc10..b227c550e 100644 --- a/src/Discord.Net/Net/WebSockets/GatewaySocket.cs +++ b/src/Discord.Net/Net/WebSockets/GatewaySocket.cs @@ -13,8 +13,10 @@ namespace Discord.Net.WebSockets { private int _lastSequence; private string _sessionId; + private string _token; + private int _reconnects; - public string Token { get; internal set; } + public string Token { get { return _token; } internal set { _token = value; _sessionId = null; } } public GatewaySocket(DiscordClient client, JsonSerializer serializer, Logger logger) : base(client, serializer, logger) @@ -29,20 +31,21 @@ namespace Discord.Net.WebSockets public async Task Connect() { await BeginConnect().ConfigureAwait(false); - SendIdentify(Token); + if (_sessionId == null) + SendIdentify(Token); + else + SendResume(); } - private async Task Redirect() - { - await BeginConnect().ConfigureAwait(false); - SendResume(); - } private async Task Reconnect() { try { var cancelToken = ParentCancelToken.Value; - await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false); - while (!cancelToken.IsCancellationRequested) + if (_reconnects++ == 0) + await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false); + else + await Task.Delay(_client.Config.FailedReconnectDelay, cancelToken).ConfigureAwait(false); + while (!cancelToken.IsCancellationRequested) { try { @@ -69,8 +72,15 @@ namespace Discord.Net.WebSockets tasks.Add(HeartbeatAsync(CancelToken)); await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false); } + protected override Task Cleanup() + { + var ex = _taskManager.Exception; + if (ex != null && (ex as WebSocketException)?.Code != 1012) + _sessionId = null; //Reset session unless close code 1012 + return base.Cleanup(); + } - protected override async Task ProcessMessage(string json) + protected override async Task ProcessMessage(string json) { await base.ProcessMessage(json).ConfigureAwait(false); var msg = JsonConvert.DeserializeObject(json); @@ -85,9 +95,10 @@ namespace Discord.Net.WebSockets JToken token = msg.Payload as JToken; if (msg.Type == "READY") { - var payload = token.ToObject(_serializer); + _reconnects = 0; + var payload = token.ToObject(_serializer); _sessionId = payload.SessionId; - _heartbeatInterval = payload.HeartbeatInterval; + _heartbeatInterval = payload.HeartbeatInterval; } else if (msg.Type == "RESUMED") { @@ -106,7 +117,7 @@ namespace Discord.Net.WebSockets { Host = payload.Url; Logger.Info("Redirected to " + payload.Url); - await Redirect().ConfigureAwait(false); + await Reconnect().ConfigureAwait(false); } } break; diff --git a/src/Discord.Net/Net/WebSockets/WS4NetEngine.cs b/src/Discord.Net/Net/WebSockets/WS4NetEngine.cs index 54261493a..5c1372d95 100644 --- a/src/Discord.Net/Net/WebSockets/WS4NetEngine.cs +++ b/src/Discord.Net/Net/WebSockets/WS4NetEngine.cs @@ -83,13 +83,7 @@ namespace Discord.Net.WebSockets { Exception ex; if (e is ClosedEventArgs) - { - int code = (e as ClosedEventArgs).Code; - string reason = (e as ClosedEventArgs).Reason; - if (String.IsNullOrEmpty(reason)) - reason = "No reason"; - ex = new Exception($"Received close code {code}: {reason}"); - } + ex = new WebSocketException((e as ClosedEventArgs).Code, (e as ClosedEventArgs).Reason); else ex = new Exception($"Connection lost"); _taskManager.SignalError(ex, isUnexpected: true); diff --git a/src/Discord.Net/Net/WebSockets/WebSocket.cs b/src/Discord.Net/Net/WebSockets/WebSocket.cs index 49e630c75..006c0d180 100644 --- a/src/Discord.Net/Net/WebSockets/WebSocket.cs +++ b/src/Discord.Net/Net/WebSockets/WebSocket.cs @@ -125,7 +125,7 @@ namespace Discord.Net.WebSockets _cancelTokenSource = null; _connectedEvent.Reset(); - if (oldState == ConnectionState.Connected) + if (oldState == ConnectionState.Connecting || oldState == ConnectionState.Connected) { var ex = _taskManager.Exception; if (ex == null)