338 lines
12 KiB
C#
Raw Normal View History

2019-11-06 14:16:20 +01:00
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc.Interception
{
/// <summary>
/// Context of an intercepted call. Provides access to parameters and results,
/// and the possibility to redirect the call to some other capability.
/// </summary>
public class CallContext
{
class PromisedAnswer : IPromisedAnswer
{
readonly CallContext _callContext;
readonly TaskCompletionSource<DeserializerState> _futureResult = new TaskCompletionSource<DeserializerState>();
readonly CancellationTokenSource _cancelFromAlice = new CancellationTokenSource();
2019-11-06 14:16:20 +01:00
public PromisedAnswer(CallContext callContext)
{
_callContext = callContext;
}
public Task<DeserializerState> WhenReturned => _futureResult.Task;
public CancellationToken CancelFromAlice => _cancelFromAlice.Token;
2019-11-06 14:16:20 +01:00
public ConsumedCapability Access(MemberAccessPath access)
2019-11-06 14:16:20 +01:00
{
2020-04-04 22:57:07 +02:00
return new LocalAnswerCapability(_futureResult.Task, access);
2019-11-06 14:16:20 +01:00
}
public ConsumedCapability Access(MemberAccessPath _, Task<IDisposable?> task)
2020-03-10 21:55:34 +01:00
{
var proxyTask = task.AsProxyTask();
2020-04-04 22:57:07 +02:00
return new LocalAnswerCapability(proxyTask);
2020-03-10 21:55:34 +01:00
}
2019-11-06 14:16:20 +01:00
public void Dispose()
{
try
{
_cancelFromAlice.Cancel();
}
catch (ObjectDisposedException)
{
// May happen when cancellation request from Alice arrives after return.
}
2019-11-06 14:16:20 +01:00
}
public void Return()
{
try
2019-11-06 14:16:20 +01:00
{
if (_callContext.ReturnCanceled)
{
_futureResult.SetCanceled();
}
else if (_callContext.Exception != null)
{
_futureResult.SetException(new RpcException(_callContext.Exception));
}
else
{
_futureResult.SetResult(_callContext.OutArgs);
}
2019-11-06 14:16:20 +01:00
}
finally
2019-11-06 14:16:20 +01:00
{
_cancelFromAlice.Dispose();
2019-11-06 14:16:20 +01:00
}
}
2020-04-05 16:53:40 +02:00
public bool IsTailCall => false;
2019-11-06 14:16:20 +01:00
}
/// <summary>
/// Target interface ID of this call
/// </summary>
2019-11-06 14:16:20 +01:00
public ulong InterfaceId { get; }
/// <summary>
/// Target method ID of this call
/// </summary>
2019-11-06 14:16:20 +01:00
public ushort MethodId { get; }
/// <summary>
/// Lifecycle state of this call
/// </summary>
2019-11-06 14:16:20 +01:00
public InterceptionState State { get; private set; }
2020-04-04 22:57:07 +02:00
SerializerState _inArgs;
/// <summary>
/// Input arguments
/// </summary>
2020-04-04 22:57:07 +02:00
public SerializerState InArgs
{
get => _inArgs;
set { _inArgs = value ?? throw new ArgumentNullException(nameof(value)); }
}
/// <summary>
/// Output arguments ("return value")
/// </summary>
2019-11-06 14:16:20 +01:00
public DeserializerState OutArgs { get; set; }
/// <summary>
/// Exception text, or null if there is no exception
/// </summary>
2020-01-11 17:21:31 +01:00
public string? Exception { get; set; }
/// <summary>
/// Whether the call should return in canceled state to Alice (the original caller).
/// In case of forwarding (<see cref="ForwardToBob()"/>) the property is automatically set according
/// to the cancellation state of Bob's answer. However, you may override it:
/// <list type="bullet">
/// <item><description>Setting it from 'false' to 'true' means that we pretend Alice a canceled call.
/// If Alice never requested cancellation this will surprise her pretty much.</description></item>
/// <item><description>Setting it from 'true' to 'false' overrides an existing cancellation. Since
/// we did not receive any output arguments from Bob (due to the cancellation), you *must* provide
/// either <see cref="OutArgs"/> or <see cref="Exception"/>.</description></item>
/// </list>
/// </summary>
public bool ReturnCanceled { get; set; }
/// <summary>
/// The cancellation token *from Alice* tells us when the original caller resigns from the call.
/// </summary>
public CancellationToken CancelFromAlice { get; private set; }
/// <summary>
/// The cancellation token *to Bob* tells the target capability when we resign from the forwarded call.
/// It is initialized with <seealso cref="CancelFromAlice"/>. Override it to achieve different behaviors:
/// E.g. set it to <code>CancellationToken.None</code> for "hiding" any cancellation request from Alice.
/// Set it to <code>new CancellationToken(true)</code> to pretend Bob a cancellation request.
/// </summary>
public CancellationToken CancelToBob { get; set; }
/// <summary>
/// Target capability. May be one of the following:
/// <list type="bullet">
/// <item><description>Capability interface implementation</description></item>
/// <item><description>A <see cref="Proxy"/>-derived object</description></item>
/// <item><description>A <see cref="Skeleton"/>-derived object</description></item>
/// <item><description>A <see cref="ConsumedCapability"/>-derived object (low level capability)</description></item>
/// </list>
/// </summary>
2020-04-04 22:57:07 +02:00
/// <remarks>
/// Note that getting/setting this property does NOT transfer ownership.
/// </remarks>
public object Bob
2019-11-06 14:16:20 +01:00
{
get => _bob;
set
{
if (value != _bob)
{
switch (value)
{
2020-04-04 22:57:07 +02:00
case null:
throw new ArgumentNullException(nameof(value));
2019-11-06 14:16:20 +01:00
2020-04-04 22:57:07 +02:00
case Proxy proxy:
BobProxy = proxy.Cast<BareProxy>(false);
2019-11-06 14:16:20 +01:00
break;
case ConsumedCapability cap:
using (var temp = CapabilityReflection.CreateProxy<object>(cap))
{
Bob = temp;
}
2019-11-06 14:16:20 +01:00
break;
2020-04-04 22:57:07 +02:00
case Skeleton skeleton:
using (var nullProxy = new Proxy())
{
Bob = (object?)skeleton.AsCapability() ?? nullProxy;
}
2019-11-06 14:16:20 +01:00
break;
default:
2020-04-10 19:23:16 +02:00
Bob = CapabilityReflection.CreateSkeletonInternal(value);
2019-11-06 14:16:20 +01:00
break;
}
2020-04-04 22:57:07 +02:00
_bob = value;
2019-11-06 14:16:20 +01:00
}
}
}
2020-01-11 17:21:31 +01:00
internal Proxy? BobProxy { get; private set; }
2019-11-06 14:16:20 +01:00
readonly CensorCapability _censorCapability;
PromisedAnswer _promisedAnswer;
2020-04-04 22:57:07 +02:00
object _bob;
2019-11-06 14:16:20 +01:00
internal IPromisedAnswer Answer => _promisedAnswer;
internal CallContext(CensorCapability censorCapability, ulong interfaceId, ushort methodId, SerializerState inArgs)
{
_censorCapability = censorCapability;
_promisedAnswer = new PromisedAnswer(this);
2020-04-04 22:57:07 +02:00
_inArgs = inArgs;
2020-04-05 16:53:40 +02:00
_bob = null!; // Will be initialized later here
CancelFromAlice = _promisedAnswer.CancelFromAlice;
CancelToBob = CancelFromAlice;
2019-11-06 14:16:20 +01:00
Bob = censorCapability.InterceptedCapability;
InterfaceId = interfaceId;
MethodId = methodId;
State = InterceptionState.RequestedFromAlice;
}
static void InterceptCaps(DeserializerState state, IInterceptionPolicy policy)
{
if (state.Caps != null)
{
for (int i = 0; i < state.Caps.Count; i++)
{
2020-01-11 17:21:31 +01:00
var cap = state.Caps[i];
if (cap != null)
{
cap = policy.Attach(cap);
state.Caps[i] = cap;
cap.AddRef();
}
2019-11-06 14:16:20 +01:00
}
}
}
static void UninterceptCaps(DeserializerState state, IInterceptionPolicy policy)
{
if (state.Caps != null)
{
for (int i = 0; i < state.Caps.Count; i++)
{
2020-01-11 17:21:31 +01:00
var cap = state.Caps[i];
if (cap != null)
{
cap = policy.Detach(cap);
state.Caps[i] = cap;
cap.AddRef();
}
2019-11-06 14:16:20 +01:00
}
}
}
/// <summary>
/// Intercepts all capabilies inside the input arguments
/// </summary>
/// <param name="policyOverride">Policy to use, or null to further use present policy</param>
2020-01-11 17:21:31 +01:00
public void InterceptInCaps(IInterceptionPolicy? policyOverride = null)
2019-11-06 14:16:20 +01:00
{
InterceptCaps(InArgs, policyOverride ?? _censorCapability.Policy);
}
/// <summary>
/// Intercepts all capabilies inside the output arguments
/// </summary>
/// <param name="policyOverride">Policy to use, or null to further use present policy</param>
2020-01-11 17:21:31 +01:00
public void InterceptOutCaps(IInterceptionPolicy? policyOverride = null)
2019-11-06 14:16:20 +01:00
{
InterceptCaps(OutArgs, policyOverride ?? _censorCapability.Policy);
}
/// <summary>
/// Unintercepts all capabilies inside the input arguments
/// </summary>
/// <param name="policyOverride">Policy to remove, or null to remove present policy</param>
2020-01-11 17:21:31 +01:00
public void UninterceptInCaps(IInterceptionPolicy? policyOverride = null)
2019-11-06 14:16:20 +01:00
{
UninterceptCaps(InArgs, policyOverride ?? _censorCapability.Policy);
}
/// <summary>
/// Unintercepts all capabilies inside the output arguments
/// </summary>
/// <param name="policyOverride">Policy to remove, or null to remove present policy</param>
2020-01-11 17:21:31 +01:00
public void UninterceptOutCaps(IInterceptionPolicy? policyOverride = null)
2019-11-06 14:16:20 +01:00
{
UninterceptCaps(OutArgs, policyOverride ?? _censorCapability.Policy);
}
/// <summary>
/// Forwards this intercepted call to the target capability ("Bob").
/// </summary>
public void ForwardToBob()
2019-11-06 14:16:20 +01:00
{
2020-01-11 17:21:31 +01:00
var answer = BobProxy!.Call(InterfaceId, MethodId, InArgs.Rewrap<DynamicSerializerState>(), default, CancelToBob);
2019-11-06 14:16:20 +01:00
State = InterceptionState.ForwardedToBob;
async void ChangeStateWhenReturned()
{
using (answer)
{
try
{
OutArgs = await answer.WhenReturned;
}
catch (TaskCanceledException)
{
ReturnCanceled = true;
2019-11-06 14:16:20 +01:00
}
catch (System.Exception exception)
{
Exception = exception.Message;
}
}
State = InterceptionState.ReturnedFromBob;
_censorCapability.Policy.OnReturnFromBob(this);
}
ChangeStateWhenReturned();
}
/// <summary>
/// Returns this intercepted call to the caller ("Alice").
/// </summary>
2019-11-06 14:16:20 +01:00
public void ReturnToAlice()
{
try
{
_promisedAnswer.Return();
}
catch (InvalidOperationException)
{
throw new InvalidOperationException("The call was already returned");
}
State = InterceptionState.ReturnedToAlice;
}
}
}