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();
}
public bool IsTailCall => false;
public void Dispose()
{
}

View File

@ -622,20 +622,6 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
}
#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
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
#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
#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,8 +193,13 @@ namespace Capnp.Net.Runtime.Tests
Assert.AreEqual(0, _enginePair.Endpoint1.ExportedCapabilityCount);
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.ImportedCapabilityCount);
Assert.AreEqual(0, _enginePair.Endpoint2.PendingQuestionCount);
Assert.AreEqual(0, _enginePair.Endpoint2.PendingAnswerCount);
GC.Collect();
GC.WaitForPendingFinalizers();

View File

@ -31,5 +31,10 @@ namespace Capnp.Rpc
/// <param name="proxyTask">Task returning the proxy whose ownership will be taken over</param>
/// <returns></returns>
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()
{
return then(await promise.WhenReturned);
var result = await promise.WhenReturned;
if (promise.IsTailCall)
throw new TailCallNoDataException();
return then(result);
}
var rtask = AwaitAnswer();

View File

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

View File

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

View File

@ -49,14 +49,14 @@ namespace Capnp.Rpc
/// <summary>
/// Question object was disposed.
/// </summary>
Disposed = 16
CanceledByDispose = 16
}
readonly TaskCompletionSource<DeserializerState> _tcs = new TaskCompletionSource<DeserializerState>();
readonly uint _questionId;
ConsumedCapability? _target;
SerializerState? _inParams;
int _inhibitFinishCounter;
int _inhibitFinishCounter, _refCounter;
internal PendingQuestion(IRpcEndpoint ep, uint id, ConsumedCapability? target, SerializerState? inParams)
{
@ -91,10 +91,13 @@ namespace Capnp.Rpc
/// </summary>
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);
set
internal set
{
if (value)
StateFlags |= State.TailCall;
@ -102,7 +105,6 @@ namespace Capnp.Rpc
StateFlags &= ~State.TailCall;
}
}
internal bool IsReturned => StateFlags.HasFlag(State.Returned);
internal void DisallowFinish()
{
@ -115,6 +117,23 @@ namespace Capnp.Rpc
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.";
internal void OnReturn(DeserializerState results)
@ -189,11 +208,6 @@ namespace Capnp.Rpc
RpcEndpoint.DeleteQuestion(this);
}
internal void RequestFinish()
{
RpcEndpoint.Finish(_questionId);
}
void AutoFinish()
{
if (StateFlags.HasFlag(State.FinishRequested))
@ -201,12 +215,13 @@ namespace Capnp.Rpc
return;
}
if ((_inhibitFinishCounter == 0 && StateFlags.HasFlag(State.Returned) && !StateFlags.HasFlag(State.TailCall))
|| StateFlags.HasFlag(State.Disposed))
if ((!IsTailCall && _inhibitFinishCounter == 0 && StateFlags.HasFlag(State.Returned)) ||
( IsTailCall && _refCounter == 0 && StateFlags.HasFlag(State.Returned)) ||
StateFlags.HasFlag(State.CanceledByDispose))
{
StateFlags |= State.FinishRequested;
RequestFinish();
RpcEndpoint.Finish(_questionId);
}
}
@ -338,9 +353,9 @@ namespace Capnp.Rpc
lock (ReentrancyBlocker)
{
if (!StateFlags.HasFlag(State.Disposed))
if (!StateFlags.HasFlag(State.CanceledByDispose))
{
StateFlags |= State.Disposed;
StateFlags |= State.CanceledByDispose;
justDisposed = true;
AutoFinish();

View File

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

View File

@ -122,7 +122,7 @@ namespace Capnp.Rpc
#if DebugEmbargos
Logger.LogDebug("Call by proxy");
#endif
if (_question.StateFlags.HasFlag(PendingQuestion.State.Disposed))
if (_question.StateFlags.HasFlag(PendingQuestion.State.CanceledByDispose))
{
args.Dispose();
throw new ObjectDisposedException(nameof(PendingQuestion));
@ -218,12 +218,12 @@ namespace Capnp.Rpc
{
lock (_question.ReentrancyBlocker)
{
if (_question.StateFlags.HasFlag(PendingQuestion.State.Disposed))
if (_question.StateFlags.HasFlag(PendingQuestion.State.CanceledByDispose))
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
{
@ -238,10 +238,6 @@ namespace Capnp.Rpc
}
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);
uint id = endpoint.AllocateExport(vine, out bool first);
@ -260,8 +256,23 @@ namespace Capnp.Rpc
protected async override void ReleaseRemotely()
{
try { using var _ = await _whenResolvedProxy; }
catch { }
if (!_question.IsTailCall)
{
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)
{
try

View File

@ -281,7 +281,7 @@ namespace Capnp.Rpc
CheckCtsDisposal();
}
if (Impl is IDisposable disposable)
if (disposing && Impl is IDisposable disposable)
{
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")
{
}
}
}