Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot debug graceful shutdown stop events #451

Closed
JohnGoldsmith opened this issue Dec 13, 2024 · 5 comments
Closed

Cannot debug graceful shutdown stop events #451

JohnGoldsmith opened this issue Dec 13, 2024 · 5 comments
Labels
workaround-available Apply this label when there is an easy workaround available.

Comments

@JohnGoldsmith
Copy link

Hi, I'm unable to hit stop events from BackgroundService while debugging in Visual Studio 2022 (17.12.3) and docker desktop (4.36.0).

Neither registering a delegate nor implementing IHostedLifecycleService appear to work. If you step out of docker and just run the standard https profile then it works fine.

From my limited understanding it seems like SIGTERM isn't handled by 'VS / docker desktop' and the process gets killed rather than going through the stopping / stopped events.

  • Is this correct?
  • And if so, is there a work around?

My ideal here would be that either closing the browser (if launched) or Stop Debugging (Shift+F5) would perform a graceful shutdown.

I've tested this with the default razor pages template (with docker support) plus the following:

Program.cs

builder.Services.AddHostedService<MyBackgroundSvc>();

MyBackgroundSvc.cs

public class MyBackgroundSvc(ILogger<MyBackgroundSvc> logger, 
                             IHostApplicationLifetime appLifetime) : BackgroundService, IHostedLifecycleService
{
    // Start... events are executed, but Stop... ones are not.
    
    public Task StartingAsync(CancellationToken cancellationToken)
    {
        logger.LogInformation("STARTING_ASYNC was called.");
        return Task.CompletedTask;
    }
    public Task StartedAsync(CancellationToken cancellationToken)
    {
        logger.LogInformation("STARTED_ASYNC was called.");
        return Task.CompletedTask;
    }
    public Task StoppingAsync(CancellationToken cancellationToken)
    {
        logger.LogInformation("STOPPING_ASYNC was called.");
        return Task.CompletedTask;
    }
    public Task StoppedAsync(CancellationToken cancellationToken)
    {
        logger.LogInformation("STOPPED_ASYNC was called.");
        return Task.CompletedTask;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            appLifetime.ApplicationStopping.Register(() =>
                    logger.LogInformation("APPLICATION_STOPPING was called."));

            await Task.Delay(2000, stoppingToken);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Error during ExecuteAsync work.");
        }
    }
}
@gregg-miskelly
Copy link
Member

One clarifying question -- From the "my ideal here would be that ... would perform a graceful shutdown" I take it that at least part of this request is to change the stop debugging behavior as you have described. Is that your entire request, or are you also having problems where if you trigger shutdown in some other way the debugger exits? If so, what are you trying?

Currently, stop debugging will just terminate the process, and no additional code will get to run. Closing the browser will be equivalent to stop debugging. I don't think we would want to change stop debugging, but I could potentially see changing the browser close behavior and/or introducing another command that would be a graceful terminate. Today, what I think would work would be to open a command window and execute something like docker exec <container> kill -2 <pid-of-target> (assuming your container contains kill).

@JohnGoldsmith
Copy link
Author

JohnGoldsmith commented Dec 17, 2024

Hello Gregg,

Thanks very much for your reply.

My use case is that on app startup I'm pulling in content from a GitHub repo and on shutdown I wanted to delete those files to ensure a clean restart. I currently have no other triggers in the app, but was assuming (incorrectly I now think) that hitting 'Restart' in the Azure portal (App Service) would cycle the app rather than the entire container. From a debug perspective, the repo content gets added to wwwroot and is part of the binding mounts, so I'd also like the content to be removed when I stop debugging.

What's confused me is my assumption that stopping debugging in VS would hit all of the lifecycle events before killing the container. I also assumed that there was a problem in my BackgroundService code, but I now understand that this is by design / expected behaviour.

If there were a launchSettings property to adjust browser shutdown behaviour or another command as you suggest, that would be great.

Re the workaround - that works thank you. It enabled me to hit all of the events:

