private SocketError InnerReleaseHandle()
{
SocketError errorCode;
// If _blockable was set in BlockingRelease, it's safe to block here, which means
// we can honor the linger options set on the socket. It also means closesocket() might return WSAEWOULDBLOCK, in which
// case we need to do some recovery.
if (_blockable)
{
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"handle:{handle}, Following 'blockable' branch");
errorCode = Interop.Winsock.closesocket(handle);
#if DEBUG
_closeSocketHandle = handle;
_closeSocketResult = errorCode;
#endif
if (errorCode == SocketError.SocketError) errorCode = (SocketError)Marshal.GetLastWin32Error();
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"handle:{handle}, closesocket()#1:{errorCode}");
// If it's not WSAEWOULDBLOCK, there's no more recourse - we either succeeded or failed.
if (errorCode != SocketError.WouldBlock)
{
return errorCode;
}
// The socket must be non-blocking with a linger timeout set.
// We have to set the socket to blocking.
int nonBlockCmd = 0;
errorCode = Interop.Winsock.ioctlsocket(
handle,
Interop.Winsock.IoctlSocketConstants.FIONBIO,
ref nonBlockCmd);
if (errorCode == SocketError.SocketError) errorCode = (SocketError)Marshal.GetLastWin32Error();
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"handle:{handle}, ioctlsocket()#1:{errorCode}");
// This can fail if there's a pending WSAEventSelect. Try canceling it.
if (errorCode == SocketError.InvalidArgument)
{
errorCode = Interop.Winsock.WSAEventSelect(
handle,
IntPtr.Zero,
Interop.Winsock.AsyncEventBits.FdNone);
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"handle:{handle}, WSAEventSelect()#1:{(errorCode == SocketError.SocketError ? (SocketError)Marshal.GetLastWin32Error() : errorCode)}");
// Now retry the ioctl.
errorCode = Interop.Winsock.ioctlsocket(
handle,
Interop.Winsock.IoctlSocketConstants.FIONBIO,
ref nonBlockCmd);
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"handle:{handle}, ioctlsocket()#2:{(errorCode == SocketError.SocketError ? (SocketError)Marshal.GetLastWin32Error() : errorCode)}");
}
// If that succeeded, try again.
if (errorCode == SocketError.Success)
{
errorCode = Interop.Winsock.closesocket(handle);
#if DEBUG
_closeSocketHandle = handle;
_closeSocketResult = errorCode;
#endif
if (errorCode == SocketError.SocketError) errorCode = (SocketError)Marshal.GetLastWin32Error();
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"handle:{handle}, closesocket#2():{errorCode}");
// If it's not WSAEWOULDBLOCK, there's no more recourse - we either succeeded or failed.
if (errorCode != SocketError.WouldBlock)
{
return errorCode;
}
}
// It failed. Fall through to the regular abortive close.
}
// By default or if CloseAsIs() path failed, set linger timeout to zero to get an abortive close (RST).
Interop.Winsock.Linger lingerStruct;
lingerStruct.OnOff = 1;
lingerStruct.Time = 0;
errorCode = Interop.Winsock.setsockopt(
handle,
SocketOptionLevel.Socket,
SocketOptionName.Linger,
ref lingerStruct,
4);
#if DEBUG
_closeSocketLinger = errorCode;
#endif
if (errorCode == SocketError.SocketError) errorCode = (SocketError)Marshal.GetLastWin32Error();
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"handle:{handle}, setsockopt():{errorCode}");
if (errorCode != SocketError.Success && errorCode != SocketError.InvalidArgument && errorCode != SocketError.ProtocolOption)
{
// Too dangerous to try closesocket() - it might block!
return errorCode;
}
errorCode = Interop.Winsock.closesocket(handle);
#if DEBUG
_closeSocketHandle = handle;
_closeSocketResult = errorCode;
#endif
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"handle:{handle}, closesocket#3():{(errorCode == SocketError.SocketError ? (SocketError)Marshal.GetLastWin32Error() : errorCode)}");
return errorCode;
}