graphql-platform icon indicating copy to clipboard operation
graphql-platform copied to clipboard

SSE subscription makes HotChocolate not being shutdown gracefully

Open sunghwan2789 opened this issue 2 years ago • 7 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues

Product

Hot Chocolate

Describe the bug

If a SSE subscription alives, HotChocolate does not shutdown gracefully.

Sending Ctrl+C (SIGINT), Kestrel requests connections closed, but HotChocolate keeps connections open and RequestAborted is false.

image

Steps to reproduce

  1. git clone https://github.com/sunghwan2789/hotchocolate.git --branch repro/sse-hangs --single-branch
  2. dotnet run --project Server
  3. Wait subscription started, and send Ctrl+C (SIGINT)

Relevant log output

No response

Additional Context?

No response

Version

14.0.0-p.9

sunghwan2789 avatar Nov 14 '23 03:11 sunghwan2789

Thank you for reporting this.

michaelstaib avatar Nov 14 '23 12:11 michaelstaib

Can you create a repro?

michaelstaib avatar Nov 14 '23 12:11 michaelstaib

Updated reproduction, thanks

sunghwan2789 avatar Nov 14 '23 13:11 sunghwan2789

Also being hit by this. This means we can't reliably update our API, because shutdown hangs whenever there's an active connection. Hoping this will get fixed soon!

cmeeren avatar Feb 20 '24 14:02 cmeeren

Now, I think this is not a bug, but a user-level mistake: the resolver is responsible for completing the subscription on application stopping.

Kestrel also does not abort the client connection immediately:

using System.Runtime.CompilerServices;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpContextAccessor();

var app = builder.Build();

app.MapGet("/", Bar);

_ = DoSubscription(app.Lifetime.ApplicationStopped);

app.Run();

async Task DoSubscription(CancellationToken stopped) {
    var client = new HttpClient();

    while (!stopped.IsCancellationRequested) {
        try {
            await foreach (var response in client.GetFromJsonAsAsyncEnumerable<int>("http://localhost:5241/", stopped)) {
                Console.WriteLine(response);
            }
        } catch (Exception ex) {
            Console.WriteLine("retry... " + ex);
            await Task.Delay(1000, stopped);
        }
    }
}

async IAsyncEnumerable<int> Bar(IHttpContextAccessor contextAccessor, [EnumeratorCancellation] CancellationToken cancellationToken) {
    var context = contextAccessor.HttpContext;
    Console.WriteLine(context?.TraceIdentifier);
    var i = 0; 
    while (!cancellationToken.IsCancellationRequested) {
        yield return i++;
        await Task.Delay(1000, cancellationToken);
    }
}

sunghwan2789 avatar Mar 20 '24 07:03 sunghwan2789

the resolver is responsible for completing the subscription on application stopping

How can this be done? I can't find any information about that in the HC subscription docs.

cmeeren avatar Mar 20 '24 10:03 cmeeren

Updated the reproduction and found that HC does not send a complete event and keep the connection open when an exception is thrown.

Now, I am sure it is a bug.

sunghwan2789 avatar Mar 21 '24 06:03 sunghwan2789

fixed in v14

sunghwan2789 avatar Jun 05 '24 21:06 sunghwan2789