Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix CancelAsync Cause Deadlock #1345

Merged
merged 9 commits into from
Mar 24, 2024
21 changes: 13 additions & 8 deletions src/Renci.SshNet/SshCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class SshCommand : IDisposable
private CommandAsyncResult _asyncResult;
private AsyncCallback _callback;
private EventWaitHandle _sessionErrorOccuredWaitHandle;
private EventWaitHandle _commandCancelledWaitHandle;
private Exception _exception;
private StringBuilder _result;
private StringBuilder _error;
Expand Down Expand Up @@ -186,7 +187,7 @@ internal SshCommand(ISession session, string commandText, Encoding encoding)
_encoding = encoding;
CommandTimeout = Session.InfiniteTimeSpan;
_sessionErrorOccuredWaitHandle = new AutoResetEvent(initialState: false);

_commandCancelledWaitHandle = new AutoResetEvent(initialState: false);
_session.Disconnected += Session_Disconnected;
_session.ErrorOccured += Session_ErrorOccured;
}
Expand Down Expand Up @@ -356,13 +357,12 @@ public string EndExecute(IAsyncResult asyncResult)
/// <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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we drop the Async suffix here (and obsolete CancelAsync) as it is not an async method. @WojciechNagorski?

{
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();
Rob-Hague marked this conversation as resolved.
Show resolved Hide resolved
}

/// <summary>
Expand Down Expand Up @@ -506,6 +506,7 @@ private void WaitOnHandle(WaitHandle waitHandle)
var waitHandles = new[]
{
_sessionErrorOccuredWaitHandle,
_commandCancelledWaitHandle,
waitHandle
};

Expand All @@ -515,7 +516,8 @@ private void WaitOnHandle(WaitHandle waitHandle)
case 0:
ExceptionDispatchInfo.Capture(_exception).Throw();
break;
case 1:
case 1: // Command cancelled
case 2:
// Specified waithandle was signaled
break;
case WaitHandle.WaitTimeout:
Expand Down Expand Up @@ -620,6 +622,9 @@ protected virtual void Dispose(bool disposing)
_sessionErrorOccuredWaitHandle = null;
}

_commandCancelledWaitHandle?.Dispose();
_commandCancelledWaitHandle = null;

_isDisposed = true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,30 @@ public void Test_Execute_SingleCommand()
}
}

[TestMethod]
[Timeout(5000)]
public void Test_CancelAsync_Running_Command()
{
using var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password);
#region Example SshCommand CancelAsync
client.Connect();
var testValue = Guid.NewGuid().ToString();
var command = $"sleep 10s; echo {testValue}";
using var cmd = client.CreateCommand(command);
try
{
var asyncResult = cmd.BeginExecute();
cmd.CancelAsync();
cmd.EndExecute(asyncResult);
}
catch (OperationCanceledException)
{
}
zeotuan marked this conversation as resolved.
Show resolved Hide resolved
client.Disconnect();
Assert.AreNotEqual(cmd.Result.Trim(), testValue);
Rob-Hague marked this conversation as resolved.
Show resolved Hide resolved
#endregion
}

[TestMethod]
public void Test_Execute_OutputStream()
{
Expand Down Expand Up @@ -222,7 +246,7 @@ public void Test_Execute_Command_ExitStatus()
client.Connect();

var cmd = client.RunCommand("exit 128");

Console.WriteLine(cmd.ExitStatus);

client.Disconnect();
Expand Down Expand Up @@ -443,7 +467,7 @@ public void Test_Execute_Invalid_Command()
}

[TestMethod]

public void Test_MultipleThread_100_MultipleConnections()
{
try
Expand Down