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

suggestion: support Ctrl+C handling #111

Closed
tmds opened this issue Jun 28, 2018 · 12 comments
Closed

suggestion: support Ctrl+C handling #111

tmds opened this issue Jun 28, 2018 · 12 comments
Assignees
Labels
enhancement help wanted We would be willing to take a well-written PR to help fix this.
Milestone

Comments

@tmds
Copy link

tmds commented Jun 28, 2018

When the application is terminated by the user (e.g. presses Ctrl+C), it terminates immediately.

The application may want to do something, for example: kill some child processes it has started.

It is possible to handle this using Console.CancelKeyPress.
It would be nicer if this was an API pattern in CommandLineUtils. For example: a CancellationToken gets passed to OnExecuteAsync. This token is canceled when the user terminates the application.

@natemcmaster
Copy link
Owner

Interesting idea. I'm thinking through some implementation ideas and it might be quirky when we get into async land. IIRC, callbacks on CancellationTokenSource.Cancel() should run inline, but wouldn't it still be possible for the CancelKeyPress event handler terminate before some async continuations finish?

The way we solved this in aspnet and dotnet-watch is that the first CTRL+C invocation begins graceful shutdown and sets ConsoleCancelEventArgs.Cancel to true. The second time CTRL+C is raised we force exit the process. I'm wondering if that would be a good pattern to use by default, or if that should be something users configure.

@tmds
Copy link
Author

tmds commented Jun 29, 2018

Interesting idea. I'm thinking through some implementation ideas and it might be quirky when we get into async land. IIRC, callbacks on CancellationTokenSource.Cancel() should run inline, but wouldn't it still be possible for the CancelKeyPress event handler terminate before some async continuations finish?

Yes. We need to e.Cancel or block the event handler until OnExecuteAsync is finished. I'm leaning towards blocking because if we want to handle SIGTERM (AssemblyLoadContext.Default.Unloading), blocking is the only way.

The way we solved this in aspnet and dotnet-watch is that the first CTRL+C invocation begins graceful shutdown and sets ConsoleCancelEventArgs.Cancel to true. The second time CTRL+C is raised we force exit the process. I'm wondering if that would be a good pattern to use by default, or if that should be something users configure.

When the user implements OnExecuteAsync that accepts a CancelationToken, maybe we can trust he does the proper thing and cancels. Then we can block until OnExecuteAsync returns.

Prototyping a bit:

using System;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Threading;
using System.Threading.Tasks;

namespace console
{
    class Program
    {
        static readonly int ExitCodeOperationCanceled;
        static readonly int ExitCodeUnhandledException;
        static Program()
        {
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                // values from http://www.febooti.com/products/automation-workshop/online-help/events/run-dos-cmd-command/exit-codes/
                ExitCodeOperationCanceled = unchecked((int)0xC000013A);
                ExitCodeUnhandledException = unchecked((int)0xE0434F4D);
            }
            else
            {
                // Match Process.ExitCode (cfr bash) which uses 128 + signo.
                ExitCodeOperationCanceled  = 130; // SIGINT
                ExitCodeUnhandledException = 134; // SIGABRT
            }
        }

        static async Task<int> Main(string[] args)
        {
            var cts = new CancellationTokenSource();
            var mre = new ManualResetEventSlim(initialState: false);
            ConsoleCancelEventHandler cancelHandler = (o, e) =>
            {
                cts.Cancel();
                mre.Wait();
            };
            Action<AssemblyLoadContext> unloadingHandler = ctx =>
            {
                cts.Cancel();
                mre.Wait();
            };
            try
            {
                Console.CancelKeyPress += cancelHandler;
                AssemblyLoadContext.Default.Unloading += unloadingHandler;

                return await OnExecuteAsync(cts.Token);
            }
            catch (OperationCanceledException)
            {
                return ExitCodeOperationCanceled;
            }
            catch (Exception e)
            {
                System.Console.WriteLine($"Unhandled exception: {e}");
                return ExitCodeUnhandledException;
            }
            finally
            {
                Console.CancelKeyPress -= cancelHandler;
                AssemblyLoadContext.Default.Unloading -= unloadingHandler;
                mre.Set();
            }
        }

