diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_ReadAheadEndInvokeException_DiscardsFurtherReadAheads.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_ReadAheadEndInvokeException_DiscardsFurtherReadAheads.cs
index 1d7708ae..ffb07990 100644
--- a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_ReadAheadEndInvokeException_DiscardsFurtherReadAheads.cs
+++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTest_ReadAheadEndInvokeException_DiscardsFurtherReadAheads.cs
@@ -10,6 +10,21 @@ using BufferedRead = Renci.SshNet.Sftp.SftpFileReader.BufferedRead;
namespace Renci.SshNet.Tests.Classes.Sftp
{
+ ///
+ /// Runs a reader with max. 2 pending reads.
+ /// The read-ahead of chunk1 starts followed by the read-ahead of chunk2.
+ /// The read-ahead of chunk1 completes successfully and the resulting chunk is read.
+ /// The read of this first chunk allows a third ahead-head to start.
+ /// The second read-ahead uses signals to forcefully block a failure completion until the read
+ /// ahead of the third chunk has completed and the semaphore is waiting for a slot to start
+ /// the read-ahead of chunk4.
+ /// The second read does not consume check3 as it is out of order, but instead waits for
+ /// the outcome of the read-ahead of chunk2.
+ ///
+ /// The completion with exception of chunk2 causes the second read to throw that same exception, and
+ /// signals the semaphore that was waiting to start the read-ahead of chunk4. However, due to the fact
+ /// that chunk2 completed with an exception, the read-ahead loop is stopped.
+ ///
[TestClass]
public class SftpFileReaderTest_ReadAheadEndInvokeException_DiscardsFurtherReadAheads : SftpFileReaderTestBase
{
@@ -23,7 +38,9 @@ namespace Renci.SshNet.Tests.Classes.Sftp
private SftpCloseAsyncResult _closeAsyncResult;
private byte[] _chunk1;
private byte[] _chunk3;
+ private ManualResetEvent _readAheadChunk2Completed;
private ManualResetEvent _readAheadChunk3Completed;
+ private ManualResetEvent _waitingForSemaphoreAfterCompletingChunk3;
private SftpFileReader _reader;
private SshException _exception;
private SshException _actualException;
@@ -35,12 +52,14 @@ namespace Renci.SshNet.Tests.Classes.Sftp
_handle = CreateByteArray(random, 5);
_chunk1 = CreateByteArray(random, ChunkLength);
_chunk3 = CreateByteArray(random, ChunkLength);
- _fileSize = 3 * ChunkLength;
+ _fileSize = 4 * ChunkLength;
_waitHandleArray = new WaitHandle[2];
_operationTimeout = random.Next(10000, 20000);
_closeAsyncResult = new SftpCloseAsyncResult(null, null);
+ _readAheadChunk2Completed = new ManualResetEvent(false);
_readAheadChunk3Completed = new ManualResetEvent(false);
+ _waitingForSemaphoreAfterCompletingChunk3 = new ManualResetEvent(false);
_exception = new SshException();
}
@@ -79,12 +98,16 @@ namespace Renci.SshNet.Tests.Classes.Sftp
{
ThreadAbstraction.ExecuteThread(() =>
{
- // wait until the read-ahead for chunk3 has completed
- _readAheadChunk3Completed.WaitOne(TimeSpan.FromSeconds(5));
-
+ // wait until the read-ahead for chunk3 has completed; this should allow
+ // the read-ahead of chunk4 to start
+ _readAheadChunk3Completed.WaitOne(TimeSpan.FromSeconds(3));
+ // wait until the semaphore wait to start with chunk4 has started
+ _waitingForSemaphoreAfterCompletingChunk3.WaitOne(TimeSpan.FromSeconds(7));
// complete async read of chunk2 with exception
var asyncResult = new SftpReadAsyncResult(callback, state);
asyncResult.SetAsCompleted(_exception, false);
+ // signal that read-ahead of chunk 2 has completed
+ _readAheadChunk2Completed.Set();
});
})
.Returns((SftpReadAsyncResult)null);
@@ -98,11 +121,16 @@ namespace Renci.SshNet.Tests.Classes.Sftp
{
var asyncResult = new SftpReadAsyncResult(callback, state);
asyncResult.SetAsCompleted(_chunk3, false);
-
// signal that we've completed the read-ahead for chunk3
_readAheadChunk3Completed.Set();
})
.Returns((SftpReadAsyncResult)null);
+ SftpSessionMock.InSequence(_seq).Setup(p => p.OperationTimeout).Returns(_operationTimeout);
+ SftpSessionMock.InSequence(_seq)
+ .Setup(p => p.WaitAny(_waitHandleArray, _operationTimeout))
+ .Callback(() => _waitingForSemaphoreAfterCompletingChunk3.Set())
+ .Returns(() => WaitAny(_waitHandleArray, _operationTimeout));
+
}
protected override void Arrange()
@@ -135,7 +163,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp
}
[TestMethod]
- public void ReahAheadOfChunk3ShouldHaveBeenDone()
+ public void ReahAheadOfChunk3ShouldHaveStarted()
{
SftpSessionMock.Verify(p => p.BeginRead(_handle, 2 * ChunkLength, ChunkLength, It.IsNotNull(), It.IsAny()), Times.Once);
}
@@ -154,6 +182,12 @@ namespace Renci.SshNet.Tests.Classes.Sftp
}
}
+ [TestMethod]
+ public void WaitAnyOFSftpSessionShouldHaveBeenInvokedFourTimes()
+ {
+ SftpSessionMock.Verify(p => p.WaitAny(_waitHandleArray, _operationTimeout), Times.Exactly(4));
+ }
+
[TestMethod]
public void DisposeShouldCloseHandleAndCompleteImmediately()
{