using Capnp.Util; using Microsoft.Extensions.Logging; using System; using System.Diagnostics; 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 { /// /// Creates a new proxy object for an existing implementation or proxy, sharing its ownership. /// /// Capability interface /// instance to share /// public static T Share(T obj) where T : class { if (obj is Proxy proxy) return proxy.Cast(false); else return BareProxy.FromImpl(obj).Cast(true); } bool _disposedValue = false; /// /// Completes when the capability gets resolved. /// public StrictlyOrderedAwaitTask WhenResolved { get { return ConsumedCap is IResolvingCapability resolving ? resolving.WhenResolved : Task.CompletedTask.EnforceAwaitOrder(); } } /// /// Returns the resolved capability /// /// Capability interface or /// the resolved capability, or null if it did not resolve yet public T? GetResolvedCapability() where T : class { if (ConsumedCap is IResolvingCapability resolving) return resolving.GetResolvedCapability(); else return CapabilityReflection.CreateProxy(ConsumedCap) as T; } ConsumedCapability _consumedCap = NullCapability.Instance; /// /// Underlying low-level capability /// public ConsumedCapability ConsumedCap => _disposedValue ? throw new ObjectDisposedException(nameof(Proxy)) : _consumedCap; /// /// Whether is this a broken capability. /// public bool IsNull => _consumedCap == NullCapability.Instance; /// /// Whether was called on this Proxy. /// public bool IsDisposed => _disposedValue; 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") /// This flag is ignored. It is there to preserve compatibility with the /// code generator and will be removed in future versions. /// 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 obsoleteAndIgnored, CancellationToken cancellationToken = default) { if (_disposedValue) { args.Dispose(); throw new ObjectDisposedException(nameof(Proxy)); } var answer = ConsumedCap.DoCall(interfaceId, methodId, args); if (cancellationToken.CanBeCanceled) { DisposeCtrWhenReturned(cancellationToken.Register(answer.Dispose), answer); } return answer; } /// /// Constructs a null instance. /// public Proxy() { #if DebugFinalizers CreatorStackTrace = Environment.StackTrace; #endif } internal Proxy(ConsumedCapability cap): this() { Bind(cap); } internal void Bind(ConsumedCapability cap) { if (ConsumedCap != NullCapability.Instance) throw new InvalidOperationException("Proxy was already bound"); _consumedCap = cap ?? throw new ArgumentNullException(nameof(cap)); cap.AddRef(); #if DebugFinalizers if (_consumedCap != null) _consumedCap.OwningProxy = this; #endif } internal async Task GetProvider() { var unwrapped = await ConsumedCap.Unwrap(); return unwrapped.AsSkeleton(); } /// /// Dispose pattern implementation /// protected virtual void Dispose(bool disposing) { if (!_disposedValue) { if (disposing) { _consumedCap.Release(); } else { // When called from the Finalizer, we must not throw. // But when reference counting goes wrong, ConsumedCapability.Release() will throw an InvalidOperationException. // The only option here is to suppress that exception. try { _consumedCap?.Release(); } catch { } } _disposedValue = true; } } /// /// Finalizer /// ~Proxy() { #if DebugFinalizers Debugger.Log(0, "DebugFinalizers", $"Caught orphaned Proxy, created from here: {CreatorStackTrace}."); #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. /// 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 { using (disposeThis ? this : null) { return (CapabilityReflection.CreateProxy(ConsumedCap) as T)!; } } internal Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer) { if (_disposedValue) throw new ObjectDisposedException(nameof(Proxy)); if (ConsumedCap == null) { writer.which = CapDescriptor.WHICH.None; return null; } else { return ConsumedCap.Export(endpoint, writer); } } #if DebugFinalizers string CreatorStackTrace { get; set; } #endif } }