Skip to content

Commit d530857

Browse files
committed
Add CancelAync Test, Add Cancel Wait handle
1 parent b5ef870 commit d530857

File tree

2 files changed

+58
-8
lines changed

2 files changed

+58
-8
lines changed

src/Renci.SshNet/SshCommand.cs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public class SshCommand : IDisposable
2727
private CommandAsyncResult _asyncResult;
2828
private AsyncCallback _callback;
2929
private EventWaitHandle _sessionErrorOccuredWaitHandle;
30+
private EventWaitHandle _commmandCancelledWaitHandle;
3031
private Exception _exception;
3132
private StringBuilder _result;
3233
private StringBuilder _error;
@@ -203,6 +204,7 @@ internal SshCommand(ISession session, string commandText, Encoding encoding)
203204
_encoding = encoding;
204205
CommandTimeout = Session.InfiniteTimeSpan;
205206
_sessionErrorOccuredWaitHandle = new AutoResetEvent(initialState: false);
207+
_commmandCancelledWaitHandle = new AutoResetEvent(initialState: false);
206208

207209
_session.Disconnected += Session_Disconnected;
208210
_session.ErrorOccured += Session_ErrorOccured;
@@ -419,20 +421,31 @@ public int EndExecuteWithStatus(IAsyncResult asyncResult)
419421
/// <returns>Exit status of the operation.</returns>
420422
public Task<int> ExecuteAsync()
421423
{
422-
return ExecuteAsync(default);
424+
return ExecuteAsync(forceKill: false, default);
423425
}
424426

425427
/// <summary>
426428
/// Executes the the command asynchronously.
427429
/// </summary>
428430
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
429431
/// <returns>Exit status of the operation.</returns>
430-
public async Task<int> ExecuteAsync(CancellationToken cancellationToken)
432+
public Task<int> ExecuteAsync(CancellationToken cancellationToken)
433+
{
434+
return ExecuteAsync(forceKill: false, cancellationToken);
435+
}
436+
437+
/// <summary>
438+
/// Executes the the command asynchronously.
439+
/// </summary>
440+
/// <param name="forceKill">if true send SIGKILL instead of SIGTERM to cancel the command.</param>
441+
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
442+
/// <returns>Exit status of the operation.</returns>
443+
public async Task<int> ExecuteAsync(bool forceKill, CancellationToken cancellationToken)
431444
{
432445
#if NET || NETSTANDARD2_1_OR_GREATER
433-
await using var ctr = cancellationToken.Register(CancelAsync, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false);
446+
await using var ctr = cancellationToken.Register(() => CancelAsync(forceKill), useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false);
434447
#else
435-
using var ctr = cancellationToken.Register(CancelAsync, useSynchronizationContext: false);
448+
using var ctr = cancellationToken.Register(() => CancelAsync(forceKill), useSynchronizationContext: false);
436449
#endif // NET || NETSTANDARD2_1_OR_GREATER
437450

438451
try
@@ -451,9 +464,12 @@ public async Task<int> ExecuteAsync(CancellationToken cancellationToken)
451464
/// <summary>
452465
/// Cancels command execution in asynchronous scenarios.
453466
/// </summary>
454-
public void CancelAsync()
467+
/// <param name="forceKill">if true send SIGKILL instead of SIGTERM.</param>
468+
public void CancelAsync(bool forceKill = false)
455469
{
456-
_ = _channel?.SendExitSignalRequest("TERM", coreDumped: false, "Command execution has been cancelled.", "en");
470+
var signal = forceKill ? "KILL" : "TERM";
471+
_ = _channel?.SendExitSignalRequest(signal, coreDumped: false, "Command execution has been cancelled.", "en");
472+
_ = _commmandCancelledWaitHandle.Set();
457473
}
458474

459475
/// <summary>
@@ -597,6 +613,7 @@ private void WaitOnHandle(WaitHandle waitHandle)
597613
var waitHandles = new[]
598614
{
599615
_sessionErrorOccuredWaitHandle,
616+
_commmandCancelledWaitHandle,
600617
waitHandle
601618
};
602619

@@ -606,7 +623,8 @@ private void WaitOnHandle(WaitHandle waitHandle)
606623
case 0:
607624
ExceptionDispatchInfo.Capture(_exception).Throw();
608625
break;
609-
case 1:
626+
case 1: // Command cancelled
627+
case 2:
610628
// Specified waithandle was signaled
611629
break;
612630
case WaitHandle.WaitTimeout:
@@ -711,6 +729,9 @@ protected virtual void Dispose(bool disposing)
711729
_sessionErrorOccuredWaitHandle = null;
712730
}
713731

732+
_commmandCancelledWaitHandle?.Dispose();
733+
_commmandCancelledWaitHandle = null;
734+
714735
_isDisposed = true;
715736
}
716737
}

test/Renci.SshNet.IntegrationTests/OldIntegrationTests/SshCommandTest.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ public async Task Test_Execute_SingleCommandAsync()
6969
}
7070

7171
[TestMethod]
72+
[Timeout(2000)]
7273
[ExpectedException(typeof(OperationCanceledException))]
7374
public async Task Test_Execute_SingleCommandAsync_WithCancelledToken()
7475
{
@@ -77,7 +78,7 @@ public async Task Test_Execute_SingleCommandAsync_WithCancelledToken()
7778
#region Example SshCommand CreateCommand ExecuteAsync With Cancelled Token
7879
using var cts = new CancellationTokenSource();
7980
await client.ConnectAsync(cts.Token);
80-
var command = $"echo {Guid.NewGuid().ToString()};/bin/sleep 5";
81+
var command = $"/bin/sleep 5; echo {Guid.NewGuid().ToString()}";
8182
using var cmd = client.CreateCommand(command);
8283
cts.CancelAfter(100);
8384
await cmd.ExecuteAsync(cts.Token).ConfigureAwait(false);
@@ -86,6 +87,34 @@ public async Task Test_Execute_SingleCommandAsync_WithCancelledToken()
8687
}
8788
}
8889

90+
[TestMethod]
91+
[Timeout(5000)]
92+
public void Test_CancelAsync_Running_Command()
93+
{
94+
using var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password);
95+
#region Example SshCommand CancelAsync
96+
client.Connect();
97+
98+
var testValue = Guid.NewGuid().ToString();
99+
var command = $"sleep 10s; echo {testValue}";
100+
using var cmd = client.CreateCommand(command);
101+
try
102+
{
103+
var asyncResult = cmd.BeginExecute();
104+
cmd.CancelAsync();
105+
cmd.EndExecute(asyncResult);
106+
}
107+
catch (OperationCanceledException)
108+
{
109+
}
110+
111+
Assert.AreNotEqual(cmd.GetResult().Trim(), testValue);
112+
113+
client.Disconnect();
114+
#endregion
115+
116+
}
117+
89118
[TestMethod]
90119
public void Test_Execute_OutputStream()
91120
{

0 commit comments

Comments
 (0)