Skip to content

Commit 3a86cab

Browse files
committed
fix: avoid deadlock in the thread invoking the CancelKeyPress handler
1 parent 3e43ac7 commit 3a86cab

File tree

4 files changed

+28
-6
lines changed

4 files changed

+28
-6
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
[See unreleased changes][unreleased].
44

5+
## [v2.4.2]
6+
* Fix [#286] - fix deadlock in CTRL+C handling on Windows
7+
8+
[#286]: https://github.com/natemcmaster/CommandLineUtils/issues/286
9+
510
## [v2.4.1]
611

712
* Fix [#277] - workaround a bug in NuGet's 'deterministic packaging' feature which causes issues based on your timezone

src/CommandLineUtils/CommandLineApplication.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -882,11 +882,12 @@ public async Task<int> ExecuteAsync(string[] args, CancellationToken cancellatio
882882
void cancelHandler(object o, ConsoleCancelEventArgs e)
883883
{
884884
handlerCancellationTokenSource.Cancel();
885-
handlerCompleted.Wait();
885+
// Stops the process from exiting forcefully
886+
e.Cancel = true;
886887
}
887888

888889
#if !NETSTANDARD1_6
889-
void unloadingHandler(object o, EventArgs e)
890+
void processExitHandler(object o, EventArgs e)
890891
{
891892
handlerCancellationTokenSource.Cancel();
892893
handlerCompleted.Wait();
@@ -899,7 +900,7 @@ void unloadingHandler(object o, EventArgs e)
899900
_context.Console.CancelKeyPress += cancelHandler;
900901
#if !NETSTANDARD1_6
901902
// blocks .NET's process unloading from completing until after async completions are done
902-
AppDomain.CurrentDomain.DomainUnload += unloadingHandler;
903+
AppDomain.CurrentDomain.ProcessExit += processExitHandler;
903904
#endif
904905

905906
return await command._handler(handlerCancellationTokenSource.Token);
@@ -912,7 +913,7 @@ void unloadingHandler(object o, EventArgs e)
912913
{
913914
_context.Console.CancelKeyPress -= cancelHandler;
914915
#if !NETSTANDARD1_6
915-
AppDomain.CurrentDomain.DomainUnload -= unloadingHandler;
916+
AppDomain.CurrentDomain.ProcessExit -= processExitHandler;
916917
#endif
917918
handlerCompleted.Set();
918919
}

src/CommandLineUtils/releasenotes.props

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project>
22
<PropertyGroup>
3-
<PackageReleaseNotes Condition="'$(VersionPrefix)' == '2.4.1'">
3+
<PackageReleaseNotes Condition="$(VersionPrefix.StartsWith('2.4.'))">
44
Features and bug fixes by some awesome contributors:
55

66
* @IanG: Attributes for files and directories that must not exist
@@ -16,6 +16,15 @@ Other things I fixed:
1616
* Add async methods that accept cancellation tokens
1717
* Handle CTRL+C by default
1818
* Support calling CommandLineApplication.Execute multiple times
19+
</PackageReleaseNotes>
20+
<PackageReleaseNotes Condition="'$(VersionPrefix)' == '2.4.2'">
21+
$(PackageReleaseNotes)
22+
23+
2.4.2 hot fix:
24+
* Fix deadlock in CTRL+C handling on Windows
25+
</PackageReleaseNotes>
26+
<PackageReleaseNotes Condition="'$(VersionPrefix)' == '2.4.1'">
27+
$(PackageReleaseNotes)
1928

2029
2.4.1 hot fix:
2130
* Workaround a bizarre NuGet bug which causes problems for users in Europe and Asia

test/CommandLineUtils.Tests/Utilities/TestConsole.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using System;
55
using System.IO;
6+
using System.Linq;
7+
using System.Reflection;
68
using Xunit.Abstractions;
79

810
namespace McMaster.Extensions.CommandLineUtils.Tests
@@ -38,7 +40,12 @@ public void ResetColor()
3840

3941
public void RaiseCancelKeyPress()
4042
{
41-
CancelKeyPress?.Invoke(this, default);
43+
// See https://github.com/dotnet/corefx/blob/f2292af3a1794378339d6f5c8adcc0f2019a2cf9/src/System.Console/src/System/ConsoleCancelEventArgs.cs#L14
44+
var eventArgs = typeof(ConsoleCancelEventArgs)
45+
.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)
46+
.First()
47+
.Invoke(new object[] { ConsoleSpecialKey.ControlC });
48+
CancelKeyPress?.Invoke(this, (ConsoleCancelEventArgs)eventArgs);
4249
}
4350
}
4451
}

0 commit comments

Comments
 (0)