@@ -26,11 +26,13 @@ public class SshCommand : IDisposable
2626 private CommandAsyncResult _asyncResult ;
2727 private AsyncCallback _callback ;
2828 private EventWaitHandle _sessionErrorOccuredWaitHandle ;
29+ private EventWaitHandle _commandCancelledWaitHandle ;
2930 private Exception _exception ;
3031 private StringBuilder _result ;
3132 private StringBuilder _error ;
3233 private bool _hasError ;
3334 private bool _isDisposed ;
35+ private bool _isCancelled ;
3436 private ChannelInputStream _inputStream ;
3537 private TimeSpan _commandTimeout ;
3638
@@ -84,7 +86,7 @@ public TimeSpan CommandTimeout
8486 /// <returns>
8587 /// The stream that can be used to transfer data to the command's input stream.
8688 /// </returns>
87- #pragma warning disable CA1859 // Use concrete types when possible for improved performance
89+ #pragma warning disable CA1859 // Use concrete types when possible for improved performance
8890 public Stream CreateInputStream ( )
8991#pragma warning restore CA1859 // Use concrete types when possible for improved performance
9092 {
@@ -186,7 +188,7 @@ internal SshCommand(ISession session, string commandText, Encoding encoding)
186188 _encoding = encoding ;
187189 CommandTimeout = Timeout . InfiniteTimeSpan ;
188190 _sessionErrorOccuredWaitHandle = new AutoResetEvent ( initialState : false ) ;
189-
191+ _commandCancelledWaitHandle = new AutoResetEvent ( initialState : false ) ;
190192 _session . Disconnected += Session_Disconnected ;
191193 _session . ErrorOccured += Session_ErrorOccured ;
192194 }
@@ -249,11 +251,11 @@ public IAsyncResult BeginExecute(AsyncCallback callback, object state)
249251
250252 // Create new AsyncResult object
251253 _asyncResult = new CommandAsyncResult
252- {
253- AsyncWaitHandle = new ManualResetEvent ( initialState : false ) ,
254- IsCompleted = false ,
255- AsyncState = state ,
256- } ;
254+ {
255+ AsyncWaitHandle = new ManualResetEvent ( initialState : false ) ,
256+ IsCompleted = false ,
257+ AsyncState = state ,
258+ } ;
257259
258260 if ( _channel is not null )
259261 {
@@ -349,20 +351,25 @@ public string EndExecute(IAsyncResult asyncResult)
349351
350352 commandAsyncResult . EndCalled = true ;
351353
352- return Result ;
354+ if ( ! _isCancelled )
355+ {
356+ return Result ;
357+ }
358+
359+ SetAsyncComplete ( ) ;
360+ throw new OperationCanceledException ( ) ;
353361 }
354362 }
355363
356364 /// <summary>
357365 /// Cancels command execution in asynchronous scenarios.
358366 /// </summary>
359- public void CancelAsync ( )
367+ /// <param name="forceKill">if true send SIGKILL instead of SIGTERM.</param>
368+ public void CancelAsync ( bool forceKill = false )
360369 {
361- if ( _channel is not null && _channel . IsOpen && _asyncResult is not null )
362- {
363- // TODO: check with Oleg if we shouldn't dispose the channel and uninitialize it ?
364- _channel . Dispose ( ) ;
365- }
370+ var signal = forceKill ? "KILL" : "TERM" ;
371+ _ = _channel ? . SendExitSignalRequest ( signal , coreDumped : false , "Command execution has been cancelled." , "en" ) ;
372+ _ = _commandCancelledWaitHandle ? . Set ( ) ;
366373 }
367374
368375 /// <summary>
@@ -430,14 +437,14 @@ private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
430437 _ = _sessionErrorOccuredWaitHandle . Set ( ) ;
431438 }
432439
433- private void Channel_Closed ( object sender , ChannelEventArgs e )
440+ private void SetAsyncComplete ( )
434441 {
435442 OutputStream ? . Flush ( ) ;
436443 ExtendedOutputStream ? . Flush ( ) ;
437444
438445 _asyncResult . IsCompleted = true ;
439446
440- if ( _callback is not null )
447+ if ( _callback is not null && ! _isCancelled )
441448 {
442449 // Execute callback on different thread
443450 ThreadAbstraction . ExecuteThread ( ( ) => _callback ( _asyncResult ) ) ;
@@ -446,6 +453,11 @@ private void Channel_Closed(object sender, ChannelEventArgs e)
446453 _ = ( ( EventWaitHandle ) _asyncResult . AsyncWaitHandle ) . Set ( ) ;
447454 }
448455
456+ private void Channel_Closed ( object sender , ChannelEventArgs e )
457+ {
458+ SetAsyncComplete ( ) ;
459+ }
460+
449461 private void Channel_RequestReceived ( object sender , ChannelRequestEventArgs e )
450462 {
451463 if ( e . Info is ExitStatusRequestInfo exitStatusInfo )
@@ -506,7 +518,8 @@ private void WaitOnHandle(WaitHandle waitHandle)
506518 var waitHandles = new [ ]
507519 {
508520 _sessionErrorOccuredWaitHandle ,
509- waitHandle
521+ waitHandle ,
522+ _commandCancelledWaitHandle
510523 } ;
511524
512525 var signaledElement = WaitHandle . WaitAny ( waitHandles , CommandTimeout ) ;
@@ -518,6 +531,9 @@ private void WaitOnHandle(WaitHandle waitHandle)
518531 case 1 :
519532 // Specified waithandle was signaled
520533 break ;
534+ case 2 :
535+ _isCancelled = true ;
536+ break ;
521537 case WaitHandle . WaitTimeout :
522538 throw new SshOperationTimeoutException ( string . Format ( CultureInfo . CurrentCulture , "Command '{0}' has timed out." , CommandText ) ) ;
523539 default :
@@ -620,6 +636,9 @@ protected virtual void Dispose(bool disposing)
620636 _sessionErrorOccuredWaitHandle = null ;
621637 }
622638
639+ _commandCancelledWaitHandle ? . Dispose ( ) ;
640+ _commandCancelledWaitHandle = null ;
641+
623642 _isDisposed = true ;
624643 }
625644 }
0 commit comments