diff --git a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj index 054cc155bd13f7..283d47ee4feda7 100644 --- a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj +++ b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj @@ -179,6 +179,8 @@ Link="Common\Interop\Windows\Kernel32\Interop.SetFileCompletionNotificationModes.cs" /> + diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs index 1cbf2d63ddfa4f..9606204175cac8 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs @@ -295,8 +295,69 @@ public static unsafe SocketError SendFile(SafeSocketHandle handle, SafeFileHandl fixed (byte* prePinnedBuffer = preBuffer) fixed (byte* postPinnedBuffer = postBuffer) { - bool success = TransmitFileHelper(handle, fileHandle, null, (IntPtr)prePinnedBuffer, preBuffer.Length, (IntPtr)postPinnedBuffer, postBuffer.Length, flags); - return success ? SocketError.Success : GetLastSocketError(); + // Get file length if we have a file + long fileLength = 0; + if (fileHandle is not null) + { + fileLength = RandomAccess.GetLength(fileHandle); + } + + // If file length exceeds int.MaxValue, we need to partition the sends + if (fileLength > int.MaxValue) + { + // Separate behavior/performance flags from terminal flags + // Behavior flags (WriteBehind, UseSystemThread, UseKernelApc) apply to all operations + // Terminal flags (Disconnect, ReuseSocket) apply only to the final operation + TransmitFileOptions behaviorFlags = flags & ~(TransmitFileOptions.Disconnect | TransmitFileOptions.ReuseSocket); + + // Send preBuffer if present + if (preBuffer.Length > 0) + { + bool success = TransmitFileHelper(handle, null, null, (IntPtr)prePinnedBuffer, preBuffer.Length, IntPtr.Zero, 0, behaviorFlags); + if (!success) + { + return GetLastSocketError(); + } + } + + // Send file in chunks of int.MaxValue bytes + long offset = 0; + long remaining = fileLength; + while (remaining > 0) + { + int chunkSize = (int)Math.Min(remaining, int.MaxValue); + + // Set the file pointer to the current offset + if (!Interop.Kernel32.SetFilePointerEx(fileHandle!, offset, out _, 0 /* FILE_BEGIN */)) + { + return GetLastSocketError(); + } + + bool success = TransmitFileHelper(handle, fileHandle, null, IntPtr.Zero, 0, IntPtr.Zero, 0, behaviorFlags, chunkSize); + if (!success) + { + return GetLastSocketError(); + } + + offset += chunkSize; + remaining -= chunkSize; + } + + // Send postBuffer with all flags (behavior + terminal flags apply to last operation) + bool finalSuccess = TransmitFileHelper(handle, null, null, IntPtr.Zero, 0, (IntPtr)postPinnedBuffer, postBuffer.Length, flags); + if (!finalSuccess) + { + return GetLastSocketError(); + } + + return SocketError.Success; + } + else + { + // File is small enough, use single call + bool success = TransmitFileHelper(handle, fileHandle, null, (IntPtr)prePinnedBuffer, preBuffer.Length, (IntPtr)postPinnedBuffer, postBuffer.Length, flags); + return success ? SocketError.Success : GetLastSocketError(); + } } } @@ -1004,7 +1065,8 @@ private static unsafe bool TransmitFileHelper( int preBufferLength, IntPtr pinnedPostBuffer, int postBufferLength, - TransmitFileOptions flags) + TransmitFileOptions flags, + int numberOfBytesToWrite = 0) { bool needTransmitFileBuffers = false; Interop.Mswsock.TransmitFileBuffers transmitFileBuffers = default; @@ -1027,14 +1089,14 @@ private static unsafe bool TransmitFileHelper( IntPtr fileHandlePtr = IntPtr.Zero; try { - if (fileHandle != null) + if (fileHandle is not null) { fileHandle.DangerousAddRef(ref releaseRef); fileHandlePtr = fileHandle.DangerousGetHandle(); } return Interop.Mswsock.TransmitFile( - socket, fileHandlePtr, 0, 0, overlapped, + socket, fileHandlePtr, numberOfBytesToWrite, 0, overlapped, needTransmitFileBuffers ? &transmitFileBuffers : null, flags); } finally diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs index 10313336876ce4..f85d68d05c8b13 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs @@ -484,7 +484,6 @@ protected SendFile_NonParallel(ITestOutputHelper output) : base(output) { } - [ActiveIssue("https://github.com/dotnet/runtime/issues/42534", TestPlatforms.Windows)] [OuterLoop("Creates and sends a file several gigabytes long")] [Fact] public async Task GreaterThan2GBFile_SendsAllBytes()