mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-23 13:07:07 +01:00
Workaround for https://github.com/ChilliCream/graphql-platform/issues/6698
This commit is contained in:
@@ -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<ITopicEventReceiver, ShutdownAwareTopicEventReceiver>()
|
||||
.AddGraphQLServer()
|
||||
.AddAuthorization()
|
||||
.ModifyOptions(options =>
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Tgstation.Server.Host.GraphQL
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in a <see cref="ISourceStream{TMessage}"/> of the <see cref="SessionInvalidationReason"/> for the <paramref name="authenticationContext"/>.</returns>
|
||||
public ValueTask<ISourceStream<SessionInvalidationReason>> 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)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Tgstation.Server.Host.GraphQL.Subscriptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of <see cref="HotChocolate.Subscriptions.ITopicEventReceiver"/> that works around the <see cref="global::System.Threading.CancellationToken"/> issue described in https://github.com/ChilliCream/graphql-platform/issues/6698.
|
||||
/// </summary>
|
||||
public interface ITopicEventReceiver : HotChocolate.Subscriptions.ITopicEventReceiver
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <inheritdoc cref="ITopicEventReceiver" />
|
||||
sealed class ShutdownAwareTopicEventReceiver : ITopicEventReceiver, IAsyncDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IHostApplicationLifetime"/> for the <see cref="ShutdownAwareTopicEventReceiver"/>.
|
||||
/// </summary>
|
||||
readonly IHostApplicationLifetime hostApplicationLifetime;
|
||||
|
||||
/// <summary>
|
||||
/// The wrapped <see cref="HotChocolate.Subscriptions.ITopicEventReceiver"/>.
|
||||
/// </summary>
|
||||
readonly HotChocolate.Subscriptions.ITopicEventReceiver hotChocolateReceiver;
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="ConcurrentBag{T}"/> of <see cref="CancellationTokenRegistration"/>s that were created for this scope.
|
||||
/// </summary>
|
||||
readonly ConcurrentBag<CancellationTokenRegistration> registrations;
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="ConcurrentBag{T}"/> of <see cref="ValueTask"/>s returned from initiating <see cref="IAsyncDisposable.DisposeAsync"/> calls on <see cref="ISourceStream"/>s.
|
||||
/// </summary>
|
||||
readonly ConcurrentBag<Task> disposeTasks;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ShutdownAwareTopicEventReceiver"/> class.
|
||||
/// </summary>
|
||||
/// <param name="hostApplicationLifetime">The value of <see cref="hostApplicationLifetime"/>.</param>
|
||||
/// <param name="hotChocolateReceiver">The value of <see cref="hotChocolateReceiver"/>.</param>
|
||||
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<CancellationTokenRegistration>();
|
||||
disposeTasks = new ConcurrentBag<Task>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
foreach (var registration in registrations)
|
||||
{
|
||||
registration.Dispose();
|
||||
}
|
||||
|
||||
await Task.WhenAll(disposeTasks);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask<ISourceStream<TMessage>> SubscribeAsync<TMessage>(string topicName, CancellationToken cancellationToken)
|
||||
=> WrapWithApplicationLifetimeCancellation(
|
||||
hotChocolateReceiver.SubscribeAsync<TMessage>(topicName, cancellationToken));
|
||||
|
||||
/// <inheritdoc />
|
||||
public ValueTask<ISourceStream<TMessage>> SubscribeAsync<TMessage>(string topicName, int? bufferCapacity, TopicBufferFullMode? bufferFullMode, CancellationToken cancellationToken)
|
||||
=> WrapWithApplicationLifetimeCancellation(
|
||||
hotChocolateReceiver.SubscribeAsync<TMessage>(topicName, bufferCapacity, bufferFullMode, cancellationToken));
|
||||
|
||||
/// <summary>
|
||||
/// Wraps a given <paramref name="sourceStreamTask"/> with <see cref="hostApplicationLifetime"/> cancellation awareness.
|
||||
/// </summary>
|
||||
/// <typeparam name="TMessage">The <see cref="Type"/> of message.</typeparam>
|
||||
/// <param name="sourceStreamTask">The result of a call to the <see cref="hotChocolateReceiver"/>.</param>
|
||||
/// <returns>The result of <paramref name="sourceStreamTask"/> with lifetime aware cancellation.</returns>
|
||||
async ValueTask<ISourceStream<TMessage>> WrapWithApplicationLifetimeCancellation<TMessage>(ValueTask<ISourceStream<TMessage>> sourceStreamTask)
|
||||
{
|
||||
var sourceStream = await sourceStreamTask;
|
||||
registrations.Add(
|
||||
hostApplicationLifetime.ApplicationStopping.Register(
|
||||
() => disposeTasks.Add(
|
||||
sourceStream.DisposeAsync().AsTask())));
|
||||
return sourceStream;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ using System.Threading.Tasks;
|
||||
|
||||
using HotChocolate;
|
||||
using HotChocolate.Execution;
|
||||
using HotChocolate.Subscriptions;
|
||||
using HotChocolate.Types;
|
||||
using HotChocolate.Types.Relay;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user