From a6a01aa15551e73fee40d48d1908e57597a1c1ae Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Mon, 19 Jun 2023 15:41:22 -0400 Subject: [PATCH] ValueTask chat `Provider`s --- .../Components/Chat/ChatManager.cs | 30 +++++++----- .../Chat/Providers/DiscordProvider.cs | 46 +++++++++---------- .../Components/Chat/Providers/IProvider.cs | 24 +++++----- .../Components/Chat/Providers/IrcProvider.cs | 16 +++---- .../Components/Chat/Providers/Provider.cs | 32 ++++++------- .../Live/DummyChatProvider.cs | 22 ++++----- 6 files changed, 87 insertions(+), 83 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs index 95246f2e48..7b92808477 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs @@ -10,6 +10,7 @@ using Newtonsoft.Json; using Serilog.Context; using Tgstation.Server.Api.Models.Internal; +using Tgstation.Server.Common.Extensions; using Tgstation.Server.Host.Components.Chat.Commands; using Tgstation.Server.Host.Components.Chat.Providers; using Tgstation.Server.Host.Components.Interop; @@ -369,7 +370,7 @@ namespace Tgstation.Server.Host.Components.Chat logger.LogTrace("Sending deployment message for RevisionInformation: {revisionInfoId}", revisionInformation.Id); - var callbacks = new List>(); + var callbacks = new List>(); var task = Task.WhenAll( wdChannels.Select( @@ -412,11 +413,12 @@ namespace Tgstation.Server.Host.Components.Chat async Task CollateTasks(string errorMessage, string dreamMakerOutput) { await task; - await Task.WhenAll( + await ValueTaskExtensions.WhenAll( callbacks.Select( x => x( errorMessage, - dreamMakerOutput))); + dreamMakerOutput)), + callbacks.Count); } return (errorMessage, dreamMakerOutput) => AddMessageTask(CollateTasks(errorMessage, dreamMakerOutput)); @@ -928,7 +930,9 @@ namespace Tgstation.Server.Host.Components.Chat lock (providers) foreach (var providerKvp in providers) if (!messageTasks.ContainsKey(providerKvp.Value)) - messageTasks.Add(providerKvp.Value, providerKvp.Value.NextMessage(cancellationToken)); + messageTasks.Add( + providerKvp.Value, + providerKvp.Value.NextMessage(cancellationToken).AsTask()); if (messageTasks.Count == 0) { @@ -999,30 +1003,32 @@ namespace Tgstation.Server.Host.Components.Chat /// A representing the running operation. Task SendMessage(IEnumerable channelIds, Message replyTo, MessageContent message, CancellationToken cancellationToken) { - channelIds = channelIds.ToList(); + var channelIdsList = channelIds.ToList(); logger.LogTrace( "Chat send \"{message}\"{embed} to channels: [{channelIdsCommaSeperated}]", message.Text, message.Embed != null ? " (with embed)" : String.Empty, - String.Join(", ", channelIds)); + String.Join(", ", channelIdsList)); - if (!channelIds.Any()) + if (!channelIdsList.Any()) return Task.CompletedTask; - return Task.WhenAll( - channelIds.Select(x => + return ValueTaskExtensions.WhenAll( + channelIdsList.Select(x => { ChannelMapping channelMapping; lock (mappedChannels) if (!mappedChannels.TryGetValue(x, out channelMapping)) - return Task.CompletedTask; + return ValueTask.CompletedTask; IProvider provider; lock (providers) if (!providers.TryGetValue(channelMapping.ProviderId, out provider)) - return Task.CompletedTask; + return ValueTask.CompletedTask; return provider.SendMessage(replyTo, message, channelMapping.ProviderChannelId, cancellationToken); - })); + }), + channelIdsList.Count) + .AsTask(); } /// diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index 81b652cf49..e1d5d486e0 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -25,6 +25,7 @@ using Remora.Rest.Results; using Remora.Results; using Tgstation.Server.Api.Models; +using Tgstation.Server.Common.Extensions; using Tgstation.Server.Host.Components.Interop; using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.Jobs; @@ -285,7 +286,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } /// - public override async Task SendMessage(Message replyTo, MessageContent message, ulong channelId, CancellationToken cancellationToken) + public override async ValueTask SendMessage(Message replyTo, MessageContent message, ulong channelId, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(message); @@ -376,7 +377,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } /// - public override async Task> SendUpdateMessage( + public override async ValueTask> SendUpdateMessage( Models.RevisionInformation revisionInformation, Version byondVersion, DateTimeOffset? estimatedCompletionTime, @@ -628,7 +629,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } /// - protected override async Task Connect(CancellationToken cancellationToken) + protected override async ValueTask Connect(CancellationToken cancellationToken) { try { @@ -688,7 +689,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } /// - protected override async Task DisconnectImpl(CancellationToken cancellationToken) + protected override async ValueTask DisconnectImpl(CancellationToken cancellationToken) { Task localGatewayTask; CancellationTokenSource localGatewayCts; @@ -713,14 +714,14 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } /// - protected override async Task>> MapChannelsImpl(IEnumerable channels, CancellationToken cancellationToken) + protected override async ValueTask>> MapChannelsImpl(IEnumerable channels, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(channels); var remapRequired = false; var guildsClient = serviceProvider.GetRequiredService(); - async Task>> GetModelChannelFromDBChannel(Models.ChatChannel channelFromDB) + async ValueTask>> GetModelChannelFromDBChannel(Models.ChatChannel channelFromDB) { if (!channelFromDB.DiscordChannelId.HasValue) throw new InvalidOperationException("ChatChannel missing DiscordChannelId!"); @@ -785,10 +786,13 @@ namespace Tgstation.Server.Host.Components.Chat.Providers var tasks = channels .Where(x => x.DiscordChannelId != 0) - .Select(GetModelChannelFromDBChannel) - .ToList(); + .Select(GetModelChannelFromDBChannel); - await Task.WhenAll(tasks); + var channelTuples = await ValueTaskExtensions.WhenAll(tasks.ToList()); + + var enumerator = channelTuples + .Where(x => x != null) + .ToList(); var channelIdZeroModel = channels.FirstOrDefault(x => x.DiscordChannelId == 0); if (channelIdZeroModel != null) @@ -798,7 +802,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers var unmappedTextChannels = allAccessibleChannels .Where(x => !tasks.Any(task => task.Result != null && new Snowflake(task.Result.Item1.DiscordChannelId.Value) == x.ID)); - async Task>> CreateMappingsForUnmappedChannels() + async ValueTask>> CreateMappingsForUnmappedChannels() { var unmappedTasks = unmappedTextChannels.Select( @@ -835,15 +839,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } var task = CreateMappingsForUnmappedChannels(); - await task; - tasks.Add(task); + var tuple = await task; + enumerator.Add(tuple); } - var enumerator = tasks - .Select(x => x.Result) - .Where(x => x != null) - .ToList(); - lock (mappedChannels) { mappedChannels.Clear(); @@ -864,7 +863,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// /// The for the operation. /// A resulting in an of accessible and compatible s. - async Task> GetAllAccessibleTextChannels(CancellationToken cancellationToken) + async ValueTask> GetAllAccessibleTextChannels(CancellationToken cancellationToken) { var usersClient = serviceProvider.GetRequiredService(); var currentGuildsResponse = await usersClient.GetCurrentUserGuildsAsync(ct: cancellationToken); @@ -878,7 +877,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers var guildsClient = serviceProvider.GetRequiredService(); - async Task> GetGuildChannels(IPartialGuild guild) + async ValueTask> GetGuildChannels(IPartialGuild guild) { var channelsTask = guildsClient.GetGuildChannelsAsync(guild.ID.Value, cancellationToken); var threads = await guildsClient.ListActiveGuildThreadsAsync(guild.ID.Value, cancellationToken); @@ -907,13 +906,12 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } var guildsChannelsTasks = currentGuildsResponse.Entity - .Select(GetGuildChannels) - .ToList(); + .Select(GetGuildChannels); - await Task.WhenAll(guildsChannelsTasks); + var guildsChannels = await ValueTaskExtensions.WhenAll(guildsChannelsTasks, currentGuildsResponse.Entity.Count); - var allAccessibleChannels = guildsChannelsTasks - .SelectMany(task => task.Result) + var allAccessibleChannels = guildsChannels + .SelectMany(channels => channels) .Where(guildChannel => SupportedGuildChannelTypes.Contains(guildChannel.Type)); return allAccessibleChannels; diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs index abf1eefe20..cb44d754b6 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs @@ -39,27 +39,27 @@ namespace Tgstation.Server.Host.Components.Chat.Providers void InitialMappingComplete(); /// - /// Get a resulting in the next the recieves or on a disconnect. + /// Get a resulting in the next the recieves or on a disconnect. /// /// The for the operation. - /// A resulting in the next available or if the needed to reconnect. - /// Note that private messages will come in the form of s not returned in . Do not the on continuations run from the returned . - Task NextMessage(CancellationToken cancellationToken); + /// A resulting in the next available or if the needed to reconnect. + /// Note that private messages will come in the form of s not returned in . + ValueTask NextMessage(CancellationToken cancellationToken); /// /// Gracefully disconnects the provider. Permanently stops the reconnection timer. /// /// The for the operation. - /// A representing the running operation. - Task Disconnect(CancellationToken cancellationToken); + /// A representing the running operation. + ValueTask Disconnect(CancellationToken cancellationToken); /// /// Get the s for given . /// /// The s to map. /// The for the operation. - /// A resulting in a of the 's s representing . - Task>> MapChannels(IEnumerable channels, CancellationToken cancellationToken); + /// A resulting in a of the 's s representing . + ValueTask>> MapChannels(IEnumerable channels, CancellationToken cancellationToken); /// /// Send a message to the . @@ -68,8 +68,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// The . /// The to send to. /// The for the operation. - /// A representing the running operation. - Task SendMessage(Message replyTo, MessageContent message, ulong channelId, CancellationToken cancellationToken); + /// A representing the running operation. + ValueTask SendMessage(Message replyTo, MessageContent message, ulong channelId, CancellationToken cancellationToken); /// /// Set the interval at which the provider starts jobs to try to reconnect. @@ -90,8 +90,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// The to send to. /// if the local deployment commit was pushed to the remote repository. /// The for the operation. - /// A resulting in a to call to update the message at the deployment's conclusion. Parameters: Error message if any, DreamMaker output if any. - Task> SendUpdateMessage( + /// A resulting in a to call to update the message at the deployment's conclusion. Parameters: Error message if any, DreamMaker output if any. + ValueTask> SendUpdateMessage( RevisionInformation revisionInformation, Version byondVersion, DateTimeOffset? estimatedCompletionTime, diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index 3312e4b8b4..481f0fbb8d 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -88,7 +88,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers ulong channelIdCounter; /// - /// The used for . + /// The used for . /// Task listenTask; @@ -164,11 +164,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } /// - public override Task SendMessage(Message replyTo, MessageContent message, ulong channelId, CancellationToken cancellationToken) + public override async ValueTask SendMessage(Message replyTo, MessageContent message, ulong channelId, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(message); - return Task.Factory.StartNew( + await Task.Factory.StartNew( () => { // IRC doesn't allow newlines @@ -218,7 +218,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } /// - public override async Task> SendUpdateMessage( + public override async ValueTask> SendUpdateMessage( Models.RevisionInformation revisionInformation, Version byondVersion, DateTimeOffset? estimatedCompletionTime, @@ -292,10 +292,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } /// - protected override Task>> MapChannelsImpl( + protected override async ValueTask>> MapChannelsImpl( IEnumerable channels, CancellationToken cancellationToken) - => Task.Factory.StartNew( + => await Task.Factory.StartNew( () => { if (channels.Any(x => x.IrcChannel == null)) @@ -366,7 +366,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers TaskScheduler.Current); /// - protected override async Task Connect(CancellationToken cancellationToken) + protected override async ValueTask Connect(CancellationToken cancellationToken) { disconnecting = false; cancellationToken.ThrowIfCancellationRequested(); @@ -455,7 +455,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } /// - protected override async Task DisconnectImpl(CancellationToken cancellationToken) + protected override async ValueTask DisconnectImpl(CancellationToken cancellationToken) { try { diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs index 1fad402354..20bae62d13 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs @@ -85,7 +85,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers ChatBot = chatBot ?? throw new ArgumentNullException(nameof(chatBot)); messageQueue = new Queue(); - nextMessage = new TaskCompletionSource(); + nextMessage = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); initialConnectionTcs = new TaskCompletionSource(); reconnectTaskLock = new object(); @@ -113,7 +113,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } /// - public async Task Disconnect(CancellationToken cancellationToken) + public async ValueTask Disconnect(CancellationToken cancellationToken) { await StopReconnectionTimer(); @@ -129,7 +129,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers public void InitialMappingComplete() => initialConnectionTcs.TrySetResult(); /// - public async Task>> MapChannels(IEnumerable channels, CancellationToken cancellationToken) + public async ValueTask>> MapChannels(IEnumerable channels, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(channels); @@ -145,7 +145,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } /// - public async Task NextMessage(CancellationToken cancellationToken) + public async ValueTask NextMessage(CancellationToken cancellationToken) { while (true) { @@ -172,17 +172,17 @@ namespace Tgstation.Server.Host.Components.Chat.Providers { stopOldTimerTask = StopReconnectionTimer(); reconnectCts = new CancellationTokenSource(); - reconnectTask = ReconnectionLoop(reconnectInterval, connectNow, reconnectCts.Token); + reconnectTask = ReconnectionLoop(reconnectInterval, connectNow, reconnectCts.Token).AsTask(); } return stopOldTimerTask; } /// - public abstract Task SendMessage(Message replyTo, MessageContent message, ulong channelId, CancellationToken cancellationToken); + public abstract ValueTask SendMessage(Message replyTo, MessageContent message, ulong channelId, CancellationToken cancellationToken); /// - public abstract Task> SendUpdateMessage( + public abstract ValueTask> SendUpdateMessage( RevisionInformation revisionInformation, Version byondVersion, DateTimeOffset? estimatedCompletionTime, @@ -196,23 +196,23 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// Attempt to connect the . /// /// The for the operation. - /// A representing the running operation. - protected abstract Task Connect(CancellationToken cancellationToken); + /// A representing the running operation. + protected abstract ValueTask Connect(CancellationToken cancellationToken); /// /// Gracefully disconnects the provider. /// /// The for the operation. - /// A representing the running operation. - protected abstract Task DisconnectImpl(CancellationToken cancellationToken); + /// A representing the running operation. + protected abstract ValueTask DisconnectImpl(CancellationToken cancellationToken); /// /// Implementation of . /// /// The s to map. /// The for the operation. - /// A resulting in a of the 's s representing . - protected abstract Task>> MapChannelsImpl( + /// A resulting in a of the 's s representing . + protected abstract ValueTask>> MapChannelsImpl( IEnumerable channels, CancellationToken cancellationToken); @@ -245,7 +245,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers reconnectCts.Cancel(); reconnectCts.Dispose(); reconnectCts = null; - Task reconnectTask = this.reconnectTask; + var reconnectTask = this.reconnectTask; this.reconnectTask = null; return reconnectTask; } @@ -261,8 +261,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// The amount of minutes to wait between reconnection attempts. /// If a connection attempt should be immediately made. /// The for the operation. - /// A representing the running operation. - async Task ReconnectionLoop(uint reconnectInterval, bool connectNow, CancellationToken cancellationToken) + /// A representing the running operation. + async ValueTask ReconnectionLoop(uint reconnectInterval, bool connectNow, CancellationToken cancellationToken) { do { diff --git a/tests/Tgstation.Server.Tests/Live/DummyChatProvider.cs b/tests/Tgstation.Server.Tests/Live/DummyChatProvider.cs index ac17312614..290c5b46bb 100644 --- a/tests/Tgstation.Server.Tests/Live/DummyChatProvider.cs +++ b/tests/Tgstation.Server.Tests/Live/DummyChatProvider.cs @@ -84,7 +84,7 @@ namespace Tgstation.Server.Tests.Live await base.DisposeAsync(); } - public override Task SendMessage(Message replyTo, MessageContent message, ulong channelId, CancellationToken cancellationToken) + public override ValueTask SendMessage(Message replyTo, MessageContent message, ulong channelId, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(message); @@ -96,10 +96,10 @@ namespace Tgstation.Server.Tests.Live if (random.Next(0, 100) > 70) throw new Exception("Random SendMessage failure!"); */ - return Task.CompletedTask; + return ValueTask.CompletedTask; } - public override Task> SendUpdateMessage(RevisionInformation revisionInformation, Version byondVersion, DateTimeOffset? estimatedCompletionTime, string gitHubOwner, string gitHubRepo, ulong channelId, bool localCommitPushed, CancellationToken cancellationToken) + public override ValueTask> SendUpdateMessage(RevisionInformation revisionInformation, Version byondVersion, DateTimeOffset? estimatedCompletionTime, string gitHubOwner, string gitHubRepo, ulong channelId, bool localCommitPushed, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(revisionInformation); ArgumentNullException.ThrowIfNull(byondVersion); @@ -114,7 +114,7 @@ namespace Tgstation.Server.Tests.Live if (random.Next(0, 100) > 70) throw new Exception("Random SendUpdateMessage failure!"); */ - return Task.FromResult>((_, _) => + return ValueTask.FromResult>((_, _) => { cancellationToken.ThrowIfCancellationRequested(); @@ -122,11 +122,11 @@ namespace Tgstation.Server.Tests.Live if (random.Next(0, 100) > 70) throw new Exception("Random SendUpdateMessage failure!"); */ - return Task.CompletedTask; + return ValueTask.CompletedTask; }); } - protected override Task Connect(CancellationToken cancellationToken) + protected override ValueTask Connect(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -136,20 +136,20 @@ namespace Tgstation.Server.Tests.Live connected = true; connectedOnce = true; - return Task.CompletedTask; + return ValueTask.CompletedTask; } - protected override Task DisconnectImpl(CancellationToken cancellationToken) + protected override ValueTask DisconnectImpl(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); connected = false; if (random.Next(0, 100) > 70) throw new Exception("Random disconnection failure!"); - return Task.CompletedTask; + return ValueTask.CompletedTask; } - protected override Task>> MapChannelsImpl(IEnumerable channels, CancellationToken cancellationToken) + protected override ValueTask>> MapChannelsImpl(IEnumerable channels, CancellationToken cancellationToken) { channels = channels.ToList(); @@ -159,7 +159,7 @@ namespace Tgstation.Server.Tests.Live if (random.Next(0, 100) > 70) throw new Exception("Random MapChannelsImpl failure!"); */ - return Task.FromResult( + return ValueTask.FromResult( new Dictionary>( channels.Select( channel => new KeyValuePair>(