Example #1
0
        /// <summary>
        /// Starts and connects the gateway client.
        /// </summary>
        /// <remarks>
        /// This task will not complete until cancelled (or faulted), maintaining the connection for the duration of it.
        ///
        /// If the gateway client encounters a fatal problem during the execution of this task, it will return with a
        /// failed result. If a shutdown is requested, it will gracefully terminate the connection and return a
        /// successful result.
        /// </remarks>
        /// <param name="stopRequested">A token by which the caller can request this method to stop.</param>
        /// <returns>A gateway connection result which may or may not have succeeded.</returns>
        public async Task <GatewayConnectionResult> RunAsync(CancellationToken stopRequested)
        {
            try
            {
                if (_connectionStatus != GatewayConnectionStatus.Offline)
                {
                    return(GatewayConnectionResult.FromError("Already connected."));
                }

                // Until cancellation has been requested or we hit a fatal error, reconnections should be attempted.
                _disconnectRequestedSource.Dispose();
                _disconnectRequestedSource = new CancellationTokenSource();

                while (!stopRequested.IsCancellationRequested)
                {
                    var iterationResult = await RunConnectionIterationAsync(stopRequested);

                    if (iterationResult.IsSuccess)
                    {
                        continue;
                    }

                    // Something has gone wrong. Close the socket, and handle it
                    // Terminate the send and receive tasks
                    _disconnectRequestedSource.Cancel();

                    // The results of the send and receive tasks are discarded here, because the iteration result will
                    // contain whichever of them failed if any of them did
                    _ = await _sendTask;
                    _ = await _receiveTask;

                    if (_transportService.IsConnected)
                    {
                        var disconnectResult = await _transportService.DisconnectAsync(stopRequested.IsCancellationRequested, stopRequested);

                        if (!disconnectResult.IsSuccess)
                        {
                            // Couldn't disconnect cleanly :(
                            return(disconnectResult);
                        }
                    }

                    // Finish up the responders
                    foreach (var runningResponder in _runningResponderDispatches)
                    {
                        await FinalizeResponderDispatchAsync(runningResponder);
                    }

                    if (stopRequested.IsCancellationRequested)
                    {
                        // The user requested a termination, and we don't intend to reconnect.
                        return(iterationResult);
                    }

                    if (ShouldReconnect(iterationResult, out var shouldTerminate, out var withNewSession))
                    {
                        if (withNewSession)
                        {
                            _sessionID        = null;
                            _connectionStatus = GatewayConnectionStatus.Disconnected;
                        }
                        else
                        {
                            _connectionStatus = GatewayConnectionStatus.Disconnected;
                        }
                    }
                    else if (shouldTerminate)
                    {
                        return(iterationResult);
                    }

                    // This token's been cancelled, we'll need a new one to reconnect.
                    _disconnectRequestedSource.Dispose();
                    _disconnectRequestedSource = new();
                }