2020-01-11 17:56:12 +01:00
using System ;
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-10 21:55:34 +01:00
public static T Share < T > ( T obj ) where T : class
{
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
#if DebugFinalizers
ILogger Logger { get ; } = Logging . CreateLogger < Proxy > ( ) ;
#endif
bool _disposedValue = false ;
/// <summary>
/// Will eventually give the resolved capability, if this is a promised capability.
/// </summary>
2020-03-10 21:55:34 +01:00
public Task < ConsumedCapability ? > WhenResolved
2019-06-12 21:56:55 +02:00
{
get
{
2020-03-10 21:55:34 +01:00
return ConsumedCap is IResolvingCapability resolving ?
resolving . WhenResolved :
Task . FromResult ( ConsumedCap ) ;
2019-06-12 21:56:55 +02:00
}
}
2019-07-12 21:48:01 +02:00
/// <summary>
/// Underlying low-level capability
/// </summary>
2020-01-11 17:21:31 +01:00
protected internal ConsumedCapability ? ConsumedCap { get ; private set ; }
2019-06-12 21:56:55 +02:00
/// <summary>
/// Whether is this a broken capability.
/// </summary>
public bool IsNull = > ConsumedCap = = null ;
static async void DisposeCtrWhenReturned ( CancellationTokenRegistration ctr , IPromisedAnswer answer )
{
try
{
await answer . WhenReturned ;
}
catch
{
}
finally
{
ctr . Dispose ( ) ;
}
}
/// <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 )
throw new ObjectDisposedException ( nameof ( Proxy ) ) ;
if ( ConsumedCap = = null )
throw new InvalidOperationException ( "Cannot call null capability" ) ;
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-01-11 17:21:31 +01:00
internal Proxy ( ConsumedCapability ? cap )
2019-06-12 21:56:55 +02:00
{
Bind ( cap ) ;
}
2020-01-11 17:21:31 +01:00
internal void Bind ( ConsumedCapability ? cap )
2019-06-12 21:56:55 +02:00
{
if ( ConsumedCap ! = null )
throw new InvalidOperationException ( "Proxy was already bound" ) ;
if ( cap = = null )
return ;
ConsumedCap = cap ;
cap . AddRef ( ) ;
}
2020-01-11 17:21:31 +01:00
internal IProvidedCapability ? GetProvider ( )
2019-06-12 21:56:55 +02:00
{
switch ( ConsumedCap )
{
case LocalCapability lcap :
return lcap . ProvidedCap ;
case null :
return null ;
default :
return Vine . Create ( ConsumedCap ) ;
}
}
/// <summary>
/// Dispose pattern implementation
/// </summary>
protected virtual void Dispose ( bool disposing )
{
if ( ! _disposedValue )
{
2019-09-04 17:59:59 +02:00
if ( disposing )
{
2020-03-10 21:55:34 +01:00
ConsumedCap ? . Release ( false ) ;
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-03-10 21:55:34 +01:00
try { ConsumedCap ? . Release ( false ) ; }
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
Logger . LogWarning ( $"Caught orphaned Proxy, created from {CreatorMemberName} in {CreatorFilePath}, line {CreatorLineNumber}." ) ;
#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>
/// <exception cref="InvalidOperationException">This capability is broken, or mismatch between generic type arguments (if capability interface is generic).</exception>
/// <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
{
if ( IsNull )
throw new InvalidOperationException ( "Capability is broken" ) ;
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
}
}
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 ) ;
}
2020-01-11 17:21:31 +01:00
internal void Freeze ( out IRpcEndpoint ? boundEndpoint )
2019-06-12 21:56:55 +02:00
{
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
}
2020-01-11 17:56:12 +01:00
}