Browse Source

Fixed reconnecting, and handling of early connection errors.

tags/docs-0.9
RogueException 9 years ago
parent
commit
3570205b0f
4 changed files with 78 additions and 32 deletions
  1. +6
    -1
      src/Discord.Net/DiscordClient.cs
  2. +27
    -3
      src/Discord.Net/Net/WebSockets/DataWebSocket.cs
  3. +5
    -5
      src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs
  4. +40
    -23
      src/Discord.Net/Net/WebSockets/WebSocket.cs

+ 6
- 1
src/Discord.Net/DiscordClient.cs View File

@@ -95,7 +95,12 @@ namespace Discord
_api = new DiscordAPIClient(_config.LogLevel, _config.APITimeout); _api = new DiscordAPIClient(_config.LogLevel, _config.APITimeout);
_dataSocket = new DataWebSocket(this); _dataSocket = new DataWebSocket(this);
_dataSocket.Connected += (s, e) => { if (_state == (int)DiscordClientState.Connecting) CompleteConnect(); }; _dataSocket.Connected += (s, e) => { if (_state == (int)DiscordClientState.Connecting) CompleteConnect(); };
_dataSocket.Disconnected += async (s, e) => { RaiseDisconnected(e); if (e.WasUnexpected) await _dataSocket.Login(_token); };
_dataSocket.Disconnected += async (s, e) =>
{
RaiseDisconnected(e);
if (e.WasUnexpected)
await _dataSocket.Reconnect(_token);
};
if (_config.EnableVoice) if (_config.EnableVoice)
{ {
_voiceSocket = new VoiceWebSocket(this); _voiceSocket = new VoiceWebSocket(this);


+ 27
- 3
src/Discord.Net/Net/WebSockets/DataWebSocket.cs View File

@@ -20,7 +20,7 @@ namespace Discord.Net.WebSockets


public async Task Login(string token) public async Task Login(string token)
{ {
await Connect();
await Connect().ConfigureAwait(false);
Commands.Login msg = new Commands.Login(); Commands.Login msg = new Commands.Login();
msg.Payload.Token = token; msg.Payload.Token = token;
@@ -29,14 +29,38 @@ namespace Discord.Net.WebSockets
} }
private async Task Redirect(string server) private async Task Redirect(string server)
{ {
await DisconnectInternal(isUnexpected: false);
await Connect();
await DisconnectInternal(isUnexpected: false).ConfigureAwait(false);
await Connect().ConfigureAwait(false);


var resumeMsg = new Commands.Resume(); var resumeMsg = new Commands.Resume();
resumeMsg.Payload.SessionId = _sessionId; resumeMsg.Payload.SessionId = _sessionId;
resumeMsg.Payload.Sequence = _lastSeq; resumeMsg.Payload.Sequence = _lastSeq;
QueueMessage(resumeMsg); QueueMessage(resumeMsg);
} }
public async Task Reconnect(string token)
{
try
{
var cancelToken = ParentCancelToken;
await Task.Delay(_client.Config.ReconnectDelay, cancelToken).ConfigureAwait(false);
while (!cancelToken.IsCancellationRequested)
{
try
{
await Login(token).ConfigureAwait(false);
break;
}
catch (OperationCanceledException) { throw; }
catch (Exception ex)
{
RaiseOnLog(LogMessageSeverity.Error, $"Reconnect failed: {ex.GetBaseException().Message}");
//Net is down? We can keep trying to reconnect until the user runs Disconnect()
await Task.Delay(_client.Config.FailedReconnectDelay, cancelToken).ConfigureAwait(false);
}
}
}
catch (OperationCanceledException) { }
}


protected override async Task ProcessMessage(string json) protected override async Task ProcessMessage(string json)
{ {


+ 5
- 5
src/Discord.Net/Net/WebSockets/VoiceWebSocket.cs View File

@@ -67,7 +67,7 @@ namespace Discord.Net.WebSockets
_sessionId = sessionId; _sessionId = sessionId;
_token = token; _token = token;


await Connect();
await Connect().ConfigureAwait(false);
} }
public async Task Reconnect() public async Task Reconnect()
{ {
@@ -85,7 +85,7 @@ namespace Discord.Net.WebSockets
catch (OperationCanceledException) { throw; } catch (OperationCanceledException) { throw; }
catch (Exception ex) catch (Exception ex)
{ {
RaiseOnLog(LogMessageSeverity.Error, $"DataSocket reconnect failed: {ex.GetBaseException().Message}");
RaiseOnLog(LogMessageSeverity.Error, $"Reconnect failed: {ex.GetBaseException().Message}");
//Net is down? We can keep trying to reconnect until the user runs Disconnect() //Net is down? We can keep trying to reconnect until the user runs Disconnect()
await Task.Delay(_client.Config.FailedReconnectDelay, cancelToken).ConfigureAwait(false); await Task.Delay(_client.Config.FailedReconnectDelay, cancelToken).ConfigureAwait(false);
} }
@@ -125,7 +125,7 @@ namespace Discord.Net.WebSockets
#endif #endif
}.Concat(base.Run()).ToArray(); }.Concat(base.Run()).ToArray();
} }
protected override Task Cleanup(bool wasUnexpected)
protected override Task Cleanup()
{ {
#if USE_THREAD #if USE_THREAD
_sendThread.Join(); _sendThread.Join();
@@ -133,7 +133,7 @@ namespace Discord.Net.WebSockets
#endif #endif


ClearPCMFrames(); ClearPCMFrames();
if (!wasUnexpected)
if (!_wasDisconnectUnexpected)
{ {
_serverId = null; _serverId = null;
_userId = null; _userId = null;
@@ -142,7 +142,7 @@ namespace Discord.Net.WebSockets
} }
_udp = null; _udp = null;


return base.Cleanup(wasUnexpected);
return base.Cleanup();
} }


