tests & fixes

This commit is contained in:
Christian Köllner 2020-04-05 16:53:40 +02:00
parent 01513ef71f
commit bdc8240e5b
15 changed files with 495 additions and 232 deletions

View File

@ -100,6 +100,8 @@ namespace Capnp.Net.Runtime.Tests
throw new NotImplementedException(); throw new NotImplementedException();
} }
public bool IsTailCall => false;
public void Dispose() public void Dispose()
{ {
} }

View File

@ -622,20 +622,6 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
} }
#endregion TestCallOrder #endregion TestCallOrder
#region TestTailCaller
class TestTailCaller : ITestTailCaller
{
public void Dispose()
{
}
public Task<TestTailCallee.TailResult> Foo(int i, ITestTailCallee callee, CancellationToken cancellationToken_)
{
return callee.Foo(i, "from TestTailCaller", cancellationToken_);
}
}
#endregion TestTailCaller
#region TestTailCaller #region TestTailCaller
class TestTailCallerImpl : ITestTailCaller class TestTailCallerImpl : ITestTailCaller
{ {
@ -660,6 +646,41 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
} }
} }
} }
class TestTailCallerImpl2 : ITestTailCaller
{
ITestCallOrder _keeper;
public TestTailCallerImpl2()
{
}
public void Dispose()
{
_keeper?.Dispose();
}
public Task<TestTailCallee.TailResult> Foo(int i, ITestTailCallee callee, CancellationToken cancellationToken_)
{
using (callee)
{
if (_keeper == null)
{
var task = callee.Foo(i, "from TestTailCaller", cancellationToken_);
_keeper = task.C();
return task;
}
else
{
return Task.FromResult(
new TestTailCallee.TailResult()
{
C = _keeper
});
}
}
}
}
#endregion TestTailCaller #endregion TestTailCaller
#region TestTailCallee #region TestTailCallee
@ -937,6 +958,92 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
} }
} }
class TestMoreStuffImpl3 : ITestMoreStuff
{
readonly TaskCompletionSource<ITestInterface> _heldCap = new TaskCompletionSource<ITestInterface>();
public Task<string> CallFoo(ITestInterface cap, CancellationToken cancellationToken_ = default)
{
using (cap)
{
return cap.Foo(123, true);
}
}
public Task<string> CallFooWhenResolved(ITestInterface Cap, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task<string> CallHeld(CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public async void Dispose()
{
using (var cap = await _heldCap.Task)
{
}
}
public Task<ITestCallOrder> Echo(ITestCallOrder Cap, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task ExpectCancel(ITestInterface Cap, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task<uint> GetCallSequence(uint Expected, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task<string> GetEnormousString(CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task<ITestHandle> GetHandle(CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public async Task<ITestInterface> GetHeld(CancellationToken cancellationToken_ = default)
{
return await _heldCap.Task;
}
public Task<ITestMoreStuff> GetNull(CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task Hold(ITestInterface Cap, CancellationToken cancellationToken_ = default)
{
_heldCap.SetResult(Cap);
return Task.CompletedTask;
}
public Task<(string, string)> MethodWithDefaults(string A, uint B, string C, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task MethodWithNullDefault(string A, ITestInterface B, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task<ITestInterface> NeverReturn(ITestInterface Cap, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
}
#endregion TestMoreStuff #endregion TestMoreStuff
#region TestHandle #region TestHandle

File diff suppressed because it is too large Load Diff

View File

@ -173,5 +173,77 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
} }
[TestMethod]
public void ExportCapToThirdParty()
{
using (var server = SetupServer())
{
var counters = new Counters();
server.Main = new TestMoreStuffImpl3();
using (var client = SetupClient())
{
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
using (var main = client.GetMain<ITestMoreStuff>())
{
var held = main.GetHeld().Eager();
using (var server2 = SetupServer())
{
server2.Main = new TestMoreStuffImpl2();
using (var client2 = SetupClient())
{
Assert.IsTrue(client2.WhenConnected.Wait(MediumNonDbgTimeout));
using (var main2 = client2.GetMain<ITestMoreStuff>())
{
var fooTask = main2.CallFoo(held);
Assert.IsTrue(main.Hold(new TestInterfaceImpl(new Counters())).Wait(MediumNonDbgTimeout));
Assert.IsTrue(fooTask.Wait(MediumNonDbgTimeout));
Assert.AreEqual("bar", fooTask.Result);
}
}
}
}
}
}
}
[TestMethod]
public void ExportTailCallCapToThirdParty()
{
using (var server = SetupServer())
{
server.Main = new TestTailCallerImpl2();
using (var client = SetupClient())
{
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
using (var main = client.GetMain<ITestTailCaller>())
{
var callee = new TestTailCalleeImpl(new Counters());
var fooTask = main.Foo(123, callee);
Assert.IsTrue(fooTask.Wait(MediumNonDbgTimeout));
using (var c = fooTask.Result.C)
using (var client2 = SetupClient())
{
Assert.IsTrue(client2.WhenConnected.Wait(MediumNonDbgTimeout));
using (var main2 = client2.GetMain<ITestTailCaller>())
{
var fooTask2 = main2.Foo(123, null);
Assert.IsTrue(fooTask2.Wait(MediumNonDbgTimeout));
Assert.IsTrue(fooTask2.C().GetCallSequence(1).Wait(MediumNonDbgTimeout));
}
}
}
}
}
}
} }
} }

View File

@ -193,9 +193,14 @@ namespace Capnp.Net.Runtime.Tests
Assert.AreEqual(0, _enginePair.Endpoint1.ExportedCapabilityCount); Assert.AreEqual(0, _enginePair.Endpoint1.ExportedCapabilityCount);
Assert.AreEqual(0, _enginePair.Endpoint1.ImportedCapabilityCount); Assert.AreEqual(0, _enginePair.Endpoint1.ImportedCapabilityCount);
Assert.AreEqual(0, _enginePair.Endpoint1.PendingQuestionCount);
Assert.AreEqual(0, _enginePair.Endpoint1.PendingAnswerCount);
Assert.AreEqual(0, _enginePair.Endpoint2.ExportedCapabilityCount); Assert.AreEqual(0, _enginePair.Endpoint2.ExportedCapabilityCount);
Assert.AreEqual(0, _enginePair.Endpoint2.ImportedCapabilityCount); Assert.AreEqual(0, _enginePair.Endpoint2.ImportedCapabilityCount);
Assert.AreEqual(0, _enginePair.Endpoint2.PendingQuestionCount);
Assert.AreEqual(0, _enginePair.Endpoint2.PendingAnswerCount);
GC.Collect(); GC.Collect();
GC.WaitForPendingFinalizers(); GC.WaitForPendingFinalizers();
GC.Collect(); GC.Collect();

View File

@ -31,5 +31,10 @@ namespace Capnp.Rpc
/// <param name="proxyTask">Task returning the proxy whose ownership will be taken over</param> /// <param name="proxyTask">Task returning the proxy whose ownership will be taken over</param>
/// <returns></returns> /// <returns></returns>
ConsumedCapability? Access(MemberAccessPath access, Task<IDisposable?> proxyTask); ConsumedCapability? Access(MemberAccessPath access, Task<IDisposable?> proxyTask);
/// <summary>
/// Whether the question was asked as tail call
/// </summary>
bool IsTailCall { get; }
} }
} }

View File

@ -26,7 +26,11 @@ namespace Capnp.Rpc
{ {
async Task<T> AwaitAnswer() async Task<T> AwaitAnswer()
{ {
return then(await promise.WhenReturned); var result = await promise.WhenReturned;
if (promise.IsTailCall)
throw new TailCallNoDataException();
return then(result);
} }
var rtask = AwaitAnswer(); var rtask = AwaitAnswer();

View File

@ -70,6 +70,8 @@ namespace Capnp.Rpc.Interception
_cancelFromAlice.Dispose(); _cancelFromAlice.Dispose();
} }
} }
public bool IsTailCall => false;
} }
/// <summary> /// <summary>
@ -194,7 +196,7 @@ namespace Capnp.Rpc.Interception
_censorCapability = censorCapability; _censorCapability = censorCapability;
_promisedAnswer = new PromisedAnswer(this); _promisedAnswer = new PromisedAnswer(this);
_inArgs = inArgs; _inArgs = inArgs;
_bob = null!; // Will beinitialized by setting Bob _bob = null!; // Will be initialized later here
CancelFromAlice = _promisedAnswer.CancelFromAlice; CancelFromAlice = _promisedAnswer.CancelFromAlice;
CancelToBob = CancelFromAlice; CancelToBob = CancelFromAlice;

