Skip to content

Commit 80ae79c

Browse files
committed
Avoid overlapping SSH_FXP_OPEN and SSH_FXP_LSTAT requests for the same file as this causes a performance degradation on Sun SSH.
Switch to a single (sync) read-ahead if the file size is known, and we're starting to read (ahead) past that file size. Harden against exceptions closing the file handle in Dispose Fixes issue #292.
1 parent a7dbfbc commit 80ae79c

File tree

26 files changed

+1383
-48
lines changed

26 files changed

+1383
-48
lines changed

src/Renci.SshNet.Tests.NET35/Renci.SshNet.Tests.NET35.csproj

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,30 @@
888888
<Compile Include="..\Renci.SshNet.Tests\Classes\Security\KeyHostAlgorithmTest.cs">
889889
<Link>Classes\Security\KeyHostAlgorithmTest.cs</Link>
890890
</Compile>
891+
<Compile Include="..\Renci.SshNet.Tests\Classes\ServiceFactoryTest_CreateSftpFileReader_EndLStatThrowsSshException.cs">
892+
<Link>Classes\ServiceFactoryTest_CreateSftpFileReader_EndLStatThrowsSshException.cs</Link>
893+
</Compile>
894+
<Compile Include="..\Renci.SshNet.Tests\Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsAlmostSixTimesGreaterThanChunkSize.cs">
895+
<Link>Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsAlmostSixTimesGreaterThanChunkSize.cs</Link>
896+
</Compile>
897+
<Compile Include="..\Renci.SshNet.Tests\Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsEqualToChunkSize.cs">
898+
<Link>Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsEqualToChunkSize.cs</Link>
899+
</Compile>
900+
<Compile Include="..\Renci.SshNet.Tests\Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsExactlyFiveTimesGreaterThanChunkSize.cs">
901+
<Link>Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsExactlyFiveTimesGreaterThanChunkSize.cs</Link>
902+
</Compile>
903+
<Compile Include="..\Renci.SshNet.Tests\Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsLessThanChunkSize.cs">
904+
<Link>Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsLessThanChunkSize.cs</Link>
905+
</Compile>
906+
<Compile Include="..\Renci.SshNet.Tests\Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsLittleMoreThanFiveTimesGreaterThanChunkSize.cs">
907+
<Link>Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsLittleMoreThanFiveTimesGreaterThanChunkSize.cs</Link>
908+
</Compile>
909+
<Compile Include="..\Renci.SshNet.Tests\Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsMoreThanTenTimesGreaterThanChunkSize.cs">
910+
<Link>Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsMoreThanTenTimesGreaterThanChunkSize.cs</Link>
911+
</Compile>
912+
<Compile Include="..\Renci.SshNet.Tests\Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsZero.cs">
913+
<Link>Classes\ServiceFactoryTest_CreateSftpFileReader_FileSizeIsZero.cs</Link>
914+
</Compile>
891915
<Compile Include="..\Renci.SshNet.Tests\Classes\SessionTest.cs">
892916
<Link>Classes\SessionTest.cs</Link>
893917
</Compile>
@@ -1083,6 +1107,15 @@
10831107
<Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileReaderTest_DisposeShouldUnblockReadAndReadAhead.cs">
10841108
<Link>Classes\Sftp\SftpFileReaderTest_DisposeShouldUnblockReadAndReadAhead.cs</Link>
10851109
</Compile>
1110+
<Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileReaderTest_Dispose_SftpSessionIsNotOpen.cs">
1111+
<Link>Classes\Sftp\SftpFileReaderTest_Dispose_SftpSessionIsNotOpen.cs</Link>
1112+
</Compile>
1113+
<Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileReaderTest_Dispose_SftpSessionIsOpen_BeginCloseThrowsException.cs">
1114+
<Link>Classes\Sftp\SftpFileReaderTest_Dispose_SftpSessionIsOpen_BeginCloseThrowsException.cs</Link>
1115+
</Compile>
1116+
<Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileReaderTest_Dispose_SftpSessionIsOpen_EndCloseThrowsException.cs">
1117+
<Link>Classes\Sftp\SftpFileReaderTest_Dispose_SftpSessionIsOpen_EndCloseThrowsException.cs</Link>
1118+
</Compile>
10861119
<Compile Include="..\Renci.SshNet.Tests\Classes\Sftp\SftpFileReaderTest_LastChunkBeforeEofIsComplete.cs">
10871120
<Link>Classes\Sftp\SftpFileReaderTest_LastChunkBeforeEofIsComplete.cs</Link>
10881121
</Compile>
@@ -1599,7 +1632,7 @@
15991632
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
16001633
<ProjectExtensions>
16011634
<VisualStudio>
1602-
<UserProperties ProjectLinkReference="c45379b9-17b1-4e89-bc2e-6d41726413e8" ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" />
1635+
<UserProperties ProjectLinkerExcludeFilter="\\?desktop(\\.*)?$;\\?silverlight(\\.*)?$;\.desktop;\.silverlight;\.xaml;^service references(\\.*)?$;\.clientconfig;^web references(\\.*)?$" ProjectLinkReference="c45379b9-17b1-4e89-bc2e-6d41726413e8" />
16031636
</VisualStudio>
16041637
</ProjectExtensions>
16051638
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
using System;
2+
using Microsoft.VisualStudio.TestTools.UnitTesting;
3+
using Moq;
4+
using Renci.SshNet.Abstractions;
5+
using Renci.SshNet.Common;
6+
using Renci.SshNet.Sftp;
7+
8+
namespace Renci.SshNet.Tests.Classes
9+
{
10+
[TestClass]
11+
public class ServiceFactoryTest_CreateSftpFileReader_EndLStatThrowsSshException
12+
{
13+
private ServiceFactory _serviceFactory;
14+
private Mock<ISftpSession> _sftpSessionMock;
15+
private Mock<ISftpFileReader> _sftpFileReaderMock;
16+
private uint _bufferSize;
17+
private string _fileName;
18+
private SftpOpenAsyncResult _openAsyncResult;
19+
private byte[] _handle;
20+
private SFtpStatAsyncResult _statAsyncResult;
21+
private uint _chunkSize;
22+
private ISftpFileReader _actual;
23+
24+
private void SetupData()
25+
{
26+
var random = new Random();
27+
28+
_bufferSize = (uint)random.Next(1, int.MaxValue);
29+
_openAsyncResult = new SftpOpenAsyncResult(null, null);
30+
_handle = CryptoAbstraction.GenerateRandom(random.Next(1, 10));
31+
_statAsyncResult = new SFtpStatAsyncResult(null, null);
32+
_fileName = random.Next().ToString();
33+
_chunkSize = (uint) random.Next(1, int.MaxValue);
34+
}
35+
36+
private void CreateMocks()
37+
{
38+
_sftpSessionMock = new Mock<ISftpSession>(MockBehavior.Strict);
39+
_sftpFileReaderMock = new Mock<ISftpFileReader>(MockBehavior.Strict);
40+
}
41+
42+
private void SetupMocks()
43+
{
44+
var seq = new MockSequence();
45+
46+
_sftpSessionMock.InSequence(seq)
47+
.Setup(p => p.BeginOpen(_fileName, Flags.Read, null, null))
48+
.Returns(_openAsyncResult);
49+
_sftpSessionMock.InSequence(seq)
50+
.Setup(p => p.EndOpen(_openAsyncResult))
51+
.Returns(_handle);
52+
_sftpSessionMock.InSequence(seq)
53+
.Setup(p => p.BeginLStat(_fileName, null, null))
54+
.Returns(_statAsyncResult);
55+
_sftpSessionMock.InSequence(seq)
56+
.Setup(p => p.CalculateOptimalReadLength(_bufferSize))
57+
.Returns(_chunkSize);
58+
_sftpSessionMock.InSequence(seq)
59+
.Setup(p => p.EndLStat(_statAsyncResult))
60+
.Throws(new SshException());
61+
_sftpSessionMock.InSequence(seq)
62+
.Setup(p => p.CreateFileReader(_handle, _sftpSessionMock.Object, _chunkSize, 3, null))
63+
.Returns(_sftpFileReaderMock.Object);
64+
}
65+
66+
private void Arrange()
67+
{
68+
SetupData();
69+
CreateMocks();
70+
SetupMocks();
71+
72+
_serviceFactory = new ServiceFactory();
73+
}
74+
75+
[TestInitialize]
76+
public void Initialize()
77+
{
78+
Arrange();
79+
Act();
80+
}
81+
82+
private void Act()
83+
{
84+
_actual = _serviceFactory.CreateSftpFileReader(_fileName, _sftpSessionMock.Object, _bufferSize);
85+
}
86+
87+
[TestMethod]
88+
public void CreateSftpFileReaderShouldReturnCreatedInstance()
89+
{
90+
Assert.IsNotNull(_actual);
91+
Assert.AreSame(_sftpFileReaderMock.Object, _actual);
92+
}
93+
94+
}
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using System;
2+
using Microsoft.VisualStudio.TestTools.UnitTesting;
3+
using Moq;
4+
using Renci.SshNet.Abstractions;
5+
using Renci.SshNet.Sftp;
6+
using Renci.SshNet.Tests.Common;
7+
8+
namespace Renci.SshNet.Tests.Classes
9+
{
10+
[TestClass]
11+
public class ServiceFactoryTest_CreateSftpFileReader_FileSizeIsAlmostSixTimesGreaterThanChunkSize
12+
{
13+
private ServiceFactory _serviceFactory;
14+
private Mock<ISftpSession> _sftpSessionMock;
15+
private Mock<ISftpFileReader> _sftpFileReaderMock;
16+
private uint _bufferSize;
17+
private string _fileName;
18+
private SftpOpenAsyncResult _openAsyncResult;
19+
private byte[] _handle;
20+
private SFtpStatAsyncResult _statAsyncResult;
21+
private uint _chunkSize;
22+
private SftpFileAttributes _fileAttributes;
23+
private long _fileSize;
24+
private ISftpFileReader _actual;
25+
26+
private void SetupData()
27+
{
28+
var random = new Random();
29+
30+
_bufferSize = (uint) random.Next(1, int.MaxValue);
31+
_openAsyncResult = new SftpOpenAsyncResult(null, null);
32+
_handle = CryptoAbstraction.GenerateRandom(random.Next(1, 10));
33+
_statAsyncResult = new SFtpStatAsyncResult(null, null);
34+
_fileName = random.Next().ToString();
35+
_chunkSize = (uint) random.Next(1000, 5000);
36+
_fileSize = (_chunkSize * 6) - 10;
37+
_fileAttributes = new SftpFileAttributesBuilder().WithSize(_fileSize).Build();
38+
}
39+
40+
private void CreateMocks()
41+
{
42+
_sftpSessionMock = new Mock<ISftpSession>(MockBehavior.Strict);
43+
_sftpFileReaderMock = new Mock<ISftpFileReader>(MockBehavior.Strict);
44+
}
45+
46+
private void SetupMocks()
47+
{
48+
var seq = new MockSequence();
49+
50+
_sftpSessionMock.InSequence(seq)
51+
.Setup(p => p.BeginOpen(_fileName, Flags.Read, null, null))
52+
.Returns(_openAsyncResult);
53+
_sftpSessionMock.InSequence(seq)
54+
.Setup(p => p.EndOpen(_openAsyncResult))
55+
.Returns(_handle);
56+
_sftpSessionMock.InSequence(seq)
57+
.Setup(p => p.BeginLStat(_fileName, null, null))
58+
.Returns(_statAsyncResult);
59+
_sftpSessionMock.InSequence(seq)
60+
.Setup(p => p.CalculateOptimalReadLength(_bufferSize))
61+
.Returns(_chunkSize);
62+
_sftpSessionMock.InSequence(seq)
63+
.Setup(p => p.EndLStat(_statAsyncResult))
64+
.Returns(_fileAttributes);
65+
_sftpSessionMock.InSequence(seq)
66+
.Setup(p => p.CreateFileReader(_handle, _sftpSessionMock.Object, _chunkSize, 7, _fileSize))
67+
.Returns(_sftpFileReaderMock.Object);
68+
}
69+
70+
private void Arrange()
71+
{
72+
SetupData();
73+
CreateMocks();
74+
SetupMocks();
75+
76+
_serviceFactory = new ServiceFactory();
77+
}
78+
79+
[TestInitialize]
80+
public void Initialize()
81+
{
82+
Arrange();
83+
Act();
84+
}
85+
86+
private void Act()
87+
{
88+
_actual = _serviceFactory.CreateSftpFileReader(_fileName, _sftpSessionMock.Object, _bufferSize);
89+
}
90+
91+
[TestMethod]
92+
public void CreateSftpFileReaderShouldReturnCreatedInstance()
93+
{
94+
Assert.IsNotNull(_actual);
95+
Assert.AreSame(_sftpFileReaderMock.Object, _actual);
96+
}
97+
}
98+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using System;
2+
using Microsoft.VisualStudio.TestTools.UnitTesting;
3+
using Moq;
4+
using Renci.SshNet.Abstractions;
5+
using Renci.SshNet.Sftp;
6+
using Renci.SshNet.Tests.Common;
7+
8+
namespace Renci.SshNet.Tests.Classes
9+
{
10+
[TestClass]
11+
public class ServiceFactoryTest_CreateSftpFileReader_FileSizeIsEqualToChunkSize
12+
{
13+
private ServiceFactory _serviceFactory;
14+
private Mock<ISftpSession> _sftpSessionMock;
15+
private Mock<ISftpFileReader> _sftpFileReaderMock;
16+
private uint _bufferSize;
17+
private string _fileName;
18+
private SftpOpenAsyncResult _openAsyncResult;
19+
private byte[] _handle;
20+
private SFtpStatAsyncResult _statAsyncResult;
21+
private uint _chunkSize;
22+
private SftpFileAttributes _fileAttributes;
23+
private long _fileSize;
24+
private ISftpFileReader _actual;
25+
26+
private void SetupData()
27+
{
28+
var random = new Random();
29+
30+
_bufferSize = (uint)random.Next(1, int.MaxValue);
31+
_openAsyncResult = new SftpOpenAsyncResult(null, null);
32+
_handle = CryptoAbstraction.GenerateRandom(random.Next(1, 10));
33+
_statAsyncResult = new SFtpStatAsyncResult(null, null);
34+
_fileName = random.Next().ToString();
35+
_chunkSize = (uint)random.Next(1000, int.MaxValue);
36+
_fileSize = _chunkSize;
37+
_fileAttributes = new SftpFileAttributesBuilder().WithSize(_fileSize).Build();
38+
}
39+
40+
private void CreateMocks()
41+
{
42+
_sftpSessionMock = new Mock<ISftpSession>(MockBehavior.Strict);
43+
_sftpFileReaderMock = new Mock<ISftpFileReader>(MockBehavior.Strict);
44+
}
45+
46+
private void SetupMocks()
47+
{
48+
var seq = new MockSequence();
49+
50+
_sftpSessionMock.InSequence(seq)
51+
.Setup(p => p.BeginOpen(_fileName, Flags.Read, null, null))
52+
.Returns(_openAsyncResult);
53+
_sftpSessionMock.InSequence(seq)
54+
.Setup(p => p.EndOpen(_openAsyncResult))
55+
.Returns(_handle);
56+
_sftpSessionMock.InSequence(seq)
57+
.Setup(p => p.BeginLStat(_fileName, null, null))
58+
.Returns(_statAsyncResult);
59+
_sftpSessionMock.InSequence(seq)
60+
.Setup(p => p.CalculateOptimalReadLength(_bufferSize))
61+
.Returns(_chunkSize);
62+
_sftpSessionMock.InSequence(seq)
63+
.Setup(p => p.EndLStat(_statAsyncResult))
64+
.Returns(_fileAttributes);
65+
_sftpSessionMock.InSequence(seq)
66+
.Setup(p => p.CreateFileReader(_handle, _sftpSessionMock.Object, _chunkSize, 2, _fileSize))
67+
.Returns(_sftpFileReaderMock.Object);
68+
}
69+
70+
private void Arrange()
71+
{
72+
SetupData();
73+
CreateMocks();
74+
SetupMocks();
75+
76+
_serviceFactory = new ServiceFactory();
77+
}
78+
79+
[TestInitialize]
80+
public void Initialize()
81+
{
82+
Arrange();
83+
Act();
84+
}
85+
86+
private void Act()
87+
{
88+
_actual = _serviceFactory.CreateSftpFileReader(_fileName, _sftpSessionMock.Object, _bufferSize);
89+
}
90+
91+
[TestMethod]
92+
public void CreateSftpFileReaderShouldReturnCreatedInstance()
93+
{
94+
Assert.IsNotNull(_actual);
95+
Assert.AreSame(_sftpFileReaderMock.Object, _actual);
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)