using System; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; namespace Capnp.Rpc { /// /// A skeleton is a wrapper around a capability interface implementation which adapts it in the way it is /// expected by the . /// public abstract class Skeleton: IProvidedCapability { class SkeletonRelinquisher: IDisposable { readonly Skeleton _skeleton; public SkeletonRelinquisher(Skeleton skeleton) { _skeleton = skeleton; } public void Dispose() { _skeleton.Relinquish(); } } #if DEBUG_DISPOSE const int NoDisposeFlag = 0x4000000; #endif static readonly ConditionalWeakTable _implMap = new ConditionalWeakTable(); internal static Skeleton GetOrCreateSkeleton(T impl, bool addRef) where T: class { if (impl == null) throw new ArgumentNullException(nameof(impl)); if (impl is Skeleton skel) return skel; skel = _implMap.GetValue(impl, _ => CapabilityReflection.CreateSkeleton(_)); if (addRef) { skel.Claim(); } return skel; } /// /// Claims ownership on the given capability, preventing its automatic disposal. /// /// Capability interface /// Capability implementation /// A disposable object. Calling Dispose() on the returned instance relinquishes ownership again. public static IDisposable Claim(T impl) where T: class { return new SkeletonRelinquisher(GetOrCreateSkeleton(impl, true)); } #if DEBUG_DISPOSE /// /// This DEBUG-only diagnostic method states that the Skeleton corresponding to a given capability is not expected to /// be disposed until the next call to EndAssertNotDisposed(). /// /// Capability interface /// Capability implementation public static void BeginAssertNotDisposed(T impl) where T : class { GetOrCreateSkeleton(impl, false).BeginAssertNotDisposed(); } /// /// This DEBUG-only diagnostic method ends a non-disposal period started with BeginAssertNotDisposed. /// /// Capability interface /// Capability implementation public static void EndAssertNotDisposed(T impl) where T : class { GetOrCreateSkeleton(impl, false).EndAssertNotDisposed(); } #endif int _refCount = 0; /// /// Calls an interface method of this capability. /// /// ID of interface to call /// ID of method to call /// Method arguments ("params struct") /// Cancellation token, indicating when the call should cancelled. /// A Task which will resolve to the call result public abstract Task Invoke(ulong interfaceId, ushort methodId, DeserializerState args, CancellationToken cancellationToken = default); internal void Claim() { Interlocked.Increment(ref _refCount); } #if DEBUG_DISPOSE internal void BeginAssertNotDisposed() { if ((Interlocked.Add(ref _refCount, NoDisposeFlag) & NoDisposeFlag) == 0) { throw new InvalidOperationException("Flag already set. State is now broken."); } } internal void EndAssertNotDisposed() { if ((Interlocked.Add(ref _refCount, -NoDisposeFlag) & NoDisposeFlag) != 0) { throw new InvalidOperationException("Flag already cleared. State is now broken."); } } #endif internal void Relinquish() { int count = Interlocked.Decrement(ref _refCount); if (0 == count) { #if DEBUG_DISPOSE if ((count & NoDisposeFlag) != 0) throw new InvalidOperationException("Unexpected Skeleton disposal"); #endif Dispose(true); GC.SuppressFinalize(this); } } internal void Relinquish(int count) { if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); while (count-- > 0) Relinquish(); } /// /// Dispose pattern implementation /// protected virtual void Dispose(bool disposing) { } /// /// Finalizer /// ~Skeleton() { Dispose(false); } internal virtual void Bind(object impl) { throw new NotSupportedException(); } } /// /// Skeleton for a specific capability interface. /// /// Capability interface public abstract class Skeleton : Skeleton, IMonoSkeleton { #if DebugEmbargos ILogger Logger { get; } = Logging.CreateLogger>(); #endif Func>[] _methods = null!; CancellationTokenSource? _disposed = new CancellationTokenSource(); readonly object _reentrancyBlocker = new object(); int _pendingCalls; /// /// Constructs an instance. /// public Skeleton() { } /// /// Populates this skeleton's method table. The method table maps method IDs (which are consecutively numbered from 0 /// onwards) to the underlying capability's method implementations. /// /// The method table. Index is method ID. protected void SetMethodTable(params Func>[] methods) { _methods = methods; } /// /// Gets the underlying capability implementation. /// protected T Impl { get; private set; } = default!; /// /// Gets the ID of the implemented interface. /// public abstract ulong InterfaceId { get; } /// /// Calls an interface method of this capability. /// /// ID of interface to call /// ID of method to call /// Method arguments ("params struct") /// Cancellation token, indicating when the call should cancelled. /// A Task which will resolve to the call result /// This Skeleton was disposed public override async Task Invoke(ulong interfaceId, ushort methodId, DeserializerState args, CancellationToken cancellationToken = default) { if (InterfaceId != InterfaceId) throw new NotImplementedException("Wrong interface id"); if (methodId >= _methods.Length) throw new NotImplementedException("Wrong method id"); lock (_reentrancyBlocker) { if (_disposed == null || _disposed.IsCancellationRequested) { throw new ObjectDisposedException(nameof(Skeleton)); } ++_pendingCalls; } var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(_disposed.Token, cancellationToken); try { return await _methods[methodId](args, linkedSource.Token); } catch (System.Exception) { throw; } finally { lock (_reentrancyBlocker) { --_pendingCalls; } linkedSource.Dispose(); CheckCtsDisposal(); } } void CheckCtsDisposal() { if (_pendingCalls == 0 && _disposed != null && _disposed.IsCancellationRequested) { _disposed.Dispose(); _disposed = null; } } /// /// Dispose pattern implementation /// protected override void Dispose(bool disposing) { lock (_reentrancyBlocker) { if (_disposed == null || _disposed.IsCancellationRequested) return; _disposed.Cancel(); CheckCtsDisposal(); } if (Impl is IDisposable disposable) { disposable.Dispose(); } } internal override void Bind(object impl) { if (Impl != null) throw new InvalidOperationException("Skeleton was already bound"); Impl = (T)impl; } } }