From cb1db852db023041a284567ac68f8851325ea165 Mon Sep 17 00:00:00 2001 From: tymlipari Date: Thu, 13 Aug 2015 22:07:08 -0700 Subject: [PATCH 1/7] Handle internal overflow of the write buffer in WriteAsync by introducing new cases: 1) Internal buffer overflows, but write can fit in next buffer - Fill the existing buffer, copy the remaining data into a new buffer, and flush the original to disk 2) Internal buffer is non-empty, overflows, and the remaining data won't fit in a second buffer - Chain the flush operation to a second write operation which writes the entire incoming block directly to disk. Additionally, adds a new unit test to ensure the different buffering cases are being covered. --- .../src/System/IO/Win32FileStream.cs | 56 ++++++++++++++++--- .../tests/FileStream/WriteAsync.cs | 24 ++++++++ 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs b/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs index ef2bff7e6cfd..83775f0ae874 100644 --- a/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs +++ b/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs @@ -520,6 +520,19 @@ private void FlushRead() _readLen = 0; } + // Returns a task that flushes the internal write buffer + private Task FlushWriteAsync(CancellationToken cancellationToken) + { + Debug.Assert(_isAsync); + Debug.Assert(_readPos == 0 && _readLen == 0, "FileStream: Read buffer must be empty in FlushWriteAsync!"); + + Task flushTask = WriteInternalCoreAsync(_buffer, 0, _writePos, CancellationToken.None); + + _writePos = 0; + + return flushTask; + } + // Writes are buffered. Anytime the buffer fills up // (_writePos + delta > _bufferSize) or the buffer switches to reading // and there is left over data (_writePos > 0), this function must be called. @@ -529,7 +542,7 @@ private void FlushWrite(bool calledFromFinalizer) if (_isAsync) { - Task writeTask = WriteInternalCoreAsync(_buffer, 0, _writePos, CancellationToken.None); + Task writeTask = FlushWriteAsync(CancellationToken.None); // With our Whidbey async IO & overlapped support for AD unloads, // we don't strictly need to block here to release resources // since that support takes care of the pinning & freeing the @@ -1277,8 +1290,9 @@ private Task WriteInternalAsync(byte[] array, int offset, int numBytes, Cancella // pipes. Debug.Assert(_readPos == 0 && _readLen == 0, "Win32FileStream must not have buffered data here! Pipes should be unidirectional."); - if (_writePos > 0) - FlushWrite(false); + if(_writePos > 0) + return FlushWriteAsync(cancellationToken) + .ContinueWith((_) => WriteInternalCoreAsync(array, offset, numBytes, cancellationToken)); return WriteInternalCoreAsync(array, offset, numBytes, cancellationToken); } @@ -1291,19 +1305,45 @@ private Task WriteInternalAsync(byte[] array, int offset, int numBytes, Cancella _readLen = 0; } - int n = _bufferSize - _writePos; - if (numBytes <= n) + int remainingBuffer = _bufferSize - _writePos; + + // if the incoming write fits in the remaining buffer, treat + // as synchronous since memcopy is so fast + if (numBytes <= remainingBuffer) { if (_writePos == 0) _buffer = new byte[_bufferSize]; Buffer.BlockCopy(array, offset, _buffer, _writePos, numBytes); _writePos += numBytes; - + return Task.CompletedTask; } - if (_writePos > 0) - FlushWrite(false); + // if the incoming write fits in the current buffer + next buffer, + // only perform a single write to flush the existing buffer and fill + // the next buffer with the remainder + if (numBytes <= (_bufferSize + remainingBuffer)) + { + if (_writePos == 0) _buffer = new byte[_bufferSize]; + + Buffer.BlockCopy(array, offset, _buffer, _writePos, remainingBuffer); + + byte[] oldBuffer = _buffer; + _buffer = new byte[_bufferSize]; + + _writePos = (numBytes - remainingBuffer); + Buffer.BlockCopy(array, offset + remainingBuffer, _buffer, 0, _writePos); + + return WriteInternalCoreAsync(oldBuffer, 0, _bufferSize, cancellationToken); + } + + // If there's data in the existing buffer, and the incoming write would overflow it and + // the subsequent buffer, chain the flush operation with the subsequent write + else if(_writePos > 0) + return FlushWriteAsync(cancellationToken) + .ContinueWith((_) => WriteInternalCoreAsync(array, offset, numBytes, cancellationToken)); + // Otherwise, there's no buffered data, and the incoming write is too big to fit + // in the buffer, so write it directly to disk return WriteInternalCoreAsync(array, offset, numBytes, cancellationToken); } diff --git a/src/System.IO.FileSystem/tests/FileStream/WriteAsync.cs b/src/System.IO.FileSystem/tests/FileStream/WriteAsync.cs index 7fcf8b6b8a87..70b6c58b4e19 100644 --- a/src/System.IO.FileSystem/tests/FileStream/WriteAsync.cs +++ b/src/System.IO.FileSystem/tests/FileStream/WriteAsync.cs @@ -270,6 +270,30 @@ public async Task WriteAsyncCancelledFile() } } + [Fact] + public async void WriteAsyncInternalBufferOverflow() + { + // Overflow into next buffer + using (FileStream fs = new FileStream(GetTestFilePath(), FileMode.Create, FileAccess.Write, FileShare.None, 2)) + { + // Fill existing buffer + await fs.WriteAsync(TestBuffer, 0, 2); + Assert.True(fs.Length == 2); + + // Overflow into next buffer + await fs.WriteAsync(TestBuffer, 0, 1); + Assert.True(fs.Length == 3); + + // Overflow bufferSize * 2 + await fs.WriteAsync(TestBuffer, 0, 5); + Assert.True(fs.Length == 8); + + // Overflow bufferSize * 2 with empty buffer + await fs.WriteAsync(TestBuffer, 0, 5); + Assert.True(fs.Length == 13); + } + } + [Fact, OuterLoop] public async Task WriteAsyncMiniStress() { From 17c5fc02f15d7a25c715659b569414e4274b2490 Mon Sep 17 00:00:00 2001 From: tymlipari Date: Fri, 14 Aug 2015 12:27:28 -0700 Subject: [PATCH 2/7] Use cancellation token in FlushWriteAsync --- src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs b/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs index 83775f0ae874..649759c68413 100644 --- a/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs +++ b/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs @@ -526,7 +526,7 @@ private Task FlushWriteAsync(CancellationToken cancellationToken) Debug.Assert(_isAsync); Debug.Assert(_readPos == 0 && _readLen == 0, "FileStream: Read buffer must be empty in FlushWriteAsync!"); - Task flushTask = WriteInternalCoreAsync(_buffer, 0, _writePos, CancellationToken.None); + Task flushTask = WriteInternalCoreAsync(_buffer, 0, _writePos, cancellationToken); _writePos = 0; From 5e05efc9d9409b6706910f18cc8717b218e74b07 Mon Sep 17 00:00:00 2001 From: tymlipari Date: Fri, 14 Aug 2015 15:14:58 -0700 Subject: [PATCH 3/7] Flush and write operation as helper function instead of using Task.ContinueWith --- .../src/System/IO/Win32FileStream.cs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs b/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs index 649759c68413..29b60d105353 100644 --- a/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs +++ b/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs @@ -526,10 +526,11 @@ private Task FlushWriteAsync(CancellationToken cancellationToken) Debug.Assert(_isAsync); Debug.Assert(_readPos == 0 && _readLen == 0, "FileStream: Read buffer must be empty in FlushWriteAsync!"); - Task flushTask = WriteInternalCoreAsync(_buffer, 0, _writePos, cancellationToken); + // If the buffer is already flushed, don't spin up the OS write + if (_writePos == 0) return Task.CompletedTask; + Task flushTask = WriteInternalCoreAsync(_buffer, 0, _writePos, cancellationToken); _writePos = 0; - return flushTask; } @@ -1264,6 +1265,14 @@ public override int ReadByte() return result; } + private async Task FlushAndWriteAsync(byte[] array, int offset, int numBytes, CancellationToken cancellationToken) + { + Debug.Assert(_isAsync); + + await FlushWriteAsync(cancellationToken); + await WriteInternalCoreAsync(array, offset, numBytes, cancellationToken); + } + [System.Security.SecuritySafeCritical] // auto-generated private Task WriteInternalAsync(byte[] array, int offset, int numBytes, CancellationToken cancellationToken) { @@ -1290,9 +1299,8 @@ private Task WriteInternalAsync(byte[] array, int offset, int numBytes, Cancella // pipes. Debug.Assert(_readPos == 0 && _readLen == 0, "Win32FileStream must not have buffered data here! Pipes should be unidirectional."); - if(_writePos > 0) - return FlushWriteAsync(cancellationToken) - .ContinueWith((_) => WriteInternalCoreAsync(array, offset, numBytes, cancellationToken)); + if (_writePos > 0) + return FlushAndWriteAsync(array, offset, numBytes, cancellationToken); return WriteInternalCoreAsync(array, offset, numBytes, cancellationToken); } @@ -1338,9 +1346,8 @@ private Task WriteInternalAsync(byte[] array, int offset, int numBytes, Cancella // If there's data in the existing buffer, and the incoming write would overflow it and // the subsequent buffer, chain the flush operation with the subsequent write - else if(_writePos > 0) - return FlushWriteAsync(cancellationToken) - .ContinueWith((_) => WriteInternalCoreAsync(array, offset, numBytes, cancellationToken)); + else if (_writePos > 0) + return FlushAndWriteAsync(array, offset, numBytes, cancellationToken); // Otherwise, there's no buffered data, and the incoming write is too big to fit // in the buffer, so write it directly to disk From 84a9ce404838de8e085896dd5e7211dd473e7e89 Mon Sep 17 00:00:00 2001 From: tymlipari Date: Wed, 19 Aug 2015 14:32:33 -0700 Subject: [PATCH 4/7] Change buffering logic to track flush task activity in order to minimize buffer allocations. --- .../src/System/IO/Win32FileStream.cs | 89 ++++++++++++------- 1 file changed, 57 insertions(+), 32 deletions(-) diff --git a/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs b/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs index 29b60d105353..d056b5402e4b 100644 --- a/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs +++ b/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs @@ -61,6 +61,7 @@ internal sealed partial class Win32FileStream : FileStreamBase private long _appendStart;// When appending, prevent overwriting file. private Task _lastSynchronouslyCompletedTask = null; + private Task _activeBufferOperation = null; [System.Security.SecuritySafeCritical] public Win32FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, FileStream parent) : base(parent) @@ -1265,12 +1266,9 @@ public override int ReadByte() return result; } - private async Task FlushAndWriteAsync(byte[] array, int offset, int numBytes, CancellationToken cancellationToken) + private bool ActiveBufferOperation() { - Debug.Assert(_isAsync); - - await FlushWriteAsync(cancellationToken); - await WriteInternalCoreAsync(array, offset, numBytes, cancellationToken); + return _activeBufferOperation != null && !_activeBufferOperation.IsCompleted; } [System.Security.SecuritySafeCritical] // auto-generated @@ -1299,13 +1297,20 @@ private Task WriteInternalAsync(byte[] array, int offset, int numBytes, Cancella // pipes. Debug.Assert(_readPos == 0 && _readLen == 0, "Win32FileStream must not have buffered data here! Pipes should be unidirectional."); + // If there's data in the write buffer, flush it before starting a new write if (_writePos > 0) - return FlushAndWriteAsync(array, offset, numBytes, cancellationToken); + { + Task flushTask = FlushWriteAsync(cancellationToken); + if (ActiveBufferOperation()) + _activeBufferOperation = Task.WhenAll(_activeBufferOperation, flushTask); + else + _activeBufferOperation = flushTask; + } return WriteInternalCoreAsync(array, offset, numBytes, cancellationToken); } - // Handle buffering. + // Ensure the buffer is clear for writing if (_writePos == 0) { if (_readPos < _readLen) FlushRead(); @@ -1313,44 +1318,64 @@ private Task WriteInternalAsync(byte[] array, int offset, int numBytes, Cancella _readLen = 0; } - int remainingBuffer = _bufferSize - _writePos; + // There are a few different cases to handle when performing the write operation + // surrounding the internal buffer. Buffer flush operations can be issued asynchronously + // so the state of any async operation that touches the internal buffer must be tracked + // to ensure there are no data races at play. + // + // 1. No active async buffer op and numBytes fits in existing buffer: + // Simply perform a memory copy operation and return synchronously. + // + // 2. Active async buffer op and numBytes <= _bufferSize + // Since there's an active buffer operation, a new buffer needs to be allocated + // to write to in order to ensure the existing buffer isn't modified mid-write. + // The incoming write can then be copied into the new buffer. + // + // 3. No active async buffer and numBytes too large for buffer + // If there's buffered data, issue a flush operation and then directly issue the + // incoming write since it can't be buffered. + // + // 4. Active async buffer op and numBytes too large for buffer + // If there's buffered data, attach it to the existing async operation using Task.WhenAll + // and then directly issue the incoming write. + // + // Note: since _writePos is reset synchronously when calling FlushWriteAsync, it can be + // assumed that if _writePos > 0 and there's an active async flush operation, the buffer + // was reallocated since that operation was initiated. - // if the incoming write fits in the remaining buffer, treat - // as synchronous since memcopy is so fast - if (numBytes <= remainingBuffer) + // Case 1 - no active buffer op & fits in remaining size + int remainingBuffer = _bufferSize - _writePos; + if(!ActiveBufferOperation() && numBytes <= remainingBuffer) { - if (_writePos == 0) _buffer = new byte[_bufferSize]; + if (_buffer == null) _buffer = new byte[_bufferSize]; + Buffer.BlockCopy(array, offset, _buffer, _writePos, numBytes); _writePos += numBytes; - return Task.CompletedTask; } - // if the incoming write fits in the current buffer + next buffer, - // only perform a single write to flush the existing buffer and fill - // the next buffer with the remainder - if (numBytes <= (_bufferSize + remainingBuffer)) + // Case 2 - active buffer op & fits in new buffer + else if (ActiveBufferOperation() && numBytes <= _bufferSize) { - if (_writePos == 0) _buffer = new byte[_bufferSize]; - - Buffer.BlockCopy(array, offset, _buffer, _writePos, remainingBuffer); - - byte[] oldBuffer = _buffer; _buffer = new byte[_bufferSize]; - _writePos = (numBytes - remainingBuffer); - Buffer.BlockCopy(array, offset + remainingBuffer, _buffer, 0, _writePos); - - return WriteInternalCoreAsync(oldBuffer, 0, _bufferSize, cancellationToken); + Buffer.BlockCopy(array, offset, _buffer, 0, numBytes); + _writePos = numBytes; + return Task.CompletedTask; } - // If there's data in the existing buffer, and the incoming write would overflow it and - // the subsequent buffer, chain the flush operation with the subsequent write - else if (_writePos > 0) - return FlushAndWriteAsync(array, offset, numBytes, cancellationToken); + // Case 3 & 4 - The incoming write can't be handled with a synchronous + // memcpy, so issue a write to disk, flushing if necessary + + if (_writePos > 0) + { + Task flushTask = FlushWriteAsync(cancellationToken); + if (ActiveBufferOperation()) + _activeBufferOperation = Task.WhenAll(_activeBufferOperation, flushTask); + else + _activeBufferOperation = flushTask; + } - // Otherwise, there's no buffered data, and the incoming write is too big to fit - // in the buffer, so write it directly to disk return WriteInternalCoreAsync(array, offset, numBytes, cancellationToken); } From cfeefdbb26fe7073cf82bfb0e7b72cbe6bbb44d6 Mon Sep 17 00:00:00 2001 From: tymlipari Date: Wed, 19 Aug 2015 14:36:31 -0700 Subject: [PATCH 5/7] Missed case for tracking async flush operations --- .../src/System/IO/Win32FileStream.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs b/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs index d056b5402e4b..d53e274d9fa0 100644 --- a/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs +++ b/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs @@ -562,6 +562,16 @@ private void FlushWrite(bool calledFromFinalizer) { writeTask.GetAwaiter().GetResult(); } + + // If not completing synchronously, ensure the async flush operation + // is tracked in case of incoming WriteAsync calls. + else + { + if (ActiveBufferOperation()) + _activeBufferOperation = Task.WhenAll(_activeBufferOperation, writeTask); + else + _activeBufferOperation = writeTask; + } } else { From 21da6c0c0148d50e5efef499d8aa44cc8a326856 Mon Sep 17 00:00:00 2001 From: tymlipari Date: Wed, 19 Aug 2015 20:27:47 -0700 Subject: [PATCH 6/7] Respond to code review feedback. --- .../src/System/IO/Win32FileStream.cs | 59 ++++++++----------- 1 file changed, 26 insertions(+), 33 deletions(-) diff --git a/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs b/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs index d53e274d9fa0..d8952b866f72 100644 --- a/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs +++ b/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs @@ -332,6 +332,10 @@ private unsafe void VerifyHandleIsSync() } } + private bool HasActiveBufferOperation + { + get { return _activeBufferOperation != null && !_activeBufferOperation.IsCompleted; } + } public override bool CanRead { @@ -532,6 +536,12 @@ private Task FlushWriteAsync(CancellationToken cancellationToken) Task flushTask = WriteInternalCoreAsync(_buffer, 0, _writePos, cancellationToken); _writePos = 0; + + // Update the active buffer operation + _activeBufferOperation = HasActiveBufferOperation ? + Task.WhenAll(_activeBufferOperation, flushTask) : + flushTask; + return flushTask; } @@ -562,16 +572,6 @@ private void FlushWrite(bool calledFromFinalizer) { writeTask.GetAwaiter().GetResult(); } - - // If not completing synchronously, ensure the async flush operation - // is tracked in case of incoming WriteAsync calls. - else - { - if (ActiveBufferOperation()) - _activeBufferOperation = Task.WhenAll(_activeBufferOperation, writeTask); - else - _activeBufferOperation = writeTask; - } } else { @@ -1276,11 +1276,6 @@ public override int ReadByte() return result; } - private bool ActiveBufferOperation() - { - return _activeBufferOperation != null && !_activeBufferOperation.IsCompleted; - } - [System.Security.SecuritySafeCritical] // auto-generated private Task WriteInternalAsync(byte[] array, int offset, int numBytes, CancellationToken cancellationToken) { @@ -1309,13 +1304,7 @@ private Task WriteInternalAsync(byte[] array, int offset, int numBytes, Cancella // If there's data in the write buffer, flush it before starting a new write if (_writePos > 0) - { - Task flushTask = FlushWriteAsync(cancellationToken); - if (ActiveBufferOperation()) - _activeBufferOperation = Task.WhenAll(_activeBufferOperation, flushTask); - else - _activeBufferOperation = flushTask; - } + FlushWriteAsync(cancellationToken); return WriteInternalCoreAsync(array, offset, numBytes, cancellationToken); } @@ -1342,7 +1331,7 @@ private Task WriteInternalAsync(byte[] array, int offset, int numBytes, Cancella // The incoming write can then be copied into the new buffer. // // 3. No active async buffer and numBytes too large for buffer - // If there's buffered data, issue a flush operation and then directly issue the + // If there's buffered data, issue a flush operation. Then, regardless, issue the // incoming write since it can't be buffered. // // 4. Active async buffer op and numBytes too large for buffer @@ -1355,7 +1344,7 @@ private Task WriteInternalAsync(byte[] array, int offset, int numBytes, Cancella // Case 1 - no active buffer op & fits in remaining size int remainingBuffer = _bufferSize - _writePos; - if(!ActiveBufferOperation() && numBytes <= remainingBuffer) + if (!ActiveBufferOperation() && numBytes <= remainingBuffer) { if (_buffer == null) _buffer = new byte[_bufferSize]; @@ -1365,7 +1354,7 @@ private Task WriteInternalAsync(byte[] array, int offset, int numBytes, Cancella } // Case 2 - active buffer op & fits in new buffer - else if (ActiveBufferOperation() && numBytes <= _bufferSize) + if (ActiveBufferOperation() && numBytes <= _bufferSize) { _buffer = new byte[_bufferSize]; @@ -1374,18 +1363,22 @@ private Task WriteInternalAsync(byte[] array, int offset, int numBytes, Cancella return Task.CompletedTask; } - // Case 3 & 4 - The incoming write can't be handled with a synchronous - // memcpy, so issue a write to disk, flushing if necessary - + // Case 3 - Incoming write can't be buffered and there's existing data in the buffer if (_writePos > 0) { Task flushTask = FlushWriteAsync(cancellationToken); - if (ActiveBufferOperation()) - _activeBufferOperation = Task.WhenAll(_activeBufferOperation, flushTask); - else - _activeBufferOperation = flushTask; - } + // If the task has already completed and was unsuccessful, avoid issuing the write. + if (flushTask.IsCanceled || flushTask.IsFaulted) return flushTask; + + // Return both the flush and write operations. It's not necessary that they complete in a + // specific order since the internal position is updated synchronously when crafting the + // tasks, but consumers will want to know if either fails so the health of the stream is known. + return Task.WhenAll(flushTask, WriteInternalCoreAsync(array, offset, numBytes, cancellationToken)); + } + + // Case 4 - Incoming write can't be buffered and there's no existing buffered data, so just + // issue the OS write directly. return WriteInternalCoreAsync(array, offset, numBytes, cancellationToken); } From 521196baf69cc700998e65b2c178a4efde38f5b3 Mon Sep 17 00:00:00 2001 From: tymlipari Date: Wed, 19 Aug 2015 21:02:19 -0700 Subject: [PATCH 7/7] Fix build break Change <= _bufferSize to < _bufferSize --- src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs b/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs index d8952b866f72..9554a5e4e552 100644 --- a/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs +++ b/src/System.IO.FileSystem/src/System/IO/Win32FileStream.cs @@ -1344,7 +1344,7 @@ private Task WriteInternalAsync(byte[] array, int offset, int numBytes, Cancella // Case 1 - no active buffer op & fits in remaining size int remainingBuffer = _bufferSize - _writePos; - if (!ActiveBufferOperation() && numBytes <= remainingBuffer) + if (!HasActiveBufferOperation && numBytes < remainingBuffer) { if (_buffer == null) _buffer = new byte[_bufferSize]; @@ -1354,7 +1354,7 @@ private Task WriteInternalAsync(byte[] array, int offset, int numBytes, Cancella } // Case 2 - active buffer op & fits in new buffer - if (ActiveBufferOperation() && numBytes <= _bufferSize) + if (HasActiveBufferOperation && numBytes < _bufferSize) { _buffer = new byte[_bufferSize];