# < Summary

Information

Class: | Renci.SshNet.Sftp.SftpFileReader
Assembly: | Renci.SshNet
File(s): | \home\appveyor\projects\ssh-net\src\Renci.SshNet\Sftp\SftpFileReader.cs

Line coverage
91%

Covered lines: | 228
Uncovered lines: | 22
Coverable lines: | 250
Total lines: | 478
Line coverage: | 91.2%

Branch coverage
80%

Covered branches: | 37
Total branches: | 46
Branch coverage: | 80.4%

Method coverage

Feature is only available for sponsors

Upgrade to PRO version

# Metrics

Method | Branch coverage | Cyclomatic complexity | Line coverage
--- | --- | --- | ---
.ctor(...) | 100% | 1 | 100%
Read() | 76.66% | 28 | 70.31%
Finalize() | 100% | 1 | 100%
Dispose() | 100% | 1 | 100%
Dispose(...) | 87.5% | 8 | 100%
StartReadAhead() | 100% | 1 | 97.36%
ContinueReadAhead() | 75% | 4 | 91.66%
ReadCompleted(...) | 100% | 2 | 100%
ReadCompletedCore(...) | 100% | 2 | 100%
HandleFailure(...) | 100% | 1 | 100%
get_ChunkIndex() | 100% | 1 | 100%
get_Data() | 100% | 1 | 100%
get_Offset() | 100% | 1 | 100%
.ctor(...) | 100% | 1 | 100%
Complete(...) | 100% | 1 | 100%

# File(s)

## \home\appveyor\projects\ssh-net\src\Renci.SshNet\Sftp\SftpFileReader.cs

