/// <summary> /// Initializes a new instance of the <see cref="DiscordGatewayClient"/> class. /// </summary> /// <param name="gatewayAPI">The gateway API.</param> /// <param name="transportService">The payload transport service.</param> /// <param name="gatewayOptions">The gateway options.</param> /// <param name="tokenStore">The token store.</param> /// <param name="random">An entropy source.</param> /// <param name="log">The logging instance.</param> /// <param name="services">The available services.</param> /// <param name="responderTypeRepository">The responder type repository.</param> public DiscordGatewayClient ( IDiscordRestGatewayAPI gatewayAPI, IPayloadTransportService transportService, IOptions <DiscordGatewayClientOptions> gatewayOptions, ITokenStore tokenStore, Random random, ILogger <DiscordGatewayClient> log, IServiceProvider services, IResponderTypeRepository responderTypeRepository ) { _gatewayAPI = gatewayAPI; _transportService = transportService; _gatewayOptions = gatewayOptions.Value; _tokenStore = tokenStore; _random = random; _log = log; _services = services; _responderTypeRepository = responderTypeRepository; _runningResponderDispatches = new ConcurrentQueue <Task <IReadOnlyList <Result> > >(); _payloadsToSend = new ConcurrentQueue <IPayload>(); _receivedPayloads = new ConcurrentQueue <IPayload>(); _connectionStatus = GatewayConnectionStatus.Offline; _disconnectRequestedSource = new CancellationTokenSource(); _sendTask = Task.FromResult(Result.FromSuccess()); _receiveTask = Task.FromResult(Result.FromSuccess()); }
/// <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 <Result> RunAsync(CancellationToken stopRequested) { try { if (_connectionStatus != GatewayConnectionStatus.Offline) { return(new GenericError("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 CancellationTokenSource(); }