        static async Task<int> OnExecuteAsync(CancellationToken token)
        {
            await Task.Delay(int.MaxValue, token);
            return 0;
        }
    }
}

@natemcmaster natemcmaster added the help wanted We would be willing to take a well-written PR to help fix this. label Jul 5, 2018
@natemcmaster natemcmaster added this to the 2.3.0 milestone Jul 5, 2018
@natemcmaster
Copy link
Owner

Tentatively putting this in 2.3.0. I would accept a well written PR for this, and I think your prototype is headed in the right direction.

@tmds
Copy link
Author

tmds commented Jul 9, 2018

I won't work on this short term. When I do, I'll let you know to avoid double work.

@stale
Copy link

stale bot commented Apr 6, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Please comment if you believe this should remain open, otherwise it will be closed in 7 days.

@stale stale bot added the closed-wontfix This issue is closed because there are no plans to fix this. label Apr 6, 2019
@tmds
Copy link
Author

tmds commented Apr 6, 2019

I've added this to System.CommandLine in dotnet/command-line-api#290. This is the documentation for that: https://github.com/dotnet/command-line-api/wiki/Process-termination-handling.

@stale stale bot removed the closed-wontfix This issue is closed because there are no plans to fix this. label Apr 6, 2019
@natemcmaster
Copy link
Owner

Cool! I would still accept a PR to add this to this library, but I don't plan to implement it myself. Glad you were able to make this idea work with System.CommandLine!

@natemcmaster natemcmaster added the closed-wontfix This issue is closed because there are no plans to fix this. label Apr 6, 2019
@stale stale bot closed this as completed Apr 13, 2019
@natemcmaster natemcmaster added closed-stale This issue is closed because it went stale and there was no action planned. It can be reopened. and removed closed-wontfix This issue is closed because there are no plans to fix this. labels May 17, 2019
@natemcmaster natemcmaster added this to the 2.4.0-beta milestone Jul 24, 2019
@natemcmaster
Copy link
Owner

Stale bot closed, but I think this is worth adding. Re-opening and adding to the 2.4 milestone

@natemcmaster natemcmaster reopened this Jul 24, 2019
@stale stale bot removed the closed-stale This issue is closed because it went stale and there was no action planned. It can be reopened. label Jul 24, 2019
@natemcmaster natemcmaster self-assigned this Jul 24, 2019
@Simonl9l
Copy link

As a related aside it now more recommended to use PosixSignalRegistration (here) to handle shutdowns outside of the HotBuilder.

It's unclear in the docs if there is signal handling built in and what the strategy is to ensure ones OnExecuteAsync can gracefully shutdown what is doing, and ensure the CommandLineApplication will wait for that.

Currently I'm trying to use my own PosixSignalRegistration handles but the rug is being pulled out from underneath me, and figure it has to be the CommandLineApplication.

@tmds
Copy link
Author

tmds commented Oct 25, 2021

@Simonl9l these are the docs: https://github.com/dotnet/command-line-api/blob/main/docs/Process-termination-handling.md. If you are seeing an issue, can you create a new ticket?

@Simonl9l
Copy link

Simonl9l commented Oct 25, 2021

@tmds Thanks for the pointer to the docs. however these are the docs for command-line-api not the CommandLIneUtils docs...! ?

@tmds
Copy link
Author

tmds commented Oct 26, 2021

@tmds Thanks for the pointer to the docs. however these are the docs for command-line-api not the CommandLIneUtils docs...! ?

Ah, wrong lib 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement help wanted We would be willing to take a well-written PR to help fix this.
Projects
None yet
Development

No branches or pull requests

3 participants