| # | Line |  | Line coverage
--- | --- | --- | --- | ---
|  | `1` |  | `using System;`
|  | `2` |  | `using System.Collections.Generic;`
|  | `3` |  | `using System.Globalization;`
|  | `4` |  | `using System.Runtime.ExceptionServices;`
|  | `5` |  | `using System.Threading;`
|  | `6` |  | ``
|  | `7` |  | `using Renci.SshNet.Abstractions;`
|  | `8` |  | `using Renci.SshNet.Common;`
|  | `9` |  | ``
|  | `10` |  | `namespace Renci.SshNet.Sftp`
|  | `11` |  | `{`
|  | `12` |  | `internal sealed class SftpFileReader : ISftpFileReader`
|  | `13` |  | `{`
|  | `14` |  | `private const int ReadAheadWaitTimeoutInMilliseconds = 1000;`
|  | `15` |  | ``
|  | `16` |  | `private readonly byte[] _handle;`
|  | `17` |  | `private readonly ISftpSession _sftpSession;`
|  | `18` |  | `private readonly uint _chunkSize;`
|  | `19` |  | `private readonly SemaphoreLight _semaphore;`
|  | `20` |  | `private readonly object _readLock;`
|  | `21` |  | `private readonly ManualResetEvent _disposingWaitHandle;`
|  | `22` |  | `private readonly ManualResetEvent _readAheadCompleted;`
|  | `23` |  | `private readonly Dictionary<int, BufferedRead> _queue;`
|  | `24` |  | `private readonly WaitHandle[] _waitHandles;`
|  | `25` |  | ``
|  | `26` |  | `/// <summary>`
|  | `27` |  | `/// Holds the size of the file, when available.`
|  | `28` |  | `/// </summary>`
|  | `29` |  | `private readonly long? _fileSize;`
|  | `30` |  | ``
|  | `31` |  | `private ulong _offset;`
|  | `32` |  | `private int _readAheadChunkIndex;`
|  | `33` |  | `private ulong _readAheadOffset;`
|  | `34` |  | `private int _nextChunkIndex;`
|  | `35` |  | ``
|  | `36` |  | `/// <summary>`
|  | `37` |  | `/// Holds a value indicating whether EOF has already been signaled by the SSH server.`
|  | `38` |  | `/// </summary>`
|  | `39` |  | `private bool _endOfFileReceived;`
|  | `40` |  | ``
|  | `41` |  | `/// <summary>`
|  | `42` |  | `/// Holds a value indicating whether the client has read up to the end of the file.`
|  | `43` |  | `/// </summary>`
|  | `44` |  | `private bool _isEndOfFileRead;`
|  | `45` |  | ``
|  | `46` |  | `private bool _disposingOrDisposed;`
|  | `47` |  | ``
|  | `48` |  | `private Exception _exception;`
|  | `49` |  | ``
|  | `50` |  | `/// <summary>`
|  | `51` |  | `/// Initializes a new instance of the <see cref="SftpFileReader"/> class with the specified handle,`
|  | `52` |  | `/// <see cref="ISftpSession"/> and the maximum number of pending reads.`
|  | `53` |  | `/// </summary>`
|  | `54` |  | `/// <param name="handle">The file handle.</param>`
|  | `55` |  | `/// <param name="sftpSession">The SFT session.</param>`
|  | `56` |  | `/// <param name="chunkSize">The size of a individual read-ahead chunk.</param>`
|  | `57` |  | `/// <param name="maxPendingReads">The maximum number of pending reads.</param>`
|  | `58` |  | `/// <param name="fileSize">The size of the file, if known; otherwise, <see langword="null"/>.</param>`
| 220 | `59` |  | `public SftpFileReader(byte[] handle, ISftpSession sftpSession, uint chunkSize, int maxPendingReads, long? fileSi`
| 220 | `60` |  | `{`
| 220 | `61` |  | `_handle = handle;`
| 220 | `62` |  | `_sftpSession = sftpSession;`
| 220 | `63` |  | `_chunkSize = chunkSize;`
| 220 | `64` |  | `_fileSize = fileSize;`
| 220 | `65` |  | `_semaphore = new SemaphoreLight(maxPendingReads);`
| 220 | `66` |  | `_queue = new Dictionary<int, BufferedRead>(maxPendingReads);`
| 220 | `67` |  | `_readLock = new object();`
| 220 | `68` |  | `_readAheadCompleted = new ManualResetEvent(initialState: false);`
| 220 | `69` |  | `_disposingWaitHandle = new ManualResetEvent(initialState: false);`
| 220 | `70` |  | `_waitHandles = _sftpSession.CreateWaitHandleArray(_disposingWaitHandle, _semaphore.AvailableWaitHandle);`
|  | `71` |  | ``
| 220 | `72` |  | `StartReadAhead();`
| 220 | `73` |  | `}`
|  | `74` |  | ``
|  | `75` |  | `public byte[] Read()`
| 4248 | `76` |  | `{`
|  | `77` |  | `#if NET7_0_OR_GREATER`
| 4063 | `78` |  | `ObjectDisposedException.ThrowIf(_disposingOrDisposed, this);`
|  | `79` |  | `#else`
| 185 | `80` |  | `if (_disposingOrDisposed)`
| 4 | `81` |  | `{`
| 4 | `82` |  | `throw new ObjectDisposedException(GetType().FullName);`
|  | `83` |  | `}`
|  | `84` |  | `#endif // NET7_0_OR_GREATER`
|  | `85` |  | ``
| 4236 | `86` |  | `if (_exception is not null)`
| 24 | `87` |  | `{`
| 24 | `88` |  | `ExceptionDispatchInfo.Capture(_exception).Throw();`
| 0 | `89` |  | `}`
|  | `90` |  | ``
| 4212 | `91` |  | `if (_isEndOfFileRead)`
| 12 | `92` |  | `{`
| 12 | `93` |  | `throw new SshException("Attempting to read beyond the end of the file.");`
|  | `94` |  | `}`
|  | `95` |  | ``
|  | `96` |  | `BufferedRead nextChunk;`
|  | `97` |  | ``
| 4200 | `98` |  | `lock (_readLock)`
| 4200 | `99` |  | `{`
|  | `100` |  | `// wait until either the next chunk is available, an exception has occurred or the current`
|  | `101` |  | `// instance is already disposed`
| 7996 | `102` |  | `while (!_queue.TryGetValue(_nextChunkIndex, out nextChunk) && _exception is null)`
| 3796 | `103` |  | `{`
| 3796 | `104` |  | `_ =Monitor.Wait(_readLock);`
| 3796 | `105` |  | `}`
|  | `106` |  | ``
|  | `107` |  | `// throw when exception occured in read-ahead, or the current instance is already disposed`
| 4200 | `108` |  | `if (_exception != null)`
| 60 | `109` |  | `{`
| 60 | `110` |  | `ExceptionDispatchInfo.Capture(_exception).Throw();`
| 0 | `111` |  | `}`
|  | `112` |  | ``
| 4140 | `113` |  | `var data = nextChunk.Data;`
|  | `114` |  | ``
| 4140 | `115` |  | `if (nextChunk.Offset == _offset)`
| 4003 | `116` |  | `{`
|  | `117` |  | `// have we reached EOF?`
| 4003 | `118` |  | `if (data.Length == 0)`
| 65 | `119` |  | `{`
|  | `120` |  | `// PERF: we do not bother updating all of the internal state when we've reached EOF`
| 65 | `121` |  | `_isEndOfFileRead = true;`
| 65 | `122` |  | `}`
|  | `123` |  | `else`
| 3938 | `124` |  | `{`
|  | `125` |  | `// remove processed chunk`
| 3938 | `126` |  | `_ = _queue.Remove(_nextChunkIndex);`
|  | `127` |  | ``
|  | `128` |  | `// update offset`
| 3938 | `129` |  | `_offset += (ulong) data.Length;`
|  | `130` |  | ``
|  | `131` |  | `// move to next chunk`
| 3938 | `132` |  | `_nextChunkIndex++;`
| 3938 | `133` |  | `}`
|  | `134` |  | ``
|  | `135` |  | `// unblock wait in read-ahead`
| 4003 | `136` |  | `_ = _semaphore.Release();`
|  | `137` |  | ``
| 4003 | `138` |  | `return data;`
|  | `139` |  | `}`
|  | `140` |  | ``
|  | `141` |  | `// When we received an EOF for the next chunk and the size of the file is known, then`
|  | `142` |  | `// we only complete the current chunk if we haven't already read up to the file size.`
|  | `143` |  | `// This way we save an extra round-trip to the server.`
| 137 | `144` |  | `if (data.Length == 0 && _fileSize.HasValue && _offset == (ulong) _fileSize.Value)`
| 59 | `145` |  | `{`
|  | `146` |  | `// avoid future reads`
| 59 | `147` |  | `_isEndOfFileRead = true;`
|  | `148` |  | ``
|  | `149` |  | `// unblock wait in read-ahead`
| 59 | `150` |  | `_ = _semaphore.Release();`
|  | `151` |  | ``
|  | `152` |  | `// signal EOF to caller`
| 59 | `153` |  | `return nextChunk.Data;`
|  | `154` |  | `}`
| 78 | `155` |  | `}`
|  | `156` |  | ``
|  | `157` |  | `/*`
|  | `158` |  | `* When the server returned less bytes than requested (for the previous chunk)`
|  | `159` |  | `* we'll synchronously request the remaining data.`
|  | `160` |  | `*`
|  | `161` |  | `* Due to the optimization above, we'll only get here in one of the following cases:`
|  | `162` |  | `* - an EOF situation for files for which we were unable to obtain the file size`
|  | `163` |  | `* - fewer bytes that requested were returned`
|  | `164` |  | `*`
|  | `165` |  | `* According to the SSH specification, this last case should never happen for normal`
|  | `166` |  | `* disk files (but can happen for device files). In practice, OpenSSH - for example -`
|  | `167` |  | `* returns less bytes than requested when requesting more than 64 KB.`
|  | `168` |  | `*`
|  | `169` |  | `* Important:`
|  | `170` |  | `* To avoid a deadlock, this read must be done outside of the read lock.`
|  | `171` |  | `*/`
|  | `172` |  | ``
| 78 | `173` |  | `var bytesToCatchUp = nextChunk.Offset - _offset;`
|  | `174` |  | ``
|  | `175` |  | `/*`
|  | `176` |  | `* TODO: break loop and interrupt blocking wait in case of exception`
|  | `177` |  | `*/`
|  | `178` |  | ``
| 78 | `179` |  | `var read = _sftpSession.RequestRead(_handle, _offset, (uint) bytesToCatchUp);`
| 78 | `180` |  | `if (read.Length == 0)`
| 0 | `181` |  | `{`
|  | `182` |  | `// process data in read lock to avoid ObjectDisposedException while releasing semaphore`
| 0 | `183` |  | `lock (_readLock)`
| 0 | `184` |  | `{`
|  | `185` |  | `// a zero-length (EOF) response is only valid for the read-back when EOF has`
|  | `186` |  | `// been signaled for the next read-ahead chunk`
| 0 | `187` |  | `if (nextChunk.Data.Length == 0)`
| 0 | `188` |  | `{`
| 0 | `189` |  | `_isEndOfFileRead = true;`
|  | `190` |  | ``
|  | `191` |  | `// ensure we've not yet disposed the current instance`
| 0 | `192` |  | `if (!_disposingOrDisposed)`
| 0 | `193` |  | `{`
|  | `194` |  | `// unblock wait in read-ahead`
| 0 | `195` |  | `_ = _semaphore.Release();`
| 0 | `196` |  | `}`
|  | `197` |  | ``
|  | `198` |  | `// signal EOF to caller`
| 0 | `199` |  | `return read;`
|  | `200` |  | `}`
|  | `201` |  | ``
|  | `202` |  | `// move reader to error state`
| 0 | `203` |  | `_exception = new SshException("Unexpectedly reached end of file.");`
|  | `204` |  | ``
|  | `205` |  | `// ensure we've not yet disposed the current instance`
| 0 | `206` |  | `if (!_disposingOrDisposed)`
| 0 | `207` |  | `{`
|  | `208` |  | `// unblock wait in read-ahead`
| 0 | `209` |  | `_ = _semaphore.Release();`
| 0 | `210` |  | `}`
|  | `211` |  | ``
|  | `212` |  | `// notify caller of error`
| 0 | `213` |  | `throw _exception;`
|  | `214` |  | `}`
|  | `215` |  | `}`
|  | `216` |  | ``
| 78 | `217` |  | `_offset += (uint) read.Length;`
|  | `218` |  | ``
| 78 | `219` |  | `return read;`
| 4140 | `220` |  | `}`
|  | `221` |  | ``
|  | `222` |  | `~SftpFileReader()`
| 210 | `223` |  | `{`
| 105 | `224` |  | `Dispose(disposing: false);`
| 210 | `225` |  | `}`
|  | `226` |  | ``
|  | `227` |  | `public void Dispose()`
| 118 | `228` |  | `{`
| 118 | `229` |  | `Dispose(disposing: true);`
| 118 | `230` |  | `GC.SuppressFinalize(this);`
| 118 | `231` |  | `}`
|  | `232` |  | ``
|  | `233` |  | `/// <summary>`
|  | `234` |  | `/// Releases unmanaged and - optionally - managed resources.`
|  | `235` |  | `/// </summary>`
|  | `236` |  | `/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langwor`
|  | `237` |  | `private void Dispose(bool disposing)`
| 223 | `238` |  | `{`
| 223 | `239` |  | `if (_disposingOrDisposed)`
| 3 | `240` |  | `{`
| 3 | `241` |  | `return;`
|  | `242` |  | `}`
|  | `243` |  | ``
|  | `244` |  | `// transition to disposing state`
| 220 | `245` |  | `_disposingOrDisposed = true;`
|  | `246` |  | ``
| 220 | `247` |  | `if (disposing)`
| 115 | `248` |  | `{`
|  | `249` |  | `// record exception to break prevent future Read()`
| 115 | `250` |  | `_exception = new ObjectDisposedException(GetType().FullName);`
|  | `251` |  | ``
|  | `252` |  | `// signal that we're disposing to interrupt wait in read-ahead`
| 115 | `253` |  | `_ = _disposingWaitHandle.Set();`
|  | `254` |  | ``
|  | `255` |  | `// wait until the read-ahead thread has completed`
| 115 | `256` |  | `_ = _readAheadCompleted.WaitOne();`
|  | `257` |  | ``
|  | `258` |  | `// unblock the Read()`
| 115 | `259` |  | `lock (_readLock)`
| 115 | `260` |  | `{`
|  | `261` |  | `// dispose semaphore in read lock to ensure we don't run into an ObjectDisposedException`
|  | `262` |  | `// in Read()`
| 115 | `263` |  | `_semaphore.Dispose();`
|  | `264` |  | ``
|  | `265` |  | `// awake Read`
| 115 | `266` |  | `Monitor.PulseAll(_readLock);`
| 115 | `267` |  | `}`
|  | `268` |  | ``
| 115 | `269` |  | `_readAheadCompleted.Dispose();`
| 115 | `270` |  | `_disposingWaitHandle.Dispose();`
|  | `271` |  | ``
| 115 | `272` |  | `if (_sftpSession.IsOpen)`
| 106 | `273` |  | `{`
|  | `274` |  | `try`
| 106 | `275` |  | `{`
| 106 | `276` |  | `var closeAsyncResult = _sftpSession.BeginClose(_handle, callback: null, state: null);`
| 97 | `277` |  | `_sftpSession.EndClose(closeAsyncResult);`
| 88 | `278` |  | `}`
| 18 | `279` |  | `catch (Exception ex)`
| 18 | `280` |  | `{`
| 18 | `281` |  | `DiagnosticAbstraction.Log("Failure closing handle: " + ex);`
| 18 | `282` |  | `}`
| 106 | `283` |  | `}`
| 115 | `284` |  | `}`
| 223 | `285` |  | `}`
|  | `286` |  | ``
|  | `287` |  | `private void StartReadAhead()`
| 220 | `288` |  | `{`
| 220 | `289` |  | `ThreadAbstraction.ExecuteThread(() =>`
| 220 | `290` |  | `{`
| 4384 | `291` |  | `while (!_endOfFileReceived && _exception is null)`
| 4235 | `292` |  | `{`
| 220 | `293` |  | `// check if we should continue with the read-ahead loop`
| 220 | `294` |  | `// note that the EOF and exception check are not included`
| 220 | `295` |  | `// in this check as they do not require Read() to be`
| 220 | `296` |  | `// unblocked (or have already done this)`
| 4235 | `297` |  | `if (!ContinueReadAhead())`
| 33 | `298` |  | `{`
| 220 | `299` |  | `// unblock the Read()`
| 33 | `300` |  | `lock (_readLock)`
| 33 | `301` |  | `{`
| 33 | `302` |  | `Monitor.PulseAll(_readLock);`
| 33 | `303` |  | `}`
| 220 | `304` |  | ``
| 220 | `305` |  | `// break the read-ahead loop`
| 33 | `306` |  | `break;`
| 220 | `307` |  | `}`
| 220 | `308` |  | ``
| 220 | `309` |  | `// attempt to obtain the semaphore; this may time out when all semaphores are`
| 220 | `310` |  | `// in use due to pending read-aheads (which in turn can happen when the server`
| 220 | `311` |  | `// is slow to respond or when the session is broken)`
| 4202 | `312` |  | `if (!_semaphore.Wait(ReadAheadWaitTimeoutInMilliseconds))`
| 0 | `313` |  | `{`
| 220 | `314` |  | `// re-evaluate whether an exception occurred, and - if not - wait again`
| 0 | `315` |  | `continue;`
| 220 | `316` |  | `}`
| 220 | `317` |  | ``
| 220 | `318` |  | `// don't bother reading any more chunks if we received EOF, an exception has occurred`
| 220 | `319` |  | `// or the current instance is disposed`
| 4202 | `320` |  | `if (_endOfFileReceived || _exception != null)`
| 29 | `321` |  | `{`
| 29 | `322` |  | `break;`
| 220 | `323` |  | `}`
| 220 | `324` |  | ``
| 220 | `325` |  | `// start reading next chunk`
| 4173 | `326` |  | `var bufferedRead = new BufferedRead(_readAheadChunkIndex, _readAheadOffset);`
| 220 | `327` |  | ``
| 220 | `328` |  | `try`
| 4173 | `329` |  | `{`
| 220 | `330` |  | `// even if we know the size of the file and have read up to EOF, we still want`
| 220 | `331` |  | `// to keep reading (ahead) until we receive zero bytes from the remote host as`
| 220 | `332` |  | `// we do not want to rely purely on the reported file size`
| 220 | `333` |  | `//`
| 220 | `334` |  | `// if the offset of the read-ahead chunk is greater than that file size, then`
| 220 | `335` |  | `// we can expect to be reading the last (zero-byte) chunk and switch to synchronous`
| 220 | `336` |  | `// mode to avoid having multiple read-aheads that read beyond EOF`
| 4173 | `337` |  | `if (_fileSize != null && (long) _readAheadOffset > _fileSize.Value)`
| 59 | `338` |  | `{`
| 59 | `339` |  | `var asyncResult = _sftpSession.BeginRead(_handle, _readAheadOffset, _chunkSize, callback: nu`
| 59 | `340` |  | `var data = _sftpSession.EndRead(asyncResult);`
| 59 | `341` |  | `ReadCompletedCore(bufferedRead, data);`
| 59 | `342` |  | `}`
| 220 | `343` |  | `else`
| 4114 | `344` |  | `{`
| 4114 | `345` |  | `_ = _sftpSession.BeginRead(_handle, _readAheadOffset, _chunkSize, ReadCompleted, bufferedRea`
| 4105 | `346` |  | `}`
| 4164 | `347` |  | `}`
| 9 | `348` |  | `catch (Exception ex)`
| 9 | `349` |  | `{`
| 9 | `350` |  | `HandleFailure(ex);`
| 9 | `351` |  | `break;`
| 220 | `352` |  | `}`
| 220 | `353` |  | ``
| 220 | `354` |  | `// advance read-ahead offset`
| 4164 | `355` |  | `_readAheadOffset += _chunkSize;`
| 220 | `356` |  | ``
| 220 | `357` |  | `// increment index of read-ahead chunk`
| 4164 | `358` |  | `_readAheadChunkIndex++;`
| 4164 | `359` |  | `}`
| 220 | `360` |  | ``
| 220 | `361` |  | `_ = _readAheadCompleted.Set();`
| 440 | `362` |  | `});`
| 220 | `363` |  | `}`
|  | `364` |  | ``
|  | `365` |  | `/// <summary>`
|  | `366` |  | `/// Returns a value indicating whether the read-ahead loop should be continued.`
|  | `367` |  | `/// </summary>`
|  | `368` |  | `/// <returns>`
|  | `369` |  | `/// <see langword="true"/> if the read-ahead loop should be continued; otherwise, <see langword="false"/>.`
|  | `370` |  | `/// </returns>`
|  | `371` |  | `private bool ContinueReadAhead()`
| 4235 | `372` |  | `{`
|  | `373` |  | `try`
| 4235 | `374` |  | `{`
| 4235 | `375` |  | `var waitResult = _sftpSession.WaitAny(_waitHandles, _sftpSession.OperationTimeout);`
| 4217 | `376` |  | `switch (waitResult)`
|  | `377` |  | `{`
|  | `378` |  | `case 0: // disposing`
| 15 | `379` |  | `return false;`
|  | `380` |  | `case 1: // semaphore available`
| 4202 | `381` |  | `return true;`
|  | `382` |  | `default:`
| 0 | `383` |  | `throw new NotImplementedException(string.Format(CultureInfo.InvariantCulture, "WaitAny return va`
|  | `384` |  | `}`
|  | `385` |  | `}`
| 18 | `386` |  | `catch (Exception ex)`
| 18 | `387` |  | `{`
| 18 | `388` |  | `_ = Interlocked.CompareExchange(ref _exception, ex, comparand: null);`
| 18 | `389` |  | `return false;`
|  | `390` |  | `}`
| 4235 | `391` |  | `}`
|  | `392` |  | ``
|  | `393` |  | `private void ReadCompleted(IAsyncResult result)`
| 4066 | `394` |  | `{`
| 4066 | `395` |  | `if (_disposingOrDisposed)`
| 12 | `396` |  | `{`
|  | `397` |  | `// skip further processing if we're disposing the current instance`
|  | `398` |  | `// to avoid accessing disposed members`
| 12 | `399` |  | `return;`
|  | `400` |  | `}`
|  | `401` |  | ``
| 4054 | `402` |  | `var readAsyncResult = (SftpReadAsyncResult) result;`
|  | `403` |  | ``
|  | `404` |  | `byte[] data;`
|  | `405` |  | ``
|  | `406` |  | `try`
| 4054 | `407` |  | `{`
| 4054 | `408` |  | `data = readAsyncResult.EndInvoke();`
| 4027 | `409` |  | `}`
| 27 | `410` |  | `catch (Exception ex)`
| 27 | `411` |  | `{`
| 27 | `412` |  | `HandleFailure(ex);`
| 27 | `413` |  | `return;`
|  | `414` |  | `}`
|  | `415` |  | ``
|  | `416` |  | `// a read that completes with a zero-byte result signals EOF`
|  | `417` |  | `// but there may be pending reads before that read`
| 4027 | `418` |  | `var bufferedRead = (BufferedRead) readAsyncResult.AsyncState;`
| 4027 | `419` |  | `ReadCompletedCore(bufferedRead, data);`
| 4066 | `420` |  | `}`
|  | `421` |  | ``
|  | `422` |  | `private void ReadCompletedCore(BufferedRead bufferedRead, byte[] data)`
| 4086 | `423` |  | `{`
| 4086 | `424` |  | `bufferedRead.Complete(data);`
|  | `425` |  | ``
| 4086 | `426` |  | `lock (_readLock)`
| 4086 | `427` |  | `{`
|  | `428` |  | `// add item to queue`
| 4086 | `429` |  | `_queue.Add(bufferedRead.ChunkIndex, bufferedRead);`
|  | `430` |  | ``
|  | `431` |  | `// Signal that a chunk has been read or EOF has been reached.`
|  | `432` |  | `// In both cases, Read() will eventually also unblock the "read-ahead" thread.`
| 4086 | `433` |  | `Monitor.PulseAll(_readLock);`
| 4086 | `434` |  | `}`
|  | `435` |  | ``
|  | `436` |  | `// check if server signaled EOF`
| 4086 | `437` |  | `if (data.Length == 0)`
| 124 | `438` |  | `{`
|  | `439` |  | `// set a flag to stop read-aheads`
| 124 | `440` |  | `_endOfFileReceived = true;`
| 124 | `441` |  | `}`
| 4086 | `442` |  | `}`
|  | `443` |  | ``
|  | `444` |  | `private void HandleFailure(Exception cause)`
| 36 | `445` |  | `{`
| 36 | `446` |  | `_ = Interlocked.CompareExchange(ref _exception, cause, comparand: null);`
|  | `447` |  | ``
|  | `448` |  | `// unblock read-ahead`
| 36 | `449` |  | `_ = _semaphore.Release();`
|  | `450` |  | ``
|  | `451` |  | `// unblock Read()`
| 36 | `452` |  | `lock (_readLock)`
| 36 | `453` |  | `{`
| 36 | `454` |  | `Monitor.PulseAll(_readLock);`
| 36 | `455` |  | `}`
| 36 | `456` |  | `}`
|  | `457` |  | ``
|  | `458` |  | `internal sealed class BufferedRead`
|  | `459` |  | `{`
| 4086 | `460` |  | `public int ChunkIndex { get; }`
|  | `461` |  | ``
| 8285 | `462` |  | `public byte[] Data { get; private set; }`
|  | `463` |  | ``
| 4218 | `464` |  | `public ulong Offset { get; }`
|  | `465` |  | ``
| 4173 | `466` |  | `public BufferedRead(int chunkIndex, ulong offset)`
| 4173 | `467` |  | `{`
| 4173 | `468` |  | `ChunkIndex = chunkIndex;`
| 4173 | `469` |  | `Offset = offset;`
| 4173 | `470` |  | `}`
|  | `471` |  | ``
|  | `472` |  | `public void Complete(byte[] data)`
| 4086 | `473` |  | `{`
| 4086 | `474` |  | `Data = data;`
| 4086 | `475` |  | `}`
|  | `476` |  | `}`
|  | `477` |  | `}`
|  | `478` |  | `}`

# Methods/Properties

.ctor(System.Byte[],Renci.SshNet.Sftp.ISftpSession,System.UInt32,System.Int32,System.Nullable`1<System.Int64>)
Read()
Finalize()
Dispose()
Dispose(System.Boolean)
StartReadAhead()
ContinueReadAhead()
ReadCompleted(System.IAsyncResult)
ReadCompletedCore(Renci.SshNet.Sftp.SftpFileReader/BufferedRead,System.Byte[])
HandleFailure(System.Exception)
get_ChunkIndex()
get_Data()
get_Offset()
.ctor(System.Int32,System.UInt64)
Complete(System.Byte[])