using System;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc.Interception
{
///
/// Context of an intercepted call. Provides access to parameters and results,
/// and the possibility to redirect the call to some other capability.
///
public class CallContext
{
class PromisedAnswer : IPromisedAnswer
{
readonly CallContext _callContext;
readonly TaskCompletionSource _futureResult = new TaskCompletionSource();
readonly CancellationTokenSource _cancelFromAlice = new CancellationTokenSource();
public PromisedAnswer(CallContext callContext)
{
_callContext = callContext;
}
public Task WhenReturned => _futureResult.Task;
public CancellationToken CancelFromAlice => _cancelFromAlice.Token;
async Task AccessWhenReturned(MemberAccessPath access)
{
await WhenReturned;
return Access(access);
}
public ConsumedCapability? Access(MemberAccessPath access)
{
if (_futureResult.Task.IsCompleted)
{
try
{
return access.Eval(WhenReturned.Result);
}
catch (AggregateException exception)
{
throw exception.InnerException!;
}
}
else
{
return new LazyCapability(AccessWhenReturned(access));
}
}
public ConsumedCapability? Access(MemberAccessPath _, Task task)
{
var proxyTask = task.AsProxyTask();
if (proxyTask.IsCompleted)
{
try
{
return proxyTask.Result?.ConsumedCap;
}
catch (AggregateException exception)
{
throw exception.InnerException!;
}
}
else
{
return new LazyCapability(proxyTask);
}
}
public void Dispose()
{
try
{
_cancelFromAlice.Cancel();
}
catch (ObjectDisposedException)
{
// May happen when cancellation request from Alice arrives after return.
}
}
public void Return()
{
try
{
if (_callContext.ReturnCanceled)
{
_futureResult.SetCanceled();
}
else if (_callContext.Exception != null)
{
_futureResult.SetException(new RpcException(_callContext.Exception));
}
else
{
_futureResult.SetResult(_callContext.OutArgs);
}
}
finally
{
_cancelFromAlice.Dispose();
}
}
}
///
/// Target interface ID of this call
///
public ulong InterfaceId { get; }
///
/// Target method ID of this call
///
public ushort MethodId { get; }
///
/// Lifecycle state of this call
///
public InterceptionState State { get; private set; }
///
/// Input arguments
///
public SerializerState? InArgs { get; set; }
///
/// Output arguments ("return value")
///
public DeserializerState OutArgs { get; set; }
///
/// Exception text, or null if there is no exception
///
public string? Exception { get; set; }
///
/// Whether the call should return in canceled state to Alice (the original caller).
/// In case of forwarding () the property is automatically set according
/// to the cancellation state of Bob's answer. However, you may override it:
///
/// 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.
/// 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 or .
///
///
public bool ReturnCanceled { get; set; }
///
/// The cancellation token *from Alice* tells us when the original caller resigns from the call.
///
public CancellationToken CancelFromAlice { get; private set; }
///
/// The cancellation token *to Bob* tells the target capability when we resign from the forwarded call.
/// It is initialized with . Override it to achieve different behaviors:
/// E.g. set it to CancellationToken.None for "hiding" any cancellation request from Alice.
/// Set it to new CancellationToken(true) to pretend Bob a cancellation request.
///
public CancellationToken CancelToBob { get; set; }
///
/// Target capability. May be one of the following:
///
/// Capability interface implementation
/// A -derived object
/// A -derived object
/// A -derived object (low level capability)
/// null
///
///
public object? Bob
{
get => _bob;
set
{
if (value != _bob)
{
BobProxy?.Dispose();
BobProxy = null;
_bob = value;
switch (value)
{
case Proxy proxy:
BobProxy = proxy;
break;
case Skeleton skeleton:
BobProxy = CapabilityReflection.CreateProxy