alef 0.62.0

Opinionated polyglot binding generator for Rust libraries
Documentation
// This file is auto-generated by alef. DO NOT EDIT.
#nullable enable

using System;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
{% if has_methods %}
using System.Text.Json;
using System.Text.Json.Serialization;
{% endif %}
{% if needs_list %}
using System.Collections.Generic;
{% endif %}
{% if needs_async %}
using System.Threading.Tasks;
{% endif %}
{% if needs_streaming %}
using System.Threading;
using System.Runtime.CompilerServices;
{% endif %}

namespace {{ namespace }};

// The native handle is the scalar `AlefHandle` (`uint64_t`) registry index, not a pointer.
// `SafeHandle`'s only storage slot is a pointer-width `IntPtr`, so the `ulong` is packed into
// that slot as a raw bit pattern (never dereferenced) to keep `SafeHandle`'s guaranteed
// finalization and `DangerousAddRef`/`DangerousRelease` ref-counting, which the borrow/lease/
// transfer machinery below builds on. This is lossless on every currently supported .NET
// target (IntPtr is 8 bytes on all 64-bit platforms .NET 5+ ships on); on a true 32-bit
// process a handle value above `uint.MaxValue` would truncate, but that requires over four
// billion concurrently live handles of this type, and 32-bit .NET targets are legacy-only. ~keep
internal sealed class {{ class_name }}SafeHandle : SafeHandle
{
    internal {{ class_name }}SafeHandle(ulong handle) : base(IntPtr.Zero, true)
    {
        SetHandle(Pack(handle));
    }

    public override bool IsInvalid => handle == IntPtr.Zero;

    internal static IntPtr Pack(ulong value) => unchecked((IntPtr)(long)value);

    internal static ulong Unpack(IntPtr value) => unchecked((ulong)(long)value);

    protected override bool ReleaseHandle()
    {
        NativeMethods.{{ free_method }}(Unpack(handle));
        return true;
    }
}
{% if doc -%}
/// <summary>
{%- for line in doc_lines %}
/// {{ line }}
{%- endfor %}
/// </summary>
{%- endif %}

