private void SendIdentify(UpdateStatusParams initialPresence) { if (_sessionId is null) // IDENTITY { Send(new GatewayPayload { Operation = GatewayOperation.Identify, Data = new IdentifyParams { Compress = false, // We don't want payload compression LargeThreshold = 50, Presence = Optional.FromObject(initialPresence), Properties = new IdentityConnectionProperties { Os = OsName, Browser = LibraryName, Device = LibraryName }, Shard = _shardId != null && _totalShards != null ? new int[] { _shardId.Value, _totalShards.Value } : Optional.Create <int[]>(), Token = Authorization != null ? new Utf8String(Authorization.Parameter) : null } }); } else // RESUME { Send(new GatewayPayload { Operation = GatewayOperation.Resume, Data = new ResumeParams { Sequence = _lastSeq, SessionId = _sessionId // TODO: Handle READY and get sessionId } }); } }
public async Task RunAsync(string url, int?shardId = null, int?totalShards = null, UpdateStatusParams initialPresence = null) { Task exceptionSignal; await _stateLock.WaitAsync().ConfigureAwait(false); try { await StopAsyncInternal().ConfigureAwait(false); _url = url; _shardId = shardId; _totalShards = totalShards; _runCts = new CancellationTokenSource(); _sessionId = null; _connectionTask = RunTaskAsync(initialPresence, _runCts.Token); exceptionSignal = _connectionTask; } finally { _stateLock.Release(); } await exceptionSignal.ConfigureAwait(false); }
private async Task RunTaskAsync(UpdateStatusParams initialPresence, CancellationToken runCancelToken) { Task[] tasks = null; bool isRecoverable = true; int backoffMillis = InitialBackoffMillis; var jitter = new Random(); while (isRecoverable) { Exception disconnectEx = null; var connectionCts = new CancellationTokenSource(); var cancelToken = CancellationTokenSource.CreateLinkedTokenSource(runCancelToken, connectionCts.Token).Token; using (var client = new ClientWebSocket()) { try { cancelToken.ThrowIfCancellationRequested(); var readySignal = new TaskCompletionSource <bool>(); _receivedData = true; // Connect State = ConnectionState.Connecting; var uri = new Uri(_url + $"?v={ApiVersion}&encoding=etf&compress=zlib-stream"); // TODO: Enable await client.ConnectAsync(uri, cancelToken).ConfigureAwait(false); _zlibStream = new DeflateStream(_compressed, CompressionMode.Decompress, true); _readZlibHeader = false; // Receive HELLO (timeout = ConnectionTimeoutMillis) var receiveTask = ReceiveAsync(client, readySignal, cancelToken); await WhenAny(new Task[] { receiveTask }, ConnectionTimeoutMillis, "Timed out waiting for HELLO").ConfigureAwait(false); var evnt = await receiveTask.ConfigureAwait(false); if (!(evnt.Data is HelloEvent helloEvent)) { throw new Exception("First event was not a HELLO event"); } int heartbeatRate = helloEvent.HeartbeatInterval; ServerNames = helloEvent.Trace; // Start tasks here since HELLO must be handled before another thread can send/receive _sendQueue = new BlockingCollection <GatewayPayload>(); tasks = new[] { RunSendAsync(client, cancelToken), RunHeartbeatAsync(heartbeatRate, cancelToken), RunReceiveAsync(client, readySignal, cancelToken) }; // Send IDENTIFY/RESUME (timeout = IdentifyTimeoutMillis) SendIdentify(initialPresence); await WhenAny(tasks.Append(readySignal.Task), IdentifyTimeoutMillis, "Timed out waiting for READY or InvalidSession").ConfigureAwait(false); if (await readySignal.Task.ConfigureAwait(false) == false) { continue; // Invalid session } // Success _lastSeq = 0; backoffMillis = InitialBackoffMillis; State = ConnectionState.Connected; Connected?.Invoke(); // Wait until an exception occurs (due to cancellation or failure) await WhenAny(tasks).ConfigureAwait(false); } catch (Exception ex) { disconnectEx = ex; isRecoverable = IsRecoverable(ex); if (!isRecoverable) { throw; } } finally { var oldState = State; State = ConnectionState.Disconnecting; // Wait for the other tasks to complete connectionCts.Cancel(); if (tasks != null) { try { await Task.WhenAll(tasks).ConfigureAwait(false); } catch { } // We already captured the root exception } // receiveTask and sendTask must have completed before we can send/receive from a different thread if (client.State == WebSocketState.Open) { try { await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None).ConfigureAwait(false); } catch { } // We don't actually care if sending a close msg fails } _sendQueue = null; ServerNames = null; State = ConnectionState.Disconnected; if (oldState == ConnectionState.Connected) { Disconnected?.Invoke(disconnectEx); } } if (isRecoverable) { backoffMillis = (int)(backoffMillis * (BackoffMultiplier + (jitter.NextDouble() * BackoffJitter * 2.0 - BackoffJitter))); if (backoffMillis > MaxBackoffMillis) { backoffMillis = MaxBackoffMillis; } await Task.Delay(backoffMillis).ConfigureAwait(false); } } } _runCts.Cancel(); // Reset to initial canceled state }
public void Run(string url, int?shardId = null, int?totalShards = null, UpdateStatusParams initialPresence = null) => RunAsync(url, shardId, totalShards, initialPresence).ConfigureAwait(false).GetAwaiter().GetResult();