| File: System\Net\Sockets\SafeSocketHandle.cs | Web Access |
| Project: src\runtime\src\libraries\System.Net.Sockets\src\System.Net.Sockets.csproj (System.Net.Sockets) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using System.Threading; using Microsoft.Win32.SafeHandles; namespace System.Net.Sockets { // This class implements a safe socket handle. // It uses an inner and outer SafeHandle to do so. The inner // SafeHandle holds the actual socket, but only ever has one // reference to it. The outer SafeHandle guards the inner // SafeHandle with real ref counting. When the outer SafeHandle // is cleaned up, it releases the inner SafeHandle - since // its ref is the only ref to the inner SafeHandle, it deterministically // gets closed at that point - no races with concurrent IO calls. // This allows Close() on the outer SafeHandle to deterministically // close the inner SafeHandle, in turn allowing the inner SafeHandle // to block the user thread in case a graceful close has been // requested. (It's not legal to block any other thread - such closes // are always abortive.) public sealed partial class SafeSocketHandle : SafeHandleMinusOneIsInvalid { #if DEBUG private SocketError _closeSocketResult = unchecked((SocketError)0xdeadbeef); private SocketError _closeSocketLinger = unchecked((SocketError)0xdeadbeef); private int _closeSocketThread; private int _closeSocketTick; #endif private bool _ownClose; /// <summary> /// Creates a <see cref="T:System.Net.Sockets.SafeSocketHandle" />. /// </summary> public SafeSocketHandle() : base(ownsHandle: true) => OwnsHandle = true; /// <summary> /// Creates a <see cref="T:System.Net.Sockets.SafeSocketHandle" /> around a socket handle. /// </summary> /// <param name="preexistingHandle">Handle to wrap</param> /// <param name="ownsHandle">Whether to control the handle lifetime</param> public SafeSocketHandle(IntPtr preexistingHandle, bool ownsHandle) : base(ownsHandle: true) // To support canceling on-going operations we need to detect // there are no more on-going operations. // For that the base-SafeHandle needs to be owning even // when the SafeSocketHandle is not. { OwnsHandle = ownsHandle; // Track if the SafesocketHandle is owning. SetHandleAndValid(preexistingHandle); } internal bool OwnsHandle { get; } internal bool HasShutdownSend => _hasShutdownSend; private bool TryOwnClose() => !Interlocked.Exchange(ref _ownClose, true); private volatile bool _released; private bool _hasShutdownSend; internal void TrackShutdown(SocketShutdown how) { if (how == SocketShutdown.Send || how == SocketShutdown.Both) { _hasShutdownSend = true; } } /// <summary>Gets a value indicating whether the handle value is invalid.</summary> /// <value><see langword="true"/> if the handle value is invalid; otherwise, <see langword="false"/>.</value> public override bool IsInvalid => IsClosed || base.IsInvalid; protected override bool ReleaseHandle() { _released = true; bool shouldClose = TryOwnClose(); if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"shouldClose={shouldClose}"); // When shouldClose is true, the user called Dispose on the SafeHandle. // When it is false, the handle was closed from the Socket via CloseAsIs. if (shouldClose) { CloseHandle(abortive: true, canceledOperations: false); } return true; } internal void CloseAsIs(bool abortive) { #if DEBUG // If this throws it could be very bad. try { #endif bool shouldClose = !IsInvalid && TryOwnClose(); if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"abortive={abortive}, shouldClose ={shouldClose}"); Dispose(); if (shouldClose) { bool canceledOperations = false; // Wait until it's safe. SpinWait sw = default; while (!_released) { // The socket was not released due to the SafeHandle being used. // Try to make those on-going calls return. // On Linux, TryUnblockSocket will unblock current operations but it doesn't prevent // a new one from starting. So we must call TryUnblockSocket multiple times. canceledOperations |= TryUnblockSocket(abortive); sw.SpinOnce(); } CloseHandle(abortive, canceledOperations); } #if DEBUG } catch (Exception exception) when (!ExceptionCheck.IsFatal(exception)) { Debug.Fail($"handle:{handle}, error:{exception}"); throw; } #endif } private bool CloseHandle(bool abortive, bool canceledOperations) { bool ret = false; #if DEBUG try { #endif if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"handle:{handle}"); canceledOperations |= OnHandleClose(); // In case we cancel operations, switch to an abortive close. // Unless the user requested a normal close using Socket.Shutdown. if (canceledOperations && !_hasShutdownSend) { abortive = true; } ret = !OwnsHandle || DoCloseHandle(abortive) == SocketError.Success; return ret; #if DEBUG } catch (Exception exception) { Debug.Assert(ExceptionCheck.IsFatal(exception), $"handle:{handle}, error:{exception}"); ret = true; // Avoid a second assert. throw; } finally { _closeSocketThread = Environment.CurrentManagedThreadId; _closeSocketTick = Environment.TickCount; Debug.Assert(ret, $"ReleaseHandle failed. handle:{handle}"); } #endif } private void SetHandleAndValid(IntPtr handle) { Debug.Assert(!IsClosed); base.SetHandle(handle); if (IsInvalid) { SetHandleAsInvalid(); } } } }