@@ -26,11 +26,13 @@ public class SshCommand : IDisposable
26
26
private CommandAsyncResult _asyncResult ;
27
27
private AsyncCallback _callback ;
28
28
private EventWaitHandle _sessionErrorOccuredWaitHandle ;
29
+ private EventWaitHandle _commandCancelledWaitHandle ;
29
30
private Exception _exception ;
30
31
private StringBuilder _result ;
31
32
private StringBuilder _error ;
32
33
private bool _hasError ;
33
34
private bool _isDisposed ;
35
+ private bool _isCancelled ;
34
36
private ChannelInputStream _inputStream ;
35
37
private TimeSpan _commandTimeout ;
36
38
@@ -84,7 +86,7 @@ public TimeSpan CommandTimeout
84
86
/// <returns>
85
87
/// The stream that can be used to transfer data to the command's input stream.
86
88
/// </returns>
87
- #pragma warning disable CA1859 // Use concrete types when possible for improved performance
89
+ #pragma warning disable CA1859 // Use concrete types when possible for improved performance
88
90
public Stream CreateInputStream ( )
89
91
#pragma warning restore CA1859 // Use concrete types when possible for improved performance
90
92
{
@@ -186,7 +188,7 @@ internal SshCommand(ISession session, string commandText, Encoding encoding)
186
188
_encoding = encoding ;
187
189
CommandTimeout = Timeout . InfiniteTimeSpan ;
188
190
_sessionErrorOccuredWaitHandle = new AutoResetEvent ( initialState : false ) ;
189
-
191
+ _commandCancelledWaitHandle = new AutoResetEvent ( initialState : false ) ;
190
192
_session . Disconnected += Session_Disconnected ;
191
193
_session . ErrorOccured += Session_ErrorOccured ;
192
194
}
@@ -249,11 +251,11 @@ public IAsyncResult BeginExecute(AsyncCallback callback, object state)
249
251
250
252
// Create new AsyncResult object
251
253
_asyncResult = new CommandAsyncResult
252
- {
253
- AsyncWaitHandle = new ManualResetEvent ( initialState : false ) ,
254
- IsCompleted = false ,
255
- AsyncState = state ,
256
- } ;
254
+ {
255
+ AsyncWaitHandle = new ManualResetEvent ( initialState : false ) ,
256
+ IsCompleted = false ,
257
+ AsyncState = state ,
258
+ } ;
257
259
258
260
if ( _channel is not null )
259
261
{
@@ -349,20 +351,25 @@ public string EndExecute(IAsyncResult asyncResult)
349
351
350
352
commandAsyncResult . EndCalled = true ;
351
353
352
- return Result ;
354
+ if ( ! _isCancelled )
355
+ {
356
+ return Result ;
357
+ }
358
+
359
+ SetAsyncComplete ( ) ;
360
+ throw new OperationCanceledException ( ) ;
353
361
}
354
362
}
355
363
356
364
/// <summary>
357
365
/// Cancels command execution in asynchronous scenarios.
358
366
/// </summary>
359
- public void CancelAsync ( )
367
+ /// <param name="forceKill">if true send SIGKILL instead of SIGTERM.</param>
368
+ public void CancelAsync ( bool forceKill = false )
360
369
{
361
- if ( _channel is not null && _channel . IsOpen && _asyncResult is not null )
362
- {
363
- // TODO: check with Oleg if we shouldn't dispose the channel and uninitialize it ?
364
- _channel . Dispose ( ) ;
365
- }
370
+ var signal = forceKill ? "KILL" : "TERM" ;
371
+ _ = _channel ? . SendExitSignalRequest ( signal , coreDumped : false , "Command execution has been cancelled." , "en" ) ;
372
+ _ = _commandCancelledWaitHandle ? . Set ( ) ;
366
373
}
367
374
368
375
/// <summary>
@@ -430,14 +437,14 @@ private void Session_ErrorOccured(object sender, ExceptionEventArgs e)
430
437
_ = _sessionErrorOccuredWaitHandle . Set ( ) ;
431
438
}
432
439
433
- private void Channel_Closed ( object sender , ChannelEventArgs e )
440
+ private void SetAsyncComplete ( )
434
441
{
435
442
OutputStream ? . Flush ( ) ;
436
443
ExtendedOutputStream ? . Flush ( ) ;
437
444
438
445
_asyncResult . IsCompleted = true ;
439
446
440
- if ( _callback is not null )
447
+ if ( _callback is not null && ! _isCancelled )
441
448
{
442
449
// Execute callback on different thread
443
450
ThreadAbstraction . ExecuteThread ( ( ) => _callback ( _asyncResult ) ) ;
@@ -446,6 +453,11 @@ private void Channel_Closed(object sender, ChannelEventArgs e)
446
453
_ = ( ( EventWaitHandle ) _asyncResult . AsyncWaitHandle ) . Set ( ) ;
447
454
}
448
455
456
+ private void Channel_Closed ( object sender , ChannelEventArgs e )
457
+ {
458
+ SetAsyncComplete ( ) ;
459
+ }
460
+
449
461
private void Channel_RequestReceived ( object sender , ChannelRequestEventArgs e )
450
462
{
451
463
if ( e . Info is ExitStatusRequestInfo exitStatusInfo )
@@ -506,7 +518,8 @@ private void WaitOnHandle(WaitHandle waitHandle)
506
518
var waitHandles = new [ ]
507
519
{
508
520
_sessionErrorOccuredWaitHandle ,
509
- waitHandle
521
+ waitHandle ,
522
+ _commandCancelledWaitHandle
510
523
} ;
511
524
512
525
var signaledElement = WaitHandle . WaitAny ( waitHandles , CommandTimeout ) ;
@@ -518,6 +531,9 @@ private void WaitOnHandle(WaitHandle waitHandle)
518
531
case 1 :
519
532
// Specified waithandle was signaled
520
533
break ;
534
+ case 2 :
535
+ _isCancelled = true ;
536
+ break ;
521
537
case WaitHandle . WaitTimeout :
522
538
throw new SshOperationTimeoutException ( string . Format ( CultureInfo . CurrentCulture , "Command '{0}' has timed out." , CommandText ) ) ;
523
539
default :
@@ -620,6 +636,9 @@ protected virtual void Dispose(bool disposing)
620
636
_sessionErrorOccuredWaitHandle = null ;
621
637
}
622
638
639
+ _commandCancelledWaitHandle ? . Dispose ( ) ;
640
+ _commandCancelledWaitHandle = null ;
641
+
623
642
_isDisposed = true ;
624
643
}
625
644
}
0 commit comments