Improve async and reconnect stability. Added support for websocket 1012

This commit is contained in:
RogueException
2015-12-26 03:58:43 -04:00
parent 75174e67bd
commit 375c25c813
10 changed files with 62 additions and 29 deletions

View File

@@ -51,14 +51,14 @@ namespace Discord.Commands
.Description("Returns information about commands.") .Description("Returns information about commands.")
.Do(async e => .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 if (e.Args.Length > 0) //Show command help
{ {
var map = _map.GetItem(string.Join(" ", e.Args)); var map = _map.GetItem(string.Join(" ", e.Args));
if (map != null) if (map != null)
await ShowCommandHelp(map, e.User, e.Channel, replyChannel); await ShowCommandHelp(map, e.User, e.Channel, replyChannel).ConfigureAwait(false);
else 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 else //Show general help
await ShowGeneralHelp(e.User, e.Channel, replyChannel); await ShowGeneralHelp(e.User, e.Channel, replyChannel);

View File

@@ -509,6 +509,9 @@
<Compile Include="..\Discord.Net\Net\TimeoutException.cs"> <Compile Include="..\Discord.Net\Net\TimeoutException.cs">
<Link>Net\TimeoutException.cs</Link> <Link>Net\TimeoutException.cs</Link>
</Compile> </Compile>
<Compile Include="..\Discord.Net\Net\WebSocketException.cs">
<Link>Net\WebSockets\WebSocketException.cs</Link>
</Compile>
<Compile Include="..\Discord.Net\Net\WebSockets\GatewaySocket.cs"> <Compile Include="..\Discord.Net\Net\WebSockets\GatewaySocket.cs">
<Link>Net\WebSockets\GatewaySocket.cs</Link> <Link>Net\WebSockets\GatewaySocket.cs</Link>
</Compile> </Compile>

View File

@@ -19,6 +19,6 @@
/// <summary> C←S - Used to notify a client that they must reconnect to another gateway. </summary> /// <summary> C←S - Used to notify a client that they must reconnect to another gateway. </summary>
Redirect = 7, Redirect = 7,
/// <summary> C→S - Used to request all members that were withheld by large_threshold </summary> /// <summary> C→S - Used to request all members that were withheld by large_threshold </summary>
RequestGuildMembers = 8 RequestGuildMembers = 99
} }
} }

View File

@@ -159,7 +159,7 @@ namespace Discord.Legacy
if (messages == null) throw new ArgumentNullException(nameof(messages)); if (messages == null) throw new ArgumentNullException(nameof(messages));
foreach (var message in messages) foreach (var message in messages)
await message.Delete(); await message.Delete().ConfigureAwait(false);
} }
[Obsolete("Use Channel.DownloadMessages")] [Obsolete("Use Channel.DownloadMessages")]

View File

@@ -128,7 +128,7 @@ namespace Discord
Connected += async (s, e) => Connected += async (s, e) =>
{ {
ClientAPI.CancelToken = CancelToken; ClientAPI.CancelToken = CancelToken;
await SendStatus(); await SendStatus().ConfigureAwait(false);
}; };
//Extensibility //Extensibility
@@ -250,7 +250,7 @@ namespace Discord
} }
//Cache other stuff //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)) _regions = regionsResponse.Select(x => new Region(x.Id, x.Name, x.Hostname, x.Port))
.ToDictionary(x => x.Id); .ToDictionary(x => x.Id);
break; break;

View File

@@ -213,7 +213,7 @@ namespace Discord
#region Invites #region Invites
/// <summary> Gets all active (non-expired) invites to this server. </summary> /// <summary> Gets all active (non-expired) invites to this server. </summary>
public async Task<IEnumerable<Invite>> GetInvites() public async Task<IEnumerable<Invite>> GetInvites()
=> (await Server.GetInvites()).Where(x => x.Channel.Id == Id); => (await Server.GetInvites().ConfigureAwait(false)).Where(x => x.Channel.Id == Id);
/// <summary> Creates a new invite to this channel. </summary> /// <summary> Creates a new invite to this channel. </summary>
/// <param name="maxAge"> Time (in seconds) until the invite expires. Set to null to never expire. </param> /// <param name="maxAge"> Time (in seconds) until the invite expires. Set to null to never expire. </param>

