mirror of
https://github.com/FabInfra/capnproto-dotnetcore_Runtime.git
synced 2025-03-12 23:01:44 +01:00
tests & fixes
This commit is contained in:
parent
01513ef71f
commit
bdc8240e5b
@ -100,6 +100,8 @@ namespace Capnp.Net.Runtime.Tests
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool IsTailCall => false;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
@ -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
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
||||
@ -259,9 +255,24 @@ namespace Capnp.Rpc
|
||||
}
|
||||
|
||||
protected async override void ReleaseRemotely()
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -281,7 +281,7 @@ namespace Capnp.Rpc
|
||||
CheckCtsDisposal();
|
||||
}
|
||||
|
||||
if (Impl is IDisposable disposable)
|
||||
if (disposing && Impl is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
|
9
Capnp.Net.Runtime/Rpc/TailCallNoDataException.cs
Normal file
9
Capnp.Net.Runtime/Rpc/TailCallNoDataException.cs
Normal 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")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user