Skip to content

Commit 8bd08ed

Browse files
mus65Rob-HagueIgorMilavec
authored
SftpClient: handle the SFTP session being closed by the server (#1362)
* SftpClient: handle the SFTP session being closed by the server If the server closes the SFTP session but keeps the TCP connection open, this currently causes IsConnected to return true, but any operation fails with "the session is not open". SftpClient.IsConnected now also check sftpSession.IsOpen. Connect() and ConnectAsync() were reworked to take into account that the Session may already/still be open, but the SFTP session may not. This is needed so a reconnect works. fixes #843 and #1153 * Always re-create session --------- Co-authored-by: Rob Hague <[email protected]> Co-authored-by: Igor Milavec <[email protected]>
1 parent 9be67c0 commit 8bd08ed

File tree

3 files changed

+78
-6
lines changed

3 files changed

+78
-6
lines changed

src/Renci.SshNet/BaseClient.cs

+15-5
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ private set
7272
/// <see langword="true"/> if this client is connected; otherwise, <see langword="false"/>.
7373
/// </value>
7474
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
75-
public bool IsConnected
75+
public virtual bool IsConnected
7676
{
7777
get
7878
{
@@ -228,14 +228,19 @@ public void Connect()
228228
// forwarded port with a client instead of with a session
229229
//
230230
// To be discussed with Oleg (or whoever is interested)
231-
if (IsSessionConnected())
231+
if (IsConnected)
232232
{
233233
throw new InvalidOperationException("The client is already connected.");
234234
}
235235

236236
OnConnecting();
237237

238-
Session = CreateAndConnectSession();
238+
// The session may already/still be connected here because e.g. in SftpClient, IsConnected also checks the internal SFTP session
239+
var session = Session;
240+
if (session is null || !session.IsConnected)
241+
{
242+
Session = CreateAndConnectSession();
243+
}
239244

240245
try
241246
{
@@ -287,14 +292,19 @@ public async Task ConnectAsync(CancellationToken cancellationToken)
287292
// forwarded port with a client instead of with a session
288293
//
289294
// To be discussed with Oleg (or whoever is interested)
290-
if (IsSessionConnected())
295+
if (IsConnected)
291296
{
292297
throw new InvalidOperationException("The client is already connected.");
293298
}
294299

295300
OnConnecting();
296301

297-
Session = await CreateAndConnectSessionAsync(cancellationToken).ConfigureAwait(false);
302+
// The session may already/still be connected here because e.g. in SftpClient, IsConnected also checks the internal SFTP session
303+
var session = Session;
304+
if (session is null || !session.IsConnected)
305+
{
306+
Session = await CreateAndConnectSessionAsync(cancellationToken).ConfigureAwait(false);
307+
}
298308

299309
try
300310
{

src/Renci.SshNet/SftpClient.cs

+25-1
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,22 @@ public uint BufferSize
108108
}
109109
}
110110

111+
/// <summary>
112+
/// Gets a value indicating whether this client is connected to the server and
113+
/// the SFTP session is open.
114+
/// </summary>
115+
/// <value>
116+
/// <see langword="true"/> if this client is connected and the SFTP session is open; otherwise, <see langword="false"/>.
117+
/// </value>
118+
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
119+
public override bool IsConnected
120+
{
121+
get
122+
{
123+
return base.IsConnected && _sftpSession.IsOpen;
124+
}
125+
}
126+
111127
/// <summary>
112128
/// Gets remote working directory.
113129
/// </summary>
@@ -2473,7 +2489,15 @@ protected override void OnConnected()
24732489
{
24742490
base.OnConnected();
24752491

2476-
_sftpSession = CreateAndConnectToSftpSession();
2492+
var sftpSession = _sftpSession;
2493+
if (sftpSession is null)
2494+
{
2495+
_sftpSession = CreateAndConnectToSftpSession();
2496+
}
2497+
else if (!sftpSession.IsOpen)
2498+
{
2499+
sftpSession.Connect();
2500+
}
24772501
}
24782502

24792503
/// <summary>

test/Renci.SshNet.IntegrationTests/ConnectivityTests.cs

+38
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,44 @@ public void Common_DetectLossOfNetworkConnectivityThroughSftpInvocation()
287287
}
288288
}
289289

290+
[TestMethod]
291+
public void SftpClient_HandleSftpSessionClose()
292+
{
293+
using (var client = new SftpClient(_connectionInfoFactory.Create()))
294+
{
295+
client.Connect();
296+
Assert.IsTrue(client.IsConnected);
297+
298+
client.SftpSession.Disconnect();
299+
Assert.IsFalse(client.IsConnected);
300+
301+
client.Connect();
302+
Assert.IsTrue(client.IsConnected);
303+
304+
client.Disconnect();
305+
Assert.IsFalse(client.IsConnected);
306+
}
307+
}
308+
309+
[TestMethod]
310+
public async Task SftpClient_HandleSftpSessionCloseAsync()
311+
{
312+
using (var client = new SftpClient(_connectionInfoFactory.Create()))
313+
{
314+
await client.ConnectAsync(CancellationToken.None);
315+
Assert.IsTrue(client.IsConnected);
316+
317+
client.SftpSession.Disconnect();
318+
Assert.IsFalse(client.IsConnected);
319+
320+
await client.ConnectAsync(CancellationToken.None);
321+
Assert.IsTrue(client.IsConnected);
322+
323+
client.Disconnect();
324+
Assert.IsFalse(client.IsConnected);
325+
}
326+
}
327+
290328
[TestMethod]
291329
public void Common_DetectSessionKilledOnServer()
292330
{

0 commit comments

Comments
 (0)