@@ -21,7 +21,7 @@ public class SshCommand : IDisposable
2121 private readonly ISession _session ;
2222 private readonly Encoding _encoding ;
2323
24- private IChannelSession ? _channel ;
24+ private IChannelSession _channel ;
2525 private TaskCompletionSource < object > ? _tcs ;
2626 private CancellationTokenSource ? _cts ;
2727 private CancellationTokenRegistration _tokenRegistration ;
@@ -142,14 +142,14 @@ public int? ExitStatus
142142 /// </example>
143143 public Stream CreateInputStream ( )
144144 {
145- if ( _channel == null )
145+ if ( ! _channel . IsOpen )
146146 {
147- throw new InvalidOperationException ( $ "The input stream can be used only after calling BeginExecute and before calling EndExecute .") ;
147+ throw new InvalidOperationException ( "The input stream can be used only during execution ." ) ;
148148 }
149149
150150 if ( _inputStream != null )
151151 {
152- throw new InvalidOperationException ( $ "The input stream already exists.") ;
152+ throw new InvalidOperationException ( "The input stream already exists." ) ;
153153 }
154154
155155 _inputStream = new ChannelInputStream ( _channel ) ;
@@ -226,6 +226,7 @@ internal SshCommand(ISession session, string commandText, Encoding encoding)
226226 ExtendedOutputStream = new PipeStream ( ) ;
227227 _session . Disconnected += Session_Disconnected ;
228228 _session . ErrorOccured += Session_ErrorOccured ;
229+ _channel = _session . CreateChannelSession ( ) ;
229230 }
230231
231232 /// <summary>
@@ -257,6 +258,8 @@ public Task ExecuteAsync(CancellationToken cancellationToken = default)
257258 throw new InvalidOperationException ( "Asynchronous operation is already in progress." ) ;
258259 }
259260
261+ UnsubscribeFromChannelEvents ( dispose : true ) ;
262+
260263 OutputStream . Dispose ( ) ;
261264 ExtendedOutputStream . Dispose ( ) ;
262265
@@ -265,6 +268,7 @@ public Task ExecuteAsync(CancellationToken cancellationToken = default)
265268 // so we just need to reinitialise them for subsequent executions.
266269 OutputStream = new PipeStream ( ) ;
267270 ExtendedOutputStream = new PipeStream ( ) ;
271+ _channel = _session . CreateChannelSession ( ) ;
268272 }
269273
270274 _exitStatus = default ;
@@ -282,7 +286,6 @@ public Task ExecuteAsync(CancellationToken cancellationToken = default)
282286 _tcs = new TaskCompletionSource < object > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
283287 _userToken = cancellationToken ;
284288
285- _channel = _session . CreateChannelSession ( ) ;
286289 _channel . DataReceived += Channel_DataReceived ;
287290 _channel . ExtendedDataReceived += Channel_ExtendedDataReceived ;
288291 _channel . RequestReceived += Channel_RequestReceived ;
@@ -542,7 +545,10 @@ private void SetAsyncComplete(bool setResult = true)
542545 }
543546 }
544547
545- UnsubscribeFromEventsAndDisposeChannel ( ) ;
548+ // We don't dispose the channel here to avoid a race condition
549+ // where SSH_MSG_CHANNEL_CLOSE arrives before _channel starts
550+ // waiting for a response in _channel.SendExecRequest().
551+ UnsubscribeFromChannelEvents ( dispose : false ) ;
546552
547553 OutputStream . Dispose ( ) ;
548554 ExtendedOutputStream . Dispose ( ) ;
@@ -568,7 +574,7 @@ private void Channel_RequestReceived(object? sender, ChannelRequestEventArgs e)
568574
569575 Debug . Assert ( ! exitSignalInfo . WantReply , "exit-signal is want_reply := false by definition." ) ;
570576 }
571- else if ( e . Info . WantReply && _channel ? . RemoteChannelNumber is uint remoteChannelNumber )
577+ else if ( e . Info . WantReply && sender is IChannel { RemoteChannelNumber : uint remoteChannelNumber } )
572578 {
573579 var replyMessage = new ChannelFailureMessage ( remoteChannelNumber ) ;
574580 _session . SendMessage ( replyMessage ) ;
@@ -591,29 +597,24 @@ private void Channel_DataReceived(object? sender, ChannelDataEventArgs e)
591597 }
592598
593599 /// <summary>
594- /// Unsubscribes the current <see cref="SshCommand"/> from channel events, and disposes
595- /// the <see cref="_channel"/>.
600+ /// Unsubscribes the current <see cref="SshCommand"/> from channel events, and optionally,
601+ /// disposes <see cref="_channel"/>.
596602 /// </summary>
597- private void UnsubscribeFromEventsAndDisposeChannel ( )
603+ private void UnsubscribeFromChannelEvents ( bool dispose )
598604 {
599605 var channel = _channel ;
600606
601- if ( channel is null )
602- {
603- return ;
604- }
605-
606- _channel = null ;
607-
608607 // unsubscribe from events as we do not want to be signaled should these get fired
609608 // during the dispose of the channel
610609 channel . DataReceived -= Channel_DataReceived ;
611610 channel . ExtendedDataReceived -= Channel_ExtendedDataReceived ;
612611 channel . RequestReceived -= Channel_RequestReceived ;
613612 channel . Closed -= Channel_Closed ;
614613
615- // actually dispose the channel
616- channel . Dispose ( ) ;
614+ if ( dispose )
615+ {
616+ channel . Dispose ( ) ;
617+ }
617618 }
618619
619620 /// <summary>
@@ -645,7 +646,7 @@ protected virtual void Dispose(bool disposing)
645646
646647 // unsubscribe from channel events to ensure other objects that we're going to dispose
647648 // are not accessed while disposing
648- UnsubscribeFromEventsAndDisposeChannel ( ) ;
649+ UnsubscribeFromChannelEvents ( dispose : true ) ;
649650
650651 _inputStream ? . Dispose ( ) ;
651652 _inputStream = null ;
0 commit comments