public async ValueTask <Stream> ReceiveAsync(CancellationToken cancellationToken) { ThrowIfDisposed(); await _receiveSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { _receiveStream.Position = 0; _receiveStream.SetLength(0); do { // See _limboCts for more info on cancellation. var receiveTask = _ws.ReceiveAsync(_receiveBuffer, _limboCts.Token).AsTask(); using (var infiniteCts = Cts.Linked(cancellationToken)) { var infiniteTask = Task.Delay(Timeout.Infinite, infiniteCts.Token); var task = await Task.WhenAny(infiniteTask, receiveTask).ConfigureAwait(false); infiniteCts.Cancel(); } if (cancellationToken.IsCancellationRequested) { throw new OperationCanceledException(cancellationToken); } var result = await receiveTask.ConfigureAwait(false); if (result.MessageType == WebSocketMessageType.Close) { var closeStatus = _ws.CloseStatus; var closeMessage = _ws.CloseMessage; try { await _ws.CloseOutputAsync(closeStatus.GetValueOrDefault(), closeMessage, default).ConfigureAwait(false); } catch { } throw new WebSocketClosedException(closeStatus, closeMessage); } _receiveStream.Write(_receiveBuffer.AsSpan(0, result.Count)); if (!result.EndOfMessage) { continue; } if (result.MessageType != WebSocketMessageType.Binary) { _receiveStream.Position = 0; return(_receiveStream); } _receiveStream.TryGetBuffer(out var streamBuffer); // We check the data for the ZLib flush which marks the end of the actual message. if (streamBuffer.Count < 4 || BinaryPrimitives.ReadUInt32BigEndian(streamBuffer[^ 4..]) != 0x0000FFFF)
/// <inheritdoc/> public virtual Task StartAsync(CancellationToken cancellationToken) { var method = GetType().GetMethod("ExecuteAsync", BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(CancellationToken) }, null); if (method.DeclaringType == method.GetBaseDefinition().DeclaringType) { return(Task.CompletedTask); } _cts = Cts.Linked(cancellationToken); var stoppingToken = _cts.Token; _executeTask = Task.Run(async() => { try { await ExecuteAsync(stoppingToken).ConfigureAwait(false); } catch (OperationCanceledException ex) when(ex.CancellationToken == stoppingToken) { // Ignore cancellation exceptions caused by the stopping token. } catch (Exception ex) { Logger.LogError(ex, "An exception occurred while executing service {0}.", GetType().Name); throw; } }, cancellationToken); if (_executeTask.IsCompleted) { return(_executeTask); } return(Task.CompletedTask); }
public override async Task RunAsync(CancellationToken stoppingToken) { StoppingToken = stoppingToken; // TODO: fallback on bot gateway data? var uri = new Uri("wss://gateway.discord.gg/"); _scopes = new Dictionary <ShardId, IServiceScope>(); var shardIds = new List <ShardId>(); if (_configuredShardCount != null) { // If the shard count is specified we generate the appropriate shard IDs. var shardCount = _configuredShardCount.Value; for (var i = 0; i < shardCount; i++) { var id = new ShardId(i, shardCount); shardIds.Add(id); } } else if (_configuredShardIds != null) { // If the shard IDs are specified we validate them and manage those instead. // TODO: validation foreach (var id in _configuredShardIds) { shardIds.Add(id); } } else { var botGatewayData = await this.FetchBotGatewayDataAsync(cancellationToken : stoppingToken); Logger.LogDebug("Using Discord's recommended shard count of {0}.", botGatewayData.RecommendedShardCount); for (var i = 0; i < botGatewayData.RecommendedShardCount; i++) { var id = new ShardId(i, botGatewayData.RecommendedShardCount); shardIds.Add(id); } } Logger.LogInformation("This sharder will manage {0} shards with IDs: {1}", shardIds.Count, shardIds.Select(x => x.Id)); var shards = GatewayClient.Shards as ISynchronizedDictionary <ShardId, IGatewayApiClient>; var dispatcher = GatewayClient.Dispatcher as DefaultGatewayDispatcher; var originalReadyHandler = dispatcher["READY"] as ReadyHandler; originalReadyHandler.InitialReadys.Clear(); // We create a service scope and a shard for every // shard ID this sharder is supposed to manage. foreach (var id in shardIds) { var scope = _services.CreateScope(); _scopes.Add(id, scope); shards.Add(id, _shardFactory.Create(id, scope.ServiceProvider)); originalReadyHandler.InitialReadys.Add(id, new Tcs()); } _initialReadyTcs.Complete(); // These two locals will be reused via the closure below. Tcs readyTcs = null; ShardId shardId = default; dispatcher["READY"] = Handler.Intercept(originalReadyHandler, (shard, _) => { if (shard.Id == shardId) { // If the shard that identified is the shard ID we're booting up // we complete the TCS to signal the code below to boot the next shard. readyTcs.Complete(); } }); var linkedCts = Cts.Linked(stoppingToken); var runTasks = new List <Task>(shards.Count); foreach (var shard in shards.Values) { // Set the TCS and shard ID for the shard we'll boot up // which are used by the intercepting ready handler above. readyTcs = new Tcs(); shardId = shard.Id; shard.DispatchReceived += dispatcher.HandleDispatchAsync; var runTask = shard.RunAsync(uri, linkedCts.Token); var task = Task.WhenAny(runTask, readyTcs.Task); try { if (await task.ConfigureAwait(false) == runTask) { // If the task that finished is the run task // it means an exception occurred when identifying. // We await it to get the exception thrown. await runTask.ConfigureAwait(false); } // If we reached here it means the shard successfully identified, // so we add the task to the list and continue. runTasks.Add(runTask); } catch (Exception ex) { Logger.LogCritical(ex, "An exception occurred when starting {0}.", shardId); // Cancel the CTS so that if some shards are running already // it's a coordinated close as they will probably break sooner or later. linkedCts.Cancel(); // We bubble the exception up to the client runner. throw; } } shardId = default; try { // We wait for any of the tasks to finish (throw) // and handle it appropriately. var runTask = await Task.WhenAny(runTasks).ConfigureAwait(false); await runTask.ConfigureAwait(false); } catch { // TODO: if sharding required redo the process with more shards blah blah linkedCts.Cancel(); throw; } }
public async ValueTask <Stream> ReceiveAsync(CancellationToken cancellationToken) { ThrowIfDisposed(); await _receiveSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try { // Ensures that the receive stream is fully read and the underlying DeflateStream acknowledges the ZLib suffix. if (_supportsZLib && _wasLastPayloadZLib && _receiveStream.Position != _receiveStream.Length) { // We just need the inflater to read further so that it picks up the suffix and knows it's done. _receiveZLibStream.Read(Array.Empty <byte>()); } _receiveStream.Position = 0; _receiveStream.SetLength(0); do { // See _limboCts for more info on cancellation. var receiveTask = _ws.ReceiveAsync(_receiveBuffer, _limboCts.Token).AsTask(); using (var infiniteCts = Cts.Linked(cancellationToken)) { var infiniteTask = Task.Delay(Timeout.Infinite, infiniteCts.Token); var task = await Task.WhenAny(infiniteTask, receiveTask).ConfigureAwait(false); infiniteCts.Cancel(); } if (cancellationToken.IsCancellationRequested) { throw new OperationCanceledException(cancellationToken); } var result = await receiveTask.ConfigureAwait(false); if (result.MessageType == WebSocketMessageType.Close) { var closeStatus = _ws.CloseStatus; var closeMessage = _ws.CloseMessage; try { await _ws.CloseOutputAsync(closeStatus.GetValueOrDefault(), closeMessage, default).ConfigureAwait(false); } catch { } throw new WebSocketClosedException(closeStatus, closeMessage); } _receiveStream.Write(_receiveBuffer.AsSpan(0, result.Count)); if (!result.EndOfMessage) { continue; } if (result.MessageType != WebSocketMessageType.Binary) { _wasLastPayloadZLib = false; _receiveStream.Position = 0; return(_receiveStream); } _receiveStream.TryGetBuffer(out var streamBuffer); // We check the data for the ZLib flush which marks the end of the actual message. if (streamBuffer.Count < 4 || BinaryPrimitives.ReadUInt32BigEndian(streamBuffer[^ 4..]) != 0x0000FFFF)