@@ -52,7 +52,7 @@ public static class CommandLineBuilderExtensions
5252 /// </param>
5353 /// <returns>The same instance of <see cref="CommandLineBuilder"/>.</returns>
5454 public static CommandLineBuilder CancelOnProcessTermination (
55- this CommandLineBuilder builder ,
55+ this CommandLineBuilder builder ,
5656 TimeSpan ? timeout = null )
5757 {
5858 // https://tldp.org/LDP/abs/html/exitcodes.html - 130 - script terminated by ctrl-c
@@ -67,70 +67,63 @@ public static CommandLineBuilder CancelOnProcessTermination(
6767 {
6868 ConsoleCancelEventHandler ? consoleHandler = null ;
6969 EventHandler ? processExitHandler = null ;
70- ManualResetEventSlim ? blockProcessExit = null ;
71- CancellationTokenSource ? cts = null ;
70+ ManualResetEventSlim blockProcessExit = new ( initialState : false ) ;
7271
73- context . AddLinkedCancellationToken ( ( ) =>
72+ processExitHandler = ( _ , _ ) =>
7473 {
75- cts = new CancellationTokenSource ( ) ;
76- blockProcessExit = new ManualResetEventSlim ( initialState : false ) ;
77- processExitHandler = ( _ , _ ) =>
74+ // Cancel asynchronously not to block the handler (as then the process might possibly run longer then what was the requested timeout)
75+ Task timeoutTask = Task . Delay ( timeout . Value ) ;
76+ Task cancelTask = Task . Factory . StartNew ( context . Cancel ) ;
77+
78+ // The process exits as soon as the event handler returns.
79+ // We provide a return value using Environment.ExitCode
80+ // because Main will not finish executing.
81+ // Wait for the invocation to finish.
82+ if ( ! blockProcessExit . Wait ( timeout > TimeSpan . Zero
83+ ? timeout . Value
84+ : Timeout . InfiniteTimeSpan ) )
7885 {
79- // Cancel asynchronously not to block the handler (as then the process might possibly run longer then what was the requested timeout)
80- Task timeoutTask = Task . Delay ( timeout . Value ) ;
81- Task cancelTask = Task . Factory . StartNew ( cts . Cancel ) ;
82-
83- // The process exits as soon as the event handler returns.
84- // We provide a return value using Environment.ExitCode
85- // because Main will not finish executing.
86- // Wait for the invocation to finish.
87- if ( ! blockProcessExit . Wait ( timeout > TimeSpan . Zero
88- ? timeout . Value
89- : Timeout . InfiniteTimeSpan ) )
90- {
91- context . ExitCode = SIGINT_EXIT_CODE ;
92- }
93- // 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)
94- else if ( Task . WaitAny ( timeoutTask , cancelTask ) == 0 )
95- {
96- // The async cancellation didn't finish in timely manner
97- context . ExitCode = SIGINT_EXIT_CODE ;
98- }
99- ExitCode = context . ExitCode ;
100- } ;
101- // Default limit for ProcesExit handler is 2 seconds
102- // https://docs.microsoft.com/en-us/dotnet/api/system.appdomain.processexit?view=net-6.0
103- consoleHandler = ( _ , args ) =>
86+ context . ExitCode = SIGINT_EXIT_CODE ;
87+ }
88+ // 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)
89+ else if ( Task . WaitAny ( timeoutTask , cancelTask ) == 0 )
10490 {
105- // Stop the process from terminating.
106- // Since the context was cancelled, the invocation should
107- // finish and Main will return.
108- args . Cancel = true ;
109-
110- // If timeout was requested - make sure cancellation processing (or any other activity within the current process)
111- // doesn't keep the process running after the timeout
112- if ( timeout ! > TimeSpan . Zero )
113- {
114- Task
115- . Delay ( timeout . Value , default )
116- . ContinueWith ( t =>
117- {
118- // Prevent our ProcessExit from intervene and block the exit
119- AppDomain . CurrentDomain . ProcessExit -= processExitHandler ;
120- Environment . Exit ( SIGINT_EXIT_CODE ) ;
121- } , ( CancellationToken ) default ) ;
122- }
91+ // The async cancellation didn't finish in timely manner
92+ context . ExitCode = SIGINT_EXIT_CODE ;
93+ }
94+ ExitCode = context . ExitCode ;
95+ } ;
96+ // Default limit for ProcesExit handler is 2 seconds
97+ // https://docs.microsoft.com/en-us/dotnet/api/system.appdomain.processexit?view=net-6.0
98+ consoleHandler = ( _ , args ) =>
99+ {
100+ // Stop the process from terminating.
101+ // Since the context was cancelled, the invocation should
102+ // finish and Main will return.
103+ args . Cancel = true ;
104+
105+ // If timeout was requested - make sure cancellation processing (or any other activity within the current process)
106+ // doesn't keep the process running after the timeout
107+ if ( timeout ! > TimeSpan . Zero )
108+ {
109+ Task
110+ . Delay ( timeout . Value , default )
111+ . ContinueWith ( t =>
112+ {
113+ // Prevent our ProcessExit from intervene and block the exit
114+ AppDomain . CurrentDomain . ProcessExit -= processExitHandler ;
115+ Environment . Exit ( SIGINT_EXIT_CODE ) ;
116+ } , ( CancellationToken ) default ) ;
117+ }
123118
124- // Cancel synchronously here - no need to perform it asynchronously as the timeout is already running (and would kill the process if needed),
125- // plus we cannot wait only on the cancellation (e.g. via `Task.Factory.StartNew(cts.Cancel).Wait(cancelationProcessingTimeout.Value)`)
126- // as we need to abort any other possible execution within the process - even outside the context of cancellation processing
127- cts ? . Cancel ( ) ;
128- } ;
129- Console . CancelKeyPress += consoleHandler ;
130- AppDomain . CurrentDomain . ProcessExit += processExitHandler ;
119+ // Cancel synchronously here - no need to perform it asynchronously as the timeout is already running (and would kill the process if needed),
120+ // plus we cannot wait only on the cancellation (e.g. via `Task.Factory.StartNew(cts.Cancel).Wait(cancelationProcessingTimeout.Value)`)
121+ // as we need to abort any other possible execution within the process - even outside the context of cancellation processing
122+ context . Cancel ( ) ;
123+ } ;
131124
132- return cts . Token ;
133- } ) ;
125+ Console . CancelKeyPress += consoleHandler ;
126+ AppDomain . CurrentDomain . ProcessExit += processExitHandler ;
134127
135128 try
136129 {
@@ -140,14 +133,13 @@ public static CommandLineBuilder CancelOnProcessTermination(
140133 {
141134 Console . CancelKeyPress -= consoleHandler ;
142135 AppDomain . CurrentDomain . ProcessExit -= processExitHandler ;
143- Interlocked . Exchange ( ref cts , null ) ? . Dispose ( ) ;
144136 blockProcessExit ? . Set ( ) ;
145137 }
146138 } , MiddlewareOrderInternal . Startup ) ;
147139
148140 return builder ;
149141 }
150-
142+
151143 /// <summary>
152144 /// Enables the parser to recognize command line directives.
153145 /// </summary>
@@ -206,7 +198,7 @@ public static CommandLineBuilder EnablePosixBundling(
206198 builder . EnablePosixBundling = value ;
207199 return builder ;
208200 }
209-
201+
210202 /// <summary>
211203 /// Ensures that the application is registered with the <c>dotnet-suggest</c> tool to enable command line completions.
212204 /// </summary>
@@ -485,7 +477,7 @@ public static CommandLineBuilder AddMiddleware(
485477
486478 return builder ;
487479 }
488-
480+
489481 /// <summary>
490482 /// Adds a middleware delegate to the invocation pipeline called before a command handler is invoked.
491483 /// </summary>
0 commit comments