View File

@ -25,6 +25,8 @@ namespace Capnp.Rpc
public Task<DeserializerState> WhenReturned { get; } public Task<DeserializerState> WhenReturned { get; }
public bool IsTailCall => false;
public ConsumedCapability Access(MemberAccessPath access) public ConsumedCapability Access(MemberAccessPath access)
{ {
return new LocalAnswerCapability(WhenReturned, access); return new LocalAnswerCapability(WhenReturned, access);

View File

@ -49,14 +49,14 @@ namespace Capnp.Rpc
/// <summary> /// <summary>
/// Question object was disposed. /// Question object was disposed.
/// </summary> /// </summary>
Disposed = 16 CanceledByDispose = 16
} }
readonly TaskCompletionSource<DeserializerState> _tcs = new TaskCompletionSource<DeserializerState>(); readonly TaskCompletionSource<DeserializerState> _tcs = new TaskCompletionSource<DeserializerState>();
readonly uint _questionId; readonly uint _questionId;
ConsumedCapability? _target; ConsumedCapability? _target;
SerializerState? _inParams; SerializerState? _inParams;
int _inhibitFinishCounter; int _inhibitFinishCounter, _refCounter;
internal PendingQuestion(IRpcEndpoint ep, uint id, ConsumedCapability? target, SerializerState? inParams) internal PendingQuestion(IRpcEndpoint ep, uint id, ConsumedCapability? target, SerializerState? inParams)
{ {
@ -91,10 +91,13 @@ namespace Capnp.Rpc
/// </summary> /// </summary>
public Task<DeserializerState> WhenReturned => _tcs.Task; public Task<DeserializerState> WhenReturned => _tcs.Task;
internal bool IsTailCall /// <summary>
/// Whether this question represents a tail call
/// </summary>
public bool IsTailCall
{ {
get => StateFlags.HasFlag(State.TailCall); get => StateFlags.HasFlag(State.TailCall);
set internal set
{ {
if (value) if (value)
StateFlags |= State.TailCall; StateFlags |= State.TailCall;
@ -102,7 +105,6 @@ namespace Capnp.Rpc
StateFlags &= ~State.TailCall; StateFlags &= ~State.TailCall;
} }
} }
internal bool IsReturned => StateFlags.HasFlag(State.Returned);
internal void DisallowFinish() internal void DisallowFinish()
{ {
@ -115,6 +117,23 @@ namespace Capnp.Rpc
AutoFinish(); AutoFinish();
} }
internal void AddRef()
{
lock (ReentrancyBlocker)
{
++_refCounter;
}
}
internal void Release()
{
lock (ReentrancyBlocker)
{
--_refCounter;
AutoFinish();
}
}
const string ReturnDespiteTailCallMessage = "Peer sent actual results despite the question was sent as tail call. This was not expected and is a protocol error."; 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) internal void OnReturn(DeserializerState results)
@ -189,11 +208,6 @@ namespace Capnp.Rpc
RpcEndpoint.DeleteQuestion(this); RpcEndpoint.DeleteQuestion(this);
} }
internal void RequestFinish()
{
RpcEndpoint.Finish(_questionId);
}
void AutoFinish() void AutoFinish()
{ {
if (StateFlags.HasFlag(State.FinishRequested)) if (StateFlags.HasFlag(State.FinishRequested))
@ -201,12 +215,13 @@ namespace Capnp.Rpc
return; return;
} }
if ((_inhibitFinishCounter == 0 && StateFlags.HasFlag(State.Returned) && !StateFlags.HasFlag(State.TailCall)) if ((!IsTailCall && _inhibitFinishCounter == 0 && StateFlags.HasFlag(State.Returned)) ||
|| StateFlags.HasFlag(State.Disposed)) ( IsTailCall && _refCounter == 0 && StateFlags.HasFlag(State.Returned)) ||
StateFlags.HasFlag(State.CanceledByDispose))
{ {
StateFlags |= State.FinishRequested; StateFlags |= State.FinishRequested;
RequestFinish(); RpcEndpoint.Finish(_questionId);
} }
} }
@ -338,9 +353,9 @@ namespace Capnp.Rpc
lock (ReentrancyBlocker) lock (ReentrancyBlocker)
{ {
if (!StateFlags.HasFlag(State.Disposed)) if (!StateFlags.HasFlag(State.CanceledByDispose))
{ {
StateFlags |= State.Disposed; StateFlags |= State.CanceledByDispose;
justDisposed = true; justDisposed = true;
AutoFinish(); AutoFinish();

View File

@ -70,7 +70,7 @@ namespace Capnp.Rpc
} }
} }
internal sealed override void AddRef() internal override void AddRef()
{ {
lock (_reentrancyBlocker) lock (_reentrancyBlocker)
{ {
@ -88,7 +88,7 @@ namespace Capnp.Rpc
} }
} }
internal sealed override void Release() internal override void Release()
{ {
lock (_reentrancyBlocker) lock (_reentrancyBlocker)
{ {

View File

@ -122,7 +122,7 @@ namespace Capnp.Rpc
#if DebugEmbargos #if DebugEmbargos
Logger.LogDebug("Call by proxy"); Logger.LogDebug("Call by proxy");
#endif #endif
if (_question.StateFlags.HasFlag(PendingQuestion.State.Disposed)) if (_question.StateFlags.HasFlag(PendingQuestion.State.CanceledByDispose))
{ {
args.Dispose(); args.Dispose();
throw new ObjectDisposedException(nameof(PendingQuestion)); throw new ObjectDisposedException(nameof(PendingQuestion));
@ -218,12 +218,12 @@ namespace Capnp.Rpc
{ {
lock (_question.ReentrancyBlocker) lock (_question.ReentrancyBlocker)
{ {
if (_question.StateFlags.HasFlag(PendingQuestion.State.Disposed)) if (_question.StateFlags.HasFlag(PendingQuestion.State.CanceledByDispose))
throw new ObjectDisposedException(nameof(PendingQuestion)); throw new ObjectDisposedException(nameof(PendingQuestion));
if (_question.StateFlags.HasFlag(PendingQuestion.State.Returned)) if (_question.StateFlags.HasFlag(PendingQuestion.State.Returned) && !_question.IsTailCall)
{ {
ResolvedCap?.Export(endpoint, writer); ResolvedCap!.Export(endpoint, writer);
} }
else else
{ {
@ -238,10 +238,6 @@ namespace Capnp.Rpc
} }
else if (_question.IsTailCall) else if (_question.IsTailCall)
{ {
// FIXME: Resource management! We should prevent finishing this
// cap as long as it is exported. Unfortunately, we cannot determine
// when it gets removed from the export table.
var vine = Vine.Create(this); var vine = Vine.Create(this);
uint id = endpoint.AllocateExport(vine, out bool first); uint id = endpoint.AllocateExport(vine, out bool first);
@ -260,8 +256,23 @@ namespace Capnp.Rpc
protected async override void ReleaseRemotely() protected async override void ReleaseRemotely()
{ {
try { using var _ = await _whenResolvedProxy; } if (!_question.IsTailCall)
catch { } {
try { using var _ = await _whenResolvedProxy; }
catch { }
}
}
internal override void AddRef()
{
base.AddRef();
_question.AddRef();
}
internal override void Release()
{
_question.Release();
base.Release();
} }
} }
} }

View File

@ -175,6 +175,34 @@ namespace Capnp.Rpc
} }
} }
/// <summary>
/// Current number of unanswered questions
/// </summary>
public int PendingQuestionCount
{
get
{
lock (_reentrancyBlocker)
{
return _questionTable.Count;
}
}
}
/// <summary>
/// Current number of unfinished answers
/// </summary>
public int PendingAnswerCount
{
get
{
lock (_reentrancyBlocker)
{
return _answerTable.Count;
}
}
}
void Tx(WireFrame frame) void Tx(WireFrame frame)
{ {
try try

View File

@ -281,7 +281,7 @@ namespace Capnp.Rpc
CheckCtsDisposal(); CheckCtsDisposal();
} }
if (Impl is IDisposable disposable) if (disposing && Impl is IDisposable disposable)
{ {
disposable.Dispose(); disposable.Dispose();
} }

View File

@ -0,0 +1,9 @@
namespace Capnp.Rpc
{
public class TailCallNoDataException: System.Exception
{
public TailCallNoDataException(): base("Because the question was asked as tail call, it won't return data")
{
}
}
}