File: System\Net\Quic\Internal\MsQuicSafeHandle.cs
Web Access
Project: src\src\libraries\System.Net.Quic\src\System.Net.Quic.csproj (System.Net.Quic)
// 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.Runtime.InteropServices;
using System.Threading;
using Microsoft.Quic;
 
namespace System.Net.Quic;
 
internal unsafe class MsQuicSafeHandle : SafeHandle
{
    // The index must correspond to SafeHandleType enum value and the value must correspond to MsQuic logging abbreviation string.
    // This is used for our logging that uses the same format of object identification as MsQuic to easily correlate log events.
    private static readonly string[] s_typeName = new string[]
    {
        " reg",
        "cnfg",
        "list",
        "conn",
        "strm"
    };
 
    private readonly delegate* unmanaged[Cdecl]<QUIC_HANDLE*, void> _releaseAction;
    private string? _traceId;
    private readonly SafeHandleType _type;
 
    public override bool IsInvalid => handle == IntPtr.Zero;
 
    public QUIC_HANDLE* QuicHandle => (QUIC_HANDLE*)DangerousGetHandle();
 
    public MsQuicSafeHandle(QUIC_HANDLE* handle, delegate* unmanaged[Cdecl]<QUIC_HANDLE*, void> releaseAction, SafeHandleType safeHandleType)
        : base((IntPtr)handle, ownsHandle: true)
    {
        _releaseAction = releaseAction;
        _type = safeHandleType;
 
        if (NetEventSource.Log.IsEnabled())
        {
            NetEventSource.Info(this, $"{this} MsQuicSafeHandle created");
        }
    }
 
    public MsQuicSafeHandle(QUIC_HANDLE* handle, SafeHandleType safeHandleType)
        : this(
            handle,
            safeHandleType switch
            {
                SafeHandleType.Registration => MsQuicApi.Api.ApiTable->RegistrationClose,
                SafeHandleType.Configuration => MsQuicApi.Api.ApiTable->ConfigurationClose,
                SafeHandleType.Listener => MsQuicApi.Api.ApiTable->ListenerClose,
                SafeHandleType.Connection => MsQuicApi.Api.ApiTable->ConnectionClose,
                SafeHandleType.Stream => MsQuicApi.Api.ApiTable->StreamClose,
                _ => throw new ArgumentException($"Unexpected value: {safeHandleType}", nameof(safeHandleType))
            },
            safeHandleType)
    { }
 
    protected override bool ReleaseHandle()
    {
        QUIC_HANDLE* quicHandle = QuicHandle;
        SetHandle(IntPtr.Zero);
        _releaseAction(quicHandle);
 
        if (NetEventSource.Log.IsEnabled())
        {
            NetEventSource.Info(this, $"{this} MsQuicSafeHandle released");
        }
 
        return true;
    }
 
    public override string ToString() => _traceId ??= $"[{s_typeName[(int)_type]}][0x{DangerousGetHandle():X11}]";
}
 
internal enum SafeHandleType
{
    Registration,
    Configuration,
    Listener,
    Connection,
    Stream
}
 
internal sealed class MsQuicContextSafeHandle : MsQuicSafeHandle
{
    /// <summary>
    /// Holds a weak reference to the managed instance.
    /// It allows delegating MsQuic events to the managed object while it still can be collected and finalized.
    /// </summary>
    private GCHandle _context;
 
    /// <summary>
    /// Optional parent safe handle, used to increment/decrement reference count with the lifetime of this instance.
    /// </summary>
    private readonly MsQuicSafeHandle? _parent;
 
    /// <summary>
    /// Additional, dependent object to be disposed only after the safe handle gets released.
    /// </summary>
    private IDisposable? _disposable;
 
    public IDisposable Disposable
    {
        set
        {
            Debug.Assert(_disposable is null);
            _disposable = value;
        }
    }
 
    public unsafe MsQuicContextSafeHandle(QUIC_HANDLE* handle, GCHandle context, SafeHandleType safeHandleType, MsQuicSafeHandle? parent = null)
        : base(handle, safeHandleType)
    {
        _context = context;
        if (parent is not null)
        {
            bool success = false;
            parent.DangerousAddRef(ref success);
            _parent = parent;
            if (NetEventSource.Log.IsEnabled())
            {
                NetEventSource.Info(this, $"{this} {_parent} ref count incremented");
            }
        }
    }
 
    protected override unsafe bool ReleaseHandle()
    {
        base.ReleaseHandle();
        if (_context.IsAllocated)
        {
            _context.Free();
        }
        if (_parent is not null)
        {
            _parent.DangerousRelease();
            if (NetEventSource.Log.IsEnabled())
            {
                NetEventSource.Info(this, $"{this} {_parent} ref count decremented");
            }
        }
        _disposable?.Dispose();
        return true;
    }
}
 
internal sealed class MsQuicConfigurationSafeHandle : MsQuicSafeHandle, ISafeHandleCachable
{
    // MsQuicConfiguration handles are cached, so we need to keep track of the
    // number of times a handle is rented. Once we decide to dispose the handle,
    // we set the _rentCount to -1.
    private volatile int _rentCount;
 
    public unsafe MsQuicConfigurationSafeHandle(QUIC_HANDLE* handle)
        : base(handle, SafeHandleType.Configuration) { }
 
    public bool TryAddRentCount()
    {
        int oldCount;
 
        do
        {
            oldCount = _rentCount;
            if (oldCount < 0)
            {
                // The handle is already disposed.
                return false;
            }
        } while (Interlocked.CompareExchange(ref _rentCount, oldCount + 1, oldCount) != oldCount);
 
        return true;
    }
 
    public bool TryMarkForDispose()
    {
        return Interlocked.CompareExchange(ref _rentCount, -1, 0) == 0;
    }
 
    protected override void Dispose(bool disposing)
    {
        if (Interlocked.Decrement(ref _rentCount) < 0)
        {
            // _rentCount is 0 if the handle was never rented (e.g. failure during creation),
            // and is -1 when evicted from cache.
            base.Dispose(disposing);
        }
    }
}