using Microsoft.Extensions.Logging; using System; using System.Threading; using System.Threading.Tasks; namespace Capnp.Rpc { /// /// Application-level wrapper for consumer-side capabilities. /// The code generator will produce a Proxy specialization for each capability interface. /// public class Proxy : IDisposable, IResolvingCapability { #if DebugFinalizers ILogger Logger { get; } = Logging.CreateLogger(); #endif bool _disposedValue = false; /// /// Will eventually give the resolved capability, if this is a promised capability. /// public Task WhenResolved { get { if (ConsumedCap is IResolvingCapability resolving) { return resolving.WhenResolved; } else { return Task.FromResult(this); } } } /// /// Underlying low-level capability /// protected internal ConsumedCapability ConsumedCap { get; private set; } /// /// Whether is this a broken capability. /// public bool IsNull => ConsumedCap == null; static async void DisposeCtrWhenReturned(CancellationTokenRegistration ctr, IPromisedAnswer answer) { try { await answer.WhenReturned; } catch { } finally { ctr.Dispose(); } } /// /// Calls a method of this capability. /// /// Interface ID to call /// Method ID to call /// Method arguments ("param struct") /// Whether it is a tail call /// For cancelling an ongoing method call /// An answer promise /// This instance was disposed, or transport-layer stream was disposed. /// Capability is broken. /// An I/O error occurs. protected internal IPromisedAnswer Call(ulong interfaceId, ushort methodId, DynamicSerializerState args, bool tailCall, CancellationToken cancellationToken = default) { if (_disposedValue) throw new ObjectDisposedException(nameof(Proxy)); if (ConsumedCap == null) throw new InvalidOperationException("Cannot call null capability"); var answer = ConsumedCap.DoCall(interfaceId, methodId, args, tailCall); if (cancellationToken.CanBeCanceled) { DisposeCtrWhenReturned(cancellationToken.Register(answer.Dispose), answer); } return answer; } /// /// Constructs a null instance. /// public Proxy() { } internal Proxy(ConsumedCapability cap) { Bind(cap); } internal void Bind(ConsumedCapability cap) { if (ConsumedCap != null) throw new InvalidOperationException("Proxy was already bound"); if (cap == null) return; ConsumedCap = cap; cap.AddRef(); } internal IProvidedCapability GetProvider() { switch (ConsumedCap) { case LocalCapability lcap: return lcap.ProvidedCap; case null: return null; default: return Vine.Create(ConsumedCap); } } /// /// Dispose pattern implementation /// protected virtual void Dispose(bool disposing) { if (!_disposedValue) { ConsumedCap?.Release(); _disposedValue = true; } } /// /// Finalizer /// ~Proxy() { #if DebugFinalizers Logger.LogWarning($"Caught orphaned Proxy, created from {CreatorMemberName} in {CreatorFilePath}, line {CreatorLineNumber}."); #endif Dispose(false); } /// /// Dispose pattern implementation /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Casts this Proxy to a different capability interface. /// /// Desired capability interface /// Whether to Dispose() this Proxy instance /// Proxy for desired capability interface /// did not qualify as capability interface. /// This capability is broken, or mismatch between generic type arguments (if capability interface is generic). /// Mismatch between generic type arguments (if capability interface is generic). /// Problem with instatiating the Proxy (constructor threw exception). /// Caller does not have permission to invoke the Proxy constructor. /// Problem with building the Proxy type, or problem with loading some dependent class. public T Cast(bool disposeThis) where T: class { if (IsNull) throw new InvalidOperationException("Capability is broken"); using (disposeThis ? this : null) { return CapabilityReflection.CreateProxy(ConsumedCap) as T; } } internal void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer) { if (_disposedValue) throw new ObjectDisposedException(nameof(Proxy)); if (ConsumedCap == null) writer.which = CapDescriptor.WHICH.None; else ConsumedCap.Export(endpoint, writer); } internal void Freeze(out IRpcEndpoint boundEndpoint) { if (_disposedValue) throw new ObjectDisposedException(nameof(Proxy)); boundEndpoint = null; ConsumedCap?.Freeze(out boundEndpoint); } internal void Unfreeze() { if (_disposedValue) throw new ObjectDisposedException(nameof(Proxy)); ConsumedCap?.Unfreeze(); } #if DebugFinalizers public string CreatorMemberName { get; set; } public string CreatorFilePath { get; set; } public int CreatorLineNumber { get; set; } #endif } }