@@ -51,7 +51,7 @@ public static class CommandLineBuilderExtensions
51
51
/// </param>
52
52
/// <returns>The same instance of <see cref="CommandLineBuilder"/>.</returns>
53
53
public static CommandLineBuilder CancelOnProcessTermination (
54
- this CommandLineBuilder builder ,
54
+ this CommandLineBuilder builder ,
55
55
TimeSpan ? timeout = null )
56
56
{
57
57
// https://tldp.org/LDP/abs/html/exitcodes.html - 130 - script terminated by ctrl-c
@@ -66,70 +66,63 @@ public static CommandLineBuilder CancelOnProcessTermination(
66
66
{
67
67
ConsoleCancelEventHandler ? consoleHandler = null ;
68
68
EventHandler ? processExitHandler = null ;
69
- ManualResetEventSlim ? blockProcessExit = null ;
70
- CancellationTokenSource ? cts = null ;
69
+ ManualResetEventSlim blockProcessExit = new ( initialState : false ) ;
71
70
72
- context . AddLinkedCancellationToken ( ( ) =>
71
+ processExitHandler = ( _ , _ ) =>
73
72
{
74
- cts = new CancellationTokenSource ( ) ;
75
- blockProcessExit = new ManualResetEventSlim ( initialState : false ) ;
76
- processExitHandler = ( _ , _ ) =>
73
+ // Cancel asynchronously not to block the handler (as then the process might possibly run longer then what was the requested timeout)
74
+ Task timeoutTask = Task . Delay ( timeout . Value ) ;
75
+ Task cancelTask = Task . Factory . StartNew ( context . Cancel ) ;
76
+
77
+ // The process exits as soon as the event handler returns.
78
+ // We provide a return value using Environment.ExitCode
79
+ // because Main will not finish executing.
80
+ // Wait for the invocation to finish.
81
+ if ( ! blockProcessExit . Wait ( timeout > TimeSpan . Zero
82
+ ? timeout . Value
83
+ : Timeout . InfiniteTimeSpan ) )
77
84
{
78
- // Cancel asynchronously not to block the handler (as then the process might possibly run longer then what was the requested timeout)
79
- Task timeoutTask = Task . Delay ( timeout . Value ) ;
80
- Task cancelTask = Task . Factory . StartNew ( cts . Cancel ) ;
81
-
82
- // The process exits as soon as the event handler returns.
83
- // We provide a return value using Environment.ExitCode
84
- // because Main will not finish executing.
85
- // Wait for the invocation to finish.
86
- if ( ! blockProcessExit . Wait ( timeout > TimeSpan . Zero
87
- ? timeout . Value
88
- : Timeout . InfiniteTimeSpan ) )
89
- {
90
- context . ExitCode = SIGINT_EXIT_CODE ;
91
- }
92
- // Let's block here (to prevent process bailing out) for the rest of the timeout (if any), for cancellation to finish (if it hasn't yet)
93
- else if ( Task . WaitAny ( timeoutTask , cancelTask ) == 0 )
94
- {
95
- // The async cancellation didn't finish in timely manner
96
- context . ExitCode = SIGINT_EXIT_CODE ;
97
- }
98
- ExitCode = context . ExitCode ;
99
- } ;
100
- // Default limit for ProcesExit handler is 2 seconds
101
- // https://docs.microsoft.com/en-us/dotnet/api/system.appdomain.processexit?view=net-6.0
102
- consoleHandler = ( _ , args ) =>
85
+ context . ExitCode = SIGINT_EXIT_CODE ;
86
+ }
87
+ // Let's block here (to prevent process bailing out) for the rest of the timeout (if any), for cancellation to finish (if it hasn't yet)
88
+ else if ( Task . WaitAny ( timeoutTask , cancelTask ) == 0 )
103
89
{
104
- // Stop the process from terminating.
105
- // Since the context was cancelled, the invocation should
106
- // finish and Main will return.
107
- args . Cancel = true ;
108
-
109
- // If timeout was requested - make sure cancellation processing (or any other activity within the current process)
110
- // doesn't keep the process running after the timeout
111
- if ( timeout ! > TimeSpan . Zero )
112
- {
113
- Task
114
- . Delay ( timeout . Value , default )
115
- . ContinueWith ( t =>
116
- {
117
- // Prevent our ProcessExit from intervene and block the exit
118
- AppDomain . CurrentDomain . ProcessExit -= processExitHandler ;
119
- Environment . Exit ( SIGINT_EXIT_CODE ) ;
120
- } , ( CancellationToken ) default ) ;
121
- }
90
+ // The async cancellation didn't finish in timely manner
91
+ context . ExitCode = SIGINT_EXIT_CODE ;
92
+ }
93
+ ExitCode = context . ExitCode ;
94
+ } ;
95
+ // Default limit for ProcesExit handler is 2 seconds
96
+ // https://docs.microsoft.com/en-us/dotnet/api/system.appdomain.processexit?view=net-6.0
97
+ consoleHandler = ( _ , args ) =>
98
+ {
99
+ // Stop the process from terminating.
100
+ // Since the context was cancelled, the invocation should
101
+ // finish and Main will return.
102
+ args . Cancel = true ;
103
+
104
+ // If timeout was requested - make sure cancellation processing (or any other activity within the current process)
105
+ // doesn't keep the process running after the timeout
106
+ if ( timeout ! > TimeSpan . Zero )
107
+ {
108
+ Task
109
+ . Delay ( timeout . Value , default )
110
+ . ContinueWith ( t =>
111
+ {
112
+ // Prevent our ProcessExit from intervene and block the exit
113
+ AppDomain . CurrentDomain . ProcessExit -= processExitHandler ;
114
+ Environment . Exit ( SIGINT_EXIT_CODE ) ;
115
+ } , ( CancellationToken ) default ) ;
116
+ }
122
117
123
- // Cancel synchronously here - no need to perform it asynchronously as the timeout is already running (and would kill the process if needed),
124
- // plus we cannot wait only on the cancellation (e.g. via `Task.Factory.StartNew(cts.Cancel).Wait(cancelationProcessingTimeout.Value)`)
125
- // as we need to abort any other possible execution within the process - even outside the context of cancellation processing
126
- cts ? . Cancel ( ) ;
127
- } ;
128
- Console . CancelKeyPress += consoleHandler ;
129
- AppDomain . CurrentDomain . ProcessExit += processExitHandler ;
118
+ // Cancel synchronously here - no need to perform it asynchronously as the timeout is already running (and would kill the process if needed),
119
+ // plus we cannot wait only on the cancellation (e.g. via `Task.Factory.StartNew(cts.Cancel).Wait(cancelationProcessingTimeout.Value)`)
120
+ // as we need to abort any other possible execution within the process - even outside the context of cancellation processing
121
+ context . Cancel ( ) ;
122
+ } ;
130
123
131
- return cts . Token ;
132
- } ) ;
124
+ Console . CancelKeyPress += consoleHandler ;
125
+ AppDomain . CurrentDomain . ProcessExit += processExitHandler ;
133
126
134
127
try
135
128
{
@@ -139,14 +132,13 @@ public static CommandLineBuilder CancelOnProcessTermination(
139
132
{
140
133
Console . CancelKeyPress -= consoleHandler ;
141
134
AppDomain . CurrentDomain . ProcessExit -= processExitHandler ;
142
- Interlocked . Exchange ( ref cts , null ) ? . Dispose ( ) ;
143
135
blockProcessExit ? . Set ( ) ;
144
136
}
145
137
} , MiddlewareOrderInternal . Startup ) ;
146
138
147
139
return builder ;
148
140
}
149
-
141
+
150
142
/// <summary>
151
143
/// Enables the parser to recognize command line directives.
152
144
/// </summary>
@@ -205,7 +197,7 @@ public static CommandLineBuilder EnablePosixBundling(
205
197
builder . EnablePosixBundling = value ;
206
198
return builder ;
207
199
}
208
-
200
+
209
201
/// <summary>
210
202
/// Ensures that the application is registered with the <c>dotnet-suggest</c> tool to enable command line completions.
211
203
/// </summary>
@@ -484,7 +476,7 @@ public static CommandLineBuilder AddMiddleware(
484
476
485
477
return builder ;
486
478
}
487
-
479
+
488
480
/// <summary>
489
481
/// Adds a middleware delegate to the invocation pipeline called before a command handler is invoked.
490
482
/// </summary>
0 commit comments