command-line-api
command-line-api copied to clipboard
Termination handling in .net framework 4.7.2 deadlocks always with Ctrl + C
This program works in .net core and .net 5. Framework, however, never works when you cancel with Ctrl + C
This is the gist of the program:
static async Task<int> Main(string[] args)
{
var thing = new Command("thing");
thing.Handler = CommandHandler.Create(async (CancellationToken token) =>
{
await Task.Delay(TimeSpan.FromSeconds(10), token);
});
var root = new RootCommand
{
thing
};
return await root.InvokeAsync(args);
}
Ran into this issue and can confirm it's easy to reproduce when using the default Invoke or a CommandLineBuilder with CancelOnProcessTermination.
The main thread gets stuck in System.Console.ControlCHooker.Unhook:
mscorlib.dll!System.Console.ControlCHooker.Unhook()
mscorlib.dll!System.Console.CancelKeyPress.remove(System.ConsoleCancelEventHandler value)
System.CommandLine.dll!System.CommandLine.Builder.CommandLineBuilderExtensions.CancelOnProcessTermination.AnonymousMethod__1_0(System.CommandLine.Invocation.InvocationContext context, System.Func<System.CommandLine.Invocation.InvocationContext, System.Threading.Tasks.Task> next)
Here's the native call-stack (Windows 11 Version 23H2 Build 22631.3296)
ntdll.dll!ZwWaitForAlertByThreadId()
[Inline Frame] ntdll.dll!RtlpWaitOnAddressWithTimeout(_RTL_WAIT_ON_ADDRESS_HASH_BUCKET *)
[Inline Frame] ntdll.dll!RtlpWaitOnAddress(volatile void *)
ntdll.dll!RtlpWaitOnCriticalSection(_RTL_CRITICAL_SECTION * CriticalSection, unsigned long OldLockCount)
ntdll.dll!RtlpEnterCriticalSectionContended(_RTL_CRITICAL_SECTION * CriticalSection)
ntdll.dll!RtlEnterCriticalSection(_RTL_CRITICAL_SECTION * CriticalSection)
KernelBase.dll!SetConsoleCtrlHandler(int(*)(unsigned long) HandlerRoutine, int Add)
mscorlib.ni.dll!00007fffc8e54252()
[Managed to Native Transition]
mscorlib.dll!System.Console.ControlCHooker.Unhook()
mscorlib.dll!System.Console.CancelKeyPress.remove(System.ConsoleCancelEventHandler value)
[Async] System.CommandLine.dll!System.CommandLine.Builder.CommandLineBuilderExtensions.CancelOnProcessTermination.AnonymousMethod__1_0(System.CommandLine.Invocation.InvocationContext context, System.Func<System.CommandLine.Invocation.InvocationContext, System.Threading.Tasks.Task> next)
The CriticalSection being entered is the ConsoleStateLock which is held by the thread that's handling the Ctrl+C, calling the ConsoleCancelEventHandler, calling cts.Cancel(). It's not clear why that thread isn't making progress.
Dupe of #832