-
-
Notifications
You must be signed in to change notification settings - Fork 252
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
Comments
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. |
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.
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;
}
}
} |
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. |
I won't work on this short term. When I do, I'll let you know to avoid double work. |
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. |
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. |
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! |
Stale bot closed, but I think this is worth adding. Re-opening and adding to the 2.4 milestone |
As a related aside it now more recommended to use 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 Currently I'm trying to use my own |
@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? |
@tmds Thanks for the pointer to the docs. however these are the docs for |
Ah, wrong lib 😄 |
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 toOnExecuteAsync
. This token is canceled when the user terminates the application.The text was updated successfully, but these errors were encountered: