File: src\System\Runtime\DependentHandle.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
 
namespace System.Runtime
{
    /// <summary>
    /// Represents a dependent GC handle, which will conditionally keep a dependent object instance alive as long as
    /// a target object instance is alive as well, without representing a strong reference to the target instance.
    /// </summary>
    /// <remarks>
    /// A <see cref="DependentHandle"/> value with a given object instance as target will not cause the target
    /// to be kept alive if there are no other strong references to it, but it will do so for the dependent
    /// object instance as long as the target is alive.
    /// <para>
    /// Using this type is conceptually equivalent to having a weak reference to a given target object instance A,
    /// with that object having a field or property (or some other strong reference) to a dependent object instance B.
    /// </para>
    /// <para>
    /// The <see cref="DependentHandle"/> type is not thread-safe, and consumers are responsible for ensuring that
    /// <see cref="Dispose"/> is not called concurrently with other APIs. Not doing so results in undefined behavior.
    /// </para>
    /// <para>
    /// The <see cref="IsAllocated"/>, <see cref="Target"/>, <see cref="Dependent"/> and <see cref="TargetAndDependent"/>
    /// properties are instead thread-safe, and safe to use if <see cref="Dispose"/> is not concurrently invoked as well.
    /// </para>
    /// </remarks>
    public partial struct DependentHandle : IDisposable
    {
        // =========================================================================================
        // This struct collects all operations on native DependentHandles. The DependentHandle
        // merely wraps an IntPtr so this struct serves mainly as a "managed typedef."
        //
        // DependentHandles exist in one of two states:
        //
        //    IsAllocated == false
        //        No actual handle is allocated underneath. Illegal to get Target, Dependent
        //        or GetTargetAndDependent(). Ok to call Dispose().
        //
        //        Initializing a DependentHandle using the nullary ctor creates a DependentHandle
        //        that's in the !IsAllocated state.
        //        (! Right now, we get this guarantee for free because (IntPtr)0 == NULL unmanaged handle.
        //         ! If that assertion ever becomes false, we'll have to add an _isAllocated field
        //         ! to compensate.)
        //
        //
        //    IsAllocated == true
        //        There's a handle allocated underneath. You must call Dispose() on this eventually
        //        or you cause a native handle table leak.
        //
        // This struct intentionally does no self-synchronization. It's up to the caller to
        // to use DependentHandles in a thread-safe way.
        // =========================================================================================
 
        private IntPtr _handle;
 
        /// <summary>
        /// Initializes a new instance of the <see cref="DependentHandle"/> struct with the specified arguments.
        /// </summary>
        /// <param name="target">The target object instance to track.</param>
        /// <param name="dependent">The dependent object instance to associate with <paramref name="target"/>.</param>
        public DependentHandle(object? target, object? dependent)
        {
            IntPtr handle = InternalAlloc(target, dependent);
            if (handle == 0)
                handle = InternalAllocWithGCTransition(target, dependent);
            _handle = handle;
        }
 
        /// <summary>
        /// Gets a value indicating whether this instance was constructed with
        /// <see cref="DependentHandle(object?, object?)"/> and has not yet been disposed.
        /// </summary>
        /// <remarks>This property is thread-safe.</remarks>
        public readonly bool IsAllocated => _handle != 0;
 
        /// <summary>
        /// Gets or sets the target object instance for the current handle. The target can only be set to a <see langword="null"/> value
        /// once the <see cref="DependentHandle"/> instance has been created. Doing so will cause <see cref="Dependent"/> to start
        /// returning <see langword="null"/> as well, and to become eligible for collection even if the previous target is still alive.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// Thrown if <see cref="IsAllocated"/> is <see langword="false"/> or if the input value is not <see langword="null"/>.</exception>
        /// <remarks>This property is thread-safe.</remarks>
        public object? Target
        {
            readonly get
            {
                IntPtr handle = _handle;
 
                if (handle == 0)
                {
                    ThrowHelper.ThrowInvalidOperationException();
                }
 
                return InternalGetTarget(handle);
            }
            set
            {
                IntPtr handle = _handle;
 
                if (handle == 0 || value is not null)
                {
                    ThrowHelper.ThrowInvalidOperationException();
                }
 
                InternalSetTargetToNull(handle);
            }
        }
 
        /// <summary>
        /// Gets or sets the dependent object instance for the current handle.
        /// </summary>
        /// <remarks>
        /// If it is needed to retrieve both <see cref="Target"/> and <see cref="Dependent"/>, it is necessary
        /// to ensure that the returned instance from <see cref="Target"/> will be kept alive until <see cref="Dependent"/>
        /// is retrieved as well, or it might be collected and result in unexpected behavior. This can be done by storing the
        /// target in a local and calling <see cref="GC.KeepAlive(object)"/> on it after <see cref="Dependent"/> is accessed.
        /// </remarks>
        /// <exception cref="InvalidOperationException">Thrown if <see cref="IsAllocated"/> is <see langword="false"/>.</exception>
        /// <remarks>This property is thread-safe.</remarks>
        public object? Dependent
        {
            readonly get
            {
                IntPtr handle = _handle;
 
                if (handle == 0)
                {
                    ThrowHelper.ThrowInvalidOperationException();
                }
 
                return InternalGetDependent(handle);
            }
            set
            {
                IntPtr handle = _handle;
 
                if (handle == 0)
                {
                    ThrowHelper.ThrowInvalidOperationException();
                }
 
                InternalSetDependent(handle, value);
            }
        }
 
        /// <summary>
        /// Gets the values of both <see cref="Target"/> and <see cref="Dependent"/> (if available) as an atomic operation.
        /// That is, even if <see cref="Target"/> is concurrently set to <see langword="null"/>, calling this method
        /// will either return <see langword="null"/> for both target and dependent, or return both previous values.
        /// If <see cref="Target"/> and <see cref="Dependent"/> were used sequentially in this scenario instead, it
        /// would be possible to sometimes successfully retrieve the previous target, but then fail to get the dependent.
        /// </summary>
        /// <returns>The values of <see cref="Target"/> and <see cref="Dependent"/>.</returns>
        /// <exception cref="InvalidOperationException">Thrown if <see cref="IsAllocated"/> is <see langword="false"/>.</exception>
        /// <remarks>This property is thread-safe.</remarks>
        public readonly (object? Target, object? Dependent) TargetAndDependent
        {
            get
            {
                IntPtr handle = _handle;
 
                if (handle == 0)
                {
                    ThrowHelper.ThrowInvalidOperationException();
                }
 
                object? target = InternalGetTargetAndDependent(handle, out object? dependent);
 
                return (target, dependent);
            }
        }
 
        /// <summary>
        /// Gets the target object instance for the current handle.
        /// </summary>
        /// <returns>The target object instance, if present.</returns>
        /// <remarks>This method mirrors <see cref="Target"/>, but without the allocation check.</remarks>
        internal readonly object? UnsafeGetTarget()
        {
            return InternalGetTarget(_handle);
        }
 
        /// <summary>
        /// Atomically retrieves the values of both <see cref="Target"/> and <see cref="Dependent"/>, if available.
        /// </summary>
        /// <param name="dependent">The dependent instance, if available.</param>
        /// <returns>The values of <see cref="Target"/> and <see cref="Dependent"/>.</returns>
        /// <remarks>
        /// This method mirrors the <see cref="TargetAndDependent"/> property, but without the allocation check.
        /// The signature is also kept the same as the one for the internal call, to improve the codegen.
        /// Note that <paramref name="dependent"/> is required to be on the stack (or it might not be tracked).
        /// </remarks>
        internal readonly object? UnsafeGetTargetAndDependent(out object? dependent)
        {
            return InternalGetTargetAndDependent(_handle, out dependent);
        }
 
        /// <summary>
        /// Sets the dependent object instance for the current handle to <see langword="null"/>.
        /// </summary>
        /// <remarks>This method mirrors the <see cref="Target"/> setter, but without allocation and input checks.</remarks>
        internal readonly void UnsafeSetTargetToNull()
        {
            InternalSetTargetToNull(_handle);
        }
 
        /// <summary>
        /// Sets the dependent object instance for the current handle.
        /// </summary>
        /// <remarks>This method mirrors <see cref="Dependent"/>, but without the allocation check.</remarks>
        internal readonly void UnsafeSetDependent(object? dependent)
        {
            InternalSetDependent(_handle, dependent);
        }
 
        /// <inheritdoc cref="IDisposable.Dispose"/>
        /// <remarks>This method is not thread-safe.</remarks>
        public void Dispose()
        {
            // Forces the DependentHandle back to non-allocated state
            // (if not already there) and frees the handle if needed.
            IntPtr handle = _handle;
 
            if (handle != 0)
            {
                _handle = 0;
 
                if (!InternalFree(handle))
                {
                    InternalFreeWithGCTransition(handle);
                }
            }
        }
 
        [MethodImpl(MethodImplOptions.InternalCall)]
        private static extern IntPtr InternalAlloc(object? target, object? dependent);
 
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static IntPtr InternalAllocWithGCTransition(object? target, object? dependent)
            => _InternalAllocWithGCTransition(ObjectHandleOnStack.Create(ref target), ObjectHandleOnStack.Create(ref dependent));
 
        [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "DependentHandle_InternalAllocWithGCTransition")]
        private static partial IntPtr _InternalAllocWithGCTransition(ObjectHandleOnStack target, ObjectHandleOnStack dependent);
 
#if DEBUG
        [MethodImpl(MethodImplOptions.InternalCall)]
        private static extern object? InternalGetTarget(IntPtr dependentHandle);
#else
        // This optimization is the same that is used in GCHandle in RELEASE mode.
        // This is not used in DEBUG builds as the runtime performs additional checks.
        // The logic below is the inlined copy of ObjectFromHandle in the unmanaged runtime.
        private static unsafe object? InternalGetTarget(IntPtr dependentHandle) => *(object*)dependentHandle;
#endif
 
        [MethodImpl(MethodImplOptions.InternalCall)]
        private static extern object? InternalGetDependent(IntPtr dependentHandle);
 
        [MethodImpl(MethodImplOptions.InternalCall)]
        private static extern object? InternalGetTargetAndDependent(IntPtr dependentHandle, out object? dependent);
 
        [MethodImpl(MethodImplOptions.InternalCall)]
        private static extern void InternalSetDependent(IntPtr dependentHandle, object? dependent);
 
        [MethodImpl(MethodImplOptions.InternalCall)]
        private static extern void InternalSetTargetToNull(IntPtr dependentHandle);
 
        [MethodImpl(MethodImplOptions.InternalCall)]
        private static extern bool InternalFree(IntPtr dependentHandle);
 
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static void InternalFreeWithGCTransition(IntPtr dependentHandle)
            => _InternalFreeWithGCTransition(dependentHandle);
 
        [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "DependentHandle_InternalFreeWithGCTransition")]
        private static partial void _InternalFreeWithGCTransition(IntPtr dependentHandle);
    }
}