From a979c49ef96096bb2065040d030b79035b208b94 Mon Sep 17 00:00:00 2001 From: Jordan Dominion Date: Wed, 25 Sep 2024 20:43:57 -0400 Subject: [PATCH] Workaround for https://github.com/ChilliCream/graphql-platform/issues/6698 --- src/Tgstation.Server.Host/Core/Application.cs | 2 + .../GraphQL/Subscription.cs | 2 +- .../Subscriptions/ITopicEventReceiver.cs | 9 ++ .../ShutdownAwareTopicEventReceiver.cs | 89 +++++++++++++++++++ .../Subscriptions/UserSubscriptions.cs | 1 - 5 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 src/Tgstation.Server.Host/GraphQL/Subscriptions/ITopicEventReceiver.cs create mode 100644 src/Tgstation.Server.Host/GraphQL/Subscriptions/ShutdownAwareTopicEventReceiver.cs diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 9e3a599bd6..fc201f72cc 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -56,6 +56,7 @@ using Tgstation.Server.Host.Controllers.Results; using Tgstation.Server.Host.Database; using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.GraphQL; +using Tgstation.Server.Host.GraphQL.Subscriptions; using Tgstation.Server.Host.GraphQL.Types; using Tgstation.Server.Host.GraphQL.Types.Interceptors; using Tgstation.Server.Host.GraphQL.Types.Scalars; @@ -295,6 +296,7 @@ namespace Tgstation.Server.Host.Core // configure graphql if (postSetupServices.InternalConfiguration.EnableGraphQL) services + .AddScoped() .AddGraphQLServer() .AddAuthorization() .ModifyOptions(options => diff --git a/src/Tgstation.Server.Host/GraphQL/Subscription.cs b/src/Tgstation.Server.Host/GraphQL/Subscription.cs index c7c9c80669..114a7622ad 100644 --- a/src/Tgstation.Server.Host/GraphQL/Subscription.cs +++ b/src/Tgstation.Server.Host/GraphQL/Subscription.cs @@ -38,7 +38,7 @@ namespace Tgstation.Server.Host.GraphQL /// The for the operation. /// A resulting in a of the for the . public ValueTask> SessionInvalidatedStream( - [Service] ITopicEventReceiver receiver, + [Service] HotChocolate.Subscriptions.ITopicEventReceiver receiver, // Intentionally not using our override here, topic callers are built to explicitly handle cases of server shutdown [Service] ISessionInvalidationTracker invalidationTracker, [Service] IAuthenticationContext authenticationContext, CancellationToken cancellationToken) diff --git a/src/Tgstation.Server.Host/GraphQL/Subscriptions/ITopicEventReceiver.cs b/src/Tgstation.Server.Host/GraphQL/Subscriptions/ITopicEventReceiver.cs new file mode 100644 index 0000000000..ec843558cb --- /dev/null +++ b/src/Tgstation.Server.Host/GraphQL/Subscriptions/ITopicEventReceiver.cs @@ -0,0 +1,9 @@ +namespace Tgstation.Server.Host.GraphQL.Subscriptions +{ + /// + /// Implementation of that works around the issue described in https://github.com/ChilliCream/graphql-platform/issues/6698. + /// + public interface ITopicEventReceiver : HotChocolate.Subscriptions.ITopicEventReceiver + { + } +} diff --git a/src/Tgstation.Server.Host/GraphQL/Subscriptions/ShutdownAwareTopicEventReceiver.cs b/src/Tgstation.Server.Host/GraphQL/Subscriptions/ShutdownAwareTopicEventReceiver.cs new file mode 100644 index 0000000000..1fec5ee14e --- /dev/null +++ b/src/Tgstation.Server.Host/GraphQL/Subscriptions/ShutdownAwareTopicEventReceiver.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; + +using HotChocolate.Execution; +using HotChocolate.Subscriptions; + +using Microsoft.Extensions.Hosting; + +namespace Tgstation.Server.Host.GraphQL.Subscriptions +{ + /// + sealed class ShutdownAwareTopicEventReceiver : ITopicEventReceiver, IAsyncDisposable + { + /// + /// The for the . + /// + readonly IHostApplicationLifetime hostApplicationLifetime; + + /// + /// The wrapped . + /// + readonly HotChocolate.Subscriptions.ITopicEventReceiver hotChocolateReceiver; + + /// + /// A of s that were created for this scope. + /// + readonly ConcurrentBag registrations; + + /// + /// A of s returned from initiating calls on s. + /// + readonly ConcurrentBag disposeTasks; + + /// + /// Initializes a new instance of the class. + /// + /// The value of . + /// The value of . + public ShutdownAwareTopicEventReceiver( + IHostApplicationLifetime hostApplicationLifetime, + HotChocolate.Subscriptions.ITopicEventReceiver hotChocolateReceiver) + { + this.hostApplicationLifetime = hostApplicationLifetime ?? throw new ArgumentNullException(nameof(hostApplicationLifetime)); + this.hotChocolateReceiver = hotChocolateReceiver ?? throw new ArgumentNullException(nameof(hotChocolateReceiver)); + + registrations = new ConcurrentBag(); + disposeTasks = new ConcurrentBag(); + } + + /// + public async ValueTask DisposeAsync() + { + foreach (var registration in registrations) + { + registration.Dispose(); + } + + await Task.WhenAll(disposeTasks); + } + + /// + public ValueTask> SubscribeAsync(string topicName, CancellationToken cancellationToken) + => WrapWithApplicationLifetimeCancellation( + hotChocolateReceiver.SubscribeAsync(topicName, cancellationToken)); + + /// + public ValueTask> SubscribeAsync(string topicName, int? bufferCapacity, TopicBufferFullMode? bufferFullMode, CancellationToken cancellationToken) + => WrapWithApplicationLifetimeCancellation( + hotChocolateReceiver.SubscribeAsync(topicName, bufferCapacity, bufferFullMode, cancellationToken)); + + /// + /// Wraps a given with cancellation awareness. + /// + /// The of message. + /// The result of a call to the . + /// The result of with lifetime aware cancellation. + async ValueTask> WrapWithApplicationLifetimeCancellation(ValueTask> sourceStreamTask) + { + var sourceStream = await sourceStreamTask; + registrations.Add( + hostApplicationLifetime.ApplicationStopping.Register( + () => disposeTasks.Add( + sourceStream.DisposeAsync().AsTask()))); + return sourceStream; + } + } +} diff --git a/src/Tgstation.Server.Host/GraphQL/Subscriptions/UserSubscriptions.cs b/src/Tgstation.Server.Host/GraphQL/Subscriptions/UserSubscriptions.cs index e8f22cc938..2369df8ab4 100644 --- a/src/Tgstation.Server.Host/GraphQL/Subscriptions/UserSubscriptions.cs +++ b/src/Tgstation.Server.Host/GraphQL/Subscriptions/UserSubscriptions.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using HotChocolate; using HotChocolate.Execution; -using HotChocolate.Subscriptions; using HotChocolate.Types; using HotChocolate.Types.Relay;