Skip to content

Commit

Permalink
Fix CancelAsync Cause Deadlock
Browse files Browse the repository at this point in the history
  • Loading branch information
zeotuan committed Mar 7, 2024
1 parent 0876e88 commit f7f50a4
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 10 deletions.
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)
{
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>
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)
{
}
client.Disconnect();
Assert.AreNotEqual(cmd.Result.Trim(), testValue);
#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

0 comments on commit f7f50a4

Please sign in to comment.