/// <inheritdoc/> public async Task <GatewayConnectionResult> DisconnectAsync ( bool reconnectionIntended, CancellationToken ct = default ) { if (_clientWebSocket is null) { return(GatewayConnectionResult.FromError("The transport service is not connected.")); } switch (_clientWebSocket.State) { case WebSocketState.Open: case WebSocketState.CloseReceived: case WebSocketState.CloseSent: { try { // 1012 is used here instead of normal closure, because close codes 1000 and 1001 don't // allow for reconnection. 1012 is referenced in the websocket protocol as "Service restart", // which makes sense for our use case. var closeCode = reconnectionIntended ? (WebSocketCloseStatus)1012 : WebSocketCloseStatus.NormalClosure; await _clientWebSocket.CloseAsync ( closeCode, "Terminating connection by user request.", ct ); } catch (WebSocketException) { // Most likely due to some kind of premature or forced disconnection; we'll live with it } break; } } _clientWebSocket.Dispose(); _clientWebSocket = null; this.IsConnected = false; return(GatewayConnectionResult.FromSuccess()); }
/// <inheritdoc /> public async Task <GatewayConnectionResult> ConnectAsync(Uri endpoint, CancellationToken ct = default) { if (_clientWebSocket is not null) { return(GatewayConnectionResult.FromError("The transport service is already connected.")); } var socket = _services.GetRequiredService <ClientWebSocket>(); try { await socket.ConnectAsync(endpoint, ct); switch (socket.State) { case WebSocketState.Open: case WebSocketState.Connecting: { break; } default: { socket.Dispose(); return(GatewayConnectionResult.FromError("Failed to connect to the endpoint.")); } } } catch (Exception e) { socket.Dispose(); return(GatewayConnectionResult.FromError(e)); } _clientWebSocket = socket; this.IsConnected = true; return(GatewayConnectionResult.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 <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(); }