protected virtual void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
if (NetEventSource.IsEnabled)
{
try
{
NetEventSource.Info(this, $"disposing:true CleanedUp:{CleanedUp}");
NetEventSource.Enter(this);
}
catch (Exception exception) when (!ExceptionCheck.IsFatal(exception)) { }
}
// Make sure we're the first call to Dispose and no SetAsyncEventSelect is in progress.
int last;
SpinWait sw = new SpinWait();
while ((last = Interlocked.CompareExchange(ref _intCleanedUp, 1, 0)) == 2)
{
sw.SpinOnce();
}
if (last == 1)
{
if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
return;
}
SetToDisconnected();
// Close the handle in one of several ways depending on the timeout.
// Ignore ObjectDisposedException just in case the handle somehow gets disposed elsewhere.
try
{
int timeout = _closeTimeout;
if (timeout == 0)
{
// Abortive.
if (NetEventSource.IsEnabled) NetEventSource.Info(this, "Calling _handle.Dispose()");
_handle.Dispose();
}
else
{
SocketError errorCode;
// Go to blocking mode. We know no WSAEventSelect is pending because of the lock and UnsetAsyncEventSelect() above.
if (!_willBlock || !_willBlockInternal)
{
bool willBlock;
errorCode = SocketPal.SetBlocking(_handle, false, out willBlock);
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"handle:{_handle} ioctlsocket(FIONBIO):{errorCode}");
}
if (timeout < 0)
{
// Close with existing user-specified linger option.
if (NetEventSource.IsEnabled) NetEventSource.Info(this, "Calling _handle.CloseAsIs()");
_handle.CloseAsIs();
}
else
{
// Since our timeout is in ms and linger is in seconds, implement our own sortof linger here.
errorCode = SocketPal.Shutdown(_handle, _isConnected, _isDisconnected, SocketShutdown.Send);
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"handle:{_handle} shutdown():{errorCode}");
// This should give us a timeout in milliseconds.
errorCode = SocketPal.SetSockOpt(
_handle,
SocketOptionLevel.Socket,
SocketOptionName.ReceiveTimeout,
timeout);
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"handle:{_handle} setsockopt():{errorCode}");
if (errorCode != SocketError.Success)
{
_handle.Dispose();
}
else
{
int unused;
errorCode = SocketPal.Receive(_handle, null, 0, 0, SocketFlags.None, out unused);
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"handle:{_handle} recv():{errorCode}");
if (errorCode != (SocketError)0)
{
// We got a timeout - abort.
_handle.Dispose();
}
else
{
// We got a FIN or data. Use ioctlsocket to find out which.
int dataAvailable = 0;
errorCode = SocketPal.GetAvailable(_handle, out dataAvailable);
if (NetEventSource.IsEnabled) NetEventSource.Info(this, $"handle:{_handle} ioctlsocket(FIONREAD):{errorCode}");
if (errorCode != SocketError.Success || dataAvailable != 0)
{
// If we have data or don't know, safest thing is to reset.
_handle.Dispose();
}
else
{
// We got a FIN. It'd be nice to block for the remainder of the timeout for the handshake to finish.
// Since there's no real way to do that, close the socket with the user's preferences. This lets
// the user decide how best to handle this case via the linger options.
_handle.CloseAsIs();
}
}
}
}
}
}
catch (ObjectDisposedException)
{
NetEventSource.Fail(this, $"handle:{_handle}, Closing the handle threw ObjectDisposedException.");
}
}