View File

@@ -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}";
}
}
}

View File

@@ -13,8 +13,10 @@ namespace Discord.Net.WebSockets
{ {
private int _lastSequence; private int _lastSequence;
private string _sessionId; 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) public GatewaySocket(DiscordClient client, JsonSerializer serializer, Logger logger)
: base(client, serializer, logger) : base(client, serializer, logger)
@@ -29,20 +31,21 @@ namespace Discord.Net.WebSockets
public async Task Connect() public async Task Connect()
{ {
await BeginConnect().ConfigureAwait(false); 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() private async Task Reconnect()
{ {
try try
{ {
var cancelToken = ParentCancelToken.Value; var cancelToken = ParentCancelToken.Value;
await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false); if (_reconnects++ == 0)
while (!cancelToken.IsCancellationRequested) await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false);
else
await Task.Delay(_client.Config.FailedReconnectDelay, cancelToken).ConfigureAwait(false);
while (!cancelToken.IsCancellationRequested)
{ {
try try
{ {
@@ -69,8 +72,15 @@ namespace Discord.Net.WebSockets
tasks.Add(HeartbeatAsync(CancelToken)); tasks.Add(HeartbeatAsync(CancelToken));
await _taskManager.Start(tasks, _cancelTokenSource).ConfigureAwait(false); 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); await base.ProcessMessage(json).ConfigureAwait(false);
var msg = JsonConvert.DeserializeObject<WebSocketMessage>(json); var msg = JsonConvert.DeserializeObject<WebSocketMessage>(json);
@@ -85,9 +95,10 @@ namespace Discord.Net.WebSockets
JToken token = msg.Payload as JToken; JToken token = msg.Payload as JToken;
if (msg.Type == "READY") if (msg.Type == "READY")
{ {
var payload = token.ToObject<ReadyEvent>(_serializer); _reconnects = 0;
var payload = token.ToObject<ReadyEvent>(_serializer);
_sessionId = payload.SessionId; _sessionId = payload.SessionId;
_heartbeatInterval = payload.HeartbeatInterval; _heartbeatInterval = payload.HeartbeatInterval;
} }
else if (msg.Type == "RESUMED") else if (msg.Type == "RESUMED")
{ {
@@ -106,7 +117,7 @@ namespace Discord.Net.WebSockets
{ {
Host = payload.Url; Host = payload.Url;
Logger.Info("Redirected to " + payload.Url); Logger.Info("Redirected to " + payload.Url);
await Redirect().ConfigureAwait(false); await Reconnect().ConfigureAwait(false);
} }
} }
break; break;

View File

@@ -83,13 +83,7 @@ namespace Discord.Net.WebSockets
{ {
Exception ex; Exception ex;
if (e is ClosedEventArgs) if (e is ClosedEventArgs)
{ ex = new WebSocketException((e as ClosedEventArgs).Code, (e as ClosedEventArgs).Reason);
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}");
}
else else
ex = new Exception($"Connection lost"); ex = new Exception($"Connection lost");
_taskManager.SignalError(ex, isUnexpected: true); _taskManager.SignalError(ex, isUnexpected: true);

View File

@@ -125,7 +125,7 @@ namespace Discord.Net.WebSockets
_cancelTokenSource = null; _cancelTokenSource = null;
_connectedEvent.Reset(); _connectedEvent.Reset();
if (oldState == ConnectionState.Connected) if (oldState == ConnectionState.Connecting || oldState == ConnectionState.Connected)
{ {
var ex = _taskManager.Exception; var ex = _taskManager.Exception;
if (ex == null) if (ex == null)