@@ -21,7 +21,7 @@ public class SshCommand : IDisposable
21
21
private readonly ISession _session ;
22
22
private readonly Encoding _encoding ;
23
23
24
- private IChannelSession ? _channel ;
24
+ private IChannelSession _channel ;
25
25
private TaskCompletionSource < object > ? _tcs ;
26
26
private CancellationTokenSource ? _cts ;
27
27
private CancellationTokenRegistration _tokenRegistration ;
@@ -142,14 +142,14 @@ public int? ExitStatus
142
142
/// </example>
143
143
public Stream CreateInputStream ( )
144
144
{
145
- if ( _channel == null )
145
+ if ( ! _channel . IsOpen )
146
146
{
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 ." ) ;
148
148
}
149
149
150
150
if ( _inputStream != null )
151
151
{
152
- throw new InvalidOperationException ( $ "The input stream already exists.") ;
152
+ throw new InvalidOperationException ( "The input stream already exists." ) ;
153
153
}
154
154
155
155
_inputStream = new ChannelInputStream ( _channel ) ;
@@ -226,6 +226,7 @@ internal SshCommand(ISession session, string commandText, Encoding encoding)
226
226
ExtendedOutputStream = new PipeStream ( ) ;
227
227
_session . Disconnected += Session_Disconnected ;
228
228
_session . ErrorOccured += Session_ErrorOccured ;
229
+ _channel = _session . CreateChannelSession ( ) ;
229
230
}
230
231
231
232
/// <summary>
@@ -257,6 +258,8 @@ public Task ExecuteAsync(CancellationToken cancellationToken = default)
257
258
throw new InvalidOperationException ( "Asynchronous operation is already in progress." ) ;
258
259
}
259
260
261
+ UnsubscribeFromChannelEvents ( dispose : true ) ;
262
+
260
263
OutputStream . Dispose ( ) ;
261
264
ExtendedOutputStream . Dispose ( ) ;
262
265
@@ -265,6 +268,7 @@ public Task ExecuteAsync(CancellationToken cancellationToken = default)
265
268
// so we just need to reinitialise them for subsequent executions.
266
269
OutputStream = new PipeStream ( ) ;
267
270
ExtendedOutputStream = new PipeStream ( ) ;
271
+ _channel = _session . CreateChannelSession ( ) ;
268
272
}
269
273
270
274
_exitStatus = default ;
@@ -282,7 +286,6 @@ public Task ExecuteAsync(CancellationToken cancellationToken = default)
282
286
_tcs = new TaskCompletionSource < object > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
283
287
_userToken = cancellationToken ;
284
288
285
- _channel = _session . CreateChannelSession ( ) ;
286
289
_channel . DataReceived += Channel_DataReceived ;
287
290
_channel . ExtendedDataReceived += Channel_ExtendedDataReceived ;
288
291
_channel . RequestReceived += Channel_RequestReceived ;
@@ -542,7 +545,10 @@ private void SetAsyncComplete(bool setResult = true)
542
545
}
543
546
}
544
547
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 ) ;
546
552
547
553
OutputStream . Dispose ( ) ;
548
554
ExtendedOutputStream . Dispose ( ) ;
@@ -568,7 +574,7 @@ private void Channel_RequestReceived(object? sender, ChannelRequestEventArgs e)
568
574
569
575
Debug . Assert ( ! exitSignalInfo . WantReply , "exit-signal is want_reply := false by definition." ) ;
570
576
}
571
- else if ( e . Info . WantReply && _channel ? . RemoteChannelNumber is uint remoteChannelNumber )
577
+ else if ( e . Info . WantReply && sender is IChannel { RemoteChannelNumber : uint remoteChannelNumber } )
572
578
{
573
579
var replyMessage = new ChannelFailureMessage ( remoteChannelNumber ) ;
574
580
_session . SendMessage ( replyMessage ) ;
@@ -591,29 +597,24 @@ private void Channel_DataReceived(object? sender, ChannelDataEventArgs e)
591
597
}
592
598
593
599
/// <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"/>.
596
602
/// </summary>
597
- private void UnsubscribeFromEventsAndDisposeChannel ( )
603
+ private void UnsubscribeFromChannelEvents ( bool dispose )
598
604
{
599
605
var channel = _channel ;
600
606
601
- if ( channel is null )
602
- {
603
- return ;
604
- }
605
-
606
- _channel = null ;
607
-
608
607
// unsubscribe from events as we do not want to be signaled should these get fired
609
608
// during the dispose of the channel
610
609
channel . DataReceived -= Channel_DataReceived ;
611
610
channel . ExtendedDataReceived -= Channel_ExtendedDataReceived ;
612
611
channel . RequestReceived -= Channel_RequestReceived ;
613
612
channel . Closed -= Channel_Closed ;
614
613
615
- // actually dispose the channel
616
- channel . Dispose ( ) ;
614
+ if ( dispose )
615
+ {
616
+ channel . Dispose ( ) ;
617
+ }
617
618
}
618
619
619
620
/// <summary>
@@ -645,7 +646,7 @@ protected virtual void Dispose(bool disposing)
645
646
646
647
// unsubscribe from channel events to ensure other objects that we're going to dispose
647
648
// are not accessed while disposing
648
- UnsubscribeFromEventsAndDisposeChannel ( ) ;
649
+ UnsubscribeFromChannelEvents ( dispose : true ) ;
649
650
650
651
_inputStream ? . Dispose ( ) ;
651
652
_inputStream = null ;
0 commit comments