// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Buffers;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
namespace System.Net.Sockets
{
public partial class SocketAsyncEventArgs : EventArgs, IDisposable
{
// Single buffer
private MemoryHandle _singleBufferHandle;
private volatile SingleBufferHandleState _singleBufferHandleState;
private enum SingleBufferHandleState : byte { None, InProcess, Set }
// BufferList property variables.
// Note that these arrays are allocated and then grown as necessary, but never shrunk.
// Thus the actual in-use length is defined by _bufferListInternal.Count, not the length of these arrays.
private WSABuffer[] _wsaBufferArray;
private GCHandle[] _multipleBufferGCHandles;
// Internal buffers for WSARecvMsg
private byte[] _wsaMessageBuffer;
private GCHandle _wsaMessageBufferGCHandle;
private byte[] _controlBuffer;
private GCHandle _controlBufferGCHandle;
private WSABuffer[] _wsaRecvMsgWSABufferArray;
private GCHandle _wsaRecvMsgWSABufferArrayGCHandle;
// Internal SocketAddress buffer
private GCHandle _socketAddressGCHandle;
private Internals.SocketAddress _pinnedSocketAddress;
// SendPacketsElements property variables.
private FileStream[] _sendPacketsFileStreams;
// Overlapped object related variables.
private PreAllocatedOverlapped _preAllocatedOverlapped;
private PinState _pinState;
private enum PinState : byte { None = 0, MultipleBuffer, SendPackets }
private void InitializeInternals()
{
// PreAllocatedOverlapped captures ExecutionContext, but SocketAsyncEventArgs ensures
// that context is properly flowed if necessary, and thus we don't need the overlapped
// infrastructure capturing and flowing as well.
bool suppressFlow = !ExecutionContext.IsFlowSuppressed();
try
{
if (suppressFlow) ExecutionContext.SuppressFlow();
_preAllocatedOverlapped = new PreAllocatedOverlapped(s_completionPortCallback, this, null);
}
finally
{
if (suppressFlow) ExecutionContext.RestoreFlow();
}
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"new PreAllocatedOverlapped {_preAllocatedOverlapped}");
}
private void FreeInternals()
{
FreePinHandles();
FreeOverlapped();
}
private unsafe NativeOverlapped* AllocateNativeOverlapped()
{
Debug.Assert(_operating == InProgress, $"Expected {nameof(_operating)} == {nameof(InProgress)}, got {_operating}");
Debug.Assert(_currentSocket != null, "_currentSocket is null");
Debug.Assert(_currentSocket.SafeHandle != null, "_currentSocket.SafeHandle is null");
Debug.Assert(_preAllocatedOverlapped != null, "_preAllocatedOverlapped is null");
ThreadPoolBoundHandle boundHandle = _currentSocket.GetOrAllocateThreadPoolBoundHandle();
return boundHandle.AllocateNativeOverlapped(_preAllocatedOverlapped);
}
private unsafe void FreeNativeOverlapped(NativeOverlapped* overlapped)
{
Debug.Assert(overlapped != null, "overlapped is null");
Debug.Assert(_operating == InProgress, $"Expected _operating == InProgress, got {_operating}");
Debug.Assert(_currentSocket != null, "_currentSocket is null");
Debug.Assert(_currentSocket.SafeHandle != null, "_currentSocket.SafeHandle is null");
Debug.Assert(_currentSocket.SafeHandle.IOCPBoundHandle != null, "_currentSocket.SafeHandle.IOCPBoundHandle is null");
Debug.Assert(_preAllocatedOverlapped != null, "_preAllocatedOverlapped is null");
_currentSocket.SafeHandle.IOCPBoundHandle.FreeNativeOverlapped(overlapped);
}
/// Handles the result of an IOCP operation.
/// true if the operation completed synchronously and successfully; otherwise, false.
/// The number of bytes transferred, if the operation completed synchronously and successfully.
/// The overlapped to be freed if the operation completed synchronously.
/// The result status of the operation.
private unsafe SocketError ProcessIOCPResult(bool success, int bytesTransferred, NativeOverlapped* overlapped)
{
// Note: We need to dispose of the overlapped iff the operation completed synchronously,
// and if we do, we must do so before we mark the operation as completed.
if (success)
{
// Synchronous success.
if (_currentSocket.SafeHandle.SkipCompletionPortOnSuccess)
{
// The socket handle is configured to skip completion on success,
// so we can set the results right now.
FreeNativeOverlapped(overlapped);
FinishOperationSyncSuccess(bytesTransferred, SocketFlags.None);
return SocketError.Success;
}
// Completed synchronously, but the handle wasn't marked as skip completion port on success,
// so we still need to fall through and behave as if the IO was pending.
}
else
{
// Get the socket error (which may be IOPending)
SocketError socketError = SocketPal.GetLastSocketError();
if (socketError != SocketError.IOPending)
{
// Completed synchronously with a failure.
FreeNativeOverlapped(overlapped);
FinishOperationSyncFailure(socketError, bytesTransferred, SocketFlags.None);
return socketError;
}
// Fall through to IOPending handling for asynchronous completion.
}
// Socket handle is going to post a completion to the completion port (may have done so already).
// Return pending and we will continue in the completion port callback.
return SocketError.IOPending;
}
/// Handles the result of an IOCP operation.
/// The result status of the operation, as returned from the API call.
/// The number of bytes transferred, if the operation completed synchronously and successfully.
/// The overlapped to be freed if the operation completed synchronously.
/// The result status of the operation.
private unsafe SocketError ProcessIOCPResultWithSingleBufferHandle(SocketError socketError, int bytesTransferred, NativeOverlapped* overlapped)
{
// Note: We need to dispose of the overlapped iff the operation completed synchronously,
// and if we do, we must do so before we mark the operation as completed.
if (socketError == SocketError.Success)
{
// Synchronous success.
if (_currentSocket.SafeHandle.SkipCompletionPortOnSuccess)
{
// The socket handle is configured to skip completion on success,
// so we can set the results right now.
_singleBufferHandleState = SingleBufferHandleState.None;
FreeNativeOverlapped(overlapped);
FinishOperationSyncSuccess(bytesTransferred, SocketFlags.None);
return SocketError.Success;
}
// Completed synchronously, but the handle wasn't marked as skip completion port on success,
// so we still need to fall through and behave as if the IO was pending.
}
else
{
// Get the socket error (which may be IOPending)
socketError = SocketPal.GetLastSocketError();
if (socketError != SocketError.IOPending)
{
// Completed synchronously with a failure.
_singleBufferHandleState = SingleBufferHandleState.None;
FreeNativeOverlapped(overlapped);
FinishOperationSyncFailure(socketError, bytesTransferred, SocketFlags.None);
return socketError;
}
// Fall through to IOPending handling for asynchronous completion.
}
// Socket handle is going to post a completion to the completion port (may have done so already).
// Return pending and we will continue in the completion port callback.
if (_singleBufferHandleState == SingleBufferHandleState.InProcess)
{
_singleBufferHandle = _buffer.Retain(pin: true);
_singleBufferHandleState = SingleBufferHandleState.Set;
}
return SocketError.IOPending;
}
internal unsafe SocketError DoOperationAccept(Socket socket, SafeCloseSocket handle, SafeCloseSocket acceptHandle)
{
bool userBuffer = _count != 0;
Debug.Assert(!userBuffer || (!_buffer.Equals(default) && _count >= _acceptAddressBufferCount));
Memory buffer = userBuffer ? _buffer : _acceptBuffer;
Debug.Assert(_singleBufferHandleState == SingleBufferHandleState.None);
NativeOverlapped* overlapped = AllocateNativeOverlapped();
try
{
_singleBufferHandle = buffer.Retain(pin: true);
_singleBufferHandleState = SingleBufferHandleState.Set;
bool success = socket.AcceptEx(
handle,
acceptHandle,
userBuffer ? (IntPtr)((byte*)_singleBufferHandle.Pointer + _offset) : (IntPtr)_singleBufferHandle.Pointer,
userBuffer ? _count - _acceptAddressBufferCount : 0,
_acceptAddressBufferCount / 2,
_acceptAddressBufferCount / 2,
out int bytesTransferred,
overlapped);
return ProcessIOCPResult(success, bytesTransferred, overlapped);
}
catch
{
FreeNativeOverlapped(overlapped);
_singleBufferHandle.Dispose();
_singleBufferHandleState = SingleBufferHandleState.None;
throw;
}
}
internal unsafe SocketError DoOperationConnect(Socket socket, SafeCloseSocket handle)
{
// ConnectEx uses a sockaddr buffer containing the remote address to which to connect.
// It can also optionally take a single buffer of data to send after the connection is complete.
// The sockaddr is pinned with a GCHandle to avoid having to use the object array form of UnsafePack.
PinSocketAddressBuffer();
NativeOverlapped* overlapped = AllocateNativeOverlapped();
try
{
Debug.Assert(_singleBufferHandleState == SingleBufferHandleState.None);
_singleBufferHandle = _buffer.Retain(pin: true);
_singleBufferHandleState = SingleBufferHandleState.Set;
bool success = socket.ConnectEx(
handle,
PtrSocketAddressBuffer,
_socketAddress.Size,
(IntPtr)((byte*)_singleBufferHandle.Pointer + _offset),
_count,
out int bytesTransferred,
overlapped);
return ProcessIOCPResult(success, bytesTransferred, overlapped);
}
catch
{
FreeNativeOverlapped(overlapped);
_singleBufferHandle.Dispose();
_singleBufferHandleState = SingleBufferHandleState.None;
throw;
}
}
internal unsafe SocketError DoOperationDisconnect(Socket socket, SafeCloseSocket handle)
{
NativeOverlapped* overlapped = AllocateNativeOverlapped();
try
{
bool success = socket.DisconnectEx(
handle,
overlapped,
(int)(DisconnectReuseSocket ? TransmitFileOptions.ReuseSocket : 0),
0);
return ProcessIOCPResult(success, 0, overlapped);
}
catch
{
FreeNativeOverlapped(overlapped);
throw;
}
}
internal SocketError DoOperationReceive(SafeCloseSocket handle) => _bufferList == null ?
DoOperationReceiveSingleBuffer(handle) :
DoOperationReceiveMultiBuffer(handle);
internal unsafe SocketError DoOperationReceiveSingleBuffer(SafeCloseSocket handle)
{
fixed (byte* bufferPtr = &MemoryMarshal.GetReference(_buffer.Span))
{
Debug.Assert(_singleBufferHandleState == SingleBufferHandleState.None, $"Expected None, got {_singleBufferHandleState}");
_singleBufferHandleState = SingleBufferHandleState.InProcess;
var wsaBuffer = new WSABuffer { Length = _count, Pointer = (IntPtr)(bufferPtr + _offset) };
NativeOverlapped* overlapped = AllocateNativeOverlapped();
try
{
SocketFlags flags = _socketFlags;
SocketError socketError = Interop.Winsock.WSARecv(
handle.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead
ref wsaBuffer,
1,
out int bytesTransferred,
ref flags,
overlapped,
IntPtr.Zero);
GC.KeepAlive(handle); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress
return ProcessIOCPResultWithSingleBufferHandle(socketError, bytesTransferred, overlapped);
}
catch
{
FreeNativeOverlapped(overlapped);
_singleBufferHandleState = SingleBufferHandleState.None;
throw;
}
}
}
internal unsafe SocketError DoOperationReceiveMultiBuffer(SafeCloseSocket handle)
{
NativeOverlapped* overlapped = AllocateNativeOverlapped();
try
{
SocketFlags flags = _socketFlags;
SocketError socketError = Interop.Winsock.WSARecv(
handle.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead
_wsaBufferArray,
_bufferListInternal.Count,
out int bytesTransferred,
ref flags,
overlapped,
IntPtr.Zero);
GC.KeepAlive(handle); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress
return ProcessIOCPResult(socketError == SocketError.Success, bytesTransferred, overlapped);
}
catch
{
FreeNativeOverlapped(overlapped);
throw;
}
}
internal unsafe SocketError DoOperationReceiveFrom(SafeCloseSocket handle)
{
// WSARecvFrom uses a WSABuffer array describing buffers in which to
// receive data and from which to send data respectively. Single and multiple buffers
// are handled differently so as to optimize performance for the more common single buffer case.
// WSARecvFrom and WSASendTo also uses a sockaddr buffer in which to store the address from which the data was received.
// The sockaddr is pinned with a GCHandle to avoid having to use the object array form of UnsafePack.
PinSocketAddressBuffer();
return _bufferList == null ?
DoOperationReceiveFromSingleBuffer(handle) :
DoOperationReceiveFromMultiBuffer(handle);
}
internal unsafe SocketError DoOperationReceiveFromSingleBuffer(SafeCloseSocket handle)
{
fixed (byte* bufferPtr = &MemoryMarshal.GetReference(_buffer.Span))
{
Debug.Assert(_singleBufferHandleState == SingleBufferHandleState.None);
_singleBufferHandleState = SingleBufferHandleState.InProcess;
var wsaBuffer = new WSABuffer { Length = _count, Pointer = (IntPtr)(bufferPtr + _offset) };
NativeOverlapped* overlapped = AllocateNativeOverlapped();
try
{
SocketFlags flags = _socketFlags;
SocketError socketError = Interop.Winsock.WSARecvFrom(
handle.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead
ref wsaBuffer,
1,
out int bytesTransferred,
ref flags,
PtrSocketAddressBuffer,
PtrSocketAddressBufferSize,
overlapped,
IntPtr.Zero);
GC.KeepAlive(handle); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress
return ProcessIOCPResultWithSingleBufferHandle(socketError, bytesTransferred, overlapped);
}
catch
{
FreeNativeOverlapped(overlapped);
_singleBufferHandleState = SingleBufferHandleState.None;
throw;
}
}
}
internal unsafe SocketError DoOperationReceiveFromMultiBuffer(SafeCloseSocket handle)
{
NativeOverlapped* overlapped = AllocateNativeOverlapped();
try
{
SocketFlags flags = _socketFlags;
SocketError socketError = Interop.Winsock.WSARecvFrom(
handle.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead
_wsaBufferArray,
_bufferListInternal.Count,
out int bytesTransferred,
ref flags,
PtrSocketAddressBuffer,
PtrSocketAddressBufferSize,
overlapped,
IntPtr.Zero);
GC.KeepAlive(handle); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress
return ProcessIOCPResult(socketError == SocketError.Success, bytesTransferred, overlapped);
}
catch
{
FreeNativeOverlapped(overlapped);
throw;
}
}
internal unsafe SocketError DoOperationReceiveMessageFrom(Socket socket, SafeCloseSocket handle)
{
// WSARecvMsg uses a WSAMsg descriptor.
// The WSAMsg buffer is pinned with a GCHandle to avoid complicating the use of Overlapped.
// WSAMsg contains a pointer to a sockaddr.
// The sockaddr is pinned with a GCHandle to avoid complicating the use of Overlapped.
// WSAMsg contains a pointer to a WSABuffer array describing data buffers.
// WSAMsg also contains a single WSABuffer describing a control buffer.
PinSocketAddressBuffer();
// Create a WSAMessageBuffer if none exists yet.
if (_wsaMessageBuffer == null)
{
Debug.Assert(!_wsaMessageBufferGCHandle.IsAllocated);
_wsaMessageBuffer = new byte[sizeof(Interop.Winsock.WSAMsg)];
}
// And ensure the WSAMessageBuffer is appropriately pinned.
Debug.Assert(!_wsaMessageBufferGCHandle.IsAllocated || _wsaMessageBufferGCHandle.Target == _wsaMessageBuffer);
if (!_wsaMessageBufferGCHandle.IsAllocated)
{
_wsaMessageBufferGCHandle = GCHandle.Alloc(_wsaMessageBuffer, GCHandleType.Pinned);
}
// Create and pin an appropriately sized control buffer if none already
IPAddress ipAddress = (_socketAddress.Family == AddressFamily.InterNetworkV6 ? _socketAddress.GetIPAddress() : null);
bool ipv4 = (_currentSocket.AddressFamily == AddressFamily.InterNetwork || (ipAddress != null && ipAddress.IsIPv4MappedToIPv6)); // DualMode
bool ipv6 = _currentSocket.AddressFamily == AddressFamily.InterNetworkV6;
if (ipv4 && (_controlBuffer == null || _controlBuffer.Length != sizeof(Interop.Winsock.ControlData)))
{
if (_controlBufferGCHandle.IsAllocated)
{
_controlBufferGCHandle.Free();
}
_controlBuffer = new byte[sizeof(Interop.Winsock.ControlData)];
}
else if (ipv6 && (_controlBuffer == null || _controlBuffer.Length != sizeof(Interop.Winsock.ControlDataIPv6)))
{
if (_controlBufferGCHandle.IsAllocated)
{
_controlBufferGCHandle.Free();
}
_controlBuffer = new byte[sizeof(Interop.Winsock.ControlDataIPv6)];
}
// If single buffer we need a single element WSABuffer.
WSABuffer[] wsaRecvMsgWSABufferArray;
uint wsaRecvMsgWSABufferCount;
if (_bufferList == null)
{
if (_wsaRecvMsgWSABufferArray == null)
{
_wsaRecvMsgWSABufferArray = new WSABuffer[1];
}
Debug.Assert(_singleBufferHandleState == SingleBufferHandleState.None);
_singleBufferHandle = _buffer.Retain(pin: true);
_singleBufferHandleState = SingleBufferHandleState.Set;
_wsaRecvMsgWSABufferArray[0].Pointer = (IntPtr)_singleBufferHandle.Pointer;
_wsaRecvMsgWSABufferArray[0].Length = _count;
wsaRecvMsgWSABufferArray = _wsaRecvMsgWSABufferArray;
wsaRecvMsgWSABufferCount = 1;
}
else
{
// Use the multi-buffer WSABuffer.
wsaRecvMsgWSABufferArray = _wsaBufferArray;
wsaRecvMsgWSABufferCount = (uint)_bufferListInternal.Count;
}
// Ensure the array is pinned.
Debug.Assert(!_wsaRecvMsgWSABufferArrayGCHandle.IsAllocated || _wsaRecvMsgWSABufferArrayGCHandle.Target == wsaRecvMsgWSABufferArray);
if (!_wsaRecvMsgWSABufferArrayGCHandle.IsAllocated)
{
_wsaRecvMsgWSABufferArrayGCHandle = GCHandle.Alloc(wsaRecvMsgWSABufferArray, GCHandleType.Pinned);
}
// Fill in WSAMessageBuffer.
unsafe
{
Interop.Winsock.WSAMsg* pMessage = (Interop.Winsock.WSAMsg*)Marshal.UnsafeAddrOfPinnedArrayElement(_wsaMessageBuffer, 0);
pMessage->socketAddress = PtrSocketAddressBuffer;
pMessage->addressLength = (uint)_socketAddress.Size;
fixed (void* ptrWSARecvMsgWSABufferArray = &wsaRecvMsgWSABufferArray[0])
{
pMessage->buffers = (IntPtr)ptrWSARecvMsgWSABufferArray;
}
pMessage->count = wsaRecvMsgWSABufferCount;
if (_controlBuffer != null)
{
Debug.Assert(_controlBuffer.Length > 0);
Debug.Assert(!_controlBufferGCHandle.IsAllocated || _controlBufferGCHandle.Target == _controlBuffer);
if (!_controlBufferGCHandle.IsAllocated)
{
_controlBufferGCHandle = GCHandle.Alloc(_controlBuffer, GCHandleType.Pinned);
}
fixed (void* ptrControlBuffer = &_controlBuffer[0])
{
pMessage->controlBuffer.Pointer = (IntPtr)ptrControlBuffer;
}
pMessage->controlBuffer.Length = _controlBuffer.Length;
}
pMessage->flags = _socketFlags;
}
NativeOverlapped* overlapped = AllocateNativeOverlapped();
try
{
SocketError socketError = socket.WSARecvMsg(
handle,
Marshal.UnsafeAddrOfPinnedArrayElement(_wsaMessageBuffer, 0),
out int bytesTransferred,
overlapped,
IntPtr.Zero);
return ProcessIOCPResultWithSingleBufferHandle(socketError, bytesTransferred, overlapped);
}
catch
{
FreeNativeOverlapped(overlapped);
_singleBufferHandle.Dispose();
_singleBufferHandleState = SingleBufferHandleState.None;
throw;
}
}
internal unsafe SocketError DoOperationSend(SafeCloseSocket handle) => _bufferList == null ?
DoOperationSendSingleBuffer(handle) :
DoOperationSendMultiBuffer(handle);
internal unsafe SocketError DoOperationSendSingleBuffer(SafeCloseSocket handle)
{
fixed (byte* bufferPtr = &MemoryMarshal.GetReference(_buffer.Span))
{
Debug.Assert(_singleBufferHandleState == SingleBufferHandleState.None);
_singleBufferHandleState = SingleBufferHandleState.InProcess;
var wsaBuffer = new WSABuffer { Length = _count, Pointer = (IntPtr)(bufferPtr + _offset) };
NativeOverlapped* overlapped = AllocateNativeOverlapped();
try
{
SocketError socketError = Interop.Winsock.WSASend(
handle.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead
ref wsaBuffer,
1,
out int bytesTransferred,
_socketFlags,
overlapped,
IntPtr.Zero);
GC.KeepAlive(handle); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress
return ProcessIOCPResultWithSingleBufferHandle(socketError, bytesTransferred, overlapped);
}
catch
{
FreeNativeOverlapped(overlapped);
_singleBufferHandleState = SingleBufferHandleState.None;
throw;
}
}
}
internal unsafe SocketError DoOperationSendMultiBuffer(SafeCloseSocket handle)
{
NativeOverlapped* overlapped = AllocateNativeOverlapped();
try
{
SocketError socketError = Interop.Winsock.WSASend(
handle.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead
_wsaBufferArray,
_bufferListInternal.Count,
out int bytesTransferred,
_socketFlags,
overlapped,
IntPtr.Zero);
GC.KeepAlive(handle); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress
return ProcessIOCPResult(socketError == SocketError.Success, bytesTransferred, overlapped);
}
catch
{
FreeNativeOverlapped(overlapped);
throw;
}
}
internal unsafe SocketError DoOperationSendPackets(Socket socket, SafeCloseSocket handle)
{
// Cache copy to avoid problems with concurrent manipulation during the async operation.
Debug.Assert(_sendPacketsElements != null);
SendPacketsElement[] sendPacketsElementsCopy = (SendPacketsElement[])_sendPacketsElements.Clone();
// TransmitPackets uses an array of TRANSMIT_PACKET_ELEMENT structs as
// descriptors for buffers and files to be sent. It also takes a send size
// and some flags. The TRANSMIT_PACKET_ELEMENT for a file contains a native file handle.
// Opens the files to get the file handles, pin down any buffers specified and builds the
// native TRANSMIT_PACKET_ELEMENT array that will be passed to TransmitPackets.
// Scan the elements to count files and buffers.
int sendPacketsElementsFileCount = 0, sendPacketsElementsBufferCount = 0;
foreach (SendPacketsElement spe in sendPacketsElementsCopy)
{
if (spe != null)
{
if (spe._filePath != null)
{
sendPacketsElementsFileCount++;
}
else if (spe._buffer != null && spe._count > 0)
{
sendPacketsElementsBufferCount++;
}
}
}
// Attempt to open the files if any were given.
if (sendPacketsElementsFileCount > 0)
{
// Create arrays for streams.
_sendPacketsFileStreams = new FileStream[sendPacketsElementsFileCount];
// Loop through the elements attempting to open each files and get its handle.
int index = 0;
foreach (SendPacketsElement spe in sendPacketsElementsCopy)
{
if (spe != null && spe._filePath != null)
{
Exception fileStreamException = null;
try
{
// Create a FileStream to open the file.
_sendPacketsFileStreams[index] =
new FileStream(spe._filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
}
catch (Exception ex)
{
// Save the exception to throw after closing any previous successful file opens.
fileStreamException = ex;
}
if (fileStreamException != null)
{
// Got an exception opening a file - close any open streams, then throw.
for (int i = 0; i < sendPacketsElementsFileCount; i++)
{
_sendPacketsFileStreams[i]?.Dispose();
}
_sendPacketsFileStreams = null;
throw fileStreamException;
}
// Get the file handle from the stream.
index++;
}
}
}
if (sendPacketsElementsFileCount + sendPacketsElementsBufferCount == 0)
{
FinishOperationSyncSuccess(0, SocketFlags.None);
return SocketError.Success;
}
Interop.Winsock.TransmitPacketsElement[] sendPacketsDescriptor = SetupPinHandlesSendPackets(sendPacketsElementsCopy, sendPacketsElementsFileCount, sendPacketsElementsBufferCount);
Debug.Assert(sendPacketsDescriptor != null);
Debug.Assert(sendPacketsDescriptor.Length > 0);
Debug.Assert(_multipleBufferGCHandles != null);
Debug.Assert(_multipleBufferGCHandles[0].IsAllocated);
Debug.Assert(_multipleBufferGCHandles[0].Target == sendPacketsDescriptor);
NativeOverlapped* overlapped = AllocateNativeOverlapped();
try
{
bool result = socket.TransmitPackets(
handle,
_multipleBufferGCHandles[0].AddrOfPinnedObject(),
sendPacketsDescriptor.Length,
_sendPacketsSendSize,
overlapped,
_sendPacketsFlags);
return ProcessIOCPResult(result, 0, overlapped);
}
catch
{
FreeNativeOverlapped(overlapped);
throw;
}
}
internal unsafe SocketError DoOperationSendTo(SafeCloseSocket handle)
{
// WSASendTo uses a WSABuffer array describing buffers in which to
// receive data and from which to send data respectively. Single and multiple buffers
// are handled differently so as to optimize performance for the more common single buffer case.
//
// WSARecvFrom and WSASendTo also uses a sockaddr buffer in which to store the address from which the data was received.
// The sockaddr is pinned with a GCHandle to avoid having to use the object array form of UnsafePack.
PinSocketAddressBuffer();
return _bufferList == null ?
DoOperationSendToSingleBuffer(handle) :
DoOperationSendToMultiBuffer(handle);
}
internal unsafe SocketError DoOperationSendToSingleBuffer(SafeCloseSocket handle)
{
fixed (byte* bufferPtr = &MemoryMarshal.GetReference(_buffer.Span))
{
Debug.Assert(_singleBufferHandleState == SingleBufferHandleState.None);
_singleBufferHandleState = SingleBufferHandleState.InProcess;
var wsaBuffer = new WSABuffer { Length = _count, Pointer = (IntPtr)(bufferPtr + _offset) };
NativeOverlapped* overlapped = AllocateNativeOverlapped();
try
{
SocketError socketError = Interop.Winsock.WSASendTo(
handle.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead
ref wsaBuffer,
1,
out int bytesTransferred,
_socketFlags,
PtrSocketAddressBuffer,
_socketAddress.Size,
overlapped,
IntPtr.Zero);
GC.KeepAlive(handle); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress
return ProcessIOCPResultWithSingleBufferHandle(socketError, bytesTransferred, overlapped);
}
catch
{
FreeNativeOverlapped(overlapped);
_singleBufferHandleState = SingleBufferHandleState.None;
throw;
}
}
}
internal unsafe SocketError DoOperationSendToMultiBuffer(SafeCloseSocket handle)
{
NativeOverlapped* overlapped = AllocateNativeOverlapped();
try
{
SocketError socketError = Interop.Winsock.WSASendTo(
handle.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead
_wsaBufferArray,
_bufferListInternal.Count,
out int bytesTransferred,
_socketFlags,
PtrSocketAddressBuffer,
_socketAddress.Size,
overlapped,
IntPtr.Zero);
GC.KeepAlive(handle); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress
return ProcessIOCPResult(socketError == SocketError.Success, bytesTransferred, overlapped);
}
catch
{
FreeNativeOverlapped(overlapped);
throw;
}
}
// Ensures Overlapped object exists with appropriate multiple buffers pinned.
private void SetupMultipleBuffers()
{
if (_bufferListInternal == null || _bufferListInternal.Count == 0)
{
// No buffer list is set so unpin any existing multiple buffer pinning.
if (_pinState == PinState.MultipleBuffer)
{
FreePinHandles();
}
}
else
{
// Need to setup a new Overlapped.
FreePinHandles();
try
{
int bufferCount = _bufferListInternal.Count;
#if DEBUG
if (_multipleBufferGCHandles != null)
{
foreach (GCHandle gcHandle in _multipleBufferGCHandles)
{
Debug.Assert(!gcHandle.IsAllocated);
}
}
#endif
// Number of things to pin is number of buffers.
// Ensure we have properly sized object array.
if (_multipleBufferGCHandles == null || (_multipleBufferGCHandles.Length < bufferCount))
{
_multipleBufferGCHandles = new GCHandle[bufferCount];
}
// Pin the buffers.
for (int i = 0; i < bufferCount; i++)
{
Debug.Assert(!_multipleBufferGCHandles[i].IsAllocated);
_multipleBufferGCHandles[i] = GCHandle.Alloc(_bufferListInternal[i].Array, GCHandleType.Pinned);
}
if (_wsaBufferArray == null || _wsaBufferArray.Length < bufferCount)
{
_wsaBufferArray = new WSABuffer[bufferCount];
}
for (int i = 0; i < bufferCount; i++)
{
ArraySegment localCopy = _bufferListInternal[i];
_wsaBufferArray[i].Pointer = Marshal.UnsafeAddrOfPinnedArrayElement(localCopy.Array, localCopy.Offset);
_wsaBufferArray[i].Length = localCopy.Count;
}
_pinState = PinState.MultipleBuffer;
}
catch (Exception)
{
FreePinHandles();
throw;
}
}
}
// Ensures appropriate SocketAddress buffer is pinned.
private void PinSocketAddressBuffer()
{
// Check if already pinned.
if (_pinnedSocketAddress == _socketAddress)
{
return;
}
// Unpin any existing.
if (_socketAddressGCHandle.IsAllocated)
{
_socketAddressGCHandle.Free();
}
// Pin down the new one.
_socketAddressGCHandle = GCHandle.Alloc(_socketAddress.Buffer, GCHandleType.Pinned);
_socketAddress.CopyAddressSizeIntoBuffer();
_pinnedSocketAddress = _socketAddress;
}
private unsafe IntPtr PtrSocketAddressBuffer
{
get
{
Debug.Assert(_pinnedSocketAddress != null);
Debug.Assert(_pinnedSocketAddress.Buffer != null);
Debug.Assert(_pinnedSocketAddress.Buffer.Length > 0);
Debug.Assert(_socketAddressGCHandle.IsAllocated);
Debug.Assert(_socketAddressGCHandle.Target == _pinnedSocketAddress.Buffer);
fixed (void* ptrSocketAddressBuffer = &_pinnedSocketAddress.Buffer[0])
{
return (IntPtr)ptrSocketAddressBuffer;
}
}
}
private IntPtr PtrSocketAddressBufferSize => PtrSocketAddressBuffer + _socketAddress.GetAddressSizeOffset();
// Cleans up any existing Overlapped object and related state variables.
private void FreeOverlapped()
{
// Free the preallocated overlapped object. This in turn will unpin
// any pinned buffers.
if (_preAllocatedOverlapped != null)
{
_preAllocatedOverlapped.Dispose();
_preAllocatedOverlapped = null;
}
}
private void FreePinHandles()
{
_pinState = PinState.None;
if (_singleBufferHandleState != SingleBufferHandleState.None)
{
_singleBufferHandle.Dispose();
_singleBufferHandleState = SingleBufferHandleState.None;
}
if (_multipleBufferGCHandles != null)
{
for (int i = 0; i < _multipleBufferGCHandles.Length; i++)
{
if (_multipleBufferGCHandles[i].IsAllocated)
{
_multipleBufferGCHandles[i].Free();
}
}
}
if (_socketAddressGCHandle.IsAllocated)
{
_socketAddressGCHandle.Free();
_pinnedSocketAddress = null;
}
if (_wsaMessageBufferGCHandle.IsAllocated)
{
_wsaMessageBufferGCHandle.Free();
}
if (_wsaRecvMsgWSABufferArrayGCHandle.IsAllocated)
{
_wsaRecvMsgWSABufferArrayGCHandle.Free();
}
if (_controlBufferGCHandle.IsAllocated)
{
_controlBufferGCHandle.Free();
}
}
// Sets up an Overlapped object for SendPacketsAsync.
private unsafe Interop.Winsock.TransmitPacketsElement[] SetupPinHandlesSendPackets(
SendPacketsElement[] sendPacketsElementsCopy, int sendPacketsElementsFileCount, int sendPacketsElementsBufferCount)
{
if (_pinState != PinState.None)
{
FreePinHandles();
}
// Alloc native descriptor.
var sendPacketsDescriptor = new Interop.Winsock.TransmitPacketsElement[sendPacketsElementsFileCount + sendPacketsElementsBufferCount];
// Number of things to pin is number of buffers + 1 (native descriptor).
// Ensure we have properly sized object array.
#if DEBUG
if (_multipleBufferGCHandles != null)
{
foreach (GCHandle gcHandle in _multipleBufferGCHandles)
{
Debug.Assert(!gcHandle.IsAllocated);
}
}
#endif
if (_multipleBufferGCHandles == null || (_multipleBufferGCHandles.Length < sendPacketsElementsBufferCount + 1))
{
_multipleBufferGCHandles = new GCHandle[sendPacketsElementsBufferCount + 1];
}
// Pin objects. Native descriptor buffer first and then user specified buffers.
Debug.Assert(!_multipleBufferGCHandles[0].IsAllocated);
_multipleBufferGCHandles[0] = GCHandle.Alloc(sendPacketsDescriptor, GCHandleType.Pinned);
int index = 1;
foreach (SendPacketsElement spe in sendPacketsElementsCopy)
{
if (spe != null && spe._buffer != null && spe._count > 0)
{
Debug.Assert(!_multipleBufferGCHandles[index].IsAllocated);
_multipleBufferGCHandles[index] = GCHandle.Alloc(spe._buffer, GCHandleType.Pinned);
index++;
}
}
// Fill in native descriptor.
int descriptorIndex = 0;
int fileIndex = 0;
foreach (SendPacketsElement spe in sendPacketsElementsCopy)
{
if (spe != null)
{
if (spe._buffer != null && spe._count > 0)
{
// This element is a buffer.
sendPacketsDescriptor[descriptorIndex].buffer = Marshal.UnsafeAddrOfPinnedArrayElement(spe._buffer, spe._offset);
sendPacketsDescriptor[descriptorIndex].length = (uint)spe._count;
sendPacketsDescriptor[descriptorIndex].flags = (Interop.Winsock.TransmitPacketsElementFlags)spe._flags;
descriptorIndex++;
}
else if (spe._filePath != null)
{
// This element is a file.
sendPacketsDescriptor[descriptorIndex].fileHandle = _sendPacketsFileStreams[fileIndex].SafeFileHandle.DangerousGetHandle();
sendPacketsDescriptor[descriptorIndex].fileOffset = spe._offset;
sendPacketsDescriptor[descriptorIndex].length = (uint)spe._count;
sendPacketsDescriptor[descriptorIndex].flags = (Interop.Winsock.TransmitPacketsElementFlags)spe._flags;
fileIndex++;
descriptorIndex++;
}
}
}
_pinState = PinState.SendPackets;
return sendPacketsDescriptor;
}
internal void LogBuffer(int size)
{
// This should only be called if tracing is enabled. However, there is the potential for a race
// condition where tracing is disabled between a calling check and here, in which case the assert
// may fire erroneously.
Debug.Assert(NetEventSource.IsEnabled);
if (_bufferList != null)
{
for (int i = 0; i < _bufferListInternal.Count; i++)
{
WSABuffer wsaBuffer = _wsaBufferArray[i];
NetEventSource.DumpBuffer(this, wsaBuffer.Pointer, Math.Min(wsaBuffer.Length, size));
if ((size -= wsaBuffer.Length) <= 0)
{
break;
}
}
}
else if (_buffer.Length != 0)
{
NetEventSource.DumpBuffer(this, _buffer, _offset, size);
}
}
private unsafe SocketError FinishOperationAccept(Internals.SocketAddress remoteSocketAddress)
{
SocketError socketError;
IntPtr localAddr;
int localAddrLength;
IntPtr remoteAddr;
try
{
Debug.Assert(_singleBufferHandleState == SingleBufferHandleState.Set);
Debug.Assert(_singleBufferHandle.HasPointer);
bool userBuffer = _count >= _acceptAddressBufferCount;
_currentSocket.GetAcceptExSockaddrs(
userBuffer ? (IntPtr)((byte*)_singleBufferHandle.Pointer + _offset) : (IntPtr)_singleBufferHandle.Pointer,
_count != 0 ? _count - _acceptAddressBufferCount : 0,
_acceptAddressBufferCount / 2,
_acceptAddressBufferCount / 2,
out localAddr,
out localAddrLength,
out remoteAddr,
out remoteSocketAddress.InternalSize
);
Marshal.Copy(remoteAddr, remoteSocketAddress.Buffer, 0, remoteSocketAddress.Size);
// Set the socket context.
IntPtr handle = _currentSocket.SafeHandle.DangerousGetHandle();
socketError = Interop.Winsock.setsockopt(
_acceptSocket.SafeHandle,
SocketOptionLevel.Socket,
SocketOptionName.UpdateAcceptContext,
ref handle,
IntPtr.Size);
if (socketError == SocketError.SocketError)
{
socketError = SocketPal.GetLastSocketError();
}
}
catch (ObjectDisposedException)
{
socketError = SocketError.OperationAborted;
}
return socketError;
}
private SocketError FinishOperationConnect()
{
try
{
// Update the socket context.
SocketError socketError = Interop.Winsock.setsockopt(
_currentSocket.SafeHandle,
SocketOptionLevel.Socket,
SocketOptionName.UpdateConnectContext,
null,
0);
return socketError == SocketError.SocketError ?
SocketPal.GetLastSocketError() :
socketError;
}
catch (ObjectDisposedException)
{
return SocketError.OperationAborted;
}
}
private unsafe int GetSocketAddressSize() => *(int*)PtrSocketAddressBufferSize;
private void CompleteCore()
{
if (_singleBufferHandleState != SingleBufferHandleState.None)
{
CompleteCoreSpin();
}
void CompleteCoreSpin() // separate out to help inline the fast path
{
var sw = new SpinWait();
while (_singleBufferHandleState == SingleBufferHandleState.InProcess)
{
sw.SpinOnce();
}
if (_singleBufferHandleState == SingleBufferHandleState.Set)
{
_singleBufferHandle.Dispose();
_singleBufferHandleState = SingleBufferHandleState.None;
}
}
}
private unsafe void FinishOperationReceiveMessageFrom()
{
Interop.Winsock.WSAMsg* PtrMessage = (Interop.Winsock.WSAMsg*)Marshal.UnsafeAddrOfPinnedArrayElement(_wsaMessageBuffer, 0);
if (_controlBuffer.Length == sizeof(Interop.Winsock.ControlData))
{
// IPv4.
_receiveMessageFromPacketInfo = SocketPal.GetIPPacketInformation((Interop.Winsock.ControlData*)PtrMessage->controlBuffer.Pointer);
}
else if (_controlBuffer.Length == sizeof(Interop.Winsock.ControlDataIPv6))
{
// IPv6.
_receiveMessageFromPacketInfo = SocketPal.GetIPPacketInformation((Interop.Winsock.ControlDataIPv6*)PtrMessage->controlBuffer.Pointer);
}
else
{
// Other.
_receiveMessageFromPacketInfo = new IPPacketInformation();
}
}
private void FinishOperationSendPackets()
{
// Close the files if open.
if (_sendPacketsFileStreams != null)
{
for (int i = 0; i < _sendPacketsFileStreams.Length; i++)
{
_sendPacketsFileStreams[i]?.Dispose();
}
_sendPacketsFileStreams = null;
}
}
private static readonly unsafe IOCompletionCallback s_completionPortCallback = delegate (uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped)
{
var saea = (SocketAsyncEventArgs)ThreadPoolBoundHandle.GetNativeOverlappedState(nativeOverlapped);
if ((SocketError)errorCode == SocketError.Success)
{
saea.FreeNativeOverlapped(nativeOverlapped);
saea.FinishOperationAsyncSuccess((int)numBytes, SocketFlags.None);
}
else
{
saea.HandleCompletionPortCallbackError(errorCode, numBytes, nativeOverlapped);
}
};
private unsafe void HandleCompletionPortCallbackError(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped)
{
SocketError socketError = (SocketError)errorCode;
SocketFlags socketFlags = SocketFlags.None;
if (socketError != SocketError.OperationAborted)
{
if (_currentSocket.CleanedUp)
{
socketError = SocketError.OperationAborted;
}
else
{
try
{
// The Async IO completed with a failure.
// here we need to call WSAGetOverlappedResult() just so GetLastSocketError() will return the correct error.
bool success = Interop.Winsock.WSAGetOverlappedResult(
_currentSocket.SafeHandle,
nativeOverlapped,
out numBytes,
false,
out socketFlags);
socketError = SocketPal.GetLastSocketError();
}
catch
{
// _currentSocket.CleanedUp check above does not always work since this code is subject to race conditions.
socketError = SocketError.OperationAborted;
}
}
}
FreeNativeOverlapped(nativeOverlapped);
FinishOperationAsyncFailure(socketError, (int)numBytes, socketFlags);
}
}
}