12:55:19:061	info: RazorPages.MyBackgroundSvc[0]
12:55:19:061	      STARTING_ASYNC was called.
12:55:19:061	RazorPages.MyBackgroundSvc: Information: STARTING_ASYNC was called.
...other logging...
12:55:19:565	info: Microsoft.Hosting.Lifetime[14]
12:55:19:565	      Now listening on: http://[::]:8080
12:55:19:565	Microsoft.Hosting.Lifetime: Information: Now listening on: http://[::]:8080
12:55:19:565	Microsoft.Hosting.Lifetime: Information: Now listening on: https://[::]:8081
12:55:19:565	info: Microsoft.Hosting.Lifetime[14]
12:55:19:565	      Now listening on: https://[::]:8081
12:55:19:565	info: RazorPages.MyBackgroundSvc[0]
12:55:19:565	      STARTED_ASYNC was called.
12:55:19:565	RazorPages.MyBackgroundSvc: Information: STARTED_ASYNC was called.
12:55:19:565	info: Microsoft.Hosting.Lifetime[0]
12:55:19:565	      Application started. Press Ctrl+C to shut down.
12:55:19:565	Microsoft.Hosting.Lifetime: Information: Application started. Press Ctrl+C to shut down.
12:55:19:565	info: Microsoft.Hosting.Lifetime[0]
12:55:19:565	      Hosting environment: Development
12:55:19:565	Microsoft.Hosting.Lifetime: Information: Hosting environment: Development
12:55:19:565	info: Microsoft.Hosting.Lifetime[0]
12:55:19:565	      Content root path: /app
12:55:19:565	Microsoft.Hosting.Lifetime: Information: Content root path: /app
13:21:29:435	info: RazorPages.MyBackgroundSvc[0]
13:21:29:435	      APPLICATION_STOPPING was called.
13:21:29:435	RazorPages.MyBackgroundSvc: Information: APPLICATION_STOPPING was called.
13:21:29:435	info: Microsoft.Hosting.Lifetime[0]
13:21:29:435	      Application is shutting down...
13:21:29:435	Microsoft.Hosting.Lifetime: Information: Application is shutting down...
13:21:29:435	info: RazorPages.MyBackgroundSvc[0]
13:21:29:435	      STOPPING_ASYNC was called.
13:21:29:435	RazorPages.MyBackgroundSvc: Information: STOPPING_ASYNC was called.
13:21:29:435	info: RazorPages.MyBackgroundSvc[0]
13:21:29:435	      STOPPED_ASYNC was called.
13:21:29:435	RazorPages.MyBackgroundSvc: Information: STOPPED_ASYNC was called.
13:21:29:435	The program 'dotnet' has exited with code 0 (0x0).

For anyone else, to get it to work in my default (debian) image I carried out the following (where 'RazorPages' is the container name):

Add procps to the base stage in DockerFile (I found I couldn't run kill without this)
RUN apt-get update && apt-get install -y procps

Get the process ID for the app dll
docker exec RazorPages ps aux

Call kill with SIGINT to the target process as per your instructions:
docker exec RazorPages kill -2 331

Anyway, thank you very much for the insight and workaround.

Best regards

John

@NCarlsonMSFT
Copy link
Member

@JohnGoldsmith a recommendation on your workaround: You can add a stage to your Dockerfile that is only used for debugging to install procps into so that it doesn't add to your final image. For an example see the testCerts stage used in my certs example.

@NCarlsonMSFT NCarlsonMSFT added the workaround-available Apply this label when there is an easy workaround available. label Dec 19, 2024
@NCarlsonMSFT
Copy link
Member

Glad to hear you're unblocked

@JohnGoldsmith
Copy link
Author

Thank you @NCarlsonMSFT for that link. That's helped my understanding a lot. With this workaround I can now debug the shutdown events, which is great. I think I would still put my hand up for a low/no touch solution to have code run on shutdown as it something I know I want to happen at the end of every debug session.

Anyway, thanks again to you both for the workaround.

For anyone else, the setup now looks like this:

Dockerfile (added withprocps stage)

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081

FROM base AS withprocps
USER root
RUN apt-get update && apt-get install -y procps
USER $APP_UID

# remaining stages...

.csproj

<PropertyGroup>
    <DockerfileFastModeStage>withprocps</DockerfileFastModeStage>
</PropertyGroup>

MyBackgroundSvc.cs

public Task StartingAsync(CancellationToken cancellationToken)
{
    logger.LogInformation("STARTING_ASYNC was called.");
	
    // Log cmd for easy copy/paste	
    var processId = Process.GetCurrentProcess().Id;
    logger.LogInformation($"GRACEFUL SHUTDOWN COMMAND:\ndocker exec RazorPages kill -2 {processId}");

    return Task.CompletedTask;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
workaround-available Apply this label when there is an easy workaround available.
Projects
None yet
Development

No branches or pull requests

3 participants