Пример #1
0
        /// <summary>
        /// Warning: Do not call from the context of the connect loop! A deadlock will occur!
        /// </summary>
        async Task FullDisconnect()
        {
            log.LogVerbose("Disconnecting...");
            state = GatewayState.Disconnected;

            handshakeCompleteCancellationSource.Cancel();

            if (connectTask != null)
            {
                // Cancel any automatic reconnection
                connectTaskCancellationSource?.Cancel();

                // Wait for the automatic reconnection to end
                try
                {
                    await connectTask.ConfigureAwait(false);
                }
                catch (OperationCanceledException) { /* Expected to happen. */ }
                catch (Exception ex)
                {
                    // Should never happen, but there isn't anything we can do here.
                    log.LogError($"[DisconnectAsync] Uncaught exception found in connect task: {ex}");
                }
            }

            // Disconnect the socket if needed
            if (socket.CanBeDisconnected)
            {
                await socket.DisconnectAsync(WebSocketCloseStatus.NormalClosure, "Disconnecting...", CancellationToken.None)
                .ConfigureAwait(false);
            }

            log.LogVerbose("Disconnected.");
        }
Пример #2
0
        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;
            }
        }
Пример #3
0
        public async Task HeartbeatLoop(int heartbeatInterval)
        {
            receivedHeartbeatAck = true;

            while (State == WebSocketState.Open && !heartbeatCancellationSource.IsCancellationRequested)
            {
                if (!receivedHeartbeatAck)
                {
                    log.LogWarning("[HeartbeatLoop] Connection timed out (did not receive ack for last heartbeat).");

                    OnTimedOut?.Invoke(this, EventArgs.Empty);
                    break;
                }

                try
                {
                    receivedHeartbeatAck = false;

                    // 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: {dwex}");
                    }
                    else
                    {
                        break;
                    }
                }

                try
                {
                    // Wait heartbeat interval
                    await Task.Delay(heartbeatInterval, heartbeatCancellationSource.Token)
                    .ConfigureAwait(false);
                }
                catch (ObjectDisposedException)
                {
                    // VoiceWebSocket was disposed between sending a heartbeat payload and beginning to wait
                    break;
                }
                catch (OperationCanceledException)
                {
                    // Socket is disconnecting
                    break;
                }
            }
        }
Пример #4
0
 public void Shutdown()
 {
     try
     {
         socket.Shutdown(SocketShutdown.Both);
     }
     catch (SocketException ex)
     {
         // If this occurs then the socket is already borked,
         // technically shouldn't happen though.
         log.LogError($"[Shutdown] Unexpected error: code = {ex.SocketErrorCode}, error = {ex}");
     }
 }
        /// <summary>
        /// This task will deadlock (for 5s then abort the socket) if called from the same thread as the receive loop!
        /// </summary>
        /// <param name="cancellationToken">Token that when cancelled will abort the entire socket.</param>
        /// <exception cref="OperationCanceledException"></exception>
        /// <exception cref="WebSocketException">Thrown if the socket is not in a valid state to be closed.</exception>
        public virtual Task DisconnectAsync(WebSocketCloseStatus closeStatus, string statusDescription,
                                            CancellationToken cancellationToken)
        {
            // Since this operation can take up to 5s and is synchronous code, wrap it in a task.
            return(Task.Run(() =>
            {
                log.LogVerbose($"Disconnecting with code {closeStatus} ({(int)closeStatus})...");

                // Since the state check will be eaten by the code below,
                // and we cannot reliably check for this below, check if
                // the socket state is valid for a close, and throw the
                // same exception CloseAsync would have if not valid.
                if (socket.State != WebSocketState.Open && socket.State != WebSocketState.CloseReceived &&
                    socket.State != WebSocketState.CloseSent)
                {
                    throw new WebSocketException(WebSocketError.InvalidState, $"Socket is in an invalid state: {socket.State}");
                }

                // CloseAsync will send the close message and then wait until
                // a close frame has been received. Once nothing is holding
                // the receive lock, CloseAsync will then try and receive
                // data until it comes to a close frame, or another receive
                // call gets it.
                Task closeTask = socket.CloseAsync(closeStatus, statusDescription, cancellationToken);

                try
                {
                    // Give the socket 5s to gracefully disconnect.
                    if (!Task.WaitAll(new Task[] { closeTask, receiveTask }, 5000, cancellationToken))
                    {
                        // Socket did not gracefully disconnect in the given time,
                        // so abort the socket and move on.
                        log.LogWarning("Socket failed to disconnect after 5s, aborting...");
                        abortCancellationSource.Cancel();
                    }
                }
                catch (OperationCanceledException)
                {
                    // Caller cancelled, socket has been aborted.
                    log.LogVerbose("Disconnect cancelled by caller, socket has been aborted.");
                    throw;
                }
                catch (AggregateException aex)
                {
                    foreach (Exception ex in aex.InnerExceptions)
                    {
                        if (ex is WebSocketException wsex)
                        {
                            if (wsex.WebSocketErrorCode == WebSocketError.InvalidState)
                            {
                                // The socket being aborted is normal.
                                continue;
                            }
                            else
                            {
                                log.LogError($"Uncaught exception found while disconnecting: code = {wsex.WebSocketErrorCode}, error = {ex}");
                            }
                        }
                        else
                        {
                            log.LogError($"Uncaught exception found while disconnecting: {ex}");
                        }
                    }
                }

                log.LogVerbose("Disconnected.");
            }));
        }