public async Task WriteUsingSingleBuffer(bool asyncOperation, bool asyncHandle) { string filePath = GetTestFilePath(); int bufferSize = Environment.SystemPageSize; int fileSize = bufferSize * 10; byte[] content = RandomNumberGenerator.GetBytes(fileSize); using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None, GetFileOptions(asyncHandle))) using (SectorAlignedMemory <byte> buffer = SectorAlignedMemory <byte> .Allocate(bufferSize)) { int total = 0; while (total != fileSize) { int take = Math.Min(content.Length - total, bufferSize); content.AsSpan(total, take).CopyTo(buffer.GetSpan()); if (asyncOperation) { await RandomAccess.WriteAsync(handle, buffer.Memory, fileOffset : total); } else { RandomAccess.Write(handle, buffer.GetSpan(), fileOffset: total); } total += buffer.Memory.Length; } } Assert.Equal(content, File.ReadAllBytes(filePath)); }
public async Task ReadWriteAsyncUsingNonPageSizedMultipleBuffers() { string filePath = GetTestFilePath(); // The Windows scatter/gather APIs accept segments that are exactly one page long. // Combined with the FILE_FLAG_NO_BUFFERING's requirements, the segments must also // be aligned at page size boundaries and have a size of a multiple of the page size. // Using segments with a length of twice the page size adheres to the second requirement // but not the first. The RandomAccess implementation will see it and issue sequential // read/write syscalls per segment, instead of one scatter/gather syscall. // This test verifies that fallback behavior. int bufferSize = Environment.SystemPageSize * 2; int fileSize = bufferSize * 2; byte[] content = RandomNumberGenerator.GetBytes(fileSize); using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, FileOptions.Asynchronous | NoBuffering)) using (SectorAlignedMemory <byte> buffer = SectorAlignedMemory <byte> .Allocate(fileSize)) { Memory <byte> firstHalf = buffer.Memory.Slice(0, bufferSize); Memory <byte> secondHalf = buffer.Memory.Slice(bufferSize); content.AsSpan().CopyTo(buffer.GetSpan()); await RandomAccess.WriteAsync(handle, new ReadOnlyMemory <byte>[] { firstHalf, secondHalf }, 0); buffer.GetSpan().Clear(); await RandomAccess.ReadAsync(handle, new Memory <byte>[] { firstHalf, secondHalf }, 0); } Assert.Equal(content, await File.ReadAllBytesAsync(filePath)); }
public async Task ReadUsingSingleBuffer(bool asyncOperation, bool asyncHandle) { const int fileSize = 1_000_000; // 1 MB string filePath = GetTestFilePath(); byte[] expected = RandomNumberGenerator.GetBytes(fileSize); File.WriteAllBytes(filePath, expected); using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open, options: GetFileOptions(asyncHandle))) using (SectorAlignedMemory <byte> buffer = SectorAlignedMemory <byte> .Allocate(Environment.SystemPageSize)) { int current = 0; int total = 0; do { current = asyncOperation ? await RandomAccess.ReadAsync(handle, buffer.Memory, fileOffset : total) : RandomAccess.Read(handle, buffer.GetSpan(), fileOffset: total); Assert.True(expected.AsSpan(total, current).SequenceEqual(buffer.GetSpan().Slice(0, current))); total += current; }while (current != 0); Assert.Equal(fileSize, total); } }
public async Task ReadShouldReturnZeroForEndOfFile(bool asyncOperation, bool asyncHandle) { int fileSize = Environment.SystemPageSize + 1; // it MUST NOT be a multiple of it (https://github.com/dotnet/runtime/issues/62851) string filePath = GetTestFilePath(); byte[] expected = RandomNumberGenerator.GetBytes(fileSize); File.WriteAllBytes(filePath, expected); using FileStream fileStream = new (filePath, FileMode.Open, FileAccess.Read, FileShare.None, 0, GetFileOptions(asyncHandle)); using SectorAlignedMemory <byte> buffer = SectorAlignedMemory <byte> .Allocate(Environment.SystemPageSize); int current = 0; int total = 0; do { current = asyncOperation ? await fileStream.ReadAsync(buffer.Memory) : fileStream.Read(buffer.GetSpan()); Assert.True(expected.AsSpan(total, current).SequenceEqual(buffer.GetSpan().Slice(0, current))); total += current; }while (current != 0); Assert.Equal(fileSize, total); }
public async Task ReadWriteAsyncUsingMultipleBuffers(bool memoryPageSized) { string filePath = GetTestFilePath(); // We test with buffers both one and two memory pages long. In the former case, // the I/O operations will issue one scatter/gather API call, and in the latter // case they will issue multiple calls; one per buffer. The buffers must still // be aligned to comply with FILE_FLAG_NO_BUFFERING's requirements. int bufferSize = Environment.SystemPageSize * (memoryPageSized ? 1 : 2); int fileSize = bufferSize * 2; byte[] content = RandomNumberGenerator.GetBytes(fileSize); using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, FileOptions.Asynchronous | NoBuffering)) using (SectorAlignedMemory <byte> buffer = SectorAlignedMemory <byte> .Allocate(fileSize)) { Memory <byte> firstHalf = buffer.Memory.Slice(0, bufferSize); Memory <byte> secondHalf = buffer.Memory.Slice(bufferSize); content.AsSpan().CopyTo(buffer.GetSpan()); await RandomAccess.WriteAsync(handle, new ReadOnlyMemory <byte>[] { firstHalf, secondHalf }, 0); buffer.GetSpan().Clear(); long nRead = await RandomAccess.ReadAsync(handle, new Memory <byte>[] { firstHalf, secondHalf }, 0); Assert.Equal(buffer.GetSpan().Length, nRead); AssertExtensions.SequenceEqual(buffer.GetSpan(), content.AsSpan()); } }
public async Task WriteAsyncUsingMultipleBuffers(bool async) { string filePath = GetTestFilePath(); int bufferSize = Environment.SystemPageSize; int fileSize = bufferSize * 10; byte[] content = RandomNumberGenerator.GetBytes(fileSize); using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None, FileOptions.Asynchronous | NoBuffering)) using (SectorAlignedMemory <byte> buffer_1 = SectorAlignedMemory <byte> .Allocate(bufferSize)) using (SectorAlignedMemory <byte> buffer_2 = SectorAlignedMemory <byte> .Allocate(bufferSize)) { long total = 0; IReadOnlyList <ReadOnlyMemory <byte> > buffers = new ReadOnlyMemory <byte>[] { buffer_1.Memory, buffer_2.Memory, }; while (total != fileSize) { content.AsSpan((int)total, bufferSize).CopyTo(buffer_1.GetSpan()); content.AsSpan((int)total + bufferSize, bufferSize).CopyTo(buffer_2.GetSpan()); total += async ? await RandomAccess.WriteAsync(handle, buffers, fileOffset : total) : RandomAccess.Write(handle, buffers, fileOffset: total); } } Assert.Equal(content, File.ReadAllBytes(filePath)); }
public async Task ReadAsyncUsingSingleBuffer() { const int fileSize = 1_000_000; // 1 MB string filePath = GetTestFilePath(); byte[] expected = RandomNumberGenerator.GetBytes(fileSize); File.WriteAllBytes(filePath, expected); using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open, options: FileOptions.Asynchronous | NoBuffering)) using (SectorAlignedMemory <byte> buffer = SectorAlignedMemory <byte> .Allocate(Environment.SystemPageSize)) { int current = 0; int total = 0; // From https://docs.microsoft.com/en-us/windows/win32/fileio/file-buffering: // "File access sizes, including the optional file offset in the OVERLAPPED structure, // if specified, must be for a number of bytes that is an integer multiple of the volume sector size." // So if buffer and physical sector size is 4096 and the file size is 4097: // the read from offset=0 reads 4096 bytes // the read from offset=4096 reads 1 byte // the read from offset=4097 THROWS (Invalid argument, offset is not a multiple of sector size!) // That is why we stop at the first incomplete read (the next one would throw). // It's possible to get 0 if we are lucky and file size is a multiple of physical sector size. do { current = await RandomAccess.ReadAsync(handle, buffer.Memory, fileOffset : total); Assert.True(expected.AsSpan(total, current).SequenceEqual(buffer.GetSpan().Slice(0, current))); total += current; }while (current == buffer.Memory.Length); Assert.Equal(fileSize, total); } }
public async Task ReadAsyncUsingMultipleBuffers(bool asyncOperation, bool asyncHandle) { const int fileSize = 1_000_000; // 1 MB string filePath = GetTestFilePath(); byte[] expected = RandomNumberGenerator.GetBytes(fileSize); File.WriteAllBytes(filePath, expected); using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Open, options: GetFileOptions(asyncHandle))) using (SectorAlignedMemory <byte> buffer_1 = SectorAlignedMemory <byte> .Allocate(Environment.SystemPageSize)) using (SectorAlignedMemory <byte> buffer_2 = SectorAlignedMemory <byte> .Allocate(Environment.SystemPageSize)) { long current = 0; long total = 0; IReadOnlyList <Memory <byte> > buffers = new Memory <byte>[] { buffer_1.Memory, buffer_2.Memory, }; do { current = asyncOperation ? await RandomAccess.ReadAsync(handle, buffers, fileOffset : total) : RandomAccess.Read(handle, buffers, fileOffset: total); int takeFromFirst = Math.Min(buffer_1.Memory.Length, (int)current); Assert.True(expected.AsSpan((int)total, takeFromFirst).SequenceEqual(buffer_1.GetSpan().Slice(0, takeFromFirst))); int takeFromSecond = (int)current - takeFromFirst; Assert.True(expected.AsSpan((int)total + takeFromFirst, takeFromSecond).SequenceEqual(buffer_2.GetSpan().Slice(0, takeFromSecond))); total += current; } while (current == buffer_1.Memory.Length + buffer_2.Memory.Length); Assert.Equal(fileSize, total); } }