private async Task ReceiveVoiceAsync() private async Task ReceiveVoiceAsync()


+ 40
- 23
src/Discord.Net/Net/WebSockets/WebSocket.cs View File

@@ -47,8 +47,9 @@ namespace Discord.Net.WebSockets
public WebSocketState State => (WebSocketState)_state; public WebSocketState State => (WebSocketState)_state;
protected int _state; protected int _state;


protected ExceptionDispatchInfo _disconnectReason;
private bool _wasDisconnectUnexpected;
protected ExceptionDispatchInfo _disconnectReason;
protected bool _wasDisconnectUnexpected;
protected WebSocketState _disconnectState;


public CancellationToken ParentCancelToken { get; set; } public CancellationToken ParentCancelToken { get; set; }
public CancellationToken CancelToken => _cancelToken; public CancellationToken CancelToken => _cancelToken;
@@ -78,9 +79,7 @@ namespace Discord.Net.WebSockets


try try
{ {
await Disconnect().ConfigureAwait(false);
_state = (int)WebSocketState.Connecting;
await Disconnect().ConfigureAwait(false);


_cancelTokenSource = new CancellationTokenSource(); _cancelTokenSource = new CancellationTokenSource();
if (ParentCancelToken != null) if (ParentCancelToken != null)
@@ -91,12 +90,13 @@ namespace Discord.Net.WebSockets
await _engine.Connect(Host, _cancelToken).ConfigureAwait(false); await _engine.Connect(Host, _cancelToken).ConfigureAwait(false);
_lastHeartbeat = DateTime.UtcNow; _lastHeartbeat = DateTime.UtcNow;


_state = (int)WebSocketState.Connecting;
_runTask = RunTasks(); _runTask = RunTasks();
} }
catch
catch (Exception ex)
{ {
await Disconnect().ConfigureAwait(false);
throw;
await DisconnectInternal(ex, isUnexpected: false).ConfigureAwait(false);
throw; //Dont handle this exception internally, send up it upwards
} }
} }
protected void CompleteConnect() protected void CompleteConnect()
@@ -108,33 +108,40 @@ namespace Discord.Net.WebSockets
=> Connect(_host, _cancelToken);*/ => Connect(_host, _cancelToken);*/


