|  | @@ -26,11 +26,13 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |          private CommandAsyncResult _asyncResult;
 | 
	
		
			
				|  |  |          private AsyncCallback _callback;
 | 
	
		
			
				|  |  |          private EventWaitHandle _sessionErrorOccuredWaitHandle;
 | 
	
		
			
				|  |  | +        private EventWaitHandle _commandCancelledWaitHandle;
 | 
	
		
			
				|  |  |          private Exception _exception;
 | 
	
		
			
				|  |  |          private StringBuilder _result;
 | 
	
		
			
				|  |  |          private StringBuilder _error;
 | 
	
		
			
				|  |  |          private bool _hasError;
 | 
	
		
			
				|  |  |          private bool _isDisposed;
 | 
	
		
			
				|  |  | +        private bool _isCancelled;
 | 
	
		
			
				|  |  |          private ChannelInputStream _inputStream;
 | 
	
		
			
				|  |  |          private TimeSpan _commandTimeout;
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -84,7 +86,7 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |          /// <returns>
 | 
	
		
			
				|  |  |          /// The stream that can be used to transfer data to the command's input stream.
 | 
	
		
			
				|  |  |          /// </returns>
 | 
	
		
			
				|  |  | - #pragma warning disable CA1859 // Use concrete types when possible for improved performance
 | 
	
		
			
				|  |  | +#pragma warning disable CA1859 // Use concrete types when possible for improved performance
 | 
	
		
			
				|  |  |          public Stream CreateInputStream()
 | 
	
		
			
				|  |  |  #pragma warning restore CA1859 // Use concrete types when possible for improved performance
 | 
	
		
			
				|  |  |          {
 | 
	
	
		
			
				|  | @@ -186,7 +188,7 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |              _encoding = encoding;
 | 
	
		
			
				|  |  |              CommandTimeout = Timeout.InfiniteTimeSpan;
 | 
	
		
			
				|  |  |              _sessionErrorOccuredWaitHandle = new AutoResetEvent(initialState: false);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +            _commandCancelledWaitHandle = new AutoResetEvent(initialState: false);
 | 
	
		
			
				|  |  |              _session.Disconnected += Session_Disconnected;
 | 
	
		
			
				|  |  |              _session.ErrorOccured += Session_ErrorOccured;
 | 
	
		
			
				|  |  |          }
 | 
	
	
		
			
				|  | @@ -249,11 +251,11 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              // Create new AsyncResult object
 | 
	
		
			
				|  |  |              _asyncResult = new CommandAsyncResult
 | 
	
		
			
				|  |  | -                {
 | 
	
		
			
				|  |  | -                    AsyncWaitHandle = new ManualResetEvent(initialState: false),
 | 
	
		
			
				|  |  | -                    IsCompleted = false,
 | 
	
		
			
				|  |  | -                    AsyncState = state,
 | 
	
		
			
				|  |  | -                };
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                AsyncWaitHandle = new ManualResetEvent(initialState: false),
 | 
	
		
			
				|  |  | +                IsCompleted = false,
 | 
	
		
			
				|  |  | +                AsyncState = state,
 | 
	
		
			
				|  |  | +            };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if (_channel is not null)
 | 
	
		
			
				|  |  |              {
 | 
	
	
		
			
				|  | @@ -349,20 +351,25 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                  commandAsyncResult.EndCalled = true;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                return Result;
 | 
	
		
			
				|  |  | +                if (!_isCancelled)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    return Result;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                SetAsyncComplete();
 | 
	
		
			
				|  |  | +                throw new OperationCanceledException();
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Cancels command execution in asynchronous scenarios.
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
		
			
				|  |  | -        public void CancelAsync()
 | 
	
		
			
				|  |  | +        /// <param name="forceKill">if true send SIGKILL instead of SIGTERM.</param>
 | 
	
		
			
				|  |  | +        public void CancelAsync(bool forceKill = false)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            if (_channel is not null && _channel.IsOpen && _asyncResult is not null)
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                // TODO: check with Oleg if we shouldn't dispose the channel and uninitialize it ?
 | 
	
		
			
				|  |  | -                _channel.Dispose();
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | +            var signal = forceKill ? "KILL" : "TERM";
 | 
	
		
			
				|  |  | +            _ = _channel?.SendExitSignalRequest(signal, coreDumped: false, "Command execution has been cancelled.", "en");
 | 
	
		
			
				|  |  | +            _ = _commandCancelledWaitHandle?.Set();
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
	
		
			
				|  | @@ -430,14 +437,14 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |              _ = _sessionErrorOccuredWaitHandle.Set();
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        private void Channel_Closed(object sender, ChannelEventArgs e)
 | 
	
		
			
				|  |  | +        private void SetAsyncComplete()
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              OutputStream?.Flush();
 | 
	
		
			
				|  |  |              ExtendedOutputStream?.Flush();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              _asyncResult.IsCompleted = true;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            if (_callback is not null)
 | 
	
		
			
				|  |  | +            if (_callback is not null && !_isCancelled)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  // Execute callback on different thread
 | 
	
		
			
				|  |  |                  ThreadAbstraction.ExecuteThread(() => _callback(_asyncResult));
 | 
	
	
		
			
				|  | @@ -446,6 +453,11 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |              _ = ((EventWaitHandle) _asyncResult.AsyncWaitHandle).Set();
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +        private void Channel_Closed(object sender, ChannelEventArgs e)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            SetAsyncComplete();
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          private void Channel_RequestReceived(object sender, ChannelRequestEventArgs e)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              if (e.Info is ExitStatusRequestInfo exitStatusInfo)
 | 
	
	
		
			
				|  | @@ -506,7 +518,8 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |              var waitHandles = new[]
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  |                      _sessionErrorOccuredWaitHandle,
 | 
	
		
			
				|  |  | -                    waitHandle
 | 
	
		
			
				|  |  | +                    waitHandle,
 | 
	
		
			
				|  |  | +                    _commandCancelledWaitHandle
 | 
	
		
			
				|  |  |                  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              var signaledElement = WaitHandle.WaitAny(waitHandles, CommandTimeout);
 | 
	
	
		
			
				|  | @@ -518,6 +531,9 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |                  case 1:
 | 
	
		
			
				|  |  |                      // Specified waithandle was signaled
 | 
	
		
			
				|  |  |                      break;
 | 
	
		
			
				|  |  | +                case 2:
 | 
	
		
			
				|  |  | +                    _isCancelled = true;
 | 
	
		
			
				|  |  | +                    break;
 | 
	
		
			
				|  |  |                  case WaitHandle.WaitTimeout:
 | 
	
		
			
				|  |  |                      throw new SshOperationTimeoutException(string.Format(CultureInfo.CurrentCulture, "Command '{0}' has timed out.", CommandText));
 | 
	
		
			
				|  |  |                  default:
 | 
	
	
		
			
				|  | @@ -620,6 +636,9 @@ namespace Renci.SshNet
 | 
	
		
			
				|  |  |                      _sessionErrorOccuredWaitHandle = null;
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +                _commandCancelledWaitHandle?.Dispose();
 | 
	
		
			
				|  |  | +                _commandCancelledWaitHandle = null;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |                  _isDisposed = true;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 |