|
|
@@ -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;
|
|
|
}
|
|
|
}
|