mirror of
https://github.com/FabInfra/capnproto-dotnetcore_Runtime.git
synced 2025-03-12 14:51:41 +01:00
some refactoring. all tests green again
This commit is contained in:
parent
f58464293b
commit
e0d8f70cfc
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net471</TargetFramework>
|
||||
<TargetFrameworks>netcoreapp2.1;net471</TargetFrameworks>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
|
||||
|
96
Capnp.Net.Runtime.Tests/Dtbdct.cs
Normal file
96
Capnp.Net.Runtime.Tests/Dtbdct.cs
Normal file
@ -0,0 +1,96 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
[TestClass]
|
||||
[TestCategory("Coverage")]
|
||||
public class Dtbdct: TestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void Embargo()
|
||||
{
|
||||
NewDtbdctTestbed().RunTest(Testsuite.Embargo);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EmbargoError()
|
||||
{
|
||||
NewDtbdctTestbed().RunTest(Testsuite.EmbargoError);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EmbargoNull()
|
||||
{
|
||||
NewDtbdctTestbed().RunTest(Testsuite.EmbargoNull);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CallBrokenPromise()
|
||||
{
|
||||
NewDtbdctTestbed().RunTest(Testsuite.CallBrokenPromise);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TailCall()
|
||||
{
|
||||
NewDtbdctTestbed().RunTest(Testsuite.TailCall);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SendTwice()
|
||||
{
|
||||
NewDtbdctTestbed().RunTest(Testsuite.SendTwice);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Cancel()
|
||||
{
|
||||
NewDtbdctTestbed().RunTest(Testsuite.Cancel);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RetainAndRelease()
|
||||
{
|
||||
NewDtbdctTestbed().RunTest(Testsuite.RetainAndRelease);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PromiseResolve()
|
||||
{
|
||||
NewDtbdctTestbed().RunTest(Testsuite.PromiseResolve);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Cancelation()
|
||||
{
|
||||
NewDtbdctTestbed().RunTest(Testsuite.Cancelation);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ReleaseOnCancel()
|
||||
{
|
||||
NewDtbdctTestbed().RunTest(Testsuite.ReleaseOnCancel);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Release()
|
||||
{
|
||||
NewDtbdctTestbed().RunTest(Testsuite.Release);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Pipeline()
|
||||
{
|
||||
NewDtbdctTestbed().RunTest(Testsuite.Pipeline);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Basic()
|
||||
{
|
||||
NewDtbdctTestbed().RunTest(Testsuite.Basic);
|
||||
}
|
||||
}
|
||||
}
|
@ -78,13 +78,13 @@ namespace Capnp.Net.Runtime.Tests
|
||||
Assert.IsTrue(fcc.Wait(MediumNonDbgTimeout));
|
||||
var cc = fcc.Result;
|
||||
|
||||
var pr = new Capnproto_test.Capnp.Test.TestInterface.Params_foo.READER(cc.InArgs);
|
||||
var pr = new Capnproto_test.Capnp.Test.TestInterface.Params_Foo.READER(cc.InArgs);
|
||||
Assert.AreEqual(123u, pr.I);
|
||||
|
||||
cc.ForwardToBob();
|
||||
|
||||
Assert.IsTrue(policy.Returns.ReceiveAsync().Wait(MediumNonDbgTimeout));
|
||||
var rr = new Capnproto_test.Capnp.Test.TestInterface.Result_foo.READER(cc.OutArgs);
|
||||
var rr = new Capnproto_test.Capnp.Test.TestInterface.Result_Foo.READER(cc.OutArgs);
|
||||
Assert.AreEqual("foo", rr.X);
|
||||
|
||||
cc.ReturnToAlice();
|
||||
@ -117,11 +117,11 @@ namespace Capnp.Net.Runtime.Tests
|
||||
|
||||
Assert.AreEqual(InterceptionState.RequestedFromAlice, cc.State);
|
||||
|
||||
var pr = new Capnproto_test.Capnp.Test.TestInterface.Params_foo.READER(cc.InArgs);
|
||||
var pr = new Capnproto_test.Capnp.Test.TestInterface.Params_Foo.READER(cc.InArgs);
|
||||
Assert.AreEqual(321u, pr.I);
|
||||
Assert.AreEqual(false, pr.J);
|
||||
|
||||
var pw = cc.InArgs.Rewrap<Capnproto_test.Capnp.Test.TestInterface.Params_foo.WRITER>();
|
||||
var pw = cc.InArgs.Rewrap<Capnproto_test.Capnp.Test.TestInterface.Params_Foo.WRITER>();
|
||||
pw.I = 123u;
|
||||
pw.J = true;
|
||||
|
||||
@ -133,12 +133,12 @@ namespace Capnp.Net.Runtime.Tests
|
||||
Assert.IsTrue(cc.State == InterceptionState.ForwardedToBob || rx.IsCompleted);
|
||||
|
||||
Assert.IsTrue(rx.Wait(MediumNonDbgTimeout));
|
||||
var rr = new Capnproto_test.Capnp.Test.TestInterface.Result_foo.READER(cc.OutArgs);
|
||||
var rr = new Capnproto_test.Capnp.Test.TestInterface.Result_Foo.READER(cc.OutArgs);
|
||||
Assert.AreEqual("foo", rr.X);
|
||||
|
||||
Assert.IsFalse(request1.IsCompleted);
|
||||
|
||||
var rw = ((DynamicSerializerState)cc.OutArgs).Rewrap<Capnproto_test.Capnp.Test.TestInterface.Result_foo.WRITER>();
|
||||
var rw = ((DynamicSerializerState)cc.OutArgs).Rewrap<Capnproto_test.Capnp.Test.TestInterface.Result_Foo.WRITER>();
|
||||
rw.X = "bar";
|
||||
cc.OutArgs = rw;
|
||||
|
||||
@ -174,7 +174,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
Assert.IsTrue(policy.Calls.TryReceive(out var cc));
|
||||
Assert.IsFalse(request1.IsCompleted);
|
||||
|
||||
var rw = SerializerState.CreateForRpc<Capnproto_test.Capnp.Test.TestInterface.Result_foo.WRITER>();
|
||||
var rw = SerializerState.CreateForRpc<Capnproto_test.Capnp.Test.TestInterface.Result_Foo.WRITER>();
|
||||
rw.X = "bar";
|
||||
cc.OutArgs = rw;
|
||||
|
||||
|
@ -20,8 +20,9 @@ namespace Capnp.Net.Runtime.Tests
|
||||
var tcs = new TaskCompletionSource<int>();
|
||||
var impl = new TestPipelineImpl2(tcs.Task);
|
||||
var bproxy = BareProxy.FromImpl(impl);
|
||||
var proxy = bproxy.Cast<ITestPipeline>(true);
|
||||
var cap = proxy.GetCap(0, null).OutBox_Cap();
|
||||
using (var proxy = bproxy.Cast<ITestPipeline>(true))
|
||||
using (var cap = proxy.GetCap(0, null).OutBox_Cap())
|
||||
{
|
||||
var foo = cap.Foo(123, true);
|
||||
tcs.SetResult(0);
|
||||
Assert.IsTrue(foo.Wait(TestBase.MediumNonDbgTimeout));
|
||||
@ -29,3 +30,4 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -574,18 +574,17 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
|
||||
class TestCallOrderImpl : ITestCallOrder
|
||||
{
|
||||
readonly object _lock = new object();
|
||||
uint _counter;
|
||||
|
||||
ILogger Logger { get; } = Logging.CreateLogger<TestCallOrderImpl>();
|
||||
|
||||
public uint Count { get; set; }
|
||||
|
||||
public uint? CountToDispose { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
Assert.IsTrue(!CountToDispose.HasValue || Count == CountToDispose, "Must not dispose at this point");
|
||||
Assert.IsTrue(!CountToDispose.HasValue || _counter == CountToDispose, $"Must not dispose at this point: {_counter} {Thread.CurrentThread.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -593,7 +592,18 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return Task.FromResult(Count++);
|
||||
return Task.FromResult(_counter++);
|
||||
}
|
||||
}
|
||||
|
||||
public uint Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _counter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -749,7 +759,7 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
|
||||
public Task<ITestInterface> GetHeld(CancellationToken cancellationToken_)
|
||||
{
|
||||
Interlocked.Increment(ref _counters.CallCount);
|
||||
return Task.FromResult(ClientToHold);
|
||||
return Task.FromResult(Proxy.Share(ClientToHold));
|
||||
}
|
||||
|
||||
public Task<ITestMoreStuff> GetNull(CancellationToken cancellationToken_)
|
File diff suppressed because it is too large
Load Diff
@ -127,7 +127,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
_.Call.Target.ImportedCap = bootCapId;
|
||||
_.Call.InterfaceId = ((TypeIdAttribute)typeof(ITestInterface).GetCustomAttributes(typeof(TypeIdAttribute), false)[0]).Id;
|
||||
_.Call.MethodId = 0;
|
||||
_.Call.Params.Content.Rewrap<TestInterface.Params_foo.WRITER>();
|
||||
_.Call.Params.Content.Rewrap<TestInterface.Params_Foo.WRITER>();
|
||||
});
|
||||
tester.ExpectAbort();
|
||||
}
|
||||
@ -154,7 +154,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
_.Call.Target.ImportedCap = bootCapId;
|
||||
_.Call.InterfaceId = ((TypeIdAttribute)typeof(ITestInterface).GetCustomAttributes(typeof(TypeIdAttribute), false)[0]).Id;
|
||||
_.Call.MethodId = 0;
|
||||
var wr = _.Call.Params.Content.Rewrap<TestInterface.Params_foo.WRITER>();
|
||||
var wr = _.Call.Params.Content.Rewrap<TestInterface.Params_Foo.WRITER>();
|
||||
wr.I = 123u;
|
||||
wr.J = true;
|
||||
});
|
||||
@ -168,7 +168,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
_.Call.Target.ImportedCap = bootCapId;
|
||||
_.Call.InterfaceId = ((TypeIdAttribute)typeof(ITestInterface).GetCustomAttributes(typeof(TypeIdAttribute), false)[0]).Id;
|
||||
_.Call.MethodId = 0;
|
||||
_.Call.Params.Content.Rewrap<TestInterface.Params_foo.WRITER>();
|
||||
_.Call.Params.Content.Rewrap<TestInterface.Params_Foo.WRITER>();
|
||||
});
|
||||
tester.ExpectAbort();
|
||||
}
|
||||
@ -565,7 +565,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
_.Call.Target.ImportedCap = bootCapId;
|
||||
_.Call.InterfaceId = new TestPipeline_Skeleton().InterfaceId;
|
||||
_.Call.MethodId = 0;
|
||||
var wr = _.Call.Params.Content.Rewrap<TestPipeline.Params_getCap.WRITER>();
|
||||
var wr = _.Call.Params.Content.Rewrap<TestPipeline.Params_GetCap.WRITER>();
|
||||
wr.InCap = null;
|
||||
_.Call.Params.CapTable.Init(1);
|
||||
_.Call.Params.CapTable[0].which = CapDescriptor.WHICH.ReceiverHosted;
|
||||
|
@ -642,7 +642,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void RetainAndReleaseClient()
|
||||
{
|
||||
using (var server = SetupServer())
|
||||
|
@ -12,6 +12,7 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
|
||||
[TestClass]
|
||||
[TestCategory("Coverage")]
|
||||
public class TcpRpcPorted: TestBase
|
||||
@ -19,99 +20,19 @@ namespace Capnp.Net.Runtime.Tests
|
||||
[TestMethod]
|
||||
public void Basic()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestInterfaceImpl(counters);
|
||||
using (var main = client.GetMain<ITestInterface>())
|
||||
{
|
||||
var request1 = main.Foo(123, true, default);
|
||||
var request3 = Assert.ThrowsExceptionAsync<RpcException>(() => main.Bar(default));
|
||||
var s = new TestAllTypes();
|
||||
Common.InitTestMessage(s);
|
||||
var request2 = main.Baz(s, default);
|
||||
|
||||
Assert.IsTrue(request1.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(request2.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(request3.Wait(MediumNonDbgTimeout));
|
||||
|
||||
Assert.AreEqual("foo", request1.Result);
|
||||
Assert.AreEqual(2, counters.CallCount);
|
||||
}
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.Basic);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Pipeline()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestPipelineImpl(counters);
|
||||
using (var main = client.GetMain<ITestPipeline>())
|
||||
{
|
||||
var chainedCallCount = new Counters();
|
||||
var request = main.GetCap(234, new TestInterfaceImpl(chainedCallCount), default);
|
||||
using (var outBox = request.OutBox_Cap())
|
||||
{
|
||||
var pipelineRequest = outBox.Foo(321, false, default);
|
||||
var pipelineRequest2 = ((Proxy)outBox).Cast<ITestExtends>(false).Grault(default);
|
||||
|
||||
Assert.IsTrue(pipelineRequest.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(pipelineRequest2.Wait(MediumNonDbgTimeout));
|
||||
|
||||
Assert.AreEqual("bar", pipelineRequest.Result);
|
||||
Common.CheckTestMessage(pipelineRequest2.Result);
|
||||
|
||||
Assert.AreEqual(3, counters.CallCount);
|
||||
Assert.AreEqual(1, chainedCallCount.CallCount);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.Pipeline);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Release()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestMoreStuffImpl(counters);
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
{
|
||||
var task1 = main.GetHandle(default);
|
||||
var task2 = main.GetHandle(default);
|
||||
Assert.IsTrue(task1.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(task2.Wait(MediumNonDbgTimeout));
|
||||
|
||||
Assert.AreEqual(2, counters.HandleCount);
|
||||
|
||||
task1.Result.Dispose();
|
||||
|
||||
Assert.IsTrue(SpinWait.SpinUntil(() => counters.HandleCount == 1, MediumNonDbgTimeout));
|
||||
|
||||
task2.Result.Dispose();
|
||||
|
||||
Assert.IsTrue(SpinWait.SpinUntil(() => counters.HandleCount == 0, MediumNonDbgTimeout));
|
||||
}
|
||||
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.Release);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@ -160,482 +81,63 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestTailCall()
|
||||
public void TailCall()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestTailCallerImpl(counters);
|
||||
using (var main = client.GetMain<ITestTailCaller>())
|
||||
{
|
||||
var calleeCallCount = new Counters();
|
||||
var callee = new TestTailCalleeImpl(calleeCallCount);
|
||||
|
||||
var promise = main.Foo(456, callee, default);
|
||||
var dependentCall0 = promise.C().GetCallSequence(0, default);
|
||||
|
||||
Assert.IsTrue(promise.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual(456u, promise.Result.I);
|
||||
Assert.AreEqual("from TestTailCaller", promise.Result.T);
|
||||
|
||||
var dependentCall1 = promise.C().GetCallSequence(0, default);
|
||||
var dependentCall2 = promise.C().GetCallSequence(0, default);
|
||||
|
||||
Assert.IsTrue(dependentCall0.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(dependentCall1.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(dependentCall2.Wait(MediumNonDbgTimeout));
|
||||
|
||||
Assert.AreEqual(1, counters.CallCount);
|
||||
Assert.AreEqual(1, calleeCallCount.CallCount);
|
||||
}
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.TailCall);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Cancelation()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestMoreStuffImpl(counters);
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
{
|
||||
var destroyed = new TaskCompletionSource<int>();
|
||||
var impl = new TestInterfaceImpl(counters, destroyed);
|
||||
var cts = new CancellationTokenSource();
|
||||
var cancelTask = main.ExpectCancel(impl, cts.Token);
|
||||
|
||||
Assert.IsFalse(SpinWait.SpinUntil(() => destroyed.Task.IsCompleted || cancelTask.IsCompleted, ShortTimeout));
|
||||
|
||||
cts.Cancel();
|
||||
|
||||
Assert.IsTrue(destroyed.Task.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsFalse(cancelTask.IsCompleted && !cancelTask.IsCanceled);
|
||||
}
|
||||
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.Cancelation);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PromiseResolve()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
server.Main = impl;
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
{
|
||||
var tcs = new TaskCompletionSource<ITestInterface>();
|
||||
var eager = tcs.Task.Eager(true);
|
||||
|
||||
var request = main.CallFoo(eager, default);
|
||||
var request2 = main.CallFooWhenResolved(eager, default);
|
||||
|
||||
var gcs = main.GetCallSequence(0, default);
|
||||
Assert.IsTrue(gcs.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual(2u, gcs.Result);
|
||||
Assert.AreEqual(3, counters.CallCount);
|
||||
|
||||
var chainedCallCount = new Counters();
|
||||
var tiimpl = new TestInterfaceImpl(chainedCallCount);
|
||||
tcs.SetResult(tiimpl);
|
||||
|
||||
Assert.IsTrue(request.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(request2.Wait(MediumNonDbgTimeout));
|
||||
|
||||
Assert.AreEqual("bar", request.Result);
|
||||
Assert.AreEqual("bar", request2.Result);
|
||||
Assert.AreEqual(3, counters.CallCount);
|
||||
Assert.AreEqual(2, chainedCallCount.CallCount);
|
||||
}
|
||||
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.PromiseResolve);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RetainAndRelease()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var destructionPromise = new TaskCompletionSource<int>();
|
||||
var destructionTask = destructionPromise.Task;
|
||||
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
server.Main = impl;
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
{
|
||||
var holdTask = main.Hold(new TestInterfaceImpl(new Counters(), destructionPromise), default);
|
||||
Assert.IsTrue(holdTask.Wait(MediumNonDbgTimeout));
|
||||
|
||||
var cstask = main.GetCallSequence(0, default);
|
||||
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual(1u, cstask.Result);
|
||||
|
||||
Assert.IsFalse(destructionTask.IsCompleted);
|
||||
|
||||
var htask = main.CallHeld(default);
|
||||
Assert.IsTrue(htask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual("bar", htask.Result);
|
||||
|
||||
var gtask = main.GetHeld(default);
|
||||
Assert.IsTrue(gtask.Wait(MediumNonDbgTimeout));
|
||||
// We can get the cap back from it.
|
||||
using (var cap = gtask.Result)
|
||||
{
|
||||
// Wait for balanced state
|
||||
WaitClientServerIdle(server, client);
|
||||
|
||||
// And call it, without any network communications.
|
||||
long oldSendCount = client.SendCount;
|
||||
var ftask = cap.Foo(123, true, default);
|
||||
Assert.IsTrue(ftask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual("foo", ftask.Result);
|
||||
Assert.AreEqual(oldSendCount, client.SendCount);
|
||||
|
||||
// We can send another copy of the same cap to another method, and it works.
|
||||
var ctask = main.CallFoo(cap, default);
|
||||
Assert.IsTrue(ctask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual("bar", ctask.Result);
|
||||
|
||||
// Give some time to settle.
|
||||
cstask = main.GetCallSequence(0, default);
|
||||
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual(5u, cstask.Result);
|
||||
cstask = main.GetCallSequence(0, default);
|
||||
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual(6u, cstask.Result);
|
||||
cstask = main.GetCallSequence(0, default);
|
||||
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual(7u, cstask.Result);
|
||||
|
||||
// Can't be destroyed, we haven't released it.
|
||||
Assert.IsFalse(destructionTask.IsCompleted);
|
||||
}
|
||||
|
||||
// In deviation from original test, we have null the held capability on the main interface.
|
||||
// This is because the main interface is the bootstrap capability and, as such, won't be disposed
|
||||
// after disconnect.
|
||||
var holdNullTask = main.Hold(null, default);
|
||||
Assert.IsTrue(holdNullTask.Wait(MediumNonDbgTimeout));
|
||||
}
|
||||
|
||||
Assert.IsTrue(destructionTask.Wait(MediumNonDbgTimeout));
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.RetainAndRelease);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Cancel()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var destructionPromise = new TaskCompletionSource<int>();
|
||||
var destructionTask = destructionPromise.Task;
|
||||
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
server.Main = impl;
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
using (var cts = new CancellationTokenSource())
|
||||
{
|
||||
var ntask = main.NeverReturn(new TestInterfaceImpl(counters, destructionPromise), cts.Token);
|
||||
|
||||
// Allow some time to settle.
|
||||
var cstask = main.GetCallSequence(0, default);
|
||||
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual(1u, cstask.Result);
|
||||
cstask = main.GetCallSequence(0, default);
|
||||
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual(2u, cstask.Result);
|
||||
|
||||
// The cap shouldn't have been destroyed yet because the call never returned.
|
||||
Assert.IsFalse(destructionTask.IsCompleted);
|
||||
|
||||
// There will be no automatic cancellation just because "ntask" goes of of scope or
|
||||
// because the Proxy is disposed. Even ntask.Dispose() would not cancel the request.
|
||||
// In .NET this needs to be done explicitly.
|
||||
cts.Cancel();
|
||||
}
|
||||
|
||||
// Now the cap should be released.
|
||||
Assert.IsTrue(destructionTask.Wait(MediumNonDbgTimeout));
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.Cancel);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SendTwice()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var destructionPromise = new TaskCompletionSource<int>();
|
||||
var destructionTask = destructionPromise.Task;
|
||||
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
server.Main = impl;
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
{
|
||||
var cap = new TestInterfaceImpl(new Counters(), destructionPromise);
|
||||
|
||||
Task<string> ftask1, ftask2;
|
||||
|
||||
using (Skeleton.Claim(cap))
|
||||
{
|
||||
var ftask = main.CallFoo(cap, default);
|
||||
Assert.IsTrue(ftask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual("bar", ftask.Result);
|
||||
|
||||
var ctask = main.GetCallSequence(0, default);
|
||||
Assert.IsTrue(ctask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual(1u, ctask.Result);
|
||||
|
||||
ftask1 = main.CallFoo(cap, default);
|
||||
ftask2 = main.CallFoo(cap, default);
|
||||
}
|
||||
|
||||
Assert.IsTrue(ftask1.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual("bar", ftask1.Result);
|
||||
Assert.IsTrue(ftask2.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual("bar", ftask2.Result);
|
||||
|
||||
// Now the cap should be released.
|
||||
Assert.IsTrue(destructionTask.Wait(MediumNonDbgTimeout));
|
||||
}
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.SendTwice);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Embargo()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
server.Main = impl;
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
{
|
||||
var resolving = main as IResolvingCapability;
|
||||
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout));
|
||||
|
||||
var cap = new TestCallOrderImpl();
|
||||
cap.CountToDispose = 6;
|
||||
|
||||
var earlyCall = main.GetCallSequence(0, default);
|
||||
|
||||
var echo = main.Echo(cap, default);
|
||||
|
||||
using (var pipeline = echo.Eager())
|
||||
{
|
||||
var call0 = pipeline.GetCallSequence(0, default);
|
||||
var call1 = pipeline.GetCallSequence(1, default);
|
||||
|
||||
Assert.IsTrue(earlyCall.Wait(MediumNonDbgTimeout));
|
||||
|
||||
impl.EnableEcho();
|
||||
|
||||
var call2 = pipeline.GetCallSequence(2, default);
|
||||
|
||||
Assert.IsTrue(echo.Wait(MediumNonDbgTimeout));
|
||||
using (var resolved = echo.Result)
|
||||
{
|
||||
var call3 = pipeline.GetCallSequence(3, default);
|
||||
var call4 = pipeline.GetCallSequence(4, default);
|
||||
var call5 = pipeline.GetCallSequence(5, default);
|
||||
|
||||
try
|
||||
{
|
||||
bool flag = call0.Wait(MediumNonDbgTimeout);
|
||||
Assert.IsTrue(flag);
|
||||
}
|
||||
catch (AggregateException exception) when (exception.InnerException is RpcException rpcException && rpcException.Message == "Cannot access a disposed object.")
|
||||
{
|
||||
Console.WriteLine($"Oops, object disposed. Counter = {cap.Count}, tx count = {client.SendCount}, rx count = {client.RecvCount}");
|
||||
throw;
|
||||
}
|
||||
|
||||
Assert.IsTrue(call1.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(call2.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(call3.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(call4.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(call5.Wait(MediumNonDbgTimeout));
|
||||
|
||||
Assert.AreEqual(0u, call0.Result);
|
||||
Assert.AreEqual(1u, call1.Result);
|
||||
Assert.AreEqual(2u, call2.Result);
|
||||
Assert.AreEqual(3u, call3.Result);
|
||||
Assert.AreEqual(4u, call4.Result);
|
||||
Assert.AreEqual(5u, call5.Result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.Embargo);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EmbargoError()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
server.Main = impl;
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
{
|
||||
var resolving = main as IResolvingCapability;
|
||||
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout));
|
||||
|
||||
var cap = new TaskCompletionSource<ITestCallOrder>();
|
||||
|
||||
var earlyCall = main.GetCallSequence(0, default);
|
||||
|
||||
var echo = main.Echo(cap.Task.Eager(true), default);
|
||||
|
||||
var pipeline = echo.Eager();
|
||||
|
||||
var call0 = pipeline.GetCallSequence(0, default);
|
||||
var call1 = pipeline.GetCallSequence(1, default);
|
||||
|
||||
Assert.IsTrue(earlyCall.Wait(MediumNonDbgTimeout));
|
||||
|
||||
impl.EnableEcho();
|
||||
|
||||
var call2 = pipeline.GetCallSequence(2, default);
|
||||
|
||||
Assert.IsTrue(echo.Wait(MediumNonDbgTimeout));
|
||||
var resolved = echo.Result;
|
||||
|
||||
var call3 = pipeline.GetCallSequence(3, default);
|
||||
var call4 = pipeline.GetCallSequence(4, default);
|
||||
var call5 = pipeline.GetCallSequence(5, default);
|
||||
|
||||
cap.SetException(new InvalidOperationException("I'm annoying"));
|
||||
|
||||
ExpectPromiseThrows(call0);
|
||||
ExpectPromiseThrows(call1);
|
||||
ExpectPromiseThrows(call2);
|
||||
ExpectPromiseThrows(call3);
|
||||
ExpectPromiseThrows(call4);
|
||||
ExpectPromiseThrows(call5);
|
||||
|
||||
// Verify that we're still connected (there were no protocol errors).
|
||||
Assert.IsTrue(main.GetCallSequence(1, default).Wait(MediumNonDbgTimeout));
|
||||
}
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.EmbargoError);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EmbargoNull()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
server.Main = impl;
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
{
|
||||
var resolving = main as IResolvingCapability;
|
||||
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout));
|
||||
|
||||
var promise = main.GetNull(default);
|
||||
|
||||
var cap = promise.Eager();
|
||||
|
||||
var call0 = cap.GetCallSequence(0, default);
|
||||
|
||||
Assert.IsTrue(promise.Wait(MediumNonDbgTimeout));
|
||||
|
||||
var call1 = cap.GetCallSequence(1, default);
|
||||
|
||||
ExpectPromiseThrows(call0);
|
||||
ExpectPromiseThrows(call1);
|
||||
|
||||
// Verify that we're still connected (there were no protocol errors).
|
||||
Assert.IsTrue(main.GetCallSequence(1, default).Wait(MediumNonDbgTimeout));
|
||||
}
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.EmbargoNull);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CallBrokenPromise()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
server.Main = impl;
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
{
|
||||
var resolving = main as IResolvingCapability;
|
||||
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout));
|
||||
|
||||
var tcs = new TaskCompletionSource<ITestInterface>();
|
||||
|
||||
var req = main.Hold(tcs.Task.Eager(true), default);
|
||||
Assert.IsTrue(req.Wait(MediumNonDbgTimeout));
|
||||
|
||||
var req2 = main.CallHeld(default);
|
||||
|
||||
Assert.IsFalse(req2.Wait(ShortTimeout));
|
||||
|
||||
tcs.SetException(new InvalidOperationException("I'm a promise-breaker!"));
|
||||
|
||||
ExpectPromiseThrows(req2);
|
||||
|
||||
// Verify that we're still connected (there were no protocol errors).
|
||||
Assert.IsTrue(main.GetCallSequence(1, default).Wait(MediumNonDbgTimeout));
|
||||
}
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.CallBrokenPromise);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
@ -60,7 +61,11 @@ namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
var t = new TcpRpcPorted();
|
||||
Repeat(100, t.Embargo);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EmbargoServer()
|
||||
{
|
||||
var t2 = new TcpRpcInterop();
|
||||
Repeat(100, t2.EmbargoServer);
|
||||
}
|
||||
@ -94,7 +99,10 @@ namespace Capnp.Net.Runtime.Tests
|
||||
[TestMethod]
|
||||
public void ScatteredTransfer()
|
||||
{
|
||||
|
||||
for (int retry = 0; retry < 10; retry++)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var server = new TcpRpcServer(IPAddress.Any, TcpPort))
|
||||
using (var client = new TcpRpcClient())
|
||||
{
|
||||
@ -125,6 +133,13 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
IncrementTcpPort();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,130 +0,0 @@
|
||||
using Capnp.Rpc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
public class TestBase
|
||||
{
|
||||
public static int TcpPort = 49152;
|
||||
public static int MediumNonDbgTimeout => Debugger.IsAttached ? Timeout.Infinite : 5000;
|
||||
public static int LargeNonDbgTimeout => Debugger.IsAttached ? Timeout.Infinite : 20000;
|
||||
public static int ShortTimeout => 500;
|
||||
|
||||
protected ILogger Logger { get; set; }
|
||||
|
||||
protected TcpRpcClient SetupClient()
|
||||
{
|
||||
var client = new TcpRpcClient();
|
||||
client.AddBuffering();
|
||||
client.Connect("localhost", TcpPort);
|
||||
return client;
|
||||
}
|
||||
|
||||
protected TcpRpcServer SetupServer()
|
||||
{
|
||||
int attempt = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var server = new TcpRpcServer(IPAddress.Any, TcpPort);
|
||||
server.AddBuffering();
|
||||
return server;
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
// If the TCP listening port is occupied by some other process,
|
||||
// retry with a different one
|
||||
|
||||
if (attempt == 5)
|
||||
throw;
|
||||
}
|
||||
|
||||
IncrementTcpPort();
|
||||
++attempt;
|
||||
}
|
||||
}
|
||||
|
||||
protected (TcpRpcServer, TcpRpcClient) SetupClientServerPair()
|
||||
{
|
||||
var server = SetupServer();
|
||||
var client = SetupClient();
|
||||
return (server, client);
|
||||
}
|
||||
|
||||
public static void IncrementTcpPort()
|
||||
{
|
||||
if (++TcpPort > 49200)
|
||||
{
|
||||
TcpPort = 49152;
|
||||
}
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void InitConsoleLogging()
|
||||
{
|
||||
Logging.LoggerFactory?.Dispose();
|
||||
Logging.LoggerFactory = new LoggerFactory().AddConsole((msg, level) => true);
|
||||
Logger = Logging.CreateLogger<TcpRpcStress>();
|
||||
if (Thread.CurrentThread.Name == null)
|
||||
Thread.CurrentThread.Name = $"Test Thread {Thread.CurrentThread.ManagedThreadId}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Somewhat ugly helper method which ensures that both Tcp client and server
|
||||
/// are waiting for data reception from each other. This is a "balanced" state, meaning
|
||||
/// that nothing will ever happen in the RcpEngines without some other thread requesting
|
||||
/// anything.
|
||||
/// </summary>
|
||||
protected void WaitClientServerIdle(TcpRpcServer server, TcpRpcClient client)
|
||||
{
|
||||
var conn = server.Connections[0];
|
||||
SpinWait.SpinUntil(() => conn.IsWaitingForData && client.IsWaitingForData &&
|
||||
conn.RecvCount == client.SendCount &&
|
||||
conn.SendCount == client.RecvCount,
|
||||
MediumNonDbgTimeout);
|
||||
}
|
||||
|
||||
protected void ExpectPromiseThrows(Task task)
|
||||
{
|
||||
async Task ExpectPromiseThrowsAsync(Task t)
|
||||
{
|
||||
try
|
||||
{
|
||||
await t;
|
||||
Assert.Fail("Did not throw");
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Happens if the call went to the resolution
|
||||
// (thus, locally). In this case, the original
|
||||
// exception is routed here.
|
||||
}
|
||||
catch (RpcException)
|
||||
{
|
||||
// Happens if the call went to the promise
|
||||
// (thus, remotely). In this case, the original
|
||||
// exception had to be serialized, so we receive
|
||||
// the wrapped version.
|
||||
}
|
||||
catch (System.Exception exception)
|
||||
{
|
||||
Assert.Fail($"Got wrong kind of exception: {exception}");
|
||||
}
|
||||
}
|
||||
|
||||
Assert.IsTrue(ExpectPromiseThrowsAsync(task).Wait(MediumNonDbgTimeout));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
523
Capnp.Net.Runtime.Tests/Testsuite.cs
Normal file
523
Capnp.Net.Runtime.Tests/Testsuite.cs
Normal file
@ -0,0 +1,523 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Capnp.Rpc;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Capnp.Net.Runtime.Tests.GenImpls;
|
||||
using Capnproto_test.Capnp.Test;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
static class Testsuite
|
||||
{
|
||||
static void ExpectPromiseThrows(this ITestbed testbed, params Task[] tasks)
|
||||
{
|
||||
async Task ExpectPromiseThrowsAsync(Task t)
|
||||
{
|
||||
try
|
||||
{
|
||||
await t;
|
||||
Assert.Fail("Did not throw");
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Happens if the call went to the resolution
|
||||
// (thus, locally). In this case, the original
|
||||
// exception is routed here.
|
||||
}
|
||||
catch (InvalidTimeZoneException)
|
||||
{
|
||||
// Happens if the call went to the resolution
|
||||
// (thus, locally). In this case, the original
|
||||
// exception is routed here.
|
||||
}
|
||||
catch (RpcException)
|
||||
{
|
||||
// Happens if the call went to the promise
|
||||
// (thus, remotely). In this case, the original
|
||||
// exception had to be serialized, so we receive
|
||||
// the wrapped version.
|
||||
}
|
||||
catch (System.Exception exception)
|
||||
{
|
||||
Assert.Fail($"Got wrong kind of exception: {exception}");
|
||||
}
|
||||
}
|
||||
|
||||
var ftasks = tasks.Select(ExpectPromiseThrowsAsync).ToArray();
|
||||
testbed.MustComplete(ftasks);
|
||||
|
||||
foreach (var ftask in ftasks)
|
||||
ftask.GetAwaiter().GetResult(); // re-throw exception
|
||||
}
|
||||
|
||||
public static void Embargo(ITestbed testbed)
|
||||
{
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
|
||||
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
|
||||
{
|
||||
var resolving = main as IResolvingCapability;
|
||||
testbed.MustComplete(resolving.WhenResolved);
|
||||
|
||||
var cap = new TestCallOrderImpl();
|
||||
cap.CountToDispose = 6;
|
||||
|
||||
var earlyCall = main.GetCallSequence(0, default);
|
||||
|
||||
var echo = main.Echo(cap, default);
|
||||
testbed.MustComplete(Task.CompletedTask);
|
||||
using (var pipeline = echo.Eager())
|
||||
{
|
||||
var call0 = pipeline.GetCallSequence(0, default);
|
||||
var call1 = pipeline.GetCallSequence(1, default);
|
||||
|
||||
testbed.MustComplete(earlyCall);
|
||||
|
||||
impl.EnableEcho();
|
||||
|
||||
var call2 = pipeline.GetCallSequence(2, default);
|
||||
|
||||
testbed.MustComplete(echo);
|
||||
using (var resolved = echo.Result)
|
||||
{
|
||||
var call3 = pipeline.GetCallSequence(3, default);
|
||||
var call4 = pipeline.GetCallSequence(4, default);
|
||||
var call5 = pipeline.GetCallSequence(5, default);
|
||||
|
||||
try
|
||||
{
|
||||
testbed.MustComplete(call0);
|
||||
testbed.MustComplete(call1);
|
||||
testbed.MustComplete(call2);
|
||||
testbed.MustComplete(call3);
|
||||
testbed.MustComplete(call4);
|
||||
testbed.MustComplete(call5);
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
cap.CountToDispose = null;
|
||||
throw;
|
||||
}
|
||||
|
||||
Assert.AreEqual(0u, call0.Result);
|
||||
Assert.AreEqual(1u, call1.Result);
|
||||
Assert.AreEqual(2u, call2.Result);
|
||||
Assert.AreEqual(3u, call3.Result);
|
||||
Assert.AreEqual(4u, call4.Result);
|
||||
Assert.AreEqual(5u, call5.Result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void EmbargoError(ITestbed testbed)
|
||||
{
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
|
||||
{
|
||||
var resolving = main as IResolvingCapability;
|
||||
testbed.MustComplete(resolving.WhenResolved);
|
||||
|
||||
var cap = new TaskCompletionSource<ITestCallOrder>();
|
||||
|
||||
var earlyCall = main.GetCallSequence(0, default);
|
||||
var echo = main.Echo(cap.Task.Eager(true), default);
|
||||
|
||||
using (var pipeline = echo.Eager())
|
||||
{
|
||||
var call0 = pipeline.GetCallSequence(0, default);
|
||||
var call1 = pipeline.GetCallSequence(1, default);
|
||||
|
||||
testbed.MustComplete(earlyCall);
|
||||
|
||||
impl.EnableEcho();
|
||||
|
||||
var call2 = pipeline.GetCallSequence(2, default);
|
||||
|
||||
testbed.MustComplete(echo);
|
||||
var resolved = echo.Result;
|
||||
var call3 = pipeline.GetCallSequence(3, default);
|
||||
var call4 = pipeline.GetCallSequence(4, default);
|
||||
var call5 = pipeline.GetCallSequence(5, default);
|
||||
|
||||
cap.SetException(new InvalidTimeZoneException("I'm annoying"));
|
||||
|
||||
testbed.ExpectPromiseThrows(call0, call1, call2, call3, call4, call5);
|
||||
|
||||
// Verify that we're still connected (there were no protocol errors).
|
||||
testbed.MustComplete(main.GetCallSequence(1, default));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void EmbargoNull(ITestbed testbed)
|
||||
{
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
|
||||
{
|
||||
var resolving = main as IResolvingCapability;
|
||||
testbed.MustComplete(resolving.WhenResolved);
|
||||
|
||||
var promise = main.GetNull(default);
|
||||
|
||||
var cap = promise.Eager();
|
||||
|
||||
var call0 = cap.GetCallSequence(0, default);
|
||||
|
||||
testbed.MustComplete(promise);
|
||||
|
||||
var call1 = cap.GetCallSequence(1, default);
|
||||
|
||||
testbed.ExpectPromiseThrows(call0, call1);
|
||||
|
||||
// Verify that we're still connected (there were no protocol errors).
|
||||
testbed.MustComplete(main.GetCallSequence(1, default));
|
||||
}
|
||||
}
|
||||
|
||||
public static void CallBrokenPromise(ITestbed testbed)
|
||||
{
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
|
||||
{
|
||||
var resolving = main as IResolvingCapability;
|
||||
testbed.MustComplete(resolving.WhenResolved);
|
||||
|
||||
var tcs = new TaskCompletionSource<ITestInterface>();
|
||||
|
||||
var req = main.Hold(tcs.Task.Eager(true), default);
|
||||
testbed.MustComplete(req);
|
||||
|
||||
var req2 = main.CallHeld(default);
|
||||
|
||||
testbed.MustNotComplete(req2);
|
||||
|
||||
tcs.SetException(new InvalidOperationException("I'm a promise-breaker!"));
|
||||
|
||||
testbed.ExpectPromiseThrows(req2);
|
||||
|
||||
// Verify that we're still connected (there were no protocol errors).
|
||||
testbed.MustComplete(main.GetCallSequence(1, default));
|
||||
}
|
||||
}
|
||||
|
||||
public static void TailCall(ITestbed testbed)
|
||||
{
|
||||
var counters = new Counters();
|
||||
var impl = new TestTailCallerImpl(counters);
|
||||
using (var main = testbed.ConnectMain<ITestTailCaller>(impl))
|
||||
{
|
||||
var calleeCallCount = new Counters();
|
||||
var callee = new TestTailCalleeImpl(calleeCallCount);
|
||||
|
||||
var promise = main.Foo(456, callee, default);
|
||||
using (var c = promise.C())
|
||||
{
|
||||
var dependentCall0 = c.GetCallSequence(0, default);
|
||||
|
||||
testbed.MustComplete(promise);
|
||||
Assert.AreEqual(456u, promise.Result.I);
|
||||
Assert.AreEqual("from TestTailCaller", promise.Result.T);
|
||||
|
||||
var dependentCall1 = c.GetCallSequence(0, default);
|
||||
var dependentCall2 = c.GetCallSequence(0, default);
|
||||
|
||||
testbed.MustComplete(dependentCall0, dependentCall1, dependentCall2);
|
||||
|
||||
Assert.AreEqual(1, counters.CallCount);
|
||||
Assert.AreEqual(1, calleeCallCount.CallCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SendTwice(ITestbed testbed)
|
||||
{
|
||||
var destructionPromise = new TaskCompletionSource<int>();
|
||||
var destructionTask = destructionPromise.Task;
|
||||
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
|
||||
{
|
||||
var cap = new TestInterfaceImpl(new Counters(), destructionPromise);
|
||||
|
||||
Task<string> ftask1, ftask2;
|
||||
|
||||
using (var claimer = Skeleton.Claim(cap))
|
||||
{
|
||||
var ftask = main.CallFoo(cap, default);
|
||||
testbed.MustComplete(ftask);
|
||||
Assert.AreEqual("bar", ftask.Result);
|
||||
|
||||
var ctask = main.GetCallSequence(0, default);
|
||||
testbed.MustComplete(ctask);
|
||||
Assert.AreEqual(1u, ctask.Result);
|
||||
|
||||
ftask1 = main.CallFoo(cap, default);
|
||||
ftask2 = main.CallFoo(cap, default);
|
||||
}
|
||||
|
||||
testbed.MustComplete(ftask1);
|
||||
Assert.AreEqual("bar", ftask1.Result);
|
||||
testbed.MustComplete(ftask2);
|
||||
Assert.AreEqual("bar", ftask2.Result);
|
||||
|
||||
// Now the cap should be released.
|
||||
testbed.MustComplete(destructionTask);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Cancel(ITestbed testbed)
|
||||
{
|
||||
var destructionPromise = new TaskCompletionSource<int>();
|
||||
var destructionTask = destructionPromise.Task;
|
||||
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
|
||||
using (var cts = new CancellationTokenSource())
|
||||
{
|
||||
var ntask = main.NeverReturn(new TestInterfaceImpl(counters, destructionPromise), cts.Token);
|
||||
|
||||
// Allow some time to settle.
|
||||
var cstask = main.GetCallSequence(0, default);
|
||||
testbed.MustComplete(cstask);
|
||||
Assert.AreEqual(1u, cstask.Result);
|
||||
cstask = main.GetCallSequence(0, default);
|
||||
testbed.MustComplete(cstask);
|
||||
Assert.AreEqual(2u, cstask.Result);
|
||||
|
||||
// The cap shouldn't have been destroyed yet because the call never returned.
|
||||
Assert.IsFalse(destructionTask.IsCompleted);
|
||||
|
||||
// There will be no automatic cancellation just because "ntask" goes of of scope or
|
||||
// because the Proxy is disposed. Even ntask.Dispose() would not cancel the request.
|
||||
// In .NET this needs to be done explicitly.
|
||||
cts.Cancel();
|
||||
}
|
||||
|
||||
// Now the cap should be released.
|
||||
testbed.MustComplete(destructionTask);
|
||||
}
|
||||
|
||||
public static void RetainAndRelease(ITestbed testbed)
|
||||
{
|
||||
var destructionPromise = new TaskCompletionSource<int>();
|
||||
var destructionTask = destructionPromise.Task;
|
||||
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
|
||||
{
|
||||
var holdTask = main.Hold(new TestInterfaceImpl(new Counters(), destructionPromise), default);
|
||||
testbed.MustComplete(holdTask);
|
||||
|
||||
var cstask = main.GetCallSequence(0, default);
|
||||
testbed.MustComplete(cstask);
|
||||
Assert.AreEqual(1u, cstask.Result);
|
||||
|
||||
Assert.IsFalse(destructionTask.IsCompleted);
|
||||
|
||||
var htask = main.CallHeld(default);
|
||||
testbed.MustComplete(htask);
|
||||
Assert.AreEqual("bar", htask.Result);
|
||||
|
||||
var gtask = main.GetHeld(default);
|
||||
testbed.MustComplete(gtask);
|
||||
// We can get the cap back from it.
|
||||
using (var cap = gtask.Result)
|
||||
{
|
||||
// Wait for balanced state
|
||||
testbed.FlushCommunication();
|
||||
|
||||
// And call it, without any network communications.
|
||||
long oldSendCount = testbed.ClientSendCount;
|
||||
var ftask = cap.Foo(123, true, default);
|
||||
testbed.MustComplete(ftask);
|
||||
Assert.AreEqual("foo", ftask.Result);
|
||||
Assert.AreEqual(oldSendCount, testbed.ClientSendCount);
|
||||
|
||||
// We can send another copy of the same cap to another method, and it works.
|
||||
// Note that this was a bug in previous versions:
|
||||
// Since passing a cap has move semantics, we need to create an explicit copy.
|
||||
var copy = Proxy.Share(cap);
|
||||
var ctask = main.CallFoo(copy, default);
|
||||
testbed.MustComplete(ctask);
|
||||
Assert.AreEqual("bar", ctask.Result);
|
||||
|
||||
// Give some time to settle.
|
||||
cstask = main.GetCallSequence(0, default);
|
||||
testbed.MustComplete(cstask);
|
||||
Assert.AreEqual(5u, cstask.Result);
|
||||
cstask = main.GetCallSequence(0, default);
|
||||
testbed.MustComplete(cstask);
|
||||
Assert.AreEqual(6u, cstask.Result);
|
||||
cstask = main.GetCallSequence(0, default);
|
||||
testbed.MustComplete(cstask);
|
||||
Assert.AreEqual(7u, cstask.Result);
|
||||
|
||||
// Can't be destroyed, we haven't released it.
|
||||
Assert.IsFalse(destructionTask.IsCompleted);
|
||||
}
|
||||
|
||||
// In deviation from original test, we have null the held capability on the main interface.
|
||||
// This is because the main interface is the bootstrap capability and, as such, won't be disposed
|
||||
// after disconnect.
|
||||
var holdNullTask = main.Hold(null, default);
|
||||
testbed.MustComplete(holdNullTask);
|
||||
}
|
||||
|
||||
testbed.MustComplete(destructionTask);
|
||||
}
|
||||
|
||||
public static void PromiseResolve(ITestbed testbed)
|
||||
{
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
|
||||
{
|
||||
var tcs = new TaskCompletionSource<ITestInterface>();
|
||||
var eager = tcs.Task.Eager(true);
|
||||
|
||||
var request = main.CallFoo(eager, default);
|
||||
var request2 = main.CallFooWhenResolved(eager, default);
|
||||
|
||||
var gcs = main.GetCallSequence(0, default);
|
||||
testbed.MustComplete(gcs);
|
||||
Assert.AreEqual(2u, gcs.Result);
|
||||
Assert.AreEqual(3, counters.CallCount);
|
||||
|
||||
var chainedCallCount = new Counters();
|
||||
var tiimpl = new TestInterfaceImpl(chainedCallCount);
|
||||
tcs.SetResult(tiimpl);
|
||||
|
||||
testbed.MustComplete(request, request2);
|
||||
|
||||
Assert.AreEqual("bar", request.Result);
|
||||
Assert.AreEqual("bar", request2.Result);
|
||||
Assert.AreEqual(3, counters.CallCount);
|
||||
Assert.AreEqual(2, chainedCallCount.CallCount);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Cancelation(ITestbed testbed)
|
||||
{
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
|
||||
{
|
||||
var destroyed = new TaskCompletionSource<int>();
|
||||
var impl2 = new TestInterfaceImpl(counters, destroyed);
|
||||
var cts = new CancellationTokenSource();
|
||||
var cancelTask = main.ExpectCancel(impl2, cts.Token);
|
||||
|
||||
testbed.MustNotComplete(destroyed.Task, cancelTask);
|
||||
|
||||
cts.Cancel();
|
||||
|
||||
testbed.MustComplete(destroyed.Task);
|
||||
Assert.IsFalse(cancelTask.IsCompleted && !cancelTask.IsCanceled);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReleaseOnCancel(ITestbed testbed)
|
||||
{
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
|
||||
{
|
||||
using (var cts = new CancellationTokenSource())
|
||||
{
|
||||
var task = main.GetHandle(cts.Token);
|
||||
testbed.MustComplete(Task.CompletedTask); // turn event loop
|
||||
cts.Cancel();
|
||||
testbed.MustComplete(task);
|
||||
try
|
||||
{
|
||||
task.Result.Dispose();
|
||||
}
|
||||
catch (AggregateException ex) when (ex.InnerException is TaskCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
testbed.FlushCommunication();
|
||||
Assert.AreEqual(0, counters.HandleCount);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Release(ITestbed testbed)
|
||||
{
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
|
||||
{
|
||||
var task1 = main.GetHandle(default);
|
||||
var task2 = main.GetHandle(default);
|
||||
testbed.MustComplete(task1, task2);
|
||||
|
||||
Assert.AreEqual(2, counters.HandleCount);
|
||||
|
||||
task1.Result.Dispose();
|
||||
|
||||
testbed.FlushCommunication();
|
||||
Assert.AreEqual(1, counters.HandleCount);
|
||||
|
||||
task2.Result.Dispose();
|
||||
|
||||
testbed.FlushCommunication();
|
||||
Assert.AreEqual(0, counters.HandleCount);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Pipeline(ITestbed testbed)
|
||||
{
|
||||
var counters = new Counters();
|
||||
var impl = new TestPipelineImpl(counters);
|
||||
using (var main = testbed.ConnectMain<ITestPipeline>(impl))
|
||||
{
|
||||
var chainedCallCount = new Counters();
|
||||
var request = main.GetCap(234, new TestInterfaceImpl(chainedCallCount), default);
|
||||
using (var outBox = request.OutBox_Cap())
|
||||
{
|
||||
var pipelineRequest = outBox.Foo(321, false, default);
|
||||
var pipelineRequest2 = ((Proxy)outBox).Cast<ITestExtends>(false).Grault(default);
|
||||
|
||||
testbed.MustComplete(pipelineRequest, pipelineRequest2);
|
||||
|
||||
Assert.AreEqual("bar", pipelineRequest.Result);
|
||||
Common.CheckTestMessage(pipelineRequest2.Result);
|
||||
|
||||
Assert.AreEqual(3, counters.CallCount);
|
||||
Assert.AreEqual(1, chainedCallCount.CallCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Basic(ITestbed testbed)
|
||||
{
|
||||
var counters = new Counters();
|
||||
var impl = new TestInterfaceImpl(counters);
|
||||
using (var main = testbed.ConnectMain<ITestInterface>(impl))
|
||||
{
|
||||
var request1 = main.Foo(123, true, default);
|
||||
var request3 = Assert.ThrowsExceptionAsync<RpcException>(() => main.Bar(default));
|
||||
var s = new TestAllTypes();
|
||||
Common.InitTestMessage(s);
|
||||
var request2 = main.Baz(s, default);
|
||||
|
||||
testbed.MustComplete(request1, request2, request3);
|
||||
|
||||
Assert.AreEqual("foo", request1.Result);
|
||||
Assert.AreEqual(2, counters.CallCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
98
Capnp.Net.Runtime.Tests/Util/DecisionTree.cs
Normal file
98
Capnp.Net.Runtime.Tests/Util/DecisionTree.cs
Normal file
@ -0,0 +1,98 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
public class DecisionTree
|
||||
{
|
||||
readonly ILogger Logger = Logging.CreateLogger<DecisionTree>();
|
||||
|
||||
readonly List<bool> _decisions = new List<bool>();
|
||||
readonly Stack<int> _freezeStack = new Stack<int>();
|
||||
int _pos, _freezeCounter;
|
||||
|
||||
public DecisionTree()
|
||||
{
|
||||
_freezeStack.Push(0);
|
||||
}
|
||||
|
||||
public DecisionTree(params bool[] initialDecisions): this()
|
||||
{
|
||||
_decisions.AddRange(initialDecisions);
|
||||
}
|
||||
|
||||
public bool MakeDecision()
|
||||
{
|
||||
if (_pos >= _decisions.Count)
|
||||
{
|
||||
_decisions.Add(false);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return _decisions[_pos++];
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
Logger.LogError($"WTF?! {_pos - 1}, {_decisions.Count}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void Freeze()
|
||||
{
|
||||
_freezeStack.Push(_pos);
|
||||
}
|
||||
|
||||
public bool NextRound()
|
||||
{
|
||||
_pos = 0;
|
||||
|
||||
for (int i = 0; i < _freezeCounter; i++)
|
||||
_freezeStack.Pop();
|
||||
|
||||
while (_freezeStack.Count > 1)
|
||||
{
|
||||
int end = _freezeStack.Pop();
|
||||
int begin = _freezeStack.Peek();
|
||||
|
||||
for (int i = end - 1; i >= begin; i--)
|
||||
{
|
||||
if (_decisions[i] == false)
|
||||
{
|
||||
_decisions[i] = true;
|
||||
_freezeStack.Clear();
|
||||
_freezeStack.Push(0);
|
||||
return true;
|
||||
}
|
||||
//else
|
||||
//{
|
||||
// _decisions.RemoveAt(i);
|
||||
//}
|
||||
}
|
||||
|
||||
++_freezeCounter;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "[" + string.Join("|", _decisions) + "]";
|
||||
}
|
||||
|
||||
public void Iterate(Action testMethod)
|
||||
{
|
||||
Logger.LogInformation("Starting decision-tree based combinatorial test");
|
||||
int iter = 0;
|
||||
do
|
||||
{
|
||||
Logger.LogInformation($"Iteration {iter}: pattern {ToString()}");
|
||||
testMethod();
|
||||
++iter;
|
||||
} while (NextRound());
|
||||
}
|
||||
}
|
||||
}
|
350
Capnp.Net.Runtime.Tests/Util/TestBase.cs
Normal file
350
Capnp.Net.Runtime.Tests/Util/TestBase.cs
Normal file
@ -0,0 +1,350 @@
|
||||
using Capnp.Rpc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
public interface ITestbed
|
||||
{
|
||||
T ConnectMain<T>(T main) where T : class;
|
||||
void MustComplete(params Task[] tasks);
|
||||
void MustNotComplete(params Task[] tasks);
|
||||
void FlushCommunication();
|
||||
|
||||
long ClientSendCount { get; }
|
||||
}
|
||||
|
||||
public interface ITestController
|
||||
{
|
||||
void RunTest(Action<ITestbed> action);
|
||||
}
|
||||
|
||||
public class TestBase
|
||||
{
|
||||
|
||||
protected class EnginePair
|
||||
{
|
||||
class EngineChannel : IEndpoint
|
||||
{
|
||||
readonly Queue<WireFrame> _frameBuffer = new Queue<WireFrame>();
|
||||
readonly Func<bool> _decide;
|
||||
bool _recursion;
|
||||
bool _dismissed;
|
||||
|
||||
public EngineChannel(Func<bool> decide)
|
||||
{
|
||||
_decide = decide;
|
||||
}
|
||||
|
||||
public RpcEngine.RpcEndpoint OtherEndpoint { get; set; }
|
||||
public bool HasBufferedFrames => _frameBuffer.Count > 0;
|
||||
public int FrameCounter { get; private set; }
|
||||
|
||||
|
||||
public void Dismiss()
|
||||
{
|
||||
OtherEndpoint.Dismiss();
|
||||
_dismissed = true;
|
||||
}
|
||||
|
||||
public void Forward(WireFrame frame)
|
||||
{
|
||||
if (_dismissed)
|
||||
return;
|
||||
|
||||
++FrameCounter;
|
||||
|
||||
if (_recursion || _frameBuffer.Count > 0 || _decide())
|
||||
{
|
||||
_frameBuffer.Enqueue(frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
_recursion = true;
|
||||
OtherEndpoint.Forward(frame);
|
||||
_recursion = false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Flush()
|
||||
{
|
||||
if (_frameBuffer.Count > 0)
|
||||
{
|
||||
var frame = _frameBuffer.Dequeue();
|
||||
_recursion = true;
|
||||
OtherEndpoint.Forward(frame);
|
||||
_recursion = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly DecisionTree _decisionTree;
|
||||
readonly EngineChannel _channel1, _channel2;
|
||||
|
||||
public RpcEngine Engine1 { get; }
|
||||
public RpcEngine Engine2 { get; }
|
||||
public RpcEngine.RpcEndpoint Endpoint1 { get; }
|
||||
public RpcEngine.RpcEndpoint Endpoint2 { get; }
|
||||
|
||||
public EnginePair(DecisionTree decisionTree)
|
||||
{
|
||||
_decisionTree = decisionTree;
|
||||
Engine1 = new RpcEngine();
|
||||
Engine2 = new RpcEngine();
|
||||
_channel1 = new EngineChannel(decisionTree.MakeDecision);
|
||||
Endpoint1 = Engine1.AddEndpoint(_channel1);
|
||||
_channel2 = new EngineChannel(() => false);
|
||||
Endpoint2 = Engine2.AddEndpoint(_channel2);
|
||||
_channel1.OtherEndpoint = Endpoint2;
|
||||
_channel2.OtherEndpoint = Endpoint1;
|
||||
}
|
||||
|
||||
public void FlushChannels(Func<bool> pred)
|
||||
{
|
||||
while (!pred())
|
||||
{
|
||||
if (!_channel1.Flush() &&
|
||||
!_channel2.Flush())
|
||||
return;
|
||||
}
|
||||
|
||||
while ((_channel1.HasBufferedFrames || _channel2.HasBufferedFrames) && _decisionTree.MakeDecision())
|
||||
{
|
||||
if (_channel1.HasBufferedFrames)
|
||||
{
|
||||
int mark = _channel2.FrameCounter;
|
||||
|
||||
while (_channel1.Flush() && _channel2.FrameCounter == mark)
|
||||
;
|
||||
}
|
||||
else if (_channel2.HasBufferedFrames)
|
||||
{
|
||||
int mark = _channel1.FrameCounter;
|
||||
|
||||
while (_channel2.Flush() && _channel1.FrameCounter == mark)
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public int Channel1SendCount => _channel1.FrameCounter;
|
||||
public int Channel2SendCount => _channel2.FrameCounter;
|
||||
}
|
||||
|
||||
protected class DtbdctTestbed : ITestbed, ITestController
|
||||
{
|
||||
readonly DecisionTree _decisionTree = new DecisionTree();
|
||||
EnginePair _enginePair;
|
||||
|
||||
public void RunTest(Action<ITestbed> action)
|
||||
{
|
||||
_decisionTree.Iterate(() => action(this));
|
||||
}
|
||||
|
||||
T ITestbed.ConnectMain<T>(T main)
|
||||
{
|
||||
return SetupEnginePair<T>(main, _decisionTree, out _enginePair);
|
||||
}
|
||||
|
||||
void ITestbed.MustComplete(params Task[] tasks)
|
||||
{
|
||||
bool AllDone() => tasks.All(t => t.IsCompleted);
|
||||
_enginePair.FlushChannels(AllDone);
|
||||
_decisionTree.Freeze();
|
||||
Assert.IsTrue(AllDone());
|
||||
}
|
||||
|
||||
void ITestbed.MustNotComplete(params Task[] tasks)
|
||||
{
|
||||
Assert.IsFalse(tasks.Any(t => t.IsCompleted));
|
||||
}
|
||||
|
||||
void ITestbed.FlushCommunication()
|
||||
{
|
||||
_enginePair.FlushChannels(() => false);
|
||||
}
|
||||
|
||||
long ITestbed.ClientSendCount => _enginePair.Channel2SendCount;
|
||||
}
|
||||
|
||||
protected class LocalhostTcpTestbed : ITestbed, ITestController
|
||||
{
|
||||
TcpRpcServer _server;
|
||||
TcpRpcClient _client;
|
||||
|
||||
public void RunTest(Action<ITestbed> action)
|
||||
{
|
||||
(_server, _client) = SetupClientServerPair();
|
||||
_client.WhenConnected.Wait(MediumNonDbgTimeout);
|
||||
|
||||
using (_server)
|
||||
using (_client)
|
||||
{
|
||||
action(this);
|
||||
}
|
||||
}
|
||||
|
||||
T ITestbed.ConnectMain<T>(T main)
|
||||
{
|
||||
_server.Main = main;
|
||||
return _client.GetMain<T>();
|
||||
}
|
||||
|
||||
void ITestbed.MustComplete(params Task[] tasks)
|
||||
{
|
||||
Assert.IsTrue(Task.WaitAll(tasks, MediumNonDbgTimeout));
|
||||
}
|
||||
|
||||
void ITestbed.MustNotComplete(params Task[] tasks)
|
||||
{
|
||||
Assert.AreEqual(-1, Task.WaitAny(tasks, ShortTimeout));
|
||||
}
|
||||
|
||||
void ITestbed.FlushCommunication()
|
||||
{
|
||||
WaitClientServerIdle(_server, _client);
|
||||
}
|
||||
|
||||
long ITestbed.ClientSendCount => _client.SendCount;
|
||||
}
|
||||
|
||||
public static int TcpPort = 49152;
|
||||
public static int MediumNonDbgTimeout => Debugger.IsAttached ? Timeout.Infinite : 5000;
|
||||
public static int LargeNonDbgTimeout => Debugger.IsAttached ? Timeout.Infinite : 20000;
|
||||
public static int ShortTimeout => 500;
|
||||
|
||||
protected ILogger Logger { get; set; }
|
||||
|
||||
protected static TcpRpcClient SetupClient()
|
||||
{
|
||||
var client = new TcpRpcClient();
|
||||
client.AddBuffering();
|
||||
client.Connect("localhost", TcpPort);
|
||||
return client;
|
||||
}
|
||||
|
||||
protected static TcpRpcServer SetupServer()
|
||||
{
|
||||
int attempt = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var server = new TcpRpcServer(IPAddress.Any, TcpPort);
|
||||
server.AddBuffering();
|
||||
return server;
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
// If the TCP listening port is occupied by some other process, retry with a different one
|
||||
// TIME_WAIT is 4min: http://blog.davidvassallo.me/2010/07/13/time_wait-and-port-reuse/
|
||||
if (attempt == 2400)
|
||||
throw;
|
||||
}
|
||||
|
||||
IncrementTcpPort();
|
||||
++attempt;
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
protected static (TcpRpcServer, TcpRpcClient) SetupClientServerPair()
|
||||
{
|
||||
var server = SetupServer();
|
||||
var client = SetupClient();
|
||||
return (server, client);
|
||||
}
|
||||
|
||||
protected static T SetupEnginePair<T>(T main, DecisionTree decisionTree, out EnginePair pair) where T: class
|
||||
{
|
||||
pair = new EnginePair(decisionTree);
|
||||
pair.Engine1.Main = main;
|
||||
return (CapabilityReflection.CreateProxy<T>(pair.Endpoint2.QueryMain()) as T);
|
||||
}
|
||||
|
||||
public static void IncrementTcpPort()
|
||||
{
|
||||
if (++TcpPort > 49200)
|
||||
{
|
||||
TcpPort = 49152;
|
||||
}
|
||||
}
|
||||
|
||||
protected static DtbdctTestbed NewDtbdctTestbed() => new DtbdctTestbed();
|
||||
protected static LocalhostTcpTestbed NewLocalhostTcpTestbed() => new LocalhostTcpTestbed();
|
||||
|
||||
[TestInitialize]
|
||||
public void InitConsoleLogging()
|
||||
{
|
||||
Logging.LoggerFactory?.Dispose();
|
||||
Logging.LoggerFactory = new LoggerFactory().AddConsole((msg, level) => true);
|
||||
Logger = Logging.CreateLogger<TcpRpcStress>();
|
||||
if (Thread.CurrentThread.Name == null)
|
||||
Thread.CurrentThread.Name = $"Test Thread {Thread.CurrentThread.ManagedThreadId}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Somewhat ugly helper method which ensures that both Tcp client and server
|
||||
/// are waiting for data reception from each other. This is a "balanced" state, meaning
|
||||
/// that nothing will ever happen in the RcpEngines without some other thread requesting
|
||||
/// anything.
|
||||
/// </summary>
|
||||
static protected void WaitClientServerIdle(TcpRpcServer server, TcpRpcClient client)
|
||||
{
|
||||
var conn = server.Connections[0];
|
||||
SpinWait.SpinUntil(() => conn.IsWaitingForData && client.IsWaitingForData &&
|
||||
conn.RecvCount == client.SendCount &&
|
||||
conn.SendCount == client.RecvCount,
|
||||
MediumNonDbgTimeout);
|
||||
}
|
||||
|
||||
protected void ExpectPromiseThrows(Task task)
|
||||
{
|
||||
async Task ExpectPromiseThrowsAsync(Task t)
|
||||
{
|
||||
try
|
||||
{
|
||||
await t;
|
||||
Assert.Fail("Did not throw");
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Happens if the call went to the resolution
|
||||
// (thus, locally). In this case, the original
|
||||
// exception is routed here.
|
||||
}
|
||||
catch (RpcException)
|
||||
{
|
||||
// Happens if the call went to the promise
|
||||
// (thus, remotely). In this case, the original
|
||||
// exception had to be serialized, so we receive
|
||||
// the wrapped version.
|
||||
}
|
||||
catch (System.Exception exception)
|
||||
{
|
||||
Assert.Fail($"Got wrong kind of exception: {exception}");
|
||||
}
|
||||
}
|
||||
|
||||
Assert.IsTrue(ExpectPromiseThrowsAsync(task).Wait(MediumNonDbgTimeout));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -29,7 +29,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<DefineConstants>DebugCapabilityLifecycle</DefineConstants>
|
||||
<DefineConstants>DebugFinalizers</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.0|AnyCPU'">
|
||||
|
@ -160,6 +160,7 @@ namespace Capnp
|
||||
/// <returns>The domain object instance. Nullability note: The returned reference may be null if
|
||||
/// <paramref name="state"/> represents the nil object.</returns>
|
||||
/// <exception cref="ArgumentException">Cannot construct object of type <typeparamref name="T"/></exception>
|
||||
/// <remarks>Note that capability ownership is moved to the domain object</remarks>
|
||||
public static T? Create<T>(DeserializerState state)
|
||||
where T: class
|
||||
{
|
||||
|
@ -9,7 +9,7 @@ namespace Capnp
|
||||
/// Although it is public, you should not use it directly. Instead, use the reader, writer, and domain class adapters which are produced
|
||||
/// by the code generator.
|
||||
/// </summary>
|
||||
public struct DeserializerState: IStructDeserializer
|
||||
public struct DeserializerState: IStructDeserializer, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// A wire message is essentially a collection of memory blocks.
|
||||
@ -50,7 +50,7 @@ namespace Capnp
|
||||
/// </summary>
|
||||
public IList<Rpc.ConsumedCapability?>? Caps { get; set; }
|
||||
/// <summary>
|
||||
/// Current segment (essentially Segments[CurrentSegmentIndex]
|
||||
/// Current segment (essentially Segments[CurrentSegmentIndex])
|
||||
/// </summary>
|
||||
public ReadOnlySpan<ulong> CurrentSegment => Segments != null ? Segments[(int)CurrentSegmentIndex].Span : default;
|
||||
|
||||
@ -106,11 +106,6 @@ namespace Capnp
|
||||
case ObjectKind.ListOfStructs:
|
||||
case ObjectKind.Nil:
|
||||
case ObjectKind.Struct:
|
||||
if (state.Caps != null)
|
||||
{
|
||||
foreach (var cap in state.Caps)
|
||||
cap?.Release(true);
|
||||
}
|
||||
return new DeserializerState(state.Allocator!.Segments)
|
||||
{
|
||||
CurrentSegmentIndex = state.SegmentIndex,
|
||||
@ -646,13 +641,10 @@ namespace Capnp
|
||||
/// <exception cref="IndexOutOfRangeException">negative index</exception>
|
||||
/// <exception cref="DeserializationException">state does not represent a struct, invalid pointer,
|
||||
/// non-capability pointer, traversal limit exceeded</exception>
|
||||
public T? ReadCap<T>(int index,
|
||||
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
|
||||
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
|
||||
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0) where T: class
|
||||
public T? ReadCap<T>(int index) where T: class
|
||||
{
|
||||
var cap = StructReadRawCap(index);
|
||||
return Rpc.CapabilityReflection.CreateProxy<T>(cap, memberName, sourceFilePath, sourceLineNumber) as T;
|
||||
return Rpc.CapabilityReflection.CreateProxy<T>(cap) as T;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -690,5 +682,16 @@ namespace Capnp
|
||||
|
||||
return (Rpc.CapabilityReflection.CreateProxy<T>(Caps[(int)CapabilityIndex]) as T)!;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Caps != null)
|
||||
{
|
||||
foreach (var cap in Caps)
|
||||
{
|
||||
cap?.Release(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -44,8 +44,6 @@ namespace Capnp
|
||||
if (state.Caps != null)
|
||||
{
|
||||
mb.InitCapTable();
|
||||
foreach (var cap in state.Caps)
|
||||
cap?.AddRef();
|
||||
}
|
||||
var sstate = mb.CreateObject<DynamicSerializerState>();
|
||||
Reserializing.DeepCopy(state, sstate);
|
||||
|
@ -262,25 +262,11 @@ namespace Capnp.Rpc
|
||||
/// <exception cref="System.Reflection.TargetInvocationException">Problem with instatiating the Proxy (constructor threw exception).</exception>
|
||||
/// <exception cref="MemberAccessException">Caller does not have permission to invoke the Proxy constructor.</exception>
|
||||
/// <exception cref="TypeLoadException">Problem with building the Proxy type, or problem with loading some dependent class.</exception>
|
||||
public static Proxy CreateProxy<TInterface>(ConsumedCapability? cap,
|
||||
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
|
||||
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
|
||||
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
|
||||
public static Proxy CreateProxy<TInterface>(ConsumedCapability? cap)
|
||||
{
|
||||
var factory = GetProxyFactory(typeof(TInterface));
|
||||
var proxy = factory.NewProxy();
|
||||
proxy.Bind(cap);
|
||||
#if DebugFinalizers
|
||||
proxy.CreatorMemberName = memberName;
|
||||
proxy.CreatorFilePath = sourceFilePath;
|
||||
proxy.CreatorLineNumber = sourceLineNumber;
|
||||
if (cap != null)
|
||||
{
|
||||
cap.CreatorFilePath = proxy.CreatorFilePath;
|
||||
cap.CreatorLineNumber = proxy.CreatorLineNumber;
|
||||
cap.CreatorMemberName = proxy.CreatorMemberName;
|
||||
}
|
||||
#endif
|
||||
return proxy;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
namespace Capnp.Rpc
|
||||
using System;
|
||||
|
||||
namespace Capnp.Rpc
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for a low-level capability at consumer side. It is created by the <see cref="RpcEngine"/>. An application does not directly interact with it
|
||||
@ -13,7 +15,7 @@
|
||||
/// which usually also means to remove it from the remote peer's export table.
|
||||
/// </summary>
|
||||
protected abstract void ReleaseRemotely();
|
||||
internal abstract void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer);
|
||||
internal abstract Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer);
|
||||
internal abstract void Freeze(out IRpcEndpoint? boundEndpoint);
|
||||
internal abstract void Unfreeze();
|
||||
|
||||
@ -23,11 +25,5 @@
|
||||
[System.Runtime.CompilerServices.CallerMemberName] string methodName = "",
|
||||
[System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
|
||||
[System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0);
|
||||
|
||||
#if DebugFinalizers
|
||||
public string CreatorMemberName { get; set; }
|
||||
public string CreatorFilePath { get; set; }
|
||||
public int CreatorLineNumber { get; set; }
|
||||
#endif
|
||||
}
|
||||
}
|
@ -24,6 +24,6 @@ namespace Capnp.Rpc
|
||||
/// <returns>Pipelined low-level capability</returns>
|
||||
ConsumedCapability? Access(MemberAccessPath access);
|
||||
|
||||
ConsumedCapability? Access(MemberAccessPath access, Task<IDisposable> proxyTask);
|
||||
ConsumedCapability? Access(MemberAccessPath access, Task<IDisposable?> proxyTask);
|
||||
}
|
||||
}
|
@ -8,7 +8,6 @@ namespace Capnp.Rpc
|
||||
PendingQuestion BeginQuestion(ConsumedCapability target, SerializerState inParams);
|
||||
void SendQuestion(SerializerState inParams, Payload.WRITER payload);
|
||||
uint AllocateExport(Skeleton providedCapability, out bool first);
|
||||
void RequestPostAction(Action postAction);
|
||||
void Finish(uint questionId);
|
||||
void ReleaseImport(uint importId);
|
||||
void Resolve(uint preliminaryId, Skeleton preliminaryCap, Func<ConsumedCapability> resolvedCapGetter);
|
||||
|
@ -94,7 +94,20 @@ namespace Capnp.Rpc
|
||||
|
||||
static async Task<Proxy> AwaitProxy<T>(Task<T> task) where T: class
|
||||
{
|
||||
var item = await task;
|
||||
T item;
|
||||
|
||||
try
|
||||
{
|
||||
item = await task;
|
||||
}
|
||||
catch (TaskCanceledException exception)
|
||||
{
|
||||
return new Proxy(LazyCapability.CreateCanceledCap(exception.CancellationToken));
|
||||
}
|
||||
catch (System.Exception exception)
|
||||
{
|
||||
return new Proxy(LazyCapability.CreateBrokenCap(exception.Message));
|
||||
}
|
||||
|
||||
switch (item)
|
||||
{
|
||||
@ -124,14 +137,11 @@ namespace Capnp.Rpc
|
||||
/// <exception cref="InvalidCapabilityInterfaceException"><typeparamref name="TInterface"/> did not
|
||||
/// quality as capability interface.</exception>
|
||||
[Obsolete("Call Eager<TInterface>(task, true) instead")]
|
||||
public static TInterface PseudoEager<TInterface>(this Task<TInterface> task,
|
||||
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
|
||||
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
|
||||
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
|
||||
public static TInterface PseudoEager<TInterface>(this Task<TInterface> task)
|
||||
where TInterface : class
|
||||
{
|
||||
var lazyCap = new LazyCapability(AwaitProxy(task));
|
||||
return (CapabilityReflection.CreateProxy<TInterface>(lazyCap, memberName, sourceFilePath, sourceLineNumber) as TInterface)!;
|
||||
return (CapabilityReflection.CreateProxy<TInterface>(lazyCap) as TInterface)!;
|
||||
}
|
||||
|
||||
static readonly MemberAccessPath Path_OneAndOnly = new MemberAccessPath(0U);
|
||||
@ -157,7 +167,7 @@ namespace Capnp.Rpc
|
||||
/// <exception cref="MemberAccessException">Caller does not have permission to invoke the Proxy constructor.</exception>
|
||||
/// <exception cref="TypeLoadException">Problem with building the Proxy type, or problem with loading some dependent class.</exception>
|
||||
public static TInterface Eager<TInterface>(this Task<TInterface> task, bool allowNoPipeliningFallback = false)
|
||||
where TInterface : class
|
||||
where TInterface : class, IDisposable
|
||||
{
|
||||
var answer = TryGetAnswer(task);
|
||||
if (answer == null)
|
||||
@ -172,7 +182,12 @@ namespace Capnp.Rpc
|
||||
}
|
||||
else
|
||||
{
|
||||
return (CapabilityReflection.CreateProxy<TInterface>(answer.Access(Path_OneAndOnly)) as TInterface)!;
|
||||
async Task<IDisposable?> AsDisposableTask()
|
||||
{
|
||||
return await task;
|
||||
}
|
||||
|
||||
return (CapabilityReflection.CreateProxy<TInterface>(answer.Access(Path_OneAndOnly, AsDisposableTask())) as TInterface)!;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
namespace Capnp.Rpc
|
||||
using System;
|
||||
|
||||
namespace Capnp.Rpc
|
||||
{
|
||||
/// <summary>
|
||||
/// Low-level capability which as imported from a remote peer.
|
||||
@ -35,7 +37,7 @@
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER capDesc)
|
||||
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER capDesc)
|
||||
{
|
||||
if (endpoint == _ep)
|
||||
{
|
||||
@ -47,6 +49,7 @@
|
||||
capDesc.which = CapDescriptor.WHICH.SenderHosted;
|
||||
capDesc.SenderHosted = endpoint.AllocateExport(Vine.Create(this), out var _);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -50,7 +50,7 @@ namespace Capnp.Rpc.Interception
|
||||
}
|
||||
}
|
||||
|
||||
public ConsumedCapability? Access(MemberAccessPath _, Task<IDisposable> task)
|
||||
public ConsumedCapability? Access(MemberAccessPath _, Task<IDisposable?> task)
|
||||
{
|
||||
var proxyTask = task.AsProxyTask();
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
namespace Capnp.Rpc.Interception
|
||||
using System;
|
||||
|
||||
namespace Capnp.Rpc.Interception
|
||||
{
|
||||
class CensorCapability : RefCountingCapability
|
||||
{
|
||||
@ -26,10 +28,11 @@
|
||||
return cc.Answer;
|
||||
}
|
||||
|
||||
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
|
||||
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
|
||||
{
|
||||
writer.which = CapDescriptor.WHICH.SenderHosted;
|
||||
writer.SenderHosted = endpoint.AllocateExport(MyVine, out bool _);
|
||||
return null;
|
||||
}
|
||||
|
||||
internal override void Freeze(out IRpcEndpoint? boundEndpoint)
|
||||
|
@ -8,19 +8,15 @@ namespace Capnp.Rpc
|
||||
{
|
||||
public static LazyCapability CreateBrokenCap(string message)
|
||||
{
|
||||
var cap = new LazyCapability(Task.FromException<ConsumedCapability?>(new RpcException(message)));
|
||||
cap.AddRef(); // Instance shall be persistent
|
||||
return cap;
|
||||
return new LazyCapability(Task.FromException<ConsumedCapability?>(new RpcException(message)));
|
||||
}
|
||||
|
||||
public static LazyCapability CreateCanceledCap(CancellationToken token)
|
||||
{
|
||||
var cap = new LazyCapability(Task.FromCanceled<ConsumedCapability?>(token));
|
||||
cap.AddRef(); // Instance shall be persistent
|
||||
return cap;
|
||||
return new LazyCapability(Task.FromCanceled<ConsumedCapability?>(token));
|
||||
}
|
||||
|
||||
public static LazyCapability Null { get; } = CreateBrokenCap("Null capability");
|
||||
public static LazyCapability Null => CreateBrokenCap("Null capability");
|
||||
|
||||
readonly Task<Proxy>? _proxyTask;
|
||||
|
||||
@ -63,16 +59,17 @@ namespace Capnp.Rpc
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
|
||||
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
|
||||
{
|
||||
if (WhenResolved.ReplacementTaskIsCompletedSuccessfully())
|
||||
{
|
||||
using var proxy = new Proxy(WhenResolved.Result);
|
||||
proxy.Export(endpoint, writer);
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ExportAsSenderPromise(endpoint, writer);
|
||||
return this.ExportAsSenderPromise(endpoint, writer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ namespace Capnp.Rpc
|
||||
return new LocalAnswerCapabilityDeprecated(WhenReturned, access);
|
||||
}
|
||||
|
||||
public ConsumedCapability Access(MemberAccessPath _, Task<IDisposable> task)
|
||||
public ConsumedCapability Access(MemberAccessPath _, Task<IDisposable?> task)
|
||||
{
|
||||
return new LocalAnswerCapability(task.AsProxyTask());
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ namespace Capnp.Rpc
|
||||
|
||||
public Task<ConsumedCapability?> WhenResolved { get; private set; }
|
||||
|
||||
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
|
||||
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
|
||||
{
|
||||
if (_whenResolvedProxy.IsCompleted)
|
||||
{
|
||||
@ -39,12 +39,14 @@ namespace Capnp.Rpc
|
||||
}
|
||||
catch (AggregateException exception)
|
||||
{
|
||||
throw exception.InnerException;
|
||||
throw exception.InnerException!;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ExportAsSenderPromise(endpoint, writer);
|
||||
return this.ExportAsSenderPromise(endpoint, writer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ namespace Capnp.Rpc
|
||||
|
||||
public Task<ConsumedCapability?> WhenResolved { get; private set; }
|
||||
|
||||
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
|
||||
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
|
||||
{
|
||||
if (_answer.IsCompleted)
|
||||
{
|
||||
@ -46,10 +46,11 @@ namespace Capnp.Rpc
|
||||
|
||||
using var proxy = new Proxy(_access.Eval(result));
|
||||
proxy.Export(endpoint, writer);
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ExportAsSenderPromise(endpoint, writer);
|
||||
return this.ExportAsSenderPromise(endpoint, writer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,10 +54,11 @@ namespace Capnp.Rpc
|
||||
return new LocalAnswer(cts, AwaitAnswer(call));
|
||||
}
|
||||
|
||||
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER capDesc)
|
||||
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER capDesc)
|
||||
{
|
||||
capDesc.which = CapDescriptor.WHICH.SenderHosted;
|
||||
capDesc.SenderHosted = endpoint.AllocateExport(ProvidedCap, out bool _);
|
||||
return null;
|
||||
}
|
||||
|
||||
internal override void Freeze(out IRpcEndpoint? boundEndpoint)
|
||||
|
@ -132,7 +132,7 @@ namespace Capnp.Rpc
|
||||
{
|
||||
if (state.Kind == ObjectKind.Nil)
|
||||
{
|
||||
return default(DeserializerState);
|
||||
return default;
|
||||
}
|
||||
|
||||
if (state.Kind != ObjectKind.Struct)
|
||||
|
@ -24,11 +24,35 @@ namespace Capnp.Rpc
|
||||
_cts = cts;
|
||||
_cancelCompleter = new TaskCompletionSource<AnswerOrCounterquestion>();
|
||||
_answerTask = CancelableAwaitWhenReady();
|
||||
|
||||
Chain(async t =>
|
||||
{
|
||||
var aorcq = default(AnswerOrCounterquestion);
|
||||
|
||||
try
|
||||
{
|
||||
aorcq = await t;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
if (aorcq.Answer != null)
|
||||
{
|
||||
if (aorcq.Answer.Caps != null)
|
||||
{
|
||||
foreach (var cap in aorcq.Answer.Caps)
|
||||
{
|
||||
cap?.AddRef();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public CancellationToken CancellationToken => _cts?.Token ?? CancellationToken.None;
|
||||
|
||||
public IReadOnlyList<CapDescriptor.WRITER> CapTable { get; set; }
|
||||
public IReadOnlyList<CapDescriptor.WRITER>? CapTable { get; set; }
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
@ -99,7 +123,7 @@ namespace Capnp.Rpc
|
||||
else
|
||||
{
|
||||
var path = MemberAccessPath.Deserialize(rd);
|
||||
var cap = new RemoteAnswerCapabilityDeprecated(aorcq.Counterquestion!, path);
|
||||
var cap = new RemoteAnswerCapability(aorcq.Counterquestion!, path);
|
||||
return new Proxy(cap);
|
||||
}
|
||||
}
|
||||
@ -111,6 +135,31 @@ namespace Capnp.Rpc
|
||||
public void Dispose()
|
||||
{
|
||||
_cts?.Dispose();
|
||||
|
||||
Chain(async t =>
|
||||
{
|
||||
AnswerOrCounterquestion aorcq;
|
||||
|
||||
try
|
||||
{
|
||||
aorcq = await t;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (aorcq.Answer != null)
|
||||
{
|
||||
if (aorcq.Answer.Caps != null)
|
||||
{
|
||||
foreach (var cap in aorcq.Answer.Caps)
|
||||
{
|
||||
cap?.Release(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -126,7 +126,6 @@ namespace Capnp.Rpc
|
||||
lock (ReentrancyBlocker)
|
||||
{
|
||||
SetReturned();
|
||||
}
|
||||
|
||||
if (StateFlags.HasFlag(State.TailCall))
|
||||
{
|
||||
@ -140,13 +139,13 @@ namespace Capnp.Rpc
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnTailCallReturn()
|
||||
{
|
||||
lock (ReentrancyBlocker)
|
||||
{
|
||||
SetReturned();
|
||||
}
|
||||
|
||||
if (!StateFlags.HasFlag(State.TailCall))
|
||||
{
|
||||
@ -157,36 +156,37 @@ namespace Capnp.Rpc
|
||||
_tcs.TrySetResult(default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnException(Exception.READER exception)
|
||||
{
|
||||
lock (ReentrancyBlocker)
|
||||
{
|
||||
SetReturned();
|
||||
}
|
||||
|
||||
_tcs.TrySetException(new RpcException(exception.Reason));
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnException(System.Exception exception)
|
||||
{
|
||||
lock (ReentrancyBlocker)
|
||||
{
|
||||
SetReturned();
|
||||
}
|
||||
|
||||
_tcs.TrySetException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnCanceled()
|
||||
{
|
||||
lock (ReentrancyBlocker)
|
||||
{
|
||||
SetReturned();
|
||||
}
|
||||
|
||||
_tcs.TrySetCanceled();
|
||||
}
|
||||
}
|
||||
|
||||
void DeleteMyQuestion()
|
||||
{
|
||||
@ -234,6 +234,7 @@ namespace Capnp.Rpc
|
||||
/// <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>
|
||||
[Obsolete("Please re-generate. Replaced by Access(MemberAccessPath access, Task<IDisposable> task)")]
|
||||
public ConsumedCapability? Access(MemberAccessPath access)
|
||||
{
|
||||
lock (ReentrancyBlocker)
|
||||
@ -252,7 +253,7 @@ namespace Capnp.Rpc
|
||||
}
|
||||
else
|
||||
{
|
||||
return new RemoteAnswerCapabilityDeprecated(this, access);
|
||||
return new RemoteAnswerCapability(this, access);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -261,32 +262,14 @@ namespace Capnp.Rpc
|
||||
/// 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>
|
||||
/// <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>
|
||||
public ConsumedCapability? Access(MemberAccessPath access, Task<IDisposable> task)
|
||||
public ConsumedCapability? Access(MemberAccessPath access, Task<IDisposable?> task)
|
||||
{
|
||||
var proxyTask = task.AsProxyTask();
|
||||
|
||||
lock (ReentrancyBlocker)
|
||||
{
|
||||
if (proxyTask.IsCompleted && !StateFlags.HasFlag(State.TailCall))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var proxy = proxyTask.Result;
|
||||
return proxy.ConsumedCap;
|
||||
}
|
||||
catch (AggregateException exception)
|
||||
{
|
||||
throw exception.InnerException!;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new RemoteAnswerCapabilityDeprecated(this, access);
|
||||
}
|
||||
}
|
||||
return new RemoteAnswerCapability(this, access, proxyTask);
|
||||
}
|
||||
|
||||
static void ReleaseCaps(ConsumedCapability? target, SerializerState? inParams)
|
||||
|
@ -78,7 +78,7 @@ namespace Capnp.Rpc
|
||||
}
|
||||
}
|
||||
|
||||
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
|
||||
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
|
||||
{
|
||||
lock (_reentrancyBlocker)
|
||||
{
|
||||
@ -98,7 +98,7 @@ namespace Capnp.Rpc
|
||||
Debug.Assert(!_released);
|
||||
++_pendingCallsOnPromise;
|
||||
|
||||
_ep.RequestPostAction(() =>
|
||||
return () =>
|
||||
{
|
||||
bool release = false;
|
||||
|
||||
@ -115,7 +115,7 @@ namespace Capnp.Rpc
|
||||
{
|
||||
_ep.ReleaseImport(_remoteId);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -123,6 +123,8 @@ namespace Capnp.Rpc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async void TrackCall(Task call)
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -99,9 +100,12 @@ namespace Capnp.Rpc
|
||||
/// </summary>
|
||||
public Proxy()
|
||||
{
|
||||
#if DebugFinalizers
|
||||
CreatorStackTrace = Environment.StackTrace;
|
||||
#endif
|
||||
}
|
||||
|
||||
internal Proxy(ConsumedCapability? cap)
|
||||
internal Proxy(ConsumedCapability? cap): this()
|
||||
{
|
||||
Bind(cap);
|
||||
}
|
||||
@ -118,7 +122,7 @@ namespace Capnp.Rpc
|
||||
cap.AddRef();
|
||||
}
|
||||
|
||||
internal IProvidedCapability? GetProvider()
|
||||
internal Skeleton? GetProvider()
|
||||
{
|
||||
switch (ConsumedCap)
|
||||
{
|
||||
@ -163,7 +167,7 @@ namespace Capnp.Rpc
|
||||
~Proxy()
|
||||
{
|
||||
#if DebugFinalizers
|
||||
Logger.LogWarning($"Caught orphaned Proxy, created from {CreatorMemberName} in {CreatorFilePath}, line {CreatorLineNumber}.");
|
||||
Logger.LogWarning($"Caught orphaned Proxy, created from here: {CreatorStackTrace}.");
|
||||
#endif
|
||||
|
||||
Dispose(false);
|
||||
@ -230,9 +234,7 @@ namespace Capnp.Rpc
|
||||
}
|
||||
|
||||
#if DebugFinalizers
|
||||
public string CreatorMemberName { get; set; }
|
||||
public string CreatorFilePath { get; set; }
|
||||
public int CreatorLineNumber { get; set; }
|
||||
string CreatorStackTrace { get; set; }
|
||||
#endif
|
||||
}
|
||||
}
|
@ -26,16 +26,33 @@ namespace Capnp.Rpc
|
||||
// Value 0 has the special meaning of being in state C.
|
||||
long _refCount = 1;
|
||||
|
||||
#if DebugCapabilityLifecycle
|
||||
#if DebugCapabilityLifecycle || DebugFinalizers
|
||||
ILogger Logger { get; } = Logging.CreateLogger<RefCountingCapability>();
|
||||
#endif
|
||||
|
||||
#if DebugCapabilityLifecycle
|
||||
string? _releasingMethodName;
|
||||
string? _releasingFilePath;
|
||||
int _releasingLineNumber;
|
||||
#endif
|
||||
|
||||
#if DebugFinalizers
|
||||
string CreatorStackTrace { get; set; }
|
||||
#endif
|
||||
|
||||
public RefCountingCapability()
|
||||
{
|
||||
#if DebugFinalizers
|
||||
CreatorStackTrace = Environment.StackTrace;
|
||||
#endif
|
||||
}
|
||||
|
||||
~RefCountingCapability()
|
||||
{
|
||||
#if DebugFinalizers
|
||||
Logger.LogWarning($"Caught orphaned capability, created from here: {CreatorStackTrace}.");
|
||||
#endif
|
||||
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,19 @@ namespace Capnp.Rpc
|
||||
WhenResolved = AwaitWhenResolved();
|
||||
}
|
||||
|
||||
static async Task<Proxy> TransferOwnershipToDummyProxy(PendingQuestion question, MemberAccessPath access)
|
||||
{
|
||||
var result = await question.WhenReturned;
|
||||
var cap = access.Eval(result);
|
||||
var proxy = new Proxy(cap);
|
||||
cap?.Release(false);
|
||||
return proxy;
|
||||
}
|
||||
|
||||
public RemoteAnswerCapability(PendingQuestion question, MemberAccessPath access) : this(question, access, TransferOwnershipToDummyProxy(question, access))
|
||||
{
|
||||
}
|
||||
|
||||
async void ReAllowFinishWhenDone(Task task)
|
||||
{
|
||||
try
|
||||
@ -64,7 +77,7 @@ namespace Capnp.Rpc
|
||||
{
|
||||
lock (_question.ReentrancyBlocker)
|
||||
{
|
||||
if (!_question.IsTailCall && WhenResolved.IsCompleted)
|
||||
if (!_question.IsTailCall && _question.StateFlags.HasFlag(PendingQuestion.State.Returned))
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -96,8 +109,8 @@ namespace Capnp.Rpc
|
||||
{
|
||||
lock (_question.ReentrancyBlocker)
|
||||
{
|
||||
if (_question.StateFlags.HasFlag(PendingQuestion.State.Returned) &&
|
||||
!_question.StateFlags.HasFlag(PendingQuestion.State.TailCall))
|
||||
if (!_question.StateFlags.HasFlag(PendingQuestion.State.TailCall) &&
|
||||
_question.StateFlags.HasFlag(PendingQuestion.State.Returned))
|
||||
{
|
||||
if (ResolvedCap == null)
|
||||
{
|
||||
@ -166,6 +179,7 @@ namespace Capnp.Rpc
|
||||
lock (_question.ReentrancyBlocker)
|
||||
{
|
||||
if ( _question.StateFlags.HasFlag(PendingQuestion.State.Returned) &&
|
||||
!_question.StateFlags.HasFlag(PendingQuestion.State.TailCall) &&
|
||||
_pendingCallsOnPromise == 0)
|
||||
{
|
||||
if (ResolvedCap == null)
|
||||
@ -200,7 +214,7 @@ namespace Capnp.Rpc
|
||||
}
|
||||
}
|
||||
|
||||
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
|
||||
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
|
||||
{
|
||||
lock (_question.ReentrancyBlocker)
|
||||
{
|
||||
@ -236,10 +250,12 @@ namespace Capnp.Rpc
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ExportAsSenderPromise(endpoint, writer);
|
||||
return this.ExportAsSenderPromise(endpoint, writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected async override void ReleaseRemotely()
|
||||
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Capnp.Rpc
|
||||
{
|
||||
#if false
|
||||
|
||||
class RemoteAnswerCapabilityDeprecated : RemoteResolvingCapability
|
||||
{
|
||||
@ -17,7 +18,6 @@ namespace Capnp.Rpc
|
||||
|
||||
readonly PendingQuestion _question;
|
||||
readonly MemberAccessPath _access;
|
||||
readonly Task<Proxy> _whenResolvedProxy;
|
||||
ConsumedCapability? _resolvedCap;
|
||||
|
||||
public RemoteAnswerCapabilityDeprecated(PendingQuestion question, MemberAccessPath access): base(question.RpcEndpoint)
|
||||
@ -36,9 +36,6 @@ namespace Capnp.Rpc
|
||||
}
|
||||
|
||||
WhenResolved = AwaitWhenResolved();
|
||||
|
||||
async Task<Proxy> AwaitProxy() => new Proxy(await WhenResolved);
|
||||
_whenResolvedProxy = AwaitProxy();
|
||||
}
|
||||
|
||||
async void ReAllowFinishWhenDone(Task task)
|
||||
@ -62,16 +59,6 @@ namespace Capnp.Rpc
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
lock (_question.ReentrancyBlocker)
|
||||
{
|
||||
using var _ = new Proxy(_resolvedCap);
|
||||
}
|
||||
}
|
||||
|
||||
protected override ConsumedCapability? ResolvedCap
|
||||
{
|
||||
get
|
||||
@ -258,8 +245,9 @@ namespace Capnp.Rpc
|
||||
|
||||
protected async override void ReleaseRemotely()
|
||||
{
|
||||
try { using var _ = await _whenResolvedProxy; }
|
||||
try { (await WhenResolved)?.Release(false); }
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
@ -5,7 +5,7 @@ namespace Capnp.Rpc
|
||||
{
|
||||
static class ResolvingCapabilityExtensions
|
||||
{
|
||||
public static void ExportAsSenderPromise<T>(this T cap, IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
|
||||
public static Action? ExportAsSenderPromise<T>(this T cap, IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
|
||||
where T: ConsumedCapability, IResolvingCapability
|
||||
{
|
||||
var vine = Vine.Create(cap);
|
||||
@ -16,7 +16,7 @@ namespace Capnp.Rpc
|
||||
|
||||
if (first)
|
||||
{
|
||||
endpoint.RequestPostAction(async () => {
|
||||
return async () => {
|
||||
|
||||
try
|
||||
{
|
||||
@ -28,14 +28,21 @@ namespace Capnp.Rpc
|
||||
endpoint.Resolve(preliminaryId, vine, () => throw exception);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static async Task<Proxy> AsProxyTask(this Task<IDisposable> task)
|
||||
return null;
|
||||
}
|
||||
|
||||
public static async Task<Proxy> AsProxyTask(this Task<IDisposable?> task)
|
||||
{
|
||||
var obj = await task;
|
||||
return obj is Proxy proxy ? proxy : BareProxy.FromImpl(obj);
|
||||
switch (obj)
|
||||
{
|
||||
case Proxy proxy: return proxy;
|
||||
case null: return new Proxy(LazyCapability.Null);
|
||||
default: return BareProxy.FromImpl(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -68,7 +68,6 @@ namespace Capnp.Rpc
|
||||
Dismissed
|
||||
}
|
||||
|
||||
static readonly ThreadLocal<Action?> _exportCapTablePostActions = new ThreadLocal<Action?>();
|
||||
static readonly ThreadLocal<PendingQuestion?> _tailCall = new ThreadLocal<PendingQuestion?>();
|
||||
static readonly ThreadLocal<bool> _canDeferCalls = new ThreadLocal<bool>();
|
||||
|
||||
@ -386,7 +385,7 @@ namespace Capnp.Rpc
|
||||
}
|
||||
}
|
||||
|
||||
IProvidedCapability? cap;
|
||||
Skeleton? callTargetCap;
|
||||
PendingAnswer pendingAnswer;
|
||||
bool releaseParamCaps = false;
|
||||
|
||||
@ -507,7 +506,7 @@ namespace Capnp.Rpc
|
||||
var inParams = req.Params.Content;
|
||||
inParams.Caps = ImportCapTable(req.Params);
|
||||
|
||||
if (cap == null)
|
||||
if (callTargetCap == null)
|
||||
{
|
||||
releaseParamCaps = true;
|
||||
pendingAnswer = new PendingAnswer(
|
||||
@ -519,7 +518,7 @@ namespace Capnp.Rpc
|
||||
try
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
var callTask = cap.Invoke(req.InterfaceId, req.MethodId, inParams, cts.Token);
|
||||
var callTask = callTargetCap.Invoke(req.InterfaceId, req.MethodId, inParams, cts.Token);
|
||||
pendingAnswer = new PendingAnswer(callTask, cts);
|
||||
}
|
||||
catch (System.Exception exception)
|
||||
@ -528,6 +527,10 @@ namespace Capnp.Rpc
|
||||
pendingAnswer = new PendingAnswer(
|
||||
Task.FromException<AnswerOrCounterquestion>(exception), null);
|
||||
}
|
||||
finally
|
||||
{
|
||||
callTargetCap.Relinquish();
|
||||
}
|
||||
}
|
||||
|
||||
AwaitAnswerAndReply();
|
||||
@ -561,7 +564,8 @@ namespace Capnp.Rpc
|
||||
{
|
||||
if (_exportTable.TryGetValue(req.Target.ImportedCap, out var rc))
|
||||
{
|
||||
cap = rc.Cap;
|
||||
callTargetCap = rc.Cap;
|
||||
callTargetCap.Claim();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -594,7 +598,8 @@ namespace Capnp.Rpc
|
||||
try
|
||||
{
|
||||
using var proxy = await t;
|
||||
cap = proxy?.GetProvider();
|
||||
callTargetCap = proxy?.GetProvider();
|
||||
callTargetCap?.Claim();
|
||||
CreateAnswerAwaitItAndReply();
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
@ -631,8 +636,9 @@ namespace Capnp.Rpc
|
||||
{
|
||||
_canDeferCalls.Value = false;
|
||||
Impatient.AskingEndpoint = null;
|
||||
_tailCall.Value?.Send();
|
||||
var call = _tailCall.Value;
|
||||
_tailCall.Value = null;
|
||||
call?.Send();
|
||||
}
|
||||
}
|
||||
|
||||
@ -948,14 +954,6 @@ namespace Capnp.Rpc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if (results != null && results.Caps != null)
|
||||
//{
|
||||
// foreach (var cap in results.Caps)
|
||||
// {
|
||||
// cap?.Release();
|
||||
// }
|
||||
//}
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -1105,7 +1103,7 @@ namespace Capnp.Rpc
|
||||
|
||||
Tx(mb.Frame);
|
||||
|
||||
var main = new RemoteAnswerCapabilityDeprecated(
|
||||
var main = new RemoteAnswerCapability(
|
||||
pendingBootstrap,
|
||||
MemberAccessPath.BootstrapAccess);
|
||||
|
||||
@ -1340,7 +1338,9 @@ namespace Capnp.Rpc
|
||||
{
|
||||
foreach (var capDesc in payload.CapTable)
|
||||
{
|
||||
list.Add(ImportCap(capDesc));
|
||||
var cap = ImportCap(capDesc);
|
||||
cap.AddRef();
|
||||
list.Add(cap);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1348,20 +1348,13 @@ namespace Capnp.Rpc
|
||||
return list;
|
||||
}
|
||||
|
||||
void IRpcEndpoint.RequestPostAction(Action postAction)
|
||||
{
|
||||
_exportCapTablePostActions.Value += postAction;
|
||||
}
|
||||
|
||||
void ExportCapTableAndSend(
|
||||
SerializerState state,
|
||||
Payload.WRITER payload)
|
||||
{
|
||||
Debug.Assert(_exportCapTablePostActions.Value == null);
|
||||
_exportCapTablePostActions.Value = null;
|
||||
|
||||
payload.CapTable.Init(state.MsgBuilder!.Caps!.Count);
|
||||
|
||||
Action? postAction = null;
|
||||
int i = 0;
|
||||
foreach (var cap in state.MsgBuilder.Caps)
|
||||
{
|
||||
@ -1373,7 +1366,7 @@ namespace Capnp.Rpc
|
||||
}
|
||||
else
|
||||
{
|
||||
cap.Export(this, capDesc);
|
||||
postAction += cap.Export(this, capDesc);
|
||||
cap.Release(false);
|
||||
}
|
||||
}
|
||||
@ -1386,9 +1379,7 @@ namespace Capnp.Rpc
|
||||
// To avoid that situation, calls to "ReExportCapWhenResolved" are queued (and
|
||||
// therefore deferred) to the postAction.
|
||||
|
||||
var pa = _exportCapTablePostActions.Value;
|
||||
_exportCapTablePostActions.Value = null;
|
||||
pa?.Invoke();
|
||||
postAction?.Invoke();
|
||||
}
|
||||
|
||||
PendingQuestion IRpcEndpoint.BeginQuestion(ConsumedCapability target, SerializerState inParams)
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -17,8 +18,24 @@ namespace Capnp.Rpc
|
||||
Vine(ConsumedCapability consumedCap)
|
||||
{
|
||||
Proxy = new Proxy(consumedCap ?? throw new ArgumentNullException(nameof(consumedCap)));
|
||||
|
||||
#if DebugFinalizers
|
||||
CreatorStackTrace = Environment.StackTrace;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if DebugFinalizers
|
||||
~Vine()
|
||||
{
|
||||
Logger.LogWarning($"Caught orphaned Vine, created from here: {CreatorStackTrace}.");
|
||||
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
ILogger Logger { get; } = Logging.CreateLogger<Vine>();
|
||||
string CreatorStackTrace { get; }
|
||||
#endif
|
||||
|
||||
internal override void Bind(object impl)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
@ -9,8 +9,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "capnpc-csharp", "capnpc-csh
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Capnp.Net.Runtime.Tests.Std20", "Capnp.Net.Runtime.Tests\Capnp.Net.Runtime.Tests.Std20.csproj", "{9ED38750-F83F-4B10-B3A3-4FD6183F9E86}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Capnp.Net.Runtime.Tests.Core21", "Capnp.Net.Runtime.Tests.Core21\Capnp.Net.Runtime.Tests.Core21.csproj", "{58E8FFC8-D207-4B0F-842A-58ED9D3D9EEF}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CapnpC.CSharp.Generator.Tests", "CapnpC.CSharp.Generator.Tests\CapnpC.CSharp.Generator.Tests.csproj", "{B77AC567-E232-4072-85C3-8689566BF3D4}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CapnpC.CSharp.MsBuild.Generation", "CapnpC.CSharp.MsBuild.Generation\CapnpC.CSharp.MsBuild.Generation.csproj", "{1EFC1F20-C7BB-4F44-8BF9-DBB123AACCF4}"
|
||||
@ -37,10 +35,6 @@ Global
|
||||
{9ED38750-F83F-4B10-B3A3-4FD6183F9E86}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9ED38750-F83F-4B10-B3A3-4FD6183F9E86}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9ED38750-F83F-4B10-B3A3-4FD6183F9E86}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{58E8FFC8-D207-4B0F-842A-58ED9D3D9EEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{58E8FFC8-D207-4B0F-842A-58ED9D3D9EEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{58E8FFC8-D207-4B0F-842A-58ED9D3D9EEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{58E8FFC8-D207-4B0F-842A-58ED9D3D9EEF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B77AC567-E232-4072-85C3-8689566BF3D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B77AC567-E232-4072-85C3-8689566BF3D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B77AC567-E232-4072-85C3-8689566BF3D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -72,6 +72,7 @@ namespace CapnpC.CSharp.Generator.CodeGen
|
||||
public string PipeliningExtensionsClassFormat { get; }
|
||||
public string ProxyClassFormat { get; }
|
||||
public string SkeletonClassFormat { get; }
|
||||
public Name AwaitProxy { get; }
|
||||
public bool NullableEnable { get; set; }
|
||||
public GenNames(GeneratorOptions options)
|
||||
{
|
||||
@ -110,6 +111,7 @@ namespace CapnpC.CSharp.Generator.CodeGen
|
||||
PipeliningExtensionsClassFormat = options.PipeliningExtensionsClassFormat;
|
||||
ProxyClassFormat = options.ProxyClassFormat;
|
||||
SkeletonClassFormat = options.SkeletonClassFormat;
|
||||
AwaitProxy = new Name(options.AwaitProxyName);
|
||||
}
|
||||
|
||||
public Name MakeTypeName(TypeDefinition def, NameUsage usage = NameUsage.Default)
|
||||
|
@ -37,6 +37,7 @@
|
||||
public string TaskParameterName { get; set; } = "task";
|
||||
public string EagerMethodName { get; set; } = "Eager";
|
||||
public string TypeIdFieldName { get; set; } = "typeId";
|
||||
public string AwaitProxyName { get; set; } = "AwaitProxy";
|
||||
public bool NullableEnableDefault { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
@ -403,9 +403,12 @@ namespace CapnpC.CSharp.Generator.CodeGen
|
||||
Argument(call),
|
||||
Argument(SimpleLambdaExpression(
|
||||
Parameter(_names.DeserializerLocal.Identifier),
|
||||
Block(
|
||||
UsingStatement(
|
||||
Block(
|
||||
MakeProxyCreateResult(method),
|
||||
MakeProxyReturnResult(method)))));
|
||||
MakeProxyReturnResult(method)))
|
||||
.WithExpression(_names.DeserializerLocal.IdentifierName)))));
|
||||
|
||||
bodyStmts.Add(ReturnStatement(pipelineAwareCall));
|
||||
}
|
||||
@ -422,19 +425,18 @@ namespace CapnpC.CSharp.Generator.CodeGen
|
||||
call,
|
||||
IdentifierName(nameof(Capnp.Rpc.IPromisedAnswer.WhenReturned)));
|
||||
|
||||
var assignAwaited = LocalDeclarationStatement(
|
||||
VariableDeclaration(
|
||||
bodyStmts.Add(UsingStatement(
|
||||
Block(
|
||||
MakeProxyCreateResult(method),
|
||||
MakeProxyReturnResult(method)))
|
||||
.WithDeclaration(VariableDeclaration(
|
||||
IdentifierName("var"))
|
||||
.AddVariables(
|
||||
VariableDeclarator(
|
||||
_names.DeserializerLocal.Identifier)
|
||||
.WithInitializer(
|
||||
EqualsValueClause(
|
||||
AwaitExpression(whenReturned)))));
|
||||
|
||||
bodyStmts.Add(assignAwaited);
|
||||
bodyStmts.Add(MakeProxyCreateResult(method));
|
||||
bodyStmts.Add(MakeProxyReturnResult(method));
|
||||
AwaitExpression(whenReturned))))));
|
||||
}
|
||||
|
||||
if (method.GenericParameters.Count > 0)
|
||||
@ -703,7 +705,10 @@ namespace CapnpC.CSharp.Generator.CodeGen
|
||||
Parameter(_names.CancellationTokenParameter.Identifier)
|
||||
.WithType(_names.Type<CancellationToken>(Nullability.NonNullable)))
|
||||
.AddBodyStatements(
|
||||
MakeSkeletonMethodBody(method).ToArray());
|
||||
UsingStatement(
|
||||
Block(
|
||||
MakeSkeletonMethodBody(method).ToArray()))
|
||||
.WithExpression(_names.DeserializerLocal.IdentifierName));
|
||||
|
||||
if (method.Results.Count == 0)
|
||||
{
|
||||
@ -808,6 +813,59 @@ namespace CapnpC.CSharp.Generator.CodeGen
|
||||
|
||||
readonly HashSet<(string, string)> _existingExtensionMethods = new HashSet<(string, string)>();
|
||||
|
||||
LocalFunctionStatementSyntax MakeLocalAwaitProxyFunction(Method method, IReadOnlyList<Field> path)
|
||||
{
|
||||
var members = new List<Name>();
|
||||
IEnumerable<Field> fields = path;
|
||||
|
||||
if (method.Results.Count >= 2)
|
||||
{
|
||||
int index = Array.IndexOf(method.ResultStruct.Fields.ToArray(), path[0]) + 1;
|
||||
members.Add(new Name($"Item{index}"));
|
||||
fields = path.Skip(1);
|
||||
}
|
||||
|
||||
foreach (var field in fields)
|
||||
{
|
||||
members.Add(_names.GetCodeIdentifier(field));
|
||||
}
|
||||
|
||||
ExpressionSyntax memberAccess =
|
||||
ParenthesizedExpression(
|
||||
AwaitExpression(
|
||||
_names.TaskParameter.IdentifierName));
|
||||
|
||||
memberAccess = MemberAccessExpression(
|
||||
SyntaxKind.SimpleMemberAccessExpression,
|
||||
memberAccess,
|
||||
members.First().IdentifierName);
|
||||
|
||||
foreach (var member in members.Skip(1))
|
||||
{
|
||||
memberAccess = ConditionalAccessExpression(
|
||||
memberAccess,
|
||||
MemberBindingExpression(member.IdentifierName));
|
||||
}
|
||||
|
||||
var idisposable = _names.MakeNullableRefType(IdentifierName(nameof(IDisposable)));
|
||||
|
||||
return LocalFunctionStatement(
|
||||
GenericName(
|
||||
Identifier(nameof(Task)))
|
||||
.WithTypeArgumentList(
|
||||
TypeArgumentList(
|
||||
SingletonSeparatedList<TypeSyntax>(
|
||||
idisposable))),
|
||||
_names.AwaitProxy.Identifier)
|
||||
.WithModifiers(
|
||||
TokenList(
|
||||
Token(SyntaxKind.AsyncKeyword)))
|
||||
.WithExpressionBody(
|
||||
ArrowExpressionClause(memberAccess))
|
||||
.WithSemicolonToken(
|
||||
Token(SyntaxKind.SemicolonToken));
|
||||
}
|
||||
|
||||
public IEnumerable<MemberDeclarationSyntax> MakePipeliningSupport(TypeDefinition type)
|
||||
{
|
||||
foreach (var method in type.Methods)
|
||||
@ -856,6 +914,7 @@ namespace CapnpC.CSharp.Generator.CodeGen
|
||||
.AddModifiers(This)
|
||||
.WithType(TransformReturnType(method)))
|
||||
.AddBodyStatements(
|
||||
MakeLocalAwaitProxyFunction(method, path),
|
||||
ReturnStatement(
|
||||
CastExpression(
|
||||
capTypeSyntax,
|
||||
@ -883,7 +942,10 @@ namespace CapnpC.CSharp.Generator.CodeGen
|
||||
IdentifierName(nameof(Capnp.Rpc.IPromisedAnswer.Access))))
|
||||
.AddArgumentListArguments(
|
||||
Argument(
|
||||
accessPath.IdentifierName)))))));
|
||||
accessPath.IdentifierName),
|
||||
Argument(
|
||||
InvocationExpression(
|
||||
_names.AwaitProxy.IdentifierName))))))));
|
||||
|
||||
yield return pathDecl;
|
||||
yield return methodDecl;
|
||||
|
Loading…
x
Reference in New Issue
Block a user