diff --git a/src/libraries/System.IO.FileSystem/tests/RandomAccess/Mixed.Windows.cs b/src/libraries/System.IO.FileSystem/tests/RandomAccess/Mixed.Windows.cs index dcc7d47924f61f..cee09fe2d62b33 100644 --- a/src/libraries/System.IO.FileSystem/tests/RandomAccess/Mixed.Windows.cs +++ b/src/libraries/System.IO.FileSystem/tests/RandomAccess/Mixed.Windows.cs @@ -62,8 +62,17 @@ static async Task Validate(SafeFileHandle handle, FileOptions options, bool[] sy { writeBuffer[0] = (byte)fileOffset; - Assert.Equal(writeBuffer.Length, syncWrite ? RandomAccess.Write(handle, writeBuffer, fileOffset) : await RandomAccess.WriteAsync(handle, writeBuffer, fileOffset)); + if (syncWrite) + { + RandomAccess.Write(handle, writeBuffer, fileOffset); + } + else + { + await RandomAccess.WriteAsync(handle, writeBuffer, fileOffset); + } + Assert.Equal(writeBuffer.Length, syncRead ? RandomAccess.Read(handle, readBuffer, fileOffset) : await RandomAccess.ReadAsync(handle, readBuffer, fileOffset)); + Assert.Equal(writeBuffer[0], readBuffer[0]); fileOffset += 1; @@ -116,8 +125,17 @@ static async Task Validate(SafeFileHandle handle, FileOptions options, bool[] sy writeBuffer_1[0] = (byte)fileOffset; writeBuffer_2[0] = (byte)(fileOffset+1); - Assert.Equal(writeBuffer_1.Length + writeBuffer_2.Length, syncWrite ? RandomAccess.Write(handle, writeBuffers, fileOffset) : await RandomAccess.WriteAsync(handle, writeBuffers, fileOffset)); + if (syncWrite) + { + RandomAccess.Write(handle, writeBuffers, fileOffset); + } + else + { + await RandomAccess.WriteAsync(handle, writeBuffers, fileOffset); + } + Assert.Equal(writeBuffer_1.Length + writeBuffer_2.Length, syncRead ? RandomAccess.Read(handle, readBuffers, fileOffset) : await RandomAccess.ReadAsync(handle, readBuffers, fileOffset)); + Assert.Equal(writeBuffer_1[0], readBuffer_1[0]); Assert.Equal(writeBuffer_2[0], readBuffer_2[0]); diff --git a/src/libraries/System.IO.FileSystem/tests/RandomAccess/NoBuffering.Windows.cs b/src/libraries/System.IO.FileSystem/tests/RandomAccess/NoBuffering.Windows.cs index 585335c4cdcb1e..d897cc7b8be25b 100644 --- a/src/libraries/System.IO.FileSystem/tests/RandomAccess/NoBuffering.Windows.cs +++ b/src/libraries/System.IO.FileSystem/tests/RandomAccess/NoBuffering.Windows.cs @@ -118,9 +118,16 @@ public async Task WriteUsingSingleBuffer(bool async) int take = Math.Min(content.Length - total, bufferSize); content.AsSpan(total, take).CopyTo(buffer.GetSpan()); - total += async - ? await RandomAccess.WriteAsync(handle, buffer.Memory, fileOffset: total) - : RandomAccess.Write(handle, buffer.GetSpan(), fileOffset: total); + if (async) + { + await RandomAccess.WriteAsync(handle, buffer.Memory, fileOffset: total); + } + else + { + RandomAccess.Write(handle, buffer.GetSpan(), fileOffset: total); + } + + total += buffer.Memory.Length; } } @@ -154,9 +161,16 @@ public async Task WriteAsyncUsingMultipleBuffers(bool async) 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); + if (async) + { + await RandomAccess.WriteAsync(handle, buffers, fileOffset: total); + } + else + { + RandomAccess.Write(handle, buffers, fileOffset: total); + } + + total += buffer_1.Memory.Length + buffer_2.Memory.Length; } } diff --git a/src/libraries/System.IO.FileSystem/tests/RandomAccess/Write.cs b/src/libraries/System.IO.FileSystem/tests/RandomAccess/Write.cs index 3cac633f53f769..8a687dc6012dec 100644 --- a/src/libraries/System.IO.FileSystem/tests/RandomAccess/Write.cs +++ b/src/libraries/System.IO.FileSystem/tests/RandomAccess/Write.cs @@ -10,7 +10,10 @@ namespace System.IO.Tests public class RandomAccess_Write : RandomAccess_Base { protected override int MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset) - => RandomAccess.Write(handle, bytes, fileOffset); + { + RandomAccess.Write(handle, bytes, fileOffset); + return bytes?.Length ?? 0; + } [Theory] [MemberData(nameof(GetSyncAsyncOptions))] @@ -24,11 +27,11 @@ public void ThrowsOnReadAccess(FileOptions options) [Theory] [MemberData(nameof(GetSyncAsyncOptions))] - public void WriteUsingEmptyBufferReturnsZero(FileOptions options) + public void WriteUsingEmptyBufferReturns(FileOptions options) { using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.Write, options: options)) { - Assert.Equal(0, RandomAccess.Write(handle, Array.Empty(), fileOffset: 0)); + RandomAccess.Write(handle, Array.Empty(), fileOffset: 0); } } @@ -41,7 +44,7 @@ public void CanUseStackAllocatedMemory(FileOptions options) using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Create, FileAccess.Write, options: options)) { - Assert.Equal(stackAllocated.Length, RandomAccess.Write(handle, stackAllocated, fileOffset: 0)); + RandomAccess.Write(handle, stackAllocated, fileOffset: 0); } Assert.Equal(stackAllocated.ToArray(), File.ReadAllBytes(filePath)); @@ -58,17 +61,14 @@ public void WritesBytesFromGivenBufferToGivenFileAtGivenOffset(FileOptions optio using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None, options)) { int total = 0; - int current = 0; while (total != fileSize) { Span buffer = content.AsSpan(total, Math.Min(content.Length - total, fileSize / 4)); - current = RandomAccess.Write(handle, buffer, fileOffset: total); - - Assert.InRange(current, 0, buffer.Length); + RandomAccess.Write(handle, buffer, fileOffset: total); - total += current; + total += buffer.Length; } } diff --git a/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteAsync.cs b/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteAsync.cs index b5c2399f9642b2..37003283982060 100644 --- a/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteAsync.cs @@ -11,9 +11,9 @@ namespace System.IO.Tests { [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser, "async file IO is not supported on browser")] - public class RandomAccess_WriteAsync : RandomAccess_Base> + public class RandomAccess_WriteAsync : RandomAccess_Base { - protected override ValueTask MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset) + protected override ValueTask MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset) => RandomAccess.WriteAsync(handle, bytes, fileOffset); [Theory] @@ -44,11 +44,11 @@ public async Task ThrowsOnReadAccess(FileOptions options) [Theory] [MemberData(nameof(GetSyncAsyncOptions))] - public async Task WriteUsingEmptyBufferReturnsZeroAsync(FileOptions options) + public async Task WriteUsingEmptyBufferReturnsAsync(FileOptions options) { using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.Write, options: options)) { - Assert.Equal(0, await RandomAccess.WriteAsync(handle, Array.Empty(), fileOffset: 0)); + await RandomAccess.WriteAsync(handle, Array.Empty(), fileOffset: 0); } } @@ -63,17 +63,14 @@ public async Task WritesBytesFromGivenBufferToGivenFileAtGivenOffsetAsync(FileOp using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None, options)) { int total = 0; - int current = 0; while (total != fileSize) { Memory buffer = content.AsMemory(total, Math.Min(content.Length - total, fileSize / 4)); - current = await RandomAccess.WriteAsync(handle, buffer, fileOffset: total); + await RandomAccess.WriteAsync(handle, buffer, fileOffset: total); - Assert.InRange(current, 0, buffer.Length); - - total += current; + total += buffer.Length; } } diff --git a/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteGather.cs b/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteGather.cs index 5af52f0c61a178..0f28b7c9c68b65 100644 --- a/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteGather.cs +++ b/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteGather.cs @@ -12,7 +12,10 @@ namespace System.IO.Tests public class RandomAccess_WriteGather : RandomAccess_Base { protected override long MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset) - => RandomAccess.Write(handle, new ReadOnlyMemory[] { bytes }, fileOffset); + { + RandomAccess.Write(handle, new ReadOnlyMemory[] { bytes }, fileOffset); + return bytes?.Length ?? 0; + } [Theory] [MemberData(nameof(GetSyncAsyncOptions))] @@ -36,11 +39,11 @@ public void ThrowsOnReadAccess(FileOptions options) [Theory] [MemberData(nameof(GetSyncAsyncOptions))] - public void WriteUsingEmptyBufferReturnsZero(FileOptions options) + public void WriteUsingEmptyBufferReturns(FileOptions options) { using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.Write, options: options)) { - Assert.Equal(0, RandomAccess.Write(handle, new ReadOnlyMemory[] { Array.Empty() }, fileOffset: 0)); + RandomAccess.Write(handle, new ReadOnlyMemory[] { Array.Empty() }, fileOffset: 0); } } @@ -55,7 +58,6 @@ public void WritesBytesFromGivenBuffersToGivenFileAtGivenOffset(FileOptions opti using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None, options)) { long total = 0; - long current = 0; while (total != fileSize) { @@ -63,7 +65,7 @@ public void WritesBytesFromGivenBuffersToGivenFileAtGivenOffset(FileOptions opti Memory buffer_1 = content.AsMemory((int)total, firstBufferLength); Memory buffer_2 = content.AsMemory((int)total + firstBufferLength); - current = RandomAccess.Write( + RandomAccess.Write( handle, new ReadOnlyMemory[] { @@ -73,9 +75,7 @@ public void WritesBytesFromGivenBuffersToGivenFileAtGivenOffset(FileOptions opti }, fileOffset: total); - Assert.InRange(current, 0, buffer_1.Length + buffer_2.Length); - - total += current; + total += buffer_1.Length + buffer_2.Length; } } @@ -94,7 +94,7 @@ public void DuplicatedBufferDuplicatesContent(FileOptions options) using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Create, FileAccess.Write, options: options)) { - Assert.Equal(repeatCount, RandomAccess.Write(handle, buffers, fileOffset: 0)); + RandomAccess.Write(handle, buffers, fileOffset: 0); } byte[] actualContent = File.ReadAllBytes(filePath); diff --git a/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteGatherAsync.cs b/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteGatherAsync.cs index d6bd235efb745d..f89cfd3b4fc623 100644 --- a/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteGatherAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/RandomAccess/WriteGatherAsync.cs @@ -13,9 +13,9 @@ namespace System.IO.Tests { [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] [SkipOnPlatform(TestPlatforms.Browser, "async file IO is not supported on browser")] - public class RandomAccess_WriteGatherAsync : RandomAccess_Base> + public class RandomAccess_WriteGatherAsync : RandomAccess_Base { - protected override ValueTask MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset) + protected override ValueTask MethodUnderTest(SafeFileHandle handle, byte[] bytes, long fileOffset) => RandomAccess.WriteAsync(handle, new ReadOnlyMemory[] { bytes }, fileOffset); [Theory] @@ -56,11 +56,11 @@ public async Task ThrowsOnReadAccess(FileOptions options) [Theory] [MemberData(nameof(GetSyncAsyncOptions))] - public async Task WriteUsingEmptyBufferReturnsZeroAsync(FileOptions options) + public async Task WriteUsingEmptyBufferReturnsAsync(FileOptions options) { using (SafeFileHandle handle = File.OpenHandle(GetTestFilePath(), FileMode.Create, FileAccess.Write, options: options)) { - Assert.Equal(0, await RandomAccess.WriteAsync(handle, new ReadOnlyMemory[] { Array.Empty() }, fileOffset: 0)); + await RandomAccess.WriteAsync(handle, new ReadOnlyMemory[] { Array.Empty() }, fileOffset: 0); } } @@ -75,7 +75,6 @@ public async Task WritesBytesFromGivenBufferToGivenFileAtGivenOffsetAsync(FileOp using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.CreateNew, FileAccess.Write, FileShare.None, options)) { long total = 0; - long current = 0; while (total != fileSize) { @@ -83,7 +82,7 @@ public async Task WritesBytesFromGivenBufferToGivenFileAtGivenOffsetAsync(FileOp Memory buffer_1 = content.AsMemory((int)total, firstBufferLength); Memory buffer_2 = content.AsMemory((int)total + firstBufferLength); - current = await RandomAccess.WriteAsync( + await RandomAccess.WriteAsync( handle, new ReadOnlyMemory[] { @@ -93,9 +92,7 @@ public async Task WritesBytesFromGivenBufferToGivenFileAtGivenOffsetAsync(FileOp }, fileOffset: total); - Assert.InRange(current, 0, buffer_1.Length + buffer_2.Length); - - total += current; + total += buffer_1.Length + buffer_2.Length; } } @@ -114,7 +111,7 @@ public async Task DuplicatedBufferDuplicatesContentAsync(FileOptions options) using (SafeFileHandle handle = File.OpenHandle(filePath, FileMode.Create, FileAccess.Write, options: options)) { - Assert.Equal(repeatCount, await RandomAccess.WriteAsync(handle, buffers, fileOffset: 0)); + await RandomAccess.WriteAsync(handle, buffers, fileOffset: 0); } byte[] actualContent = File.ReadAllBytes(filePath); diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.OverlappedValueTaskSource.Windows.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.OverlappedValueTaskSource.Windows.cs index cd4e18e7486dbe..367184e9a455ea 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.OverlappedValueTaskSource.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.OverlappedValueTaskSource.Windows.cs @@ -86,10 +86,8 @@ internal static Exception GetIOError(int errorCode, string? path) public ValueTaskSourceStatus GetStatus(short token) => _source.GetStatus(token); public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => _source.OnCompleted(continuation, state, token, flags); - void IValueTaskSource.GetResult(short token) => GetResultAndRelease(token); - int IValueTaskSource.GetResult(short token) => GetResultAndRelease(token); - - private int GetResultAndRelease(short token) + void IValueTaskSource.GetResult(short token) => GetResult(token); + public int GetResult(short token) { try { diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.ThreadPoolValueTaskSource.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.ThreadPoolValueTaskSource.cs index 49638af80f34fe..abb6238b521395 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.ThreadPoolValueTaskSource.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.ThreadPoolValueTaskSource.cs @@ -54,7 +54,15 @@ private void ValidateInvariants() Debug.Assert(op == Operation.None, $"An operation was queued before the previous {op}'s completion."); } - private long GetResultAndRelease(short token) + public ValueTaskSourceStatus GetStatus(short token) => + _source.GetStatus(token); + + public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => + _source.OnCompleted(continuation, state, token, flags); + + void IValueTaskSource.GetResult(short token) => GetResult(token); + int IValueTaskSource.GetResult(short token) => (int)GetResult(token); + public long GetResult(short token) { try { @@ -67,13 +75,6 @@ private long GetResultAndRelease(short token) } } - public ValueTaskSourceStatus GetStatus(short token) => _source.GetStatus(token); - public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) => - _source.OnCompleted(continuation, state, token, flags); - int IValueTaskSource.GetResult(short token) => (int) GetResultAndRelease(token); - long IValueTaskSource.GetResult(short token) => GetResultAndRelease(token); - void IValueTaskSource.GetResult(short token) => GetResultAndRelease(token); - private void ExecuteInternal() { Debug.Assert(_operation >= Operation.Read && _operation <= Operation.WriteGather); @@ -96,7 +97,7 @@ private void ExecuteInternal() result = RandomAccess.ReadAtOffset(_fileHandle, writableSingleSegment.Span, _fileOffset); break; case Operation.Write: - result = RandomAccess.WriteAtOffset(_fileHandle, _singleSegment.Span, _fileOffset); + RandomAccess.WriteAtOffset(_fileHandle, _singleSegment.Span, _fileOffset); break; case Operation.ReadScatter: Debug.Assert(_readScatterBuffers != null); @@ -104,7 +105,7 @@ private void ExecuteInternal() break; case Operation.WriteGather: Debug.Assert(_writeGatherBuffers != null); - result = RandomAccess.WriteGatherAtOffset(_fileHandle, _writeGatherBuffers, _fileOffset); + RandomAccess.WriteGatherAtOffset(_fileHandle, _writeGatherBuffers, _fileOffset); break; } } @@ -164,7 +165,7 @@ public ValueTask QueueRead(Memory buffer, long fileOffset, Cancellati return new ValueTask(this, _source.Version); } - public ValueTask QueueWrite(ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken) + public ValueTask QueueWrite(ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken) { ValidateInvariants(); @@ -174,7 +175,7 @@ public ValueTask QueueWrite(ReadOnlyMemory buffer, long fileOffset, C _cancellationToken = cancellationToken; QueueToThreadPool(); - return new ValueTask(this, _source.Version); + return new ValueTask(this, _source.Version); } public ValueTask QueueReadScatter(IReadOnlyList> buffers, long fileOffset, CancellationToken cancellationToken) @@ -190,7 +191,7 @@ public ValueTask QueueReadScatter(IReadOnlyList> buffers, lon return new ValueTask(this, _source.Version); } - public ValueTask QueueWriteGather(IReadOnlyList> buffers, long fileOffset, CancellationToken cancellationToken) + public ValueTask QueueWriteGather(IReadOnlyList> buffers, long fileOffset, CancellationToken cancellationToken) { ValidateInvariants(); @@ -200,7 +201,7 @@ public ValueTask QueueWriteGather(IReadOnlyList> buff _cancellationToken = cancellationToken; QueueToThreadPool(); - return new ValueTask(this, _source.Version); + return new ValueTask(this, _source.Version); } private enum Operation : byte diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Unix.cs index 41de56fcbea7ed..b82627112f35a5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Unix.cs @@ -3,7 +3,9 @@ using System.Buffers; using System.Collections.Generic; +using System.Diagnostics; using System.IO.Strategies; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -79,41 +81,116 @@ private static ValueTask ReadScatterAtOffsetAsync(SafeFileHandle handle, I long fileOffset, CancellationToken cancellationToken) => ScheduleSyncReadScatterAtOffsetAsync(handle, buffers, fileOffset, cancellationToken); - internal static unsafe int WriteAtOffset(SafeFileHandle handle, ReadOnlySpan buffer, long fileOffset) + internal static unsafe void WriteAtOffset(SafeFileHandle handle, ReadOnlySpan buffer, long fileOffset) { - fixed (byte* bufPtr = &MemoryMarshal.GetReference(buffer)) + while (!buffer.IsEmpty) { - // The Windows implementation uses WriteFile, which ignores the offset if the handle - // isn't seekable. We do the same manually with PWrite vs Write, in order to enable - // the function to be used by FileStream for all the same situations. - int result = handle.CanSeek ? - Interop.Sys.PWrite(handle, bufPtr, buffer.Length, fileOffset) : - Interop.Sys.Write(handle, bufPtr, buffer.Length); - FileStreamHelpers.CheckFileCall(result, handle.Path); - return result; + fixed (byte* bufPtr = &MemoryMarshal.GetReference(buffer)) + { + // The Windows implementation uses WriteFile, which ignores the offset if the handle + // isn't seekable. We do the same manually with PWrite vs Write, in order to enable + // the function to be used by FileStream for all the same situations. + int bytesWritten = handle.CanSeek ? + Interop.Sys.PWrite(handle, bufPtr, GetNumberOfBytesToWrite(buffer.Length), fileOffset) : + Interop.Sys.Write(handle, bufPtr, GetNumberOfBytesToWrite(buffer.Length)); + + FileStreamHelpers.CheckFileCall(bytesWritten, handle.Path); + if (bytesWritten == buffer.Length) + { + break; + } + + // The write completed successfully but for fewer bytes than requested. + // We need to try again for the remainder. + buffer = buffer.Slice(bytesWritten); + fileOffset += bytesWritten; + } } } - internal static unsafe long WriteGatherAtOffset(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetNumberOfBytesToWrite(int byteCount) { - MemoryHandle[] handles = new MemoryHandle[buffers.Count]; - Span vectors = buffers.Count <= IovStackThreshold ? stackalloc Interop.Sys.IOVector[IovStackThreshold] : new Interop.Sys.IOVector[buffers.Count ]; +#if DEBUG + // In debug only, to assist with testing, simulate writing fewer than the requested number of bytes. + if (byteCount > 1 && // ensure we don't turn the read into a zero-byte read + byteCount < 512) // avoid on larger buffers that might have a length used to meet an alignment requirement + { + byteCount /= 2; + } +#endif + return byteCount; + } - long result; - try + internal static unsafe void WriteGatherAtOffset(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset) + { + int buffersCount = buffers.Count; + if (buffersCount == 0) { - int buffersCount = buffers.Count; - for (int i = 0; i < buffersCount; i++) - { - ReadOnlyMemory buffer = buffers[i]; - MemoryHandle memoryHandle = buffer.Pin(); - vectors[i] = new Interop.Sys.IOVector { Base = (byte*)memoryHandle.Pointer, Count = (UIntPtr)buffer.Length }; - handles[i] = memoryHandle; - } + return; + } - fixed (Interop.Sys.IOVector* pinnedVectors = &MemoryMarshal.GetReference(vectors)) + var handles = new MemoryHandle[buffersCount]; + Span vectors = buffersCount <= IovStackThreshold ? + stackalloc Interop.Sys.IOVector[IovStackThreshold] : + new Interop.Sys.IOVector[buffersCount]; + + try + { + int buffersOffset = 0, firstBufferOffset = 0; + while (true) { - result = Interop.Sys.PWriteV(handle, pinnedVectors, buffers.Count, fileOffset); + long totalBytesToWrite = 0; + + for (int i = buffersOffset; i < buffersCount; i++) + { + ReadOnlyMemory buffer = buffers[i]; + totalBytesToWrite += buffer.Length; + + MemoryHandle memoryHandle = buffer.Pin(); + vectors[i] = new Interop.Sys.IOVector { Base = firstBufferOffset + (byte*)memoryHandle.Pointer, Count = (UIntPtr)buffer.Length }; + handles[i] = memoryHandle; + + firstBufferOffset = 0; + } + + if (totalBytesToWrite == 0) + { + break; + } + + long bytesWritten; + fixed (Interop.Sys.IOVector* pinnedVectors = &MemoryMarshal.GetReference(vectors)) + { + bytesWritten = Interop.Sys.PWriteV(handle, pinnedVectors, buffersCount, fileOffset); + } + + FileStreamHelpers.CheckFileCall(bytesWritten, handle.Path); + if (bytesWritten == totalBytesToWrite) + { + break; + } + + // The write completed successfully but for fewer bytes than requested. + // We need to try again for the remainder. + for (int i = 0; i < buffersCount; i++) + { + int n = buffers[i].Length; + if (n <= bytesWritten) + { + buffersOffset++; + bytesWritten -= n; + if (bytesWritten == 0) + { + break; + } + } + else + { + firstBufferOffset = (int)(bytesWritten - n); + break; + } + } } } finally @@ -123,14 +200,12 @@ internal static unsafe long WriteGatherAtOffset(SafeFileHandle handle, IReadOnly memoryHandle.Dispose(); } } - - return FileStreamHelpers.CheckFileCall(result, handle.Path); } - internal static ValueTask WriteAtOffsetAsync(SafeFileHandle handle, ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken) + internal static ValueTask WriteAtOffsetAsync(SafeFileHandle handle, ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken) => ScheduleSyncWriteAtOffsetAsync(handle, buffer, fileOffset, cancellationToken); - private static ValueTask WriteGatherAtOffsetAsync(SafeFileHandle handle, IReadOnlyList> buffers, + private static ValueTask WriteGatherAtOffsetAsync(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset, CancellationToken cancellationToken) => ScheduleSyncWriteGatherAtOffsetAsync(handle, buffers, fileOffset, cancellationToken); } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Windows.cs index 63639cc8503a75..8711ce23e9d2c6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Windows.cs @@ -120,11 +120,17 @@ private static unsafe int ReadSyncUsingAsyncHandle(SafeFileHandle handle, Span buffer, long fileOffset) + internal static unsafe void WriteAtOffset(SafeFileHandle handle, ReadOnlySpan buffer, long fileOffset) { + if (buffer.IsEmpty) + { + return; + } + if (handle.IsAsync) { - return WriteSyncUsingAsyncHandle(handle, buffer, fileOffset); + WriteSyncUsingAsyncHandle(handle, buffer, fileOffset); + return; } NativeOverlapped overlapped = GetNativeOverlappedForSyncHandle(handle, fileOffset); @@ -132,22 +138,28 @@ internal static unsafe int WriteAtOffset(SafeFileHandle handle, ReadOnlySpan buffer, long fileOffset) + private static unsafe void WriteSyncUsingAsyncHandle(SafeFileHandle handle, ReadOnlySpan buffer, long fileOffset) { + if (buffer.IsEmpty) + { + return; + } + handle.EnsureThreadPoolBindingInitialized(); CallbackResetEvent resetEvent = new CallbackResetEvent(handle.ThreadPoolBinding!); @@ -173,8 +185,8 @@ private static unsafe int WriteSyncUsingAsyncHandle(SafeFileHandle handle, ReadO int result = 0; if (Interop.Kernel32.GetOverlappedResult(handle, overlapped, ref result, bWait: false)) { - Debug.Assert(result >= 0 && result <= buffer.Length, $"GetOverlappedResult returned {result} for {buffer.Length} bytes request"); - return result; + Debug.Assert(result == buffer.Length, $"GetOverlappedResult returned {result} for {buffer.Length} bytes request"); + return; } errorCode = FileStreamHelpers.GetLastWin32ErrorAndDisposeHandleIfInvalid(handle); @@ -184,7 +196,7 @@ private static unsafe int WriteSyncUsingAsyncHandle(SafeFileHandle handle, ReadO { case Interop.Errors.ERROR_NO_DATA: // For pipes, ERROR_NO_DATA is not an error, but the pipe is closing. - return 0; + return; case Interop.Errors.ERROR_INVALID_PARAMETER: // ERROR_INVALID_PARAMETER may be returned for writes @@ -209,17 +221,28 @@ private static unsafe int WriteSyncUsingAsyncHandle(SafeFileHandle handle, ReadO } internal static ValueTask ReadAtOffsetAsync(SafeFileHandle handle, Memory buffer, long fileOffset, CancellationToken cancellationToken) - => handle.IsAsync - ? Map(QueueAsyncReadFile(handle, buffer, fileOffset, cancellationToken)) - : ScheduleSyncReadAtOffsetAsync(handle, buffer, fileOffset, cancellationToken); + { + if (handle.IsAsync) + { + (SafeFileHandle.OverlappedValueTaskSource? vts, int errorCode) = QueueAsyncReadFile(handle, buffer, fileOffset, cancellationToken); + + if (vts is not null) + { + return new ValueTask(vts, vts.Version); + } + + if (errorCode == 0) + { + return ValueTask.FromResult(0); + } + + return ValueTask.FromException(Win32Marshal.GetExceptionForWin32Error(errorCode)); + } - private static ValueTask Map((SafeFileHandle.OverlappedValueTaskSource? vts, int errorCode) tuple) - => tuple.vts != null - ? new ValueTask(tuple.vts, tuple.vts.Version) - : tuple.errorCode == 0 ? ValueTask.FromResult(0) : ValueTask.FromException(Win32Marshal.GetExceptionForWin32Error(tuple.errorCode)); + return ScheduleSyncReadAtOffsetAsync(handle, buffer, fileOffset, cancellationToken); + } - internal static unsafe (SafeFileHandle.OverlappedValueTaskSource? vts, int errorCode) QueueAsyncReadFile( - SafeFileHandle handle, Memory buffer, long fileOffset, CancellationToken cancellationToken) + internal static unsafe (SafeFileHandle.OverlappedValueTaskSource? vts, int errorCode) QueueAsyncReadFile(SafeFileHandle handle, Memory buffer, long fileOffset, CancellationToken cancellationToken) { handle.EnsureThreadPoolBindingInitialized(); @@ -269,13 +292,29 @@ internal static unsafe (SafeFileHandle.OverlappedValueTaskSource? vts, int error return (vts, -1); } - internal static ValueTask WriteAtOffsetAsync(SafeFileHandle handle, ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken) - => handle.IsAsync - ? Map(QueueAsyncWriteFile(handle, buffer, fileOffset, cancellationToken)) - : ScheduleSyncWriteAtOffsetAsync(handle, buffer, fileOffset, cancellationToken); + internal static ValueTask WriteAtOffsetAsync(SafeFileHandle handle, ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken) + { + if (handle.IsAsync) + { + (SafeFileHandle.OverlappedValueTaskSource? vts, int errorCode) = QueueAsyncWriteFile(handle, buffer, fileOffset, cancellationToken); - internal static unsafe (SafeFileHandle.OverlappedValueTaskSource? vts, int errorCode) QueueAsyncWriteFile( - SafeFileHandle handle, ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken) + if (vts is not null) + { + return new ValueTask(vts, vts.Version); + } + + if (errorCode == 0) + { + return ValueTask.CompletedTask; + } + + return ValueTask.FromException(Win32Marshal.GetExceptionForWin32Error(errorCode)); + } + + return ScheduleSyncWriteAtOffsetAsync(handle, buffer, fileOffset, cancellationToken); + } + + internal static unsafe (SafeFileHandle.OverlappedValueTaskSource? vts, int errorCode) QueueAsyncWriteFile(SafeFileHandle handle, ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken) { handle.EnsureThreadPoolBindingInitialized(); @@ -323,7 +362,8 @@ internal static long ReadScatterAtOffset(SafeFileHandle handle, IReadOnlyList span = buffers[i].Span; int read = ReadAtOffset(handle, span, fileOffset + total); @@ -340,26 +380,17 @@ internal static long ReadScatterAtOffset(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset) + internal static void WriteGatherAtOffset(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset) { - long total = 0; - // WriteFileGather does not support sync handles, so we just call WriteFile in a loop - for (int i = 0; i < buffers.Count; i++) + int bytesWritten = 0; + int buffersCount = buffers.Count; + for (int i = 0; i < buffersCount; i++) { ReadOnlySpan span = buffers[i].Span; - int written = WriteAtOffset(handle, span, fileOffset + total); - total += written; - - // We stop on the first incomplete write. - // Most probably the disk became full and the next write is going to throw. - if (written != span.Length) - { - break; - } + WriteAtOffset(handle, span, fileOffset + bytesWritten); + bytesWritten += span.Length; } - - return total; } private static ValueTask ReadScatterAtOffsetAsync(SafeFileHandle handle, IReadOnlyList> buffers, @@ -373,7 +404,8 @@ private static ValueTask ReadScatterAtOffsetAsync(SafeFileHandle handle, I if (CanUseScatterGatherWindowsAPIs(handle)) { long totalBytes = 0; - for (int i = 0; i < buffers.Count; i++) + int buffersCount = buffers.Count; + for (int i = 0; i < buffersCount; i++) { totalBytes += buffers[i].Length; } @@ -392,25 +424,25 @@ private static ValueTask ReadScatterAtOffsetAsync(SafeFileHandle handle, I private static bool CanUseScatterGatherWindowsAPIs(SafeFileHandle handle) => handle.IsAsync && ((handle.GetFileOptions() & SafeFileHandle.NoBuffering) != 0); - private static async ValueTask ReadScatterAtOffsetSingleSyscallAsync(SafeFileHandle handle, - IReadOnlyList> buffers, long fileOffset, int totalBytes, CancellationToken cancellationToken) + private static async ValueTask ReadScatterAtOffsetSingleSyscallAsync(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset, int totalBytes, CancellationToken cancellationToken) { - if (buffers.Count == 1) + int buffersCount = buffers.Count; + if (buffersCount == 1) { // we have to await it because we can't cast a VT to VT return await ReadAtOffsetAsync(handle, buffers[0], fileOffset, cancellationToken).ConfigureAwait(false); } // "The array must contain enough elements to store nNumberOfBytesToWrite bytes of data, and one element for the terminating NULL. " - long[] fileSegments = new long[buffers.Count + 1]; - fileSegments[buffers.Count] = 0; + long[] fileSegments = new long[buffersCount + 1]; + fileSegments[buffersCount] = 0; - MemoryHandle[] memoryHandles = new MemoryHandle[buffers.Count]; + MemoryHandle[] memoryHandles = new MemoryHandle[buffersCount]; MemoryHandle pinnedSegments = fileSegments.AsMemory().Pin(); try { - for (int i = 0; i < buffers.Count; i++) + for (int i = 0; i < buffersCount; i++) { Memory buffer = buffers[i]; MemoryHandle memoryHandle = buffer.Pin(); @@ -434,8 +466,7 @@ private static async ValueTask ReadScatterAtOffsetSingleSyscallAsync(SafeF } } - private static unsafe ValueTask ReadFileScatterAsync(SafeFileHandle handle, MemoryHandle pinnedSegments, - int bytesToRead, long fileOffset, CancellationToken cancellationToken) + private static unsafe ValueTask ReadFileScatterAsync(SafeFileHandle handle, MemoryHandle pinnedSegments, int bytesToRead, long fileOffset, CancellationToken cancellationToken) { handle.EnsureThreadPoolBindingInitialized(); @@ -484,12 +515,12 @@ private static unsafe ValueTask ReadFileScatterAsync(SafeFileHandle handle, return new ValueTask(vts, vts.Version); } - private static async ValueTask ReadScatterAtOffsetMultipleSyscallsAsync(SafeFileHandle handle, IReadOnlyList> buffers, - long fileOffset, CancellationToken cancellationToken) + private static async ValueTask ReadScatterAtOffsetMultipleSyscallsAsync(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset, CancellationToken cancellationToken) { long total = 0; - for (int i = 0; i < buffers.Count; i++) + int buffersCount = buffers.Count; + for (int i = 0; i < buffersCount; i++) { Memory buffer = buffers[i]; int read = await ReadAtOffsetAsync(handle, buffer, fileOffset + total, cancellationToken).ConfigureAwait(false); @@ -504,8 +535,7 @@ private static async ValueTask ReadScatterAtOffsetMultipleSyscallsAsync(Sa return total; } - private static ValueTask WriteGatherAtOffsetAsync(SafeFileHandle handle, IReadOnlyList> buffers, - long fileOffset, CancellationToken cancellationToken) + private static ValueTask WriteGatherAtOffsetAsync(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset, CancellationToken cancellationToken) { if (!handle.IsAsync) { @@ -529,69 +559,65 @@ private static ValueTask WriteGatherAtOffsetAsync(SafeFileHandle handle, I return WriteGatherAtOffsetMultipleSyscallsAsync(handle, buffers, fileOffset, cancellationToken); } - private static async ValueTask WriteGatherAtOffsetMultipleSyscallsAsync(SafeFileHandle handle, IReadOnlyList> buffers, - long fileOffset, CancellationToken cancellationToken) + private static async ValueTask WriteGatherAtOffsetMultipleSyscallsAsync(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset, CancellationToken cancellationToken) { - long total = 0; - - for (int i = 0; i < buffers.Count; i++) + long bytesWritten = 0; + int buffersCount = buffers.Count; + for (int i = 0; i < buffersCount; i++) { - ReadOnlyMemory buffer = buffers[i]; - int written = await WriteAtOffsetAsync(handle, buffer, fileOffset + total, cancellationToken).ConfigureAwait(false); - total += written; - - if (written != buffer.Length) - { - break; - } + ReadOnlyMemory rom = buffers[i]; + await WriteAtOffsetAsync(handle, rom, fileOffset + bytesWritten, cancellationToken).ConfigureAwait(false); + bytesWritten += rom.Length; } - - return total; } - private static async ValueTask WriteGatherAtOffsetSingleSyscallAsync(SafeFileHandle handle, IReadOnlyList> buffers, - long fileOffset, int totalBytes, CancellationToken cancellationToken) + private static ValueTask WriteGatherAtOffsetSingleSyscallAsync(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset, int totalBytes, CancellationToken cancellationToken) { if (buffers.Count == 1) { - return await WriteAtOffsetAsync(handle, buffers[0], fileOffset, cancellationToken).ConfigureAwait(false); + return WriteAtOffsetAsync(handle, buffers[0], fileOffset, cancellationToken); } - // "The array must contain enough elements to store nNumberOfBytesToWrite bytes of data, and one element for the terminating NULL. " - long[] fileSegments = new long[buffers.Count + 1]; - fileSegments[buffers.Count] = 0; - - MemoryHandle[] memoryHandles = new MemoryHandle[buffers.Count]; - MemoryHandle pinnedSegments = fileSegments.AsMemory().Pin(); + return Core(handle, buffers, fileOffset, totalBytes, cancellationToken); - try + static async ValueTask Core(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset, int totalBytes, CancellationToken cancellationToken) { - for (int i = 0; i < buffers.Count; i++) - { - ReadOnlyMemory buffer = buffers[i]; - MemoryHandle memoryHandle = buffer.Pin(); - memoryHandles[i] = memoryHandle; + // "The array must contain enough elements to store nNumberOfBytesToWrite bytes of data, and one element for the terminating NULL. " + int buffersCount = buffers.Count; + long[] fileSegments = new long[buffersCount + 1]; + fileSegments[buffersCount] = 0; - unsafe // awaits can't be in an unsafe context + MemoryHandle[] memoryHandles = new MemoryHandle[buffersCount]; + MemoryHandle pinnedSegments = fileSegments.AsMemory().Pin(); + + try + { + for (int i = 0; i < buffersCount; i++) { - fileSegments[i] = new IntPtr(memoryHandle.Pointer).ToInt64(); + ReadOnlyMemory buffer = buffers[i]; + MemoryHandle memoryHandle = buffer.Pin(); + memoryHandles[i] = memoryHandle; + + unsafe // awaits can't be in an unsafe context + { + fileSegments[i] = new IntPtr(memoryHandle.Pointer).ToInt64(); + } } - } - return await WriteFileGatherAsync(handle, pinnedSegments, totalBytes, fileOffset, cancellationToken).ConfigureAwait(false); - } - finally - { - foreach (MemoryHandle memoryHandle in memoryHandles) + await WriteFileGatherAsync(handle, pinnedSegments, totalBytes, fileOffset, cancellationToken).ConfigureAwait(false); + } + finally { - memoryHandle.Dispose(); + foreach (MemoryHandle memoryHandle in memoryHandles) + { + memoryHandle.Dispose(); + } + pinnedSegments.Dispose(); } - pinnedSegments.Dispose(); } } - private static unsafe ValueTask WriteFileGatherAsync(SafeFileHandle handle, MemoryHandle pinnedSegments, - int bytesToWrite, long fileOffset, CancellationToken cancellationToken) + private static unsafe ValueTask WriteFileGatherAsync(SafeFileHandle handle, MemoryHandle pinnedSegments, int bytesToWrite, long fileOffset, CancellationToken cancellationToken) { handle.EnsureThreadPoolBindingInitialized(); @@ -617,8 +643,8 @@ private static unsafe ValueTask WriteFileGatherAsync(SafeFileHandle handle, // Error. Callback will not be invoked. vts.Dispose(); return errorCode == Interop.Errors.ERROR_NO_DATA // EOF on a pipe. IO callback will not be called. - ? ValueTask.FromResult(0) - : ValueTask.FromException(SafeFileHandle.OverlappedValueTaskSource.GetIOError(errorCode, path: null)); + ? ValueTask.CompletedTask + : ValueTask.FromException(SafeFileHandle.OverlappedValueTaskSource.GetIOError(errorCode, path: null)); } } } @@ -630,7 +656,7 @@ private static unsafe ValueTask WriteFileGatherAsync(SafeFileHandle handle, // Completion handled by callback. vts.FinishedScheduling(); - return new ValueTask(vts, vts.Version); + return new ValueTask(vts, vts.Version); } private static unsafe NativeOverlapped* GetNativeOverlappedForAsyncHandle(ThreadPoolBoundHandle threadPoolBinding, long fileOffset, CallbackResetEvent resetEvent) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.cs b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.cs index 4d768cad53edae..ad190fe2f75e6d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Win32.SafeHandles; @@ -134,7 +135,6 @@ public static ValueTask ReadAsync(SafeFileHandle handle, IReadOnlyListThe file handle. /// A region of memory. This method copies the contents of this region to the file. /// The file position to write to. - /// The total number of bytes written into the file. This can be less than the number of bytes provided in the buffer and it's not an error. /// is . /// is invalid. /// The file is closed. @@ -143,11 +143,11 @@ public static ValueTask ReadAsync(SafeFileHandle handle, IReadOnlyList was not opened for writing. /// An I/O error occurred. /// Position of the file is not advanced. - public static int Write(SafeFileHandle handle, ReadOnlySpan buffer, long fileOffset) + public static void Write(SafeFileHandle handle, ReadOnlySpan buffer, long fileOffset) { ValidateInput(handle, fileOffset); - return WriteAtOffset(handle, buffer, fileOffset); + WriteAtOffset(handle, buffer, fileOffset); } /// @@ -156,7 +156,6 @@ public static int Write(SafeFileHandle handle, ReadOnlySpan buffer, long f /// The file handle. /// A list of memory buffers. This method copies the contents of these buffers to the file. /// The file position to write to. - /// The total number of bytes written into the file. This can be less than the number of bytes provided in the buffers and it's not an error. /// or is . /// is invalid. /// The file is closed. @@ -165,12 +164,12 @@ public static int Write(SafeFileHandle handle, ReadOnlySpan buffer, long f /// was not opened for writing. /// An I/O error occurred. /// Position of the file is not advanced. - public static long Write(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset) + public static void Write(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset) { ValidateInput(handle, fileOffset); ValidateBuffers(buffers); - return WriteGatherAtOffset(handle, buffers, fileOffset); + WriteGatherAtOffset(handle, buffers, fileOffset); } /// @@ -180,7 +179,7 @@ public static long Write(SafeFileHandle handle, IReadOnlyListA region of memory. This method copies the contents of this region to the file. /// The file position to write to. /// The token to monitor for cancellation requests. The default value is . - /// The total number of bytes written into the file. This can be less than the number of bytes provided in the buffer and it's not an error. + /// A task representing the asynchronous completion of the write operation. /// is . /// is invalid. /// The file is closed. @@ -189,13 +188,13 @@ public static long Write(SafeFileHandle handle, IReadOnlyList was not opened for writing. /// An I/O error occurred. /// Position of the file is not advanced. - public static ValueTask WriteAsync(SafeFileHandle handle, ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken = default) + public static ValueTask WriteAsync(SafeFileHandle handle, ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken = default) { ValidateInput(handle, fileOffset); if (cancellationToken.IsCancellationRequested) { - return ValueTask.FromCanceled(cancellationToken); + return ValueTask.FromCanceled(cancellationToken); } return WriteAtOffsetAsync(handle, buffer, fileOffset, cancellationToken); @@ -208,7 +207,7 @@ public static ValueTask WriteAsync(SafeFileHandle handle, ReadOnlyMemoryA list of memory buffers. This method copies the contents of these buffers to the file. /// The file position to write to. /// The token to monitor for cancellation requests. The default value is . - /// The total number of bytes written into the file. This can be less than the number of bytes provided in the buffers and it's not an error. + /// A task representing the asynchronous completion of the write operation. /// or is . /// is invalid. /// The file is closed. @@ -217,14 +216,14 @@ public static ValueTask WriteAsync(SafeFileHandle handle, ReadOnlyMemory was not opened for writing. /// An I/O error occurred. /// Position of the file is not advanced. - public static ValueTask WriteAsync(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset, CancellationToken cancellationToken = default) + public static ValueTask WriteAsync(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset, CancellationToken cancellationToken = default) { ValidateInput(handle, fileOffset); ValidateBuffers(buffers); if (cancellationToken.IsCancellationRequested) { - return ValueTask.FromCanceled(cancellationToken); + return ValueTask.FromCanceled(cancellationToken); } return WriteGatherAtOffsetAsync(handle, buffers, fileOffset, cancellationToken); @@ -276,13 +275,13 @@ private static ValueTask ScheduleSyncReadScatterAtOffsetAsync(SafeFileHand return handle.GetThreadPoolValueTaskSource().QueueReadScatter(buffers, fileOffset, cancellationToken); } - private static ValueTask ScheduleSyncWriteAtOffsetAsync(SafeFileHandle handle, ReadOnlyMemory buffer, + private static ValueTask ScheduleSyncWriteAtOffsetAsync(SafeFileHandle handle, ReadOnlyMemory buffer, long fileOffset, CancellationToken cancellationToken) { return handle.GetThreadPoolValueTaskSource().QueueWrite(buffer, fileOffset, cancellationToken); } - private static ValueTask ScheduleSyncWriteGatherAtOffsetAsync(SafeFileHandle handle, IReadOnlyList> buffers, + private static ValueTask ScheduleSyncWriteGatherAtOffsetAsync(SafeFileHandle handle, IReadOnlyList> buffers, long fileOffset, CancellationToken cancellationToken) { return handle.GetThreadPoolValueTaskSource().QueueWriteGather(buffers, fileOffset, cancellationToken); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/OSFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/OSFileStreamStrategy.cs index b49b8ce2f33f8d..7652fc768c1f35 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/OSFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/OSFileStreamStrategy.cs @@ -290,10 +290,17 @@ public sealed override void Write(ReadOnlySpan buffer) ThrowHelper.ThrowNotSupportedException_UnwritableStream(); } - int r = RandomAccess.WriteAtOffset(_fileHandle, buffer, _filePosition); - Debug.Assert(r >= 0, $"RandomAccess.WriteAtOffset returned {r}."); - _filePosition += r; + try + { + RandomAccess.WriteAtOffset(_fileHandle, buffer, _filePosition); + } + catch + { + _length = -1; // invalidate cached length + throw; + } + _filePosition += buffer.Length; UpdateLengthOnChangePosition(); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs index c88665bf0b8d8f..6f0f9f9562dab5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/UnixFileStreamStrategy.cs @@ -58,14 +58,9 @@ public override void EndWrite(IAsyncResult asyncResult) => TaskToApm.End(asyncResult); public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => - WriteAsyncCore(new ReadOnlyMemory(buffer, offset, count), cancellationToken).AsTask(); + WriteAsync(new ReadOnlyMemory(buffer, offset, count), cancellationToken).AsTask(); - public override ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken) => -#pragma warning disable CA2012 // The analyzer doesn't know the internal AsValueTask is safe. - WriteAsyncCore(source, cancellationToken).AsValueTask(); -#pragma warning restore CA2012 - - private ValueTask WriteAsyncCore(ReadOnlyMemory source, CancellationToken cancellationToken) + public override ValueTask WriteAsync(ReadOnlyMemory source, CancellationToken cancellationToken) { if (!CanWrite) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs index 1b74aa3a23674c..3118072c757232 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/ValueTask.cs @@ -571,31 +571,6 @@ public Task AsTask() /// Gets a that may be used at any point in the future. public ValueTask Preserve() => _obj == null ? this : new ValueTask(AsTask()); - /// Casts a to . - internal ValueTask AsValueTask() - { - object? obj = _obj; - Debug.Assert(obj == null || obj is Task || obj is IValueTaskSource); - - if (obj is null) - { - return default; - } - - if (obj is Task t) - { - return new ValueTask(t); - } - - if (obj is IValueTaskSource vts) - { - // This assumes the token used with IVTS is the same as used with IVTS. - return new ValueTask(vts, _token); - } - - return new ValueTask(GetTaskForValueTaskSource(Unsafe.As>(obj))); - } - /// Creates a to represent the . /// /// The is passed in rather than reading and casting diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 67e80443421df5..4f0c63dd430920 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -10873,10 +10873,10 @@ public static partial class RandomAccess public static long Read(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Collections.Generic.IReadOnlyList> buffers, long fileOffset) { throw null; } public static System.Threading.Tasks.ValueTask ReadAsync(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Memory buffer, long fileOffset, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public static System.Threading.Tasks.ValueTask ReadAsync(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Collections.Generic.IReadOnlyList> buffers, long fileOffset, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public static int Write(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.ReadOnlySpan buffer, long fileOffset) { throw null; } - public static long Write(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Collections.Generic.IReadOnlyList> buffers, long fileOffset) { throw null; } - public static System.Threading.Tasks.ValueTask WriteAsync(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.ReadOnlyMemory buffer, long fileOffset, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public static System.Threading.Tasks.ValueTask WriteAsync(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Collections.Generic.IReadOnlyList> buffers, long fileOffset, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static void Write(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.ReadOnlySpan buffer, long fileOffset) { throw null; } + public static void Write(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Collections.Generic.IReadOnlyList> buffers, long fileOffset) { throw null; } + public static System.Threading.Tasks.ValueTask WriteAsync(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.ReadOnlyMemory buffer, long fileOffset, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public static System.Threading.Tasks.ValueTask WriteAsync(Microsoft.Win32.SafeHandles.SafeFileHandle handle, System.Collections.Generic.IReadOnlyList> buffers, long fileOffset, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } } namespace System.IO.Enumeration