| @@ -0,0 +1,12 @@ | |||||
| using System; | |||||
| namespace Discord.Net | |||||
| { | |||||
| public class FatalException : Exception | |||||
| { | |||||
| public FatalException(string reason, Exception inner) | |||||
| : base(reason, inner) | |||||
| { | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,4 +1,5 @@ | |||||
| using System; | |||||
| using System; | |||||
| namespace Discord.Net | namespace Discord.Net | ||||
| { | { | ||||
| public class WebSocketClosedException : Exception | public class WebSocketClosedException : Exception | ||||
| @@ -1,4 +1,4 @@ | |||||
| using Discord.API.Voice; | |||||
| using Discord.API.Voice; | |||||
| using Discord.Audio.Streams; | using Discord.Audio.Streams; | ||||
| using Discord.Logging; | using Discord.Logging; | ||||
| using Discord.Net.Converters; | using Discord.Net.Converters; | ||||
| @@ -71,7 +71,7 @@ namespace Discord.Audio | |||||
| ApiClient.ReceivedPacket += ProcessPacketAsync; | ApiClient.ReceivedPacket += ProcessPacketAsync; | ||||
| _stateLock = new SemaphoreSlim(1, 1); | _stateLock = new SemaphoreSlim(1, 1); | ||||
| _connection = new ConnectionManager(_stateLock, _audioLogger, 30000, | |||||
| _connection = new ConnectionManager(_stateLock, _audioLogger, 30000, false, | |||||
| OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); | OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); | ||||
| _connection.Connected += () => _connectedEvent.InvokeAsync(); | _connection.Connected += () => _connectedEvent.InvokeAsync(); | ||||
| _connection.Disconnected += (ex, recon) => _disconnectedEvent.InvokeAsync(ex); | _connection.Disconnected += (ex, recon) => _disconnectedEvent.InvokeAsync(ex); | ||||
| @@ -1,13 +1,17 @@ | |||||
| using Discord.Logging; | using Discord.Logging; | ||||
| using Discord.Net; | |||||
| using System; | using System; | ||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Discord.Net; | |||||
| using System.Linq; | |||||
| namespace Discord | namespace Discord | ||||
| { | { | ||||
| internal class ConnectionManager | internal class ConnectionManager | ||||
| { | { | ||||
| // close codes that cannot be recovered from | |||||
| private static readonly int[] _fatalErrorCodes = { 4004, 4010, 4011 }; | |||||
| public event Func<Task> Connected { add { _connectedEvent.Add(value); } remove { _connectedEvent.Remove(value); } } | public event Func<Task> Connected { add { _connectedEvent.Add(value); } remove { _connectedEvent.Remove(value); } } | ||||
| private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>(); | private readonly AsyncEvent<Func<Task>> _connectedEvent = new AsyncEvent<Func<Task>>(); | ||||
| public event Func<Exception, bool, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } | public event Func<Exception, bool, Task> Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } | ||||
| @@ -26,7 +30,7 @@ namespace Discord | |||||
| public ConnectionState State { get; private set; } | public ConnectionState State { get; private set; } | ||||
| public CancellationToken CancelToken { get; private set; } | public CancellationToken CancelToken { get; private set; } | ||||
| internal ConnectionManager(SemaphoreSlim stateLock, Logger logger, int connectionTimeout, | |||||
| internal ConnectionManager(SemaphoreSlim stateLock, Logger logger, int connectionTimeout, bool invalidStateFatal, | |||||
| Func<Task> onConnecting, Func<Exception, Task> onDisconnecting, Action<Func<Exception, Task>> clientDisconnectHandler) | Func<Task> onConnecting, Func<Exception, Task> onDisconnecting, Action<Func<Exception, Task>> clientDisconnectHandler) | ||||
| { | { | ||||
| _stateLock = stateLock; | _stateLock = stateLock; | ||||
| @@ -40,8 +44,8 @@ namespace Discord | |||||
| if (ex != null) | if (ex != null) | ||||
| { | { | ||||
| var ex2 = ex as WebSocketClosedException; | var ex2 = ex as WebSocketClosedException; | ||||
| if (ex2?.CloseCode == 4006) | |||||
| CriticalError(new Exception("WebSocket session expired", ex)); | |||||
| if ((invalidStateFatal && ex2?.CloseCode == 4006) || _fatalErrorCodes.Contains(ex2?.CloseCode ?? 0)) | |||||
| FatalError(new FatalException("WebSocket connection was closed with an unrecoverable error", ex)); | |||||
| else | else | ||||
| Error(new Exception("WebSocket connection was closed", ex)); | Error(new Exception("WebSocket connection was closed", ex)); | ||||
| } | } | ||||
| @@ -186,7 +190,7 @@ namespace Discord | |||||
| _connectionPromise.TrySetException(ex); | _connectionPromise.TrySetException(ex); | ||||
| _connectionCancelToken?.Cancel(); | _connectionCancelToken?.Cancel(); | ||||
| } | } | ||||
| public void CriticalError(Exception ex) | |||||
| public void FatalError(Exception ex) | |||||
| { | { | ||||
| _reconnectCancelToken?.Cancel(); | _reconnectCancelToken?.Cancel(); | ||||
| Error(ex); | Error(ex); | ||||
| @@ -207,4 +211,4 @@ namespace Discord | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | |||||
| } | |||||
| @@ -1,4 +1,4 @@ | |||||
| #pragma warning disable CS1591 | |||||
| #pragma warning disable CS1591 | |||||
| using Discord.API.Gateway; | using Discord.API.Gateway; | ||||
| using Discord.API.Rest; | using Discord.API.Rest; | ||||
| using Discord.Net.Queue; | using Discord.Net.Queue; | ||||
| @@ -90,7 +90,7 @@ namespace Discord.WebSocket | |||||
| _stateLock = new SemaphoreSlim(1, 1); | _stateLock = new SemaphoreSlim(1, 1); | ||||
| _gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); | _gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); | ||||
| _connection = new ConnectionManager(_stateLock, _gatewayLogger, config.ConnectionTimeout, | |||||
| _connection = new ConnectionManager(_stateLock, _gatewayLogger, config.ConnectionTimeout, config.InvalidStateFatal, | |||||
| OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); | OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); | ||||
| _connection.Connected += () => TimedInvokeAsync(_connectedEvent, nameof(Connected)); | _connection.Connected += () => TimedInvokeAsync(_connectedEvent, nameof(Connected)); | ||||
| _connection.Disconnected += (ex, recon) => TimedInvokeAsync(_disconnectedEvent, nameof(Disconnected), ex); | _connection.Disconnected += (ex, recon) => TimedInvokeAsync(_disconnectedEvent, nameof(Disconnected), ex); | ||||
| @@ -466,7 +466,7 @@ namespace Discord.WebSocket | |||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| _connection.CriticalError(new Exception("Processing READY failed", ex)); | |||||
| _connection.FatalError(new Exception("Processing READY failed", ex)); | |||||
| return; | return; | ||||
| } | } | ||||
| @@ -1,4 +1,4 @@ | |||||
| using Discord.Net.Udp; | |||||
| using Discord.Net.Udp; | |||||
| using Discord.Net.WebSockets; | using Discord.Net.WebSockets; | ||||
| using Discord.Rest; | using Discord.Rest; | ||||
| @@ -25,6 +25,10 @@ namespace Discord.WebSocket | |||||
| /// Gets or sets the max number of users a guild may have for offline users to be included in the READY packet. Max is 250. | /// Gets or sets the max number of users a guild may have for offline users to be included in the READY packet. Max is 250. | ||||
| /// </summary> | /// </summary> | ||||
| public int LargeThreshold { get; set; } = 250; | public int LargeThreshold { get; set; } = 250; | ||||
| /// <summary> | |||||
| /// Gets or sets whether a State Invalidation from Discord should be fatal. Setting this to false could lead to unexpected behavior when Discord is unstable. | |||||
| /// </summary> | |||||
| public bool InvalidStateFatal { get; set; } = true; | |||||
| /// <summary> Gets or sets the provider used to generate new websocket connections. </summary> | /// <summary> Gets or sets the provider used to generate new websocket connections. </summary> | ||||
| public WebSocketProvider WebSocketProvider { get; set; } | public WebSocketProvider WebSocketProvider { get; set; } | ||||