2019-06-12 21:56:55 +02:00
using System ;
2020-03-29 00:07:16 +01:00
using System.Collections.Generic ;
2019-06-12 21:56:55 +02:00
using System.Diagnostics ;
using System.Threading.Tasks ;
namespace Capnp.Rpc
{
/// <summary>
/// A promised answer due to RPC.
/// </summary>
/// <remarks>
/// Disposing the instance before the answer is available results in a best effort attempt to cancel
/// the ongoing call.
/// </remarks>
public sealed class PendingQuestion : IPromisedAnswer
{
2019-07-12 21:48:01 +02:00
/// <summary>
/// Question lifetime management state
/// </summary>
2019-06-12 21:56:55 +02:00
[Flags]
public enum State
{
2019-07-12 21:48:01 +02:00
/// <summary>
/// The question has not yet been sent.
/// </summary>
2019-06-12 21:56:55 +02:00
None = 0 ,
2019-07-12 21:48:01 +02:00
/// <summary>
/// Tail call flag
/// </summary>
2019-06-12 21:56:55 +02:00
TailCall = 1 ,
2019-07-12 21:48:01 +02:00
/// <summary>
/// The question has been sent.
/// </summary>
2019-06-12 21:56:55 +02:00
Sent = 2 ,
2019-07-12 21:48:01 +02:00
/// <summary>
/// The question has been answered.
/// </summary>
2019-06-12 21:56:55 +02:00
Returned = 4 ,
2019-07-12 21:48:01 +02:00
/// <summary>
/// A 'finish' request was sent to the peer, indicating that no further requests will refer
/// to this question.
/// </summary>
2019-06-12 21:56:55 +02:00
FinishRequested = 8 ,
2019-07-12 21:48:01 +02:00
/// <summary>
/// Question object was disposed.
/// </summary>
2020-04-05 16:53:40 +02:00
CanceledByDispose = 16
2019-06-12 21:56:55 +02:00
}
readonly TaskCompletionSource < DeserializerState > _tcs = new TaskCompletionSource < DeserializerState > ( ) ;
readonly uint _questionId ;
2020-01-11 17:21:31 +01:00
ConsumedCapability ? _target ;
SerializerState ? _inParams ;
2020-04-05 16:53:40 +02:00
int _inhibitFinishCounter , _refCounter ;
2019-06-12 21:56:55 +02:00
2020-04-10 18:29:06 +02:00
internal PendingQuestion ( IRpcEndpoint ep , uint id , ConsumedCapability target , SerializerState ? inParams )
2019-06-12 21:56:55 +02:00
{
RpcEndpoint = ep ? ? throw new ArgumentNullException ( nameof ( ep ) ) ;
_questionId = id ;
_target = target ;
_inParams = inParams ;
StateFlags = inParams = = null ? State . Sent : State . None ;
if ( target ! = null )
{
target . AddRef ( ) ;
}
}
internal IRpcEndpoint RpcEndpoint { get ; }
internal object ReentrancyBlocker { get ; } = new object ( ) ;
internal uint QuestionId = > _questionId ;
internal State StateFlags { get ; private set ; }
2020-03-29 00:07:16 +01:00
internal IReadOnlyList < CapDescriptor . WRITER > ? CapTable { get ; set ; }
2019-07-12 21:48:01 +02:00
/// <summary>
/// Eventually returns the server answer
/// </summary>
2019-06-12 21:56:55 +02:00
public Task < DeserializerState > WhenReturned = > _tcs . Task ;
2019-07-12 21:48:01 +02:00
2020-04-05 16:53:40 +02:00
/// <summary>
/// Whether this question represents a tail call
/// </summary>
public bool IsTailCall
2019-06-12 21:56:55 +02:00
{
get = > StateFlags . HasFlag ( State . TailCall ) ;
2020-04-05 16:53:40 +02:00
internal set
2019-06-12 21:56:55 +02:00
{
if ( value )
StateFlags | = State . TailCall ;
else
StateFlags & = ~ State . TailCall ;
}
}
internal void DisallowFinish ( )
{
+ + _inhibitFinishCounter ;
}
internal void AllowFinish ( )
{
- - _inhibitFinishCounter ;
AutoFinish ( ) ;
}
2020-04-05 16:53:40 +02:00
internal void AddRef ( )
{
lock ( ReentrancyBlocker )
{
+ + _refCounter ;
}
}
internal void Release ( )
{
lock ( ReentrancyBlocker )
{
- - _refCounter ;
AutoFinish ( ) ;
}
}
2019-06-12 21:56:55 +02:00
const string ReturnDespiteTailCallMessage = "Peer sent actual results despite the question was sent as tail call. This was not expected and is a protocol error." ;
internal void OnReturn ( DeserializerState results )
{
lock ( ReentrancyBlocker )
{
SetReturned ( ) ;
2020-03-22 00:12:50 +01:00
}
2019-06-12 21:56:55 +02:00
2020-03-22 00:12:50 +01:00
if ( StateFlags . HasFlag ( State . TailCall ) )
{
_tcs . TrySetException ( new RpcException ( ReturnDespiteTailCallMessage ) ) ;
}
else
{
if ( ! _tcs . TrySetResult ( results ) )
2020-03-21 13:27:46 +01:00
{
2020-03-22 00:12:50 +01:00
ReleaseOutCaps ( results ) ;
2019-08-25 15:59:07 +02:00
}
2019-06-12 21:56:55 +02:00
}
}
internal void OnTailCallReturn ( )
{
lock ( ReentrancyBlocker )
{
SetReturned ( ) ;
2020-03-22 00:12:50 +01:00
}
2019-06-12 21:56:55 +02:00
2020-03-22 00:12:50 +01:00
if ( ! StateFlags . HasFlag ( State . TailCall ) )
{
_tcs . TrySetException ( new RpcException ( "Peer sent the results of this questions somewhere else. This was not expected and is a protocol error." ) ) ;
}
else
{
_tcs . TrySetResult ( default ) ;
2019-06-12 21:56:55 +02:00
}
}
internal void OnException ( Exception . READER exception )
{
lock ( ReentrancyBlocker )
{
SetReturned ( ) ;
2020-03-21 13:27:46 +01:00
}
2020-03-22 00:12:50 +01:00
2020-03-26 21:30:30 +01:00
_tcs . TrySetException ( new RpcException ( exception . Reason ? ? "unknown reason" ) ) ;
2019-06-12 21:56:55 +02:00
}
internal void OnException ( System . Exception exception )
{
lock ( ReentrancyBlocker )
{
SetReturned ( ) ;
2020-03-21 13:27:46 +01:00
}
2020-03-22 00:12:50 +01:00
_tcs . TrySetException ( exception ) ;
2019-06-12 21:56:55 +02:00
}
internal void OnCanceled ( )
{
lock ( ReentrancyBlocker )
{
SetReturned ( ) ;
2020-03-21 13:27:46 +01:00
}
2020-03-22 00:12:50 +01:00
_tcs . TrySetCanceled ( ) ;
2019-06-12 21:56:55 +02:00
}
void DeleteMyQuestion ( )
{
RpcEndpoint . DeleteQuestion ( this ) ;
}
void AutoFinish ( )
{
if ( StateFlags . HasFlag ( State . FinishRequested ) )
{
return ;
}
2020-04-05 16:53:40 +02:00
if ( ( ! IsTailCall & & _inhibitFinishCounter = = 0 & & StateFlags . HasFlag ( State . Returned ) ) | |
( IsTailCall & & _refCounter = = 0 & & StateFlags . HasFlag ( State . Returned ) ) | |
StateFlags . HasFlag ( State . CanceledByDispose ) )
2019-06-12 21:56:55 +02:00
{
StateFlags | = State . FinishRequested ;
2020-04-05 16:53:40 +02:00
RpcEndpoint . Finish ( _questionId ) ;
2019-06-12 21:56:55 +02:00
}
}
void SetReturned ( )
{
if ( StateFlags . HasFlag ( State . Returned ) )
{
throw new InvalidOperationException ( "Return state already set" ) ;
}
StateFlags | = State . Returned ;
AutoFinish ( ) ;
DeleteMyQuestion ( ) ;
}
2019-07-12 21:48:01 +02:00
/// <summary>
/// Refer to a (possibly nested) member of this question's (possibly future) result and return
/// it as a capability.
/// </summary>
/// <param name="access">Access path</param>
/// <returns>Low-level capability</returns>
/// <exception cref="DeserializationException">The referenced member does not exist or does not resolve to a capability pointer.</exception>
2020-04-10 18:29:06 +02:00
public ConsumedCapability Access ( MemberAccessPath access )
2019-06-12 21:56:55 +02:00
{
lock ( ReentrancyBlocker )
{
if ( StateFlags . HasFlag ( State . Returned ) & &
! StateFlags . HasFlag ( State . TailCall ) )
{
try
{
return access . Eval ( WhenReturned . Result ) ;
}
catch ( AggregateException exception )
{
2020-01-11 17:21:31 +01:00
throw exception . InnerException ! ;
2019-06-12 21:56:55 +02:00
}
}
else
{
2020-03-21 13:27:46 +01:00
return new RemoteAnswerCapability ( this , access ) ;
2020-03-10 21:55:34 +01:00
}
}
}
/// <summary>
/// Refer to a (possibly nested) member of this question's (possibly future) result and return
/// it as a capability.
/// </summary>
2020-03-21 13:27:46 +01:00
/// <param name="access">Access path</param>
2020-03-10 21:55:34 +01:00
/// <param name="task">promises the cap whose ownership is transferred to this object</param>
/// <returns>Low-level capability</returns>
/// <exception cref="DeserializationException">The referenced member does not exist or does not resolve to a capability pointer.</exception>
2020-04-10 18:29:06 +02:00
public ConsumedCapability Access ( MemberAccessPath access , Task < IDisposable ? > task )
2020-03-10 21:55:34 +01:00
{
var proxyTask = task . AsProxyTask ( ) ;
2020-03-21 13:27:46 +01:00
return new RemoteAnswerCapability ( this , access , proxyTask ) ;
2019-06-12 21:56:55 +02:00
}
2019-08-25 15:59:07 +02:00
static void ReleaseOutCaps ( DeserializerState outParams )
{
2020-01-11 17:21:31 +01:00
foreach ( var cap in outParams . Caps ! )
2019-08-25 15:59:07 +02:00
{
2020-04-04 23:10:54 +02:00
cap . Release ( ) ;
2019-08-25 15:59:07 +02:00
}
}
2019-06-12 21:56:55 +02:00
internal void Send ( )
{
2020-01-11 17:21:31 +01:00
SerializerState ? inParams ;
ConsumedCapability ? target ;
2019-06-12 21:56:55 +02:00
lock ( ReentrancyBlocker )
{
2020-01-11 17:21:31 +01:00
if ( StateFlags . HasFlag ( State . Sent ) )
throw new InvalidOperationException ( "Already sent" ) ;
2019-06-12 21:56:55 +02:00
inParams = _inParams ;
_inParams = null ;
target = _target ;
_target = null ;
StateFlags | = State . Sent ;
}
2020-01-11 17:21:31 +01:00
var msg = ( Message . WRITER ) inParams ! . MsgBuilder ! . Root ! ;
2020-03-26 21:30:30 +01:00
Debug . Assert ( msg . Call ! . Target . which ! = MessageTarget . WHICH . undefined ) ;
2019-06-12 21:56:55 +02:00
var call = msg . Call ;
call . QuestionId = QuestionId ;
2020-04-10 00:01:12 +02:00
call . SendResultsTo . which = IsTailCall ?
Call . sendResultsTo . WHICH . Yourself :
2019-06-12 21:56:55 +02:00
Call . sendResultsTo . WHICH . Caller ;
try
{
RpcEndpoint . SendQuestion ( inParams , call . Params ) ;
2020-03-29 00:07:16 +01:00
CapTable = call . Params . CapTable ;
2019-06-12 21:56:55 +02:00
}
catch ( System . Exception exception )
{
OnException ( exception ) ;
}
2020-04-10 00:01:12 +02:00
target ? . Release ( ) ;
2019-06-12 21:56:55 +02:00
}
2020-04-04 23:10:54 +02:00
/// <summary>
/// Implements <see cref="IDisposable"/>.
/// </summary>
public void Dispose ( )
2019-06-12 21:56:55 +02:00
{
2019-08-25 15:59:07 +02:00
bool justDisposed = false ;
2019-06-12 21:56:55 +02:00
lock ( ReentrancyBlocker )
{
2020-04-05 16:53:40 +02:00
if ( ! StateFlags . HasFlag ( State . CanceledByDispose ) )
2019-06-12 21:56:55 +02:00
{
2020-04-05 16:53:40 +02:00
StateFlags | = State . CanceledByDispose ;
2020-04-04 23:10:54 +02:00
justDisposed = true ;
2019-06-12 21:56:55 +02:00
2020-04-04 23:10:54 +02:00
AutoFinish ( ) ;
2019-06-12 21:56:55 +02:00
}
}
2019-08-25 15:59:07 +02:00
if ( justDisposed )
{
_tcs . TrySetCanceled ( ) ;
}
2019-06-12 21:56:55 +02:00
}
}
2020-01-11 17:56:12 +01:00
}