2020-04-23 22:34:45 +02:00
|
|
|
|
using Capnp.Util;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
2020-03-21 13:27:46 +01:00
|
|
|
|
using System;
|
2020-04-10 00:01:12 +02:00
|
|
|
|
using System.Diagnostics;
|
2019-06-12 21:56:55 +02:00
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
namespace Capnp.Rpc
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Application-level wrapper for consumer-side capabilities.
|
|
|
|
|
/// The code generator will produce a Proxy specialization for each capability interface.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class Proxy : IDisposable, IResolvingCapability
|
|
|
|
|
{
|
2020-03-22 16:08:28 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Creates a new proxy object for an existing implementation or proxy, sharing its ownership.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="T">Capability interface</typeparam>
|
|
|
|
|
/// <param name="obj">instance to share</param>
|
|
|
|
|
/// <returns></returns>
|
2020-04-03 22:10:40 +02:00
|
|
|
|
public static T Share<T>(T obj) where T : class
|
2020-03-10 21:55:34 +01:00
|
|
|
|
{
|
|
|
|
|
if (obj is Proxy proxy)
|
|
|
|
|
return proxy.Cast<T>(false);
|
|
|
|
|
else
|
|
|
|
|
return BareProxy.FromImpl(obj).Cast<T>(true);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-12 21:56:55 +02:00
|
|
|
|
bool _disposedValue = false;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2020-04-03 22:10:40 +02:00
|
|
|
|
/// Completes when the capability gets resolved.
|
2019-06-12 21:56:55 +02:00
|
|
|
|
/// </summary>
|
2020-04-23 22:34:45 +02:00
|
|
|
|
public StrictlyOrderedAwaitTask WhenResolved
|
2019-06-12 21:56:55 +02:00
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
2020-04-03 22:10:40 +02:00
|
|
|
|
return ConsumedCap is IResolvingCapability resolving ?
|
2020-04-23 22:34:45 +02:00
|
|
|
|
resolving.WhenResolved : Task.CompletedTask.EnforceAwaitOrder();
|
2019-06-12 21:56:55 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-03 22:10:40 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns the resolved capability
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="T">Capability interface or <see cref="BareProxy"/></typeparam>
|
|
|
|
|
/// <returns>the resolved capability, or null if it did not resolve yet</returns>
|
|
|
|
|
public T? GetResolvedCapability<T>() where T : class
|
|
|
|
|
{
|
|
|
|
|
if (ConsumedCap is IResolvingCapability resolving)
|
|
|
|
|
return resolving.GetResolvedCapability<T>();
|
|
|
|
|
else
|
|
|
|
|
return CapabilityReflection.CreateProxy<T>(ConsumedCap) as T;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-10 18:29:06 +02:00
|
|
|
|
ConsumedCapability _consumedCap = NullCapability.Instance;
|
2020-04-10 00:01:12 +02:00
|
|
|
|
|
2019-07-12 21:48:01 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Underlying low-level capability
|
|
|
|
|
/// </summary>
|
2020-04-11 23:57:04 +02:00
|
|
|
|
public ConsumedCapability ConsumedCap => _disposedValue ?
|
2020-04-10 00:01:12 +02:00
|
|
|
|
throw new ObjectDisposedException(nameof(Proxy)) : _consumedCap;
|
2019-06-12 21:56:55 +02:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Whether is this a broken capability.
|
|
|
|
|
/// </summary>
|
2020-04-10 18:29:06 +02:00
|
|
|
|
public bool IsNull => _consumedCap == NullCapability.Instance;
|
2020-04-10 00:01:12 +02:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Whether <see cref="Dispose()"/> was called on this Proxy.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool IsDisposed => _disposedValue;
|
2019-06-12 21:56:55 +02:00
|
|
|
|
|
|
|
|
|
static async void DisposeCtrWhenReturned(CancellationTokenRegistration ctr, IPromisedAnswer answer)
|
|
|
|
|
{
|
2020-03-22 13:57:02 +01:00
|
|
|
|
try { await answer.WhenReturned; }
|
|
|
|
|
catch { }
|
|
|
|
|
finally { ctr.Dispose(); }
|
2019-06-12 21:56:55 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Calls a method of this capability.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="interfaceId">Interface ID to call</param>
|
|
|
|
|
/// <param name="methodId">Method ID to call</param>
|
|
|
|
|
/// <param name="args">Method arguments ("param struct")</param>
|
2019-11-06 18:48:25 +01:00
|
|
|
|
/// <param name="obsoleteAndIgnored">This flag is ignored. It is there to preserve compatibility with the
|
|
|
|
|
/// code generator and will be removed in future versions.</param>
|
2019-06-12 21:56:55 +02:00
|
|
|
|
/// <param name="cancellationToken">For cancelling an ongoing method call</param>
|
|
|
|
|
/// <returns>An answer promise</returns>
|
|
|
|
|
/// <exception cref="ObjectDisposedException">This instance was disposed, or transport-layer stream was disposed.</exception>
|
|
|
|
|
/// <exception cref="InvalidOperationException">Capability is broken.</exception>
|
2019-07-12 21:48:01 +02:00
|
|
|
|
/// <exception cref="System.IO.IOException">An I/O error occurs.</exception>
|
2019-11-06 18:48:25 +01:00
|
|
|
|
protected internal IPromisedAnswer Call(ulong interfaceId, ushort methodId, DynamicSerializerState args,
|
|
|
|
|
bool obsoleteAndIgnored, CancellationToken cancellationToken = default)
|
2019-06-12 21:56:55 +02:00
|
|
|
|
{
|
|
|
|
|
if (_disposedValue)
|
2020-03-22 13:57:02 +01:00
|
|
|
|
{
|
|
|
|
|
args.Dispose();
|
2019-06-12 21:56:55 +02:00
|
|
|
|
throw new ObjectDisposedException(nameof(Proxy));
|
2020-03-22 13:57:02 +01:00
|
|
|
|
}
|
2019-06-12 21:56:55 +02:00
|
|
|
|
|
2019-11-06 18:48:25 +01:00
|
|
|
|
var answer = ConsumedCap.DoCall(interfaceId, methodId, args);
|
2019-06-12 21:56:55 +02:00
|
|
|
|
|
|
|
|
|
if (cancellationToken.CanBeCanceled)
|
|
|
|
|
{
|
|
|
|
|
DisposeCtrWhenReturned(cancellationToken.Register(answer.Dispose), answer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return answer;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-12 21:48:01 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Constructs a null instance.
|
|
|
|
|
/// </summary>
|
2019-06-12 21:56:55 +02:00
|
|
|
|
public Proxy()
|
|
|
|
|
{
|
2020-03-21 13:27:46 +01:00
|
|
|
|
#if DebugFinalizers
|
|
|
|
|
CreatorStackTrace = Environment.StackTrace;
|
|
|
|
|
#endif
|
2019-06-12 21:56:55 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-10 18:29:06 +02:00
|
|
|
|
internal Proxy(ConsumedCapability cap): this()
|
2019-06-12 21:56:55 +02:00
|
|
|
|
{
|
|
|
|
|
Bind(cap);
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-10 18:29:06 +02:00
|
|
|
|
internal void Bind(ConsumedCapability cap)
|
2019-06-12 21:56:55 +02:00
|
|
|
|
{
|
2020-04-10 18:29:06 +02:00
|
|
|
|
if (ConsumedCap != NullCapability.Instance)
|
2019-06-12 21:56:55 +02:00
|
|
|
|
throw new InvalidOperationException("Proxy was already bound");
|
|
|
|
|
|
2020-04-10 18:29:06 +02:00
|
|
|
|
_consumedCap = cap ?? throw new ArgumentNullException(nameof(cap));
|
2019-06-12 21:56:55 +02:00
|
|
|
|
cap.AddRef();
|
2020-03-22 00:12:50 +01:00
|
|
|
|
|
|
|
|
|
#if DebugFinalizers
|
2020-04-10 00:01:12 +02:00
|
|
|
|
if (_consumedCap != null)
|
|
|
|
|
_consumedCap.OwningProxy = this;
|
2020-03-22 00:12:50 +01:00
|
|
|
|
#endif
|
2019-06-12 21:56:55 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-29 00:07:16 +01:00
|
|
|
|
internal async Task<Skeleton> GetProvider()
|
2019-06-12 21:56:55 +02:00
|
|
|
|
{
|
2020-03-29 00:07:16 +01:00
|
|
|
|
var unwrapped = await ConsumedCap.Unwrap();
|
2020-04-10 18:29:06 +02:00
|
|
|
|
return unwrapped.AsSkeleton();
|
2019-06-12 21:56:55 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Dispose pattern implementation
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
|
|
|
{
|
|
|
|
|
if (!_disposedValue)
|
|
|
|
|
{
|
2019-09-04 17:59:59 +02:00
|
|
|
|
if (disposing)
|
|
|
|
|
{
|
2020-04-10 22:33:46 +02:00
|
|
|
|
_consumedCap.Release();
|
2019-09-04 17:59:59 +02:00
|
|
|
|
}
|
|
|
|
|
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.
|
2020-04-13 20:22:52 +02:00
|
|
|
|
try { _consumedCap?.Release(); }
|
2020-03-10 21:55:34 +01:00
|
|
|
|
catch { }
|
2019-09-04 17:59:59 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-12 21:56:55 +02:00
|
|
|
|
_disposedValue = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-12 21:48:01 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Finalizer
|
|
|
|
|
/// </summary>
|
2019-06-12 21:56:55 +02:00
|
|
|
|
~Proxy()
|
|
|
|
|
{
|
|
|
|
|
#if DebugFinalizers
|
2020-04-10 00:01:12 +02:00
|
|
|
|
Debugger.Log(0, "DebugFinalizers", $"Caught orphaned Proxy, created from here: {CreatorStackTrace}.");
|
2019-06-12 21:56:55 +02:00
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
Dispose(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Dispose pattern implementation
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
Dispose(true);
|
|
|
|
|
GC.SuppressFinalize(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Casts this Proxy to a different capability interface.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <typeparam name="T">Desired capability interface</typeparam>
|
|
|
|
|
/// <param name="disposeThis">Whether to Dispose() this Proxy instance</param>
|
|
|
|
|
/// <returns>Proxy for desired capability interface</returns>
|
|
|
|
|
/// <exception cref="InvalidCapabilityInterfaceException"><typeparamref name="T"/> did not qualify as capability interface.</exception>
|
2020-03-31 22:01:43 +02:00
|
|
|
|
/// <exception cref="InvalidOperationException">Mismatch between generic type arguments (if capability interface is generic).</exception>
|
2019-06-12 21:56:55 +02:00
|
|
|
|
/// <exception cref="ArgumentException">Mismatch between generic type arguments (if capability interface is generic).</exception>
|
|
|
|
|
/// <exception cref="System.Reflection.TargetInvocationException">Problem with instatiating the Proxy (constructor threw exception).</exception>
|
|
|
|
|
/// <exception cref="MemberAccessException">Caller does not have permission to invoke the Proxy constructor.</exception>
|
|
|
|
|
/// <exception cref="TypeLoadException">Problem with building the Proxy type, or problem with loading some dependent class.</exception>
|
|
|
|
|
public T Cast<T>(bool disposeThis) where T: class
|
|
|
|
|
{
|
|
|
|
|
using (disposeThis ? this : null)
|
|
|
|
|
{
|
2020-01-11 17:21:31 +01:00
|
|
|
|
return (CapabilityReflection.CreateProxy<T>(ConsumedCap) as T)!;
|
2019-06-12 21:56:55 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-29 00:07:16 +01:00
|
|
|
|
internal Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
|
2019-06-12 21:56:55 +02:00
|
|
|
|
{
|
|
|
|
|
if (_disposedValue)
|
|
|
|
|
throw new ObjectDisposedException(nameof(Proxy));
|
|
|
|
|
|
|
|
|
|
if (ConsumedCap == null)
|
2020-03-29 00:07:16 +01:00
|
|
|
|
{
|
2019-06-12 21:56:55 +02:00
|
|
|
|
writer.which = CapDescriptor.WHICH.None;
|
2020-03-29 00:07:16 +01:00
|
|
|
|
return null;
|
|
|
|
|
}
|
2019-06-12 21:56:55 +02:00
|
|
|
|
else
|
2020-03-29 00:07:16 +01:00
|
|
|
|
{
|
|
|
|
|
return ConsumedCap.Export(endpoint, writer);
|
|
|
|
|
}
|
2019-06-12 21:56:55 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if DebugFinalizers
|
2020-03-21 13:27:46 +01:00
|
|
|
|
string CreatorStackTrace { get; set; }
|
2019-06-12 21:56:55 +02:00
|
|
|
|
#endif
|
|
|
|
|
}
|
2020-01-11 17:56:12 +01:00
|
|
|
|
}
|