@@ -177,7 +177,7 @@ namespace Discord.Net.WebSockets | |||||
if (packetLength > 0 && endpoint.Equals(_endpoint)) | if (packetLength > 0 && endpoint.Equals(_endpoint)) | ||||
{ | { | ||||
if (_state != (int)ConnectionState.Connected) | |||||
if (_state != ConnectionState.Connected) | |||||
{ | { | ||||
if (packetLength != 70) | if (packetLength != 70) | ||||
return; | return; | ||||
@@ -247,7 +247,7 @@ namespace Discord.Net.WebSockets | |||||
{ | { | ||||
try | try | ||||
{ | { | ||||
while (!cancelToken.IsCancellationRequested && _state != (int)ConnectionState.Connected) | |||||
while (!cancelToken.IsCancellationRequested && _state != ConnectionState.Connected) | |||||
Thread.Sleep(1); | Thread.Sleep(1); | ||||
if (cancelToken.IsCancellationRequested) | if (cancelToken.IsCancellationRequested) | ||||
@@ -399,7 +399,7 @@ namespace Discord.Net.WebSockets | |||||
{ | { | ||||
case VoiceOpCodes.Ready: | case VoiceOpCodes.Ready: | ||||
{ | { | ||||
if (_state != (int)ConnectionState.Connected) | |||||
if (_state != ConnectionState.Connected) | |||||
{ | { | ||||
var payload = (msg.Payload as JToken).ToObject<VoiceReadyEvent>(_serializer); | var payload = (msg.Payload as JToken).ToObject<VoiceReadyEvent>(_serializer); | ||||
_heartbeatInterval = payload.HeartbeatInterval; | _heartbeatInterval = payload.HeartbeatInterval; | ||||
@@ -488,7 +488,7 @@ namespace Discord.Net.WebSockets | |||||
} | } | ||||
catch (OperationCanceledException) | catch (OperationCanceledException) | ||||
{ | { | ||||
ThrowError(); | |||||
_taskManager.ThrowException(); | |||||
} | } | ||||
}); | }); | ||||
} | } | ||||
@@ -68,8 +68,8 @@ namespace Discord | |||||
private readonly DiscordConfig _config; | private readonly DiscordConfig _config; | ||||
/// <summary> Returns the current connection state of this client. </summary> | /// <summary> Returns the current connection state of this client. </summary> | ||||
public ConnectionState State => (ConnectionState)_state; | |||||
private int _state; | |||||
public ConnectionState State => _state; | |||||
private ConnectionState _state; | |||||
/// <summary> Gives direct access to the underlying DiscordAPIClient. This can be used to modify objects not in cache. </summary> | /// <summary> Gives direct access to the underlying DiscordAPIClient. This can be used to modify objects not in cache. </summary> | ||||
public DiscordAPIClient APIClient => _api; | public DiscordAPIClient APIClient => _api; | ||||
@@ -145,7 +145,7 @@ namespace Discord | |||||
var settings = new JsonSerializerSettings(); | var settings = new JsonSerializerSettings(); | ||||
_webSocket.Connected += (s, e) => | _webSocket.Connected += (s, e) => | ||||
{ | { | ||||
if (_state == (int)ConnectionState.Connecting) | |||||
if (_state == ConnectionState.Connecting) | |||||
EndConnect(); | EndConnect(); | ||||
}; | }; | ||||
_webSocket.Disconnected += (s, e) => | _webSocket.Disconnected += (s, e) => | ||||
@@ -277,7 +277,7 @@ namespace Discord | |||||
if (!_sentInitialLog) | if (!_sentInitialLog) | ||||
SendInitialLog(); | SendInitialLog(); | ||||
if (State != (int)ConnectionState.Disconnected) | |||||
if (State != ConnectionState.Disconnected) | |||||
await Disconnect().ConfigureAwait(false); | await Disconnect().ConfigureAwait(false); | ||||
_token = token; | _token = token; | ||||
@@ -293,7 +293,8 @@ namespace Discord | |||||
try | try | ||||
{ | { | ||||
await _taskManager.Stop().ConfigureAwait(false); | await _taskManager.Stop().ConfigureAwait(false); | ||||
_state = (int)ConnectionState.Connecting; | |||||
_taskManager.ClearException(); | |||||
_state = ConnectionState.Connecting; | |||||
var gatewayResponse = await _api.Gateway().ConfigureAwait(false); | var gatewayResponse = await _api.Gateway().ConfigureAwait(false); | ||||
string gateway = gatewayResponse.Url; | string gateway = gatewayResponse.Url; | ||||
@@ -326,7 +327,7 @@ namespace Discord | |||||
} | } | ||||
catch (OperationCanceledException) | catch (OperationCanceledException) | ||||
{ | { | ||||
_webSocket.ThrowError(); //Throws data socket's internal error if any occured | |||||
_webSocket.TaskManager.ThrowException(); //Throws data socket's internal error if any occured | |||||
throw; | throw; | ||||
} | } | ||||
} | } | ||||
@@ -335,15 +336,15 @@ namespace Discord | |||||
_lock.Release(); | _lock.Release(); | ||||
} | } | ||||
} | } | ||||
catch | |||||
catch (Exception ex) | |||||
{ | { | ||||
await Disconnect().ConfigureAwait(false); | |||||
_taskManager.SignalError(ex, true); | |||||
throw; | throw; | ||||
} | } | ||||
} | } | ||||
private void EndConnect() | private void EndConnect() | ||||
{ | { | ||||
_state = (int)ConnectionState.Connected; | |||||
_state = ConnectionState.Connected; | |||||
_connectedEvent.Set(); | _connectedEvent.Set(); | ||||
RaiseConnected(); | RaiseConnected(); | ||||
} | } | ||||
@@ -352,8 +353,9 @@ namespace Discord | |||||
public Task Disconnect() => _taskManager.Stop(); | public Task Disconnect() => _taskManager.Stop(); | ||||
private async Task Cleanup() | private async Task Cleanup() | ||||
{ | |||||
if (Config.UseMessageQueue) | |||||
{ | |||||
_state = ConnectionState.Disconnecting; | |||||
if (Config.UseMessageQueue) | |||||
{ | { | ||||
MessageQueueItem ignored; | MessageQueueItem ignored; | ||||
while (_pendingMessages.TryDequeue(out ignored)) { } | while (_pendingMessages.TryDequeue(out ignored)) { } | ||||
@@ -373,8 +375,8 @@ namespace Discord | |||||
_token = null; | _token = null; | ||||
_state = (int)ConnectionState.Disconnected; | _state = (int)ConnectionState.Disconnected; | ||||
_disconnectedEvent.Set(); | |||||
_connectedEvent.Reset(); | _connectedEvent.Reset(); | ||||
_disconnectedEvent.Set(); | |||||
} | } | ||||
private void OnReceivedEvent(WebSocketEventEventArgs e) | private void OnReceivedEvent(WebSocketEventEventArgs e) | ||||
@@ -822,11 +824,11 @@ namespace Discord | |||||
{ | { | ||||
switch (_state) | switch (_state) | ||||
{ | { | ||||
case (int)ConnectionState.Disconnecting: | |||||
case ConnectionState.Disconnecting: | |||||
throw new InvalidOperationException("The client is disconnecting."); | throw new InvalidOperationException("The client is disconnecting."); | ||||
case (int)ConnectionState.Disconnected: | |||||
case ConnectionState.Disconnected: | |||||
throw new InvalidOperationException("The client is not connected to Discord"); | throw new InvalidOperationException("The client is not connected to Discord"); | ||||
case (int)ConnectionState.Connecting: | |||||
case ConnectionState.Connecting: | |||||
throw new InvalidOperationException("The client is connecting."); | throw new InvalidOperationException("The client is connecting."); | ||||
} | } | ||||
} | } | ||||
@@ -16,8 +16,8 @@ namespace Discord | |||||
private CancellationTokenSource _cancelSource; | private CancellationTokenSource _cancelSource; | ||||
private Task _task; | private Task _task; | ||||
public bool WasUnexpected => _wasUnexpected; | |||||
private bool _wasUnexpected; | |||||
public bool WasUnexpected => _wasStopUnexpected; | |||||
private bool _wasStopUnexpected; | |||||
public Exception Exception => _stopReason.SourceException; | public Exception Exception => _stopReason.SourceException; | ||||
private ExceptionDispatchInfo _stopReason; | private ExceptionDispatchInfo _stopReason; | ||||
@@ -53,7 +53,7 @@ namespace Discord | |||||
continue; //Another thread sneaked in and started this manager before we got a lock, loop and try again | continue; //Another thread sneaked in and started this manager before we got a lock, loop and try again | ||||
_stopReason = null; | _stopReason = null; | ||||
_wasUnexpected = false; | |||||
_wasStopUnexpected = false; | |||||
Task[] tasksArray = tasks.ToArray(); | Task[] tasksArray = tasks.ToArray(); | ||||
Task<Task> anyTask = Task.WhenAny(tasksArray); | Task<Task> anyTask = Task.WhenAny(tasksArray); | ||||
@@ -74,8 +74,10 @@ namespace Discord | |||||
await allTasks.ConfigureAwait(false); | await allTasks.ConfigureAwait(false); | ||||
//Run the cleanup function within our lock | //Run the cleanup function within our lock | ||||
await _stopAction().ConfigureAwait(false); | |||||
if (_stopAction != null) | |||||
await _stopAction().ConfigureAwait(false); | |||||
_task = null; | _task = null; | ||||
_cancelSource = null; | |||||
}); | }); | ||||
return; | return; | ||||
} | } | ||||
@@ -89,7 +91,8 @@ namespace Discord | |||||
if (_task == null) return; //Are we running? | if (_task == null) return; //Are we running? | ||||
if (_cancelSource.IsCancellationRequested) return; | if (_cancelSource.IsCancellationRequested) return; | ||||
_cancelSource.Cancel(); | |||||
if (_cancelSource != null) | |||||
_cancelSource.Cancel(); | |||||
} | } | ||||
} | } | ||||
public Task Stop() | public Task Stop() | ||||
@@ -102,7 +105,8 @@ namespace Discord | |||||
if (task == null) return TaskHelper.CompletedTask; //Are we running? | if (task == null) return TaskHelper.CompletedTask; //Are we running? | ||||
if (_cancelSource.IsCancellationRequested) return task; | if (_cancelSource.IsCancellationRequested) return task; | ||||
_cancelSource.Cancel(); | |||||
if (_cancelSource != null) | |||||
_cancelSource.Cancel(); | |||||
} | } | ||||
return task; | return task; | ||||
} | } | ||||
@@ -111,11 +115,12 @@ namespace Discord | |||||
{ | { | ||||
lock (_lock) | lock (_lock) | ||||
{ | { | ||||
if (_task == null) return; //Are we running? | |||||
if (_stopReason != null) return; | |||||
_stopReason = ExceptionDispatchInfo.Capture(ex); | _stopReason = ExceptionDispatchInfo.Capture(ex); | ||||
_wasUnexpected = isUnexpected; | |||||
_cancelSource.Cancel(); | |||||
_wasStopUnexpected = isUnexpected; | |||||
if (_cancelSource != null) | |||||
_cancelSource.Cancel(); | |||||
} | } | ||||
} | } | ||||
public Task Error(Exception ex, bool isUnexpected = true) | public Task Error(Exception ex, bool isUnexpected = true) | ||||
@@ -123,20 +128,22 @@ namespace Discord | |||||
Task task; | Task task; | ||||
lock (_lock) | lock (_lock) | ||||
{ | { | ||||
if (_stopReason != null) return TaskHelper.CompletedTask; | |||||
//Cache the task so we still have something to await if Cleanup is run really quickly | //Cache the task so we still have something to await if Cleanup is run really quickly | ||||
task = _task; | |||||
if (task == null) return TaskHelper.CompletedTask; //Are we running? | |||||
task = _task ?? TaskHelper.CompletedTask; | |||||
if (_cancelSource.IsCancellationRequested) return task; | if (_cancelSource.IsCancellationRequested) return task; | ||||
_stopReason = ExceptionDispatchInfo.Capture(ex); | _stopReason = ExceptionDispatchInfo.Capture(ex); | ||||
_wasUnexpected = isUnexpected; | |||||
_cancelSource.Cancel(); | |||||
_wasStopUnexpected = isUnexpected; | |||||
if (_cancelSource != null) | |||||
_cancelSource.Cancel(); | |||||
} | } | ||||
return task; | return task; | ||||
} | } | ||||
/// <summary> Throws an exception if one was captured. </summary> | /// <summary> Throws an exception if one was captured. </summary> | ||||
public void Throw() | |||||
public void ThrowException() | |||||
{ | { | ||||
lock (_lock) | lock (_lock) | ||||
{ | { | ||||
@@ -144,5 +151,13 @@ namespace Discord | |||||
_stopReason.Throw(); | _stopReason.Throw(); | ||||
} | } | ||||
} | } | ||||
public void ClearException() | |||||
{ | |||||
lock (_lock) | |||||
{ | |||||
_stopReason = null; | |||||
_wasStopUnexpected = false; | |||||
} | |||||
} | |||||
} | } | ||||
} | } |
@@ -57,6 +57,7 @@ namespace Discord.Net.WebSockets | |||||
_waitUntilConnect.Reset(); | _waitUntilConnect.Reset(); | ||||
_webSocket.Open(); | _webSocket.Open(); | ||||
_waitUntilConnect.Wait(cancelToken); | _waitUntilConnect.Wait(cancelToken); | ||||
_parent.TaskManager.ThrowException(); //In case our connection failed | |||||
return TaskHelper.CompletedTask; | return TaskHelper.CompletedTask; | ||||
} | } | ||||
@@ -34,8 +34,8 @@ namespace Discord.Net.WebSockets | |||||
public string Host { get { return _host; } set { _host = value; } } | public string Host { get { return _host; } set { _host = value; } } | ||||
private string _host; | private string _host; | ||||
public ConnectionState State => (ConnectionState)_state; | |||||
protected int _state; | |||||
public ConnectionState State => _state; | |||||
protected ConnectionState _state; | |||||
public event EventHandler Connected; | public event EventHandler Connected; | ||||
private void RaiseConnected() | private void RaiseConnected() | ||||
@@ -104,8 +104,9 @@ namespace Discord.Net.WebSockets | |||||
_lock.WaitOne(); | _lock.WaitOne(); | ||||
try | try | ||||
{ | { | ||||
await _taskManager.Stop().ConfigureAwait(false); | |||||
_state = (int)ConnectionState.Connecting; | |||||
await _taskManager.Stop().ConfigureAwait(false); | |||||
_taskManager.ClearException(); | |||||
_state = ConnectionState.Connecting; | |||||
_cancelTokenSource = new CancellationTokenSource(); | _cancelTokenSource = new CancellationTokenSource(); | ||||
_cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelTokenSource.Token, ParentCancelToken.Value).Token; | _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelTokenSource.Token, ParentCancelToken.Value).Token; | ||||
@@ -122,13 +123,14 @@ namespace Discord.Net.WebSockets | |||||
catch (Exception ex) | catch (Exception ex) | ||||
{ | { | ||||
_taskManager.SignalError(ex, true); | _taskManager.SignalError(ex, true); | ||||
throw; | |||||
} | } | ||||
} | } | ||||
protected void EndConnect() | protected void EndConnect() | ||||
{ | { | ||||
try | try | ||||
{ | { | ||||
_state = (int)ConnectionState.Connected; | |||||
_state = ConnectionState.Connected; | |||||
_connectedEvent.Set(); | _connectedEvent.Set(); | ||||
RaiseConnected(); | RaiseConnected(); | ||||
@@ -142,17 +144,17 @@ namespace Discord.Net.WebSockets | |||||
protected abstract Task Run(); | protected abstract Task Run(); | ||||
protected virtual async Task Cleanup() | protected virtual async Task Cleanup() | ||||
{ | { | ||||
await _engine.Disconnect().ConfigureAwait(false); | |||||
var oldState = _state; | |||||
_state = ConnectionState.Disconnecting; | |||||
await _engine.Disconnect().ConfigureAwait(false); | |||||
_cancelTokenSource = null; | _cancelTokenSource = null; | ||||
var oldState = _state; | |||||
_connectedEvent.Reset(); | _connectedEvent.Reset(); | ||||
if (oldState == (int)ConnectionState.Connected) | |||||
{ | |||||
_state = (int)ConnectionState.Disconnected; | |||||
if (oldState == ConnectionState.Connected) | |||||
RaiseDisconnected(_taskManager.WasUnexpected, _taskManager.Exception); | RaiseDisconnected(_taskManager.WasUnexpected, _taskManager.Exception); | ||||
} | |||||
} | |||||
_state = ConnectionState.Disconnected; | |||||
} | |||||
protected virtual Task ProcessMessage(string json) | protected virtual Task ProcessMessage(string json) | ||||
{ | { | ||||
@@ -176,23 +178,18 @@ namespace Discord.Net.WebSockets | |||||
{ | { | ||||
while (!cancelToken.IsCancellationRequested) | while (!cancelToken.IsCancellationRequested) | ||||
{ | { | ||||
if (_state == (int)ConnectionState.Connected) | |||||
if (_state == ConnectionState.Connected) | |||||
{ | { | ||||
SendHeartbeat(); | SendHeartbeat(); | ||||
await Task.Delay(_heartbeatInterval, cancelToken).ConfigureAwait(false); | await Task.Delay(_heartbeatInterval, cancelToken).ConfigureAwait(false); | ||||
} | } | ||||
else | else | ||||
await Task.Delay(100, cancelToken).ConfigureAwait(false); | |||||
await Task.Delay(1000, cancelToken).ConfigureAwait(false); | |||||
} | } | ||||
} | } | ||||
catch (OperationCanceledException) { } | catch (OperationCanceledException) { } | ||||
}); | }); | ||||
} | } | ||||
public abstract void SendHeartbeat(); | public abstract void SendHeartbeat(); | ||||
protected internal void ThrowError() | |||||
{ | |||||
_taskManager.Throw(); | |||||
} | |||||
} | } | ||||
} | } |