diff --git a/src/Renci.SshNet/BaseClient.cs b/src/Renci.SshNet/BaseClient.cs index db11a6178..6c721bebc 100644 --- a/src/Renci.SshNet/BaseClient.cs +++ b/src/Renci.SshNet/BaseClient.cs @@ -72,7 +72,7 @@ private set /// if this client is connected; otherwise, . /// /// The method was called after the client was disposed. - public bool IsConnected + public virtual bool IsConnected { get { @@ -228,14 +228,19 @@ public void Connect() // forwarded port with a client instead of with a session // // To be discussed with Oleg (or whoever is interested) - if (IsSessionConnected()) + if (IsConnected) { throw new InvalidOperationException("The client is already connected."); } OnConnecting(); - Session = CreateAndConnectSession(); + // The session may already/still be connected here because e.g. in SftpClient, IsConnected also checks the internal SFTP session + var session = Session; + if (session is null || !session.IsConnected) + { + Session = CreateAndConnectSession(); + } try { @@ -287,14 +292,19 @@ public async Task ConnectAsync(CancellationToken cancellationToken) // forwarded port with a client instead of with a session // // To be discussed with Oleg (or whoever is interested) - if (IsSessionConnected()) + if (IsConnected) { throw new InvalidOperationException("The client is already connected."); } OnConnecting(); - Session = await CreateAndConnectSessionAsync(cancellationToken).ConfigureAwait(false); + // The session may already/still be connected here because e.g. in SftpClient, IsConnected also checks the internal SFTP session + var session = Session; + if (session is null || !session.IsConnected) + { + Session = await CreateAndConnectSessionAsync(cancellationToken).ConfigureAwait(false); + } try { diff --git a/src/Renci.SshNet/SftpClient.cs b/src/Renci.SshNet/SftpClient.cs index e9a73c484..16f15ada6 100644 --- a/src/Renci.SshNet/SftpClient.cs +++ b/src/Renci.SshNet/SftpClient.cs @@ -108,6 +108,22 @@ public uint BufferSize } } + /// + /// Gets a value indicating whether this client is connected to the server and + /// the SFTP session is open. + /// + /// + /// if this client is connected and the SFTP session is open; otherwise, . + /// + /// The method was called after the client was disposed. + public override bool IsConnected + { + get + { + return base.IsConnected && _sftpSession.IsOpen; + } + } + /// /// Gets remote working directory. /// @@ -2473,7 +2489,15 @@ protected override void OnConnected() { base.OnConnected(); - _sftpSession = CreateAndConnectToSftpSession(); + var sftpSession = _sftpSession; + if (sftpSession is null) + { + _sftpSession = CreateAndConnectToSftpSession(); + } + else if (!sftpSession.IsOpen) + { + sftpSession.Connect(); + } } /// diff --git a/test/Renci.SshNet.IntegrationTests/ConnectivityTests.cs b/test/Renci.SshNet.IntegrationTests/ConnectivityTests.cs index 61ba9e424..a6ff3465c 100644 --- a/test/Renci.SshNet.IntegrationTests/ConnectivityTests.cs +++ b/test/Renci.SshNet.IntegrationTests/ConnectivityTests.cs @@ -287,6 +287,44 @@ public void Common_DetectLossOfNetworkConnectivityThroughSftpInvocation() } } + [TestMethod] + public void SftpClient_HandleSftpSessionClose() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + client.Connect(); + Assert.IsTrue(client.IsConnected); + + client.SftpSession.Disconnect(); + Assert.IsFalse(client.IsConnected); + + client.Connect(); + Assert.IsTrue(client.IsConnected); + + client.Disconnect(); + Assert.IsFalse(client.IsConnected); + } + } + + [TestMethod] + public async Task SftpClient_HandleSftpSessionCloseAsync() + { + using (var client = new SftpClient(_connectionInfoFactory.Create())) + { + await client.ConnectAsync(CancellationToken.None); + Assert.IsTrue(client.IsConnected); + + client.SftpSession.Disconnect(); + Assert.IsFalse(client.IsConnected); + + await client.ConnectAsync(CancellationToken.None); + Assert.IsTrue(client.IsConnected); + + client.Disconnect(); + Assert.IsFalse(client.IsConnected); + } + } + [TestMethod] public void Common_DetectSessionKilledOnServer() {