public sealed class {{ class_name }} : IDisposable
{
{%- if has_methods %}
    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        Converters = { new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower) },
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
    };

    private static readonly JsonSerializerOptions JsonSerializationOptions = new()
    {
        Converters = { new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower) },
    };
{%- endif %}

    private {{ class_name }}SafeHandle _safeHandle;
    private readonly object _handleLock = new();
    private int _activeBorrows;
    private bool _handleUnavailable;
    private bool _transferPending;

    internal sealed class HandleLease : IDisposable
    {
        private {{ class_name }}? _owner;

        internal HandleLease({{ class_name }} owner, ulong handle)
        {
            _owner = owner;
            Handle = handle;
        }

        internal ulong Handle { get; }

        public void Dispose()
        {
            var owner = global::System.Threading.Interlocked.Exchange(ref _owner, null);
            owner?.ReleaseBorrow();
        }
    }

    private sealed class HandleTransfer : IDisposable
    {
        private {{ class_name }}? _owner;
        private {{ class_name }}SafeHandle? _transferredHandle;

        internal HandleTransfer({{ class_name }} owner, ulong handle)
        {
            _owner = owner;
            _transferredHandle = new {{ class_name }}SafeHandle(handle);
        }

        internal ulong Handle => _transferredHandle is { } transferredHandle
            ? {{ class_name }}SafeHandle.Unpack(transferredHandle.DangerousGetHandle())
            : throw new InvalidOperationException("Handle transfer is no longer active");

        internal void Commit()
        {
            var transferredHandle = _transferredHandle
                ?? throw new InvalidOperationException("Handle transfer is no longer active");
            transferredHandle.SetHandleAsInvalid();
            _transferredHandle = null;
            var owner = _owner;
            _owner = null;
            owner?.CommitTransfer();
        }

        public void Dispose()
        {
            var transferredHandle = global::System.Threading.Interlocked.Exchange(ref _transferredHandle, null);
            var owner = global::System.Threading.Interlocked.Exchange(ref _owner, null);
            if (transferredHandle is null) return;
            if (owner is null)
            {
                transferredHandle.Dispose();
                return;
            }
            var handle = {{ class_name }}SafeHandle.Unpack(transferredHandle.DangerousGetHandle());
            transferredHandle.SetHandleAsInvalid();
            owner.RollbackTransfer(handle);
        }
    }

    internal {{ class_name }}(ulong handle)
    {
        _safeHandle = new {{ class_name }}SafeHandle(handle);
    }

    internal ulong Handle => GetHandle();

    private ulong GetHandle()
    {
        lock (_handleLock)
        {
            ThrowIfHandleUnavailable();
            return {{ class_name }}SafeHandle.Unpack(_safeHandle.DangerousGetHandle());
        }
    }

    internal HandleLease BorrowHandle()
    {
        lock (_handleLock)
        {
            ThrowIfHandleUnavailable();
            var lease = new HandleLease(this, {{ class_name }}SafeHandle.Unpack(_safeHandle.DangerousGetHandle()));
            var addRefSucceeded = false;
            _safeHandle.DangerousAddRef(ref addRefSucceeded);
            if (!addRefSucceeded) throw new ObjectDisposedException(nameof({{ class_name }}));
            _activeBorrows++;
            return lease;
        }
    }

    private void ReleaseBorrow()
    {
        lock (_handleLock)
        {
            _safeHandle.DangerousRelease();
            _activeBorrows--;
            if (_activeBorrows == 0) global::System.Threading.Monitor.PulseAll(_handleLock);
        }
    }

    private HandleTransfer TakeHandle()
    {
        lock (_handleLock)
        {
            ThrowIfHandleUnavailable();
            WaitForBorrowedCalls();
            var transfer = new HandleTransfer(this, {{ class_name }}SafeHandle.Unpack(_safeHandle.DangerousGetHandle()));
            _handleUnavailable = true;
            _safeHandle.SetHandleAsInvalid();
            _transferPending = true;
            return transfer;
        }
    }

    private void RollbackTransfer(ulong handle)
    {
        lock (_handleLock)
        {
            _transferPending = false;
            _safeHandle = new {{ class_name }}SafeHandle(handle);
            _handleUnavailable = false;
            global::System.Threading.Monitor.PulseAll(_handleLock);
        }
    }

    private void CommitTransfer()
    {
        lock (_handleLock)
        {
            _transferPending = false;
            global::System.Threading.Monitor.PulseAll(_handleLock);
        }
    }

    private void WaitForBorrowedCalls()
    {
        var interrupted = false;
        while (_activeBorrows != 0)
        {
            try
            {
                global::System.Threading.Monitor.Wait(_handleLock);
            }
            catch (global::System.Threading.ThreadInterruptedException)
            {
                interrupted = true;
            }
        }
        if (interrupted) global::System.Threading.Thread.CurrentThread.Interrupt();
    }

    private void WaitForPendingTransfer()
    {
        var interrupted = false;
        while (_transferPending)
        {
            try
            {
                global::System.Threading.Monitor.Wait(_handleLock);
            }
            catch (global::System.Threading.ThreadInterruptedException)
            {
                interrupted = true;
            }
        }
        if (interrupted) global::System.Threading.Thread.CurrentThread.Interrupt();
    }

    private void ThrowIfHandleUnavailable()
    {
        if (_handleUnavailable || _safeHandle.IsClosed || _safeHandle.IsInvalid)
        {
            throw new ObjectDisposedException(nameof({{ class_name }}));
        }
    }

    public void Dispose()
    {
        lock (_handleLock)
        {
            if (_transferPending)
            {
                WaitForPendingTransfer();
                if (_handleUnavailable) return;
            }
            if (_handleUnavailable) return;
            _handleUnavailable = true;
            WaitForBorrowedCalls();
            _safeHandle.Dispose();
        }
    }