protected override void OnCloseReceived(WebSocketCloseStatus closeStatus, string closeDescription) { // If we initiated a disconnect, this is just the remote end's acknowledgment // and we should not start reconnecting if (areWeDisconnecting) { return; } GatewayCloseCode code = (GatewayCloseCode)closeStatus; switch (code) { case GatewayCloseCode.InvalidShard: case GatewayCloseCode.AuthenticationFailed: case GatewayCloseCode.ShardingRequired: // Not safe to reconnect log.LogError($"[{code} ({(int)code})] Unsafe to continue, NOT reconnecting gateway."); OnFatalDisconnection?.Invoke(this, code); break; case GatewayCloseCode.InvalidSeq: case GatewayCloseCode.InvalidSession: case GatewayCloseCode.SessionTimeout: case GatewayCloseCode.UnknownError: // Safe to reconnect, but needs a new session. OnReconnectionRequired?.Invoke(this, new ReconnectionEventArgs(true)); break; case GatewayCloseCode.NotAuthenticated: // This really should never happen, but will require a new session. log.LogWarning("[NotAuthenticated] Sent gateway payload before we identified!"); OnReconnectionRequired?.Invoke(this, new ReconnectionEventArgs(true)); break; case GatewayCloseCode.RateLimited: // Doesn't require a new session, but we need to wait a bit. log.LogError("Gateway is being rate limited!"); // Error level because we have code that should prevent this. OnReconnectionRequired?.Invoke(this, new ReconnectionEventArgs(false, 5000)); break; default: // Safe to just resume OnReconnectionRequired?.Invoke(this, new ReconnectionEventArgs(false)); break; } }
void HandleInvalidSessionPayload(DiscordApiData payload, DiscordApiData data) { bool isResumable = data.ToBoolean().Value; if (isResumable) { // Resume log.LogInfo("[InvalidSession] Resuming..."); OnReconnectionRequired?.Invoke(this, new ReconnectionEventArgs(false, 5000)); } else { // Start new session log.LogInfo("[InvalidSession] Starting new session..."); OnReconnectionRequired?.Invoke(this, new ReconnectionEventArgs(true, rnd.Next(1000, 5001))); } }
void HandleReconnectPayload(DiscordApiData payload, DiscordApiData data) { // Resume log.LogInfo("[Reconnect] Performing resume..."); OnReconnectionRequired?.Invoke(this, new ReconnectionEventArgs(false)); }
async Task HeartbeatLoop() { // Default to true for the first heartbeat payload we send. receivedHeartbeatAck = true; log.LogVerbose("[HeartbeatLoop] Begin."); while (State == WebSocketState.Open && !heartbeatCancellationSource.IsCancellationRequested) { if (receivedHeartbeatAck) { receivedHeartbeatAck = false; try { // Send heartbeat await SendHeartbeatPayload().ConfigureAwait(false); } catch (InvalidOperationException) { // Socket was closed between the loop check and sending the heartbeat break; } catch (DiscordWebSocketException dwex) { // Expected to be the socket closing while sending a heartbeat if (dwex.Error != DiscordWebSocketError.ConnectionClosed) { // Unexpected errors may not be the socket closing/aborting, so just log and loop around. log.LogError("[HeartbeatLoop] Unexpected error occured while sending a heartbeat: " + $"code = {dwex.Error}, error = {dwex}"); } else { break; } } try { // Wait heartbeat interval await Task.Delay(heartbeatInterval, heartbeatCancellationSource.Token) .ConfigureAwait(false); } catch (ObjectDisposedException) { // GatewaySocket was disposed between sending a heartbeat payload and beginning to wait break; } catch (OperationCanceledException) { // Socket is disconnecting break; } } else { // Gateway connection has timed out log.LogInfo("Gateway connection timed out (did not receive ack for last heartbeat)."); // Notify that this connection needs to be resumed OnReconnectionRequired?.Invoke(this, new ReconnectionEventArgs(false)); break; } } log.LogVerbose($"[HeartbeatLoop] Done. isDisposed = {isDisposed}"); }
protected override void OnClosedPrematurely() { // Attempt to resume OnReconnectionRequired?.Invoke(this, new ReconnectionEventArgs(false)); }