public Task Disconnect() => DisconnectInternal(new Exception("Disconnect was requested by user."), isUnexpected: false); public Task Disconnect() => DisconnectInternal(new Exception("Disconnect was requested by user."), isUnexpected: false);
protected Task DisconnectInternal(Exception ex = null, bool isUnexpected = true, bool skipAwait = false)
protected async Task DisconnectInternal(Exception ex = null, bool isUnexpected = true, bool skipAwait = false)
{ {
int oldState; int oldState;
bool hasWriterLock; bool hasWriterLock;


//If in either connecting or connected state, get a lock by being the first to switch to disconnecting //If in either connecting or connected state, get a lock by being the first to switch to disconnecting
oldState = Interlocked.CompareExchange(ref _state, (int)WebSocketState.Disconnecting, (int)WebSocketState.Connecting); oldState = Interlocked.CompareExchange(ref _state, (int)WebSocketState.Disconnecting, (int)WebSocketState.Connecting);
if (oldState == (int)WebSocketState.Disconnected) return TaskHelper.CompletedTask; //Already disconnected
if (oldState == (int)WebSocketState.Disconnected) return; //Already disconnected
hasWriterLock = oldState == (int)WebSocketState.Connecting; //Caused state change hasWriterLock = oldState == (int)WebSocketState.Connecting; //Caused state change
if (!hasWriterLock) if (!hasWriterLock)
{ {
oldState = Interlocked.CompareExchange(ref _state, (int)WebSocketState.Disconnecting, (int)WebSocketState.Connected); oldState = Interlocked.CompareExchange(ref _state, (int)WebSocketState.Disconnecting, (int)WebSocketState.Connected);
if (oldState == (int)WebSocketState.Disconnected) return TaskHelper.CompletedTask; //Already disconnected
if (oldState == (int)WebSocketState.Disconnected) return; //Already disconnected
hasWriterLock = oldState == (int)WebSocketState.Connected; //Caused state change hasWriterLock = oldState == (int)WebSocketState.Connected; //Caused state change
} }


if (hasWriterLock) if (hasWriterLock)
{ {
_wasDisconnectUnexpected = isUnexpected; _wasDisconnectUnexpected = isUnexpected;
_disconnectState = (WebSocketState)oldState;
_disconnectReason = ex != null ? ExceptionDispatchInfo.Capture(ex) : null; _disconnectReason = ex != null ? ExceptionDispatchInfo.Capture(ex) : null;

if (_disconnectState == WebSocketState.Connecting) //_runTask was never made
await Cleanup();
_cancelTokenSource.Cancel(); _cancelTokenSource.Cancel();
} }


if (!skipAwait) if (!skipAwait)
return _runTask ?? TaskHelper.CompletedTask;
{
Task task = _runTask ?? TaskHelper.CompletedTask;
await task;
}
else else
return TaskHelper.CompletedTask;
await TaskHelper.CompletedTask;
} }


protected virtual async Task RunTasks() protected virtual async Task RunTasks()
@@ -143,19 +150,19 @@ namespace Discord.Net.WebSockets
Task firstTask = Task.WhenAny(tasks); Task firstTask = Task.WhenAny(tasks);
Task allTasks = Task.WhenAll(tasks); Task allTasks = Task.WhenAll(tasks);


//Wait until the first task ends/errors and capture the error
try { await firstTask.ConfigureAwait(false); } try { await firstTask.ConfigureAwait(false); }
catch (Exception ex) { await DisconnectInternal(ex: ex, skipAwait: true).ConfigureAwait(false); } catch (Exception ex) { await DisconnectInternal(ex: ex, skipAwait: true).ConfigureAwait(false); }


//When the first task ends, make sure the rest do too
//Ensure all other tasks are signaled to end.
await DisconnectInternal(skipAwait: true); await DisconnectInternal(skipAwait: true);

//Wait for the remaining tasks to complete
try { await allTasks.ConfigureAwait(false); } try { await allTasks.ConfigureAwait(false); }
catch { } catch { }

bool wasUnexpected = _wasDisconnectUnexpected;
_wasDisconnectUnexpected = false;
await Cleanup(wasUnexpected).ConfigureAwait(false);
_runTask = null;
//Clean up state variables and raise disconnect event
await Cleanup().ConfigureAwait(false);
} }
protected virtual Task[] Run() protected virtual Task[] Run()
{ {
@@ -164,12 +171,22 @@ namespace Discord.Net.WebSockets
.Concat(new Task[] { HeartbeatAsync(cancelToken) }) .Concat(new Task[] { HeartbeatAsync(cancelToken) })
.ToArray(); .ToArray();
} }
protected virtual Task Cleanup(bool wasUnexpected)
protected virtual async Task Cleanup()
{ {
var disconnectState = _disconnectState;
_disconnectState = WebSocketState.Disconnected;
var wasDisconnectUnexpected = _wasDisconnectUnexpected;
_wasDisconnectUnexpected = false;
//Dont reset disconnectReason, we may called ThrowError() later

await _engine.Disconnect();
_cancelTokenSource = null; _cancelTokenSource = null;
_state = (int)WebSocketState.Disconnected;
RaiseDisconnected(wasUnexpected, _disconnectReason?.SourceException);
return _engine.Disconnect();
var oldState = _state;
_state = (int)WebSocketState.Disconnected;
_runTask = null;

if (disconnectState == WebSocketState.Connected)
RaiseDisconnected(wasDisconnectUnexpected, _disconnectReason?.SourceException);
} }


protected abstract Task ProcessMessage(string json); protected abstract Task ProcessMessage(string json);


Loading…
Cancel
Save