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">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net471</TargetFramework>
|
<TargetFrameworks>netcoreapp2.1;net471</TargetFrameworks>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<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));
|
Assert.IsTrue(fcc.Wait(MediumNonDbgTimeout));
|
||||||
var cc = fcc.Result;
|
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);
|
Assert.AreEqual(123u, pr.I);
|
||||||
|
|
||||||
cc.ForwardToBob();
|
cc.ForwardToBob();
|
||||||
|
|
||||||
Assert.IsTrue(policy.Returns.ReceiveAsync().Wait(MediumNonDbgTimeout));
|
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);
|
Assert.AreEqual("foo", rr.X);
|
||||||
|
|
||||||
cc.ReturnToAlice();
|
cc.ReturnToAlice();
|
||||||
@ -117,11 +117,11 @@ namespace Capnp.Net.Runtime.Tests
|
|||||||
|
|
||||||
Assert.AreEqual(InterceptionState.RequestedFromAlice, cc.State);
|
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(321u, pr.I);
|
||||||
Assert.AreEqual(false, pr.J);
|
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.I = 123u;
|
||||||
pw.J = true;
|
pw.J = true;
|
||||||
|
|
||||||
@ -133,12 +133,12 @@ namespace Capnp.Net.Runtime.Tests
|
|||||||
Assert.IsTrue(cc.State == InterceptionState.ForwardedToBob || rx.IsCompleted);
|
Assert.IsTrue(cc.State == InterceptionState.ForwardedToBob || rx.IsCompleted);
|
||||||
|
|
||||||
Assert.IsTrue(rx.Wait(MediumNonDbgTimeout));
|
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.AreEqual("foo", rr.X);
|
||||||
|
|
||||||
Assert.IsFalse(request1.IsCompleted);
|
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";
|
rw.X = "bar";
|
||||||
cc.OutArgs = rw;
|
cc.OutArgs = rw;
|
||||||
|
|
||||||
@ -174,7 +174,7 @@ namespace Capnp.Net.Runtime.Tests
|
|||||||
Assert.IsTrue(policy.Calls.TryReceive(out var cc));
|
Assert.IsTrue(policy.Calls.TryReceive(out var cc));
|
||||||
Assert.IsFalse(request1.IsCompleted);
|
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";
|
rw.X = "bar";
|
||||||
cc.OutArgs = rw;
|
cc.OutArgs = rw;
|
||||||
|
|
||||||
|
@ -20,12 +20,14 @@ namespace Capnp.Net.Runtime.Tests
|
|||||||
var tcs = new TaskCompletionSource<int>();
|
var tcs = new TaskCompletionSource<int>();
|
||||||
var impl = new TestPipelineImpl2(tcs.Task);
|
var impl = new TestPipelineImpl2(tcs.Task);
|
||||||
var bproxy = BareProxy.FromImpl(impl);
|
var bproxy = BareProxy.FromImpl(impl);
|
||||||
var proxy = bproxy.Cast<ITestPipeline>(true);
|
using (var proxy = bproxy.Cast<ITestPipeline>(true))
|
||||||
var cap = proxy.GetCap(0, null).OutBox_Cap();
|
using (var cap = proxy.GetCap(0, null).OutBox_Cap())
|
||||||
var foo = cap.Foo(123, true);
|
{
|
||||||
tcs.SetResult(0);
|
var foo = cap.Foo(123, true);
|
||||||
Assert.IsTrue(foo.Wait(TestBase.MediumNonDbgTimeout));
|
tcs.SetResult(0);
|
||||||
Assert.AreEqual("bar", foo.Result);
|
Assert.IsTrue(foo.Wait(TestBase.MediumNonDbgTimeout));
|
||||||
|
Assert.AreEqual("bar", foo.Result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -574,18 +574,17 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
|
|||||||
class TestCallOrderImpl : ITestCallOrder
|
class TestCallOrderImpl : ITestCallOrder
|
||||||
{
|
{
|
||||||
readonly object _lock = new object();
|
readonly object _lock = new object();
|
||||||
|
uint _counter;
|
||||||
|
|
||||||
ILogger Logger { get; } = Logging.CreateLogger<TestCallOrderImpl>();
|
ILogger Logger { get; } = Logging.CreateLogger<TestCallOrderImpl>();
|
||||||
|
|
||||||
public uint Count { get; set; }
|
|
||||||
|
|
||||||
public uint? CountToDispose { get; set; }
|
public uint? CountToDispose { get; set; }
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
lock (_lock)
|
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)
|
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_)
|
public Task<ITestInterface> GetHeld(CancellationToken cancellationToken_)
|
||||||
{
|
{
|
||||||
Interlocked.Increment(ref _counters.CallCount);
|
Interlocked.Increment(ref _counters.CallCount);
|
||||||
return Task.FromResult(ClientToHold);
|
return Task.FromResult(Proxy.Share(ClientToHold));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ITestMoreStuff> GetNull(CancellationToken cancellationToken_)
|
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.Target.ImportedCap = bootCapId;
|
||||||
_.Call.InterfaceId = ((TypeIdAttribute)typeof(ITestInterface).GetCustomAttributes(typeof(TypeIdAttribute), false)[0]).Id;
|
_.Call.InterfaceId = ((TypeIdAttribute)typeof(ITestInterface).GetCustomAttributes(typeof(TypeIdAttribute), false)[0]).Id;
|
||||||
_.Call.MethodId = 0;
|
_.Call.MethodId = 0;
|
||||||
_.Call.Params.Content.Rewrap<TestInterface.Params_foo.WRITER>();
|
_.Call.Params.Content.Rewrap<TestInterface.Params_Foo.WRITER>();
|
||||||
});
|
});
|
||||||
tester.ExpectAbort();
|
tester.ExpectAbort();
|
||||||
}
|
}
|
||||||
@ -154,7 +154,7 @@ namespace Capnp.Net.Runtime.Tests
|
|||||||
_.Call.Target.ImportedCap = bootCapId;
|
_.Call.Target.ImportedCap = bootCapId;
|
||||||
_.Call.InterfaceId = ((TypeIdAttribute)typeof(ITestInterface).GetCustomAttributes(typeof(TypeIdAttribute), false)[0]).Id;
|
_.Call.InterfaceId = ((TypeIdAttribute)typeof(ITestInterface).GetCustomAttributes(typeof(TypeIdAttribute), false)[0]).Id;
|
||||||
_.Call.MethodId = 0;
|
_.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.I = 123u;
|
||||||
wr.J = true;
|
wr.J = true;
|
||||||
});
|
});
|
||||||
@ -168,7 +168,7 @@ namespace Capnp.Net.Runtime.Tests
|
|||||||
_.Call.Target.ImportedCap = bootCapId;
|
_.Call.Target.ImportedCap = bootCapId;
|
||||||
_.Call.InterfaceId = ((TypeIdAttribute)typeof(ITestInterface).GetCustomAttributes(typeof(TypeIdAttribute), false)[0]).Id;
|
_.Call.InterfaceId = ((TypeIdAttribute)typeof(ITestInterface).GetCustomAttributes(typeof(TypeIdAttribute), false)[0]).Id;
|
||||||
_.Call.MethodId = 0;
|
_.Call.MethodId = 0;
|
||||||
_.Call.Params.Content.Rewrap<TestInterface.Params_foo.WRITER>();
|
_.Call.Params.Content.Rewrap<TestInterface.Params_Foo.WRITER>();
|
||||||
});
|
});
|
||||||
tester.ExpectAbort();
|
tester.ExpectAbort();
|
||||||
}
|
}
|
||||||
@ -565,7 +565,7 @@ namespace Capnp.Net.Runtime.Tests
|
|||||||
_.Call.Target.ImportedCap = bootCapId;
|
_.Call.Target.ImportedCap = bootCapId;
|
||||||
_.Call.InterfaceId = new TestPipeline_Skeleton().InterfaceId;
|
_.Call.InterfaceId = new TestPipeline_Skeleton().InterfaceId;
|
||||||
_.Call.MethodId = 0;
|
_.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;
|
wr.InCap = null;
|
||||||
_.Call.Params.CapTable.Init(1);
|
_.Call.Params.CapTable.Init(1);
|
||||||
_.Call.Params.CapTable[0].which = CapDescriptor.WHICH.ReceiverHosted;
|
_.Call.Params.CapTable[0].which = CapDescriptor.WHICH.ReceiverHosted;
|
||||||
|
@ -642,7 +642,7 @@ namespace Capnp.Net.Runtime.Tests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod]
|
||||||
public void RetainAndReleaseClient()
|
public void RetainAndReleaseClient()
|
||||||
{
|
{
|
||||||
using (var server = SetupServer())
|
using (var server = SetupServer())
|
||||||
|
@ -12,6 +12,7 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace Capnp.Net.Runtime.Tests
|
namespace Capnp.Net.Runtime.Tests
|
||||||
{
|
{
|
||||||
|
|
||||||
[TestClass]
|
[TestClass]
|
||||||
[TestCategory("Coverage")]
|
[TestCategory("Coverage")]
|
||||||
public class TcpRpcPorted: TestBase
|
public class TcpRpcPorted: TestBase
|
||||||
@ -19,99 +20,19 @@ namespace Capnp.Net.Runtime.Tests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Basic()
|
public void Basic()
|
||||||
{
|
{
|
||||||
(var server, var client) = SetupClientServerPair();
|
NewLocalhostTcpTestbed().RunTest(Testsuite.Basic);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Pipeline()
|
public void Pipeline()
|
||||||
{
|
{
|
||||||
(var server, var client) = SetupClientServerPair();
|
NewLocalhostTcpTestbed().RunTest(Testsuite.Pipeline);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Release()
|
public void Release()
|
||||||
{
|
{
|
||||||
(var server, var client) = SetupClientServerPair();
|
NewLocalhostTcpTestbed().RunTest(Testsuite.Release);
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@ -160,482 +81,63 @@ namespace Capnp.Net.Runtime.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TestTailCall()
|
public void TailCall()
|
||||||
{
|
{
|
||||||
(var server, var client) = SetupClientServerPair();
|
NewLocalhostTcpTestbed().RunTest(Testsuite.TailCall);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Cancelation()
|
public void Cancelation()
|
||||||
{
|
{
|
||||||
(var server, var client) = SetupClientServerPair();
|
NewLocalhostTcpTestbed().RunTest(Testsuite.Cancelation);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void PromiseResolve()
|
public void PromiseResolve()
|
||||||
{
|
{
|
||||||
(var server, var client) = SetupClientServerPair();
|
NewLocalhostTcpTestbed().RunTest(Testsuite.PromiseResolve);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void RetainAndRelease()
|
public void RetainAndRelease()
|
||||||
{
|
{
|
||||||
(var server, var client) = SetupClientServerPair();
|
NewLocalhostTcpTestbed().RunTest(Testsuite.RetainAndRelease);
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Cancel()
|
public void Cancel()
|
||||||
{
|
{
|
||||||
(var server, var client) = SetupClientServerPair();
|
NewLocalhostTcpTestbed().RunTest(Testsuite.Cancel);
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void SendTwice()
|
public void SendTwice()
|
||||||
{
|
{
|
||||||
(var server, var client) = SetupClientServerPair();
|
NewLocalhostTcpTestbed().RunTest(Testsuite.SendTwice);
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Embargo()
|
public void Embargo()
|
||||||
{
|
{
|
||||||
(var server, var client) = SetupClientServerPair();
|
NewLocalhostTcpTestbed().RunTest(Testsuite.Embargo);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void EmbargoError()
|
public void EmbargoError()
|
||||||
{
|
{
|
||||||
(var server, var client) = SetupClientServerPair();
|
NewLocalhostTcpTestbed().RunTest(Testsuite.EmbargoError);
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void EmbargoNull()
|
public void EmbargoNull()
|
||||||
{
|
{
|
||||||
(var server, var client) = SetupClientServerPair();
|
NewLocalhostTcpTestbed().RunTest(Testsuite.EmbargoNull);
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void CallBrokenPromise()
|
public void CallBrokenPromise()
|
||||||
{
|
{
|
||||||
(var server, var client) = SetupClientServerPair();
|
NewLocalhostTcpTestbed().RunTest(Testsuite.CallBrokenPromise);
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
@ -60,7 +61,11 @@ namespace Capnp.Net.Runtime.Tests
|
|||||||
{
|
{
|
||||||
var t = new TcpRpcPorted();
|
var t = new TcpRpcPorted();
|
||||||
Repeat(100, t.Embargo);
|
Repeat(100, t.Embargo);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void EmbargoServer()
|
||||||
|
{
|
||||||
var t2 = new TcpRpcInterop();
|
var t2 = new TcpRpcInterop();
|
||||||
Repeat(100, t2.EmbargoServer);
|
Repeat(100, t2.EmbargoServer);
|
||||||
}
|
}
|
||||||
@ -94,35 +99,45 @@ namespace Capnp.Net.Runtime.Tests
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void ScatteredTransfer()
|
public void ScatteredTransfer()
|
||||||
{
|
{
|
||||||
|
for (int retry = 0; retry < 10; retry++)
|
||||||
using (var server = new TcpRpcServer(IPAddress.Any, TcpPort))
|
|
||||||
using (var client = new TcpRpcClient())
|
|
||||||
{
|
{
|
||||||
server.InjectMidlayer(s => new ScatteringStream(s, 7));
|
try
|
||||||
client.InjectMidlayer(s => new ScatteringStream(s, 10));
|
|
||||||
client.Connect("localhost", TcpPort);
|
|
||||||
client.WhenConnected.Wait();
|
|
||||||
|
|
||||||
var counters = new Counters();
|
|
||||||
server.Main = new TestInterfaceImpl(counters);
|
|
||||||
using (var main = client.GetMain<ITestInterface>())
|
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 100; i++)
|
using (var server = new TcpRpcServer(IPAddress.Any, TcpPort))
|
||||||
|
using (var client = new TcpRpcClient())
|
||||||
{
|
{
|
||||||
var request1 = main.Foo(123, true, default);
|
server.InjectMidlayer(s => new ScatteringStream(s, 7));
|
||||||
var request3 = Assert.ThrowsExceptionAsync<RpcException>(() => main.Bar(default));
|
client.InjectMidlayer(s => new ScatteringStream(s, 10));
|
||||||
var s = new TestAllTypes();
|
client.Connect("localhost", TcpPort);
|
||||||
Common.InitTestMessage(s);
|
client.WhenConnected.Wait();
|
||||||
var request2 = main.Baz(s, default);
|
|
||||||
|
|
||||||
Assert.IsTrue(request1.Wait(MediumNonDbgTimeout));
|
var counters = new Counters();
|
||||||
Assert.IsTrue(request2.Wait(MediumNonDbgTimeout));
|
server.Main = new TestInterfaceImpl(counters);
|
||||||
Assert.IsTrue(request3.Wait(MediumNonDbgTimeout));
|
using (var main = client.GetMain<ITestInterface>())
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
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.AreEqual("foo", request1.Result);
|
Assert.IsTrue(request1.Wait(MediumNonDbgTimeout));
|
||||||
Assert.AreEqual(2, counters.CallCount);
|
Assert.IsTrue(request2.Wait(MediumNonDbgTimeout));
|
||||||
counters.CallCount = 0;
|
Assert.IsTrue(request3.Wait(MediumNonDbgTimeout));
|
||||||
|
|
||||||
|
Assert.AreEqual("foo", request1.Result);
|
||||||
|
Assert.AreEqual(2, counters.CallCount);
|
||||||
|
counters.CallCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
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>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<DefineConstants>DebugCapabilityLifecycle</DefineConstants>
|
<DefineConstants>DebugFinalizers</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.0|AnyCPU'">
|
<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
|
/// <returns>The domain object instance. Nullability note: The returned reference may be null if
|
||||||
/// <paramref name="state"/> represents the nil object.</returns>
|
/// <paramref name="state"/> represents the nil object.</returns>
|
||||||
/// <exception cref="ArgumentException">Cannot construct object of type <typeparamref name="T"/></exception>
|
/// <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)
|
public static T? Create<T>(DeserializerState state)
|
||||||
where T: class
|
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
|
/// 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.
|
/// by the code generator.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public struct DeserializerState: IStructDeserializer
|
public struct DeserializerState: IStructDeserializer, IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A wire message is essentially a collection of memory blocks.
|
/// A wire message is essentially a collection of memory blocks.
|
||||||
@ -50,7 +50,7 @@ namespace Capnp
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IList<Rpc.ConsumedCapability?>? Caps { get; set; }
|
public IList<Rpc.ConsumedCapability?>? Caps { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Current segment (essentially Segments[CurrentSegmentIndex]
|
/// Current segment (essentially Segments[CurrentSegmentIndex])
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ReadOnlySpan<ulong> CurrentSegment => Segments != null ? Segments[(int)CurrentSegmentIndex].Span : default;
|
public ReadOnlySpan<ulong> CurrentSegment => Segments != null ? Segments[(int)CurrentSegmentIndex].Span : default;
|
||||||
|
|
||||||
@ -106,11 +106,6 @@ namespace Capnp
|
|||||||
case ObjectKind.ListOfStructs:
|
case ObjectKind.ListOfStructs:
|
||||||
case ObjectKind.Nil:
|
case ObjectKind.Nil:
|
||||||
case ObjectKind.Struct:
|
case ObjectKind.Struct:
|
||||||
if (state.Caps != null)
|
|
||||||
{
|
|
||||||
foreach (var cap in state.Caps)
|
|
||||||
cap?.Release(true);
|
|
||||||
}
|
|
||||||
return new DeserializerState(state.Allocator!.Segments)
|
return new DeserializerState(state.Allocator!.Segments)
|
||||||
{
|
{
|
||||||
CurrentSegmentIndex = state.SegmentIndex,
|
CurrentSegmentIndex = state.SegmentIndex,
|
||||||
@ -646,13 +641,10 @@ namespace Capnp
|
|||||||
/// <exception cref="IndexOutOfRangeException">negative index</exception>
|
/// <exception cref="IndexOutOfRangeException">negative index</exception>
|
||||||
/// <exception cref="DeserializationException">state does not represent a struct, invalid pointer,
|
/// <exception cref="DeserializationException">state does not represent a struct, invalid pointer,
|
||||||
/// non-capability pointer, traversal limit exceeded</exception>
|
/// non-capability pointer, traversal limit exceeded</exception>
|
||||||
public T? ReadCap<T>(int index,
|
public T? ReadCap<T>(int index) where T: class
|
||||||
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
|
|
||||||
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
|
|
||||||
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0) where T: class
|
|
||||||
{
|
{
|
||||||
var cap = StructReadRawCap(index);
|
var cap = StructReadRawCap(index);
|
||||||
return Rpc.CapabilityReflection.CreateProxy<T>(cap, memberName, sourceFilePath, sourceLineNumber) as T;
|
return Rpc.CapabilityReflection.CreateProxy<T>(cap) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -690,5 +682,16 @@ namespace Capnp
|
|||||||
|
|
||||||
return (Rpc.CapabilityReflection.CreateProxy<T>(Caps[(int)CapabilityIndex]) as T)!;
|
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)
|
if (state.Caps != null)
|
||||||
{
|
{
|
||||||
mb.InitCapTable();
|
mb.InitCapTable();
|
||||||
foreach (var cap in state.Caps)
|
|
||||||
cap?.AddRef();
|
|
||||||
}
|
}
|
||||||
var sstate = mb.CreateObject<DynamicSerializerState>();
|
var sstate = mb.CreateObject<DynamicSerializerState>();
|
||||||
Reserializing.DeepCopy(state, sstate);
|
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="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="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>
|
/// <exception cref="TypeLoadException">Problem with building the Proxy type, or problem with loading some dependent class.</exception>
|
||||||
public static Proxy CreateProxy<TInterface>(ConsumedCapability? cap,
|
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)
|
|
||||||
{
|
{
|
||||||
var factory = GetProxyFactory(typeof(TInterface));
|
var factory = GetProxyFactory(typeof(TInterface));
|
||||||
var proxy = factory.NewProxy();
|
var proxy = factory.NewProxy();
|
||||||
proxy.Bind(cap);
|
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;
|
return proxy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
namespace Capnp.Rpc
|
using System;
|
||||||
|
|
||||||
|
namespace Capnp.Rpc
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <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
|
/// 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.
|
/// which usually also means to remove it from the remote peer's export table.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected abstract void ReleaseRemotely();
|
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 Freeze(out IRpcEndpoint? boundEndpoint);
|
||||||
internal abstract void Unfreeze();
|
internal abstract void Unfreeze();
|
||||||
|
|
||||||
@ -23,11 +25,5 @@
|
|||||||
[System.Runtime.CompilerServices.CallerMemberName] string methodName = "",
|
[System.Runtime.CompilerServices.CallerMemberName] string methodName = "",
|
||||||
[System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
|
[System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
|
||||||
[System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0);
|
[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>
|
/// <returns>Pipelined low-level capability</returns>
|
||||||
ConsumedCapability? Access(MemberAccessPath access);
|
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);
|
PendingQuestion BeginQuestion(ConsumedCapability target, SerializerState inParams);
|
||||||
void SendQuestion(SerializerState inParams, Payload.WRITER payload);
|
void SendQuestion(SerializerState inParams, Payload.WRITER payload);
|
||||||
uint AllocateExport(Skeleton providedCapability, out bool first);
|
uint AllocateExport(Skeleton providedCapability, out bool first);
|
||||||
void RequestPostAction(Action postAction);
|
|
||||||
void Finish(uint questionId);
|
void Finish(uint questionId);
|
||||||
void ReleaseImport(uint importId);
|
void ReleaseImport(uint importId);
|
||||||
void Resolve(uint preliminaryId, Skeleton preliminaryCap, Func<ConsumedCapability> resolvedCapGetter);
|
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
|
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)
|
switch (item)
|
||||||
{
|
{
|
||||||
@ -124,14 +137,11 @@ namespace Capnp.Rpc
|
|||||||
/// <exception cref="InvalidCapabilityInterfaceException"><typeparamref name="TInterface"/> did not
|
/// <exception cref="InvalidCapabilityInterfaceException"><typeparamref name="TInterface"/> did not
|
||||||
/// quality as capability interface.</exception>
|
/// quality as capability interface.</exception>
|
||||||
[Obsolete("Call Eager<TInterface>(task, true) instead")]
|
[Obsolete("Call Eager<TInterface>(task, true) instead")]
|
||||||
public static TInterface PseudoEager<TInterface>(this Task<TInterface> task,
|
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)
|
|
||||||
where TInterface : class
|
where TInterface : class
|
||||||
{
|
{
|
||||||
var lazyCap = new LazyCapability(AwaitProxy(task));
|
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);
|
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="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>
|
/// <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)
|
public static TInterface Eager<TInterface>(this Task<TInterface> task, bool allowNoPipeliningFallback = false)
|
||||||
where TInterface : class
|
where TInterface : class, IDisposable
|
||||||
{
|
{
|
||||||
var answer = TryGetAnswer(task);
|
var answer = TryGetAnswer(task);
|
||||||
if (answer == null)
|
if (answer == null)
|
||||||
@ -172,7 +182,12 @@ namespace Capnp.Rpc
|
|||||||
}
|
}
|
||||||
else
|
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>
|
/// <summary>
|
||||||
/// Low-level capability which as imported from a remote peer.
|
/// 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)
|
if (endpoint == _ep)
|
||||||
{
|
{
|
||||||
@ -47,6 +49,7 @@
|
|||||||
capDesc.which = CapDescriptor.WHICH.SenderHosted;
|
capDesc.which = CapDescriptor.WHICH.SenderHosted;
|
||||||
capDesc.SenderHosted = endpoint.AllocateExport(Vine.Create(this), out var _);
|
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();
|
var proxyTask = task.AsProxyTask();
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
namespace Capnp.Rpc.Interception
|
using System;
|
||||||
|
|
||||||
|
namespace Capnp.Rpc.Interception
|
||||||
{
|
{
|
||||||
class CensorCapability : RefCountingCapability
|
class CensorCapability : RefCountingCapability
|
||||||
{
|
{
|
||||||
@ -26,10 +28,11 @@
|
|||||||
return cc.Answer;
|
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.which = CapDescriptor.WHICH.SenderHosted;
|
||||||
writer.SenderHosted = endpoint.AllocateExport(MyVine, out bool _);
|
writer.SenderHosted = endpoint.AllocateExport(MyVine, out bool _);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void Freeze(out IRpcEndpoint? boundEndpoint)
|
internal override void Freeze(out IRpcEndpoint? boundEndpoint)
|
||||||
|
@ -8,19 +8,15 @@ namespace Capnp.Rpc
|
|||||||
{
|
{
|
||||||
public static LazyCapability CreateBrokenCap(string message)
|
public static LazyCapability CreateBrokenCap(string message)
|
||||||
{
|
{
|
||||||
var cap = new LazyCapability(Task.FromException<ConsumedCapability?>(new RpcException(message)));
|
return new LazyCapability(Task.FromException<ConsumedCapability?>(new RpcException(message)));
|
||||||
cap.AddRef(); // Instance shall be persistent
|
|
||||||
return cap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LazyCapability CreateCanceledCap(CancellationToken token)
|
public static LazyCapability CreateCanceledCap(CancellationToken token)
|
||||||
{
|
{
|
||||||
var cap = new LazyCapability(Task.FromCanceled<ConsumedCapability?>(token));
|
return new LazyCapability(Task.FromCanceled<ConsumedCapability?>(token));
|
||||||
cap.AddRef(); // Instance shall be persistent
|
|
||||||
return cap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LazyCapability Null { get; } = CreateBrokenCap("Null capability");
|
public static LazyCapability Null => CreateBrokenCap("Null capability");
|
||||||
|
|
||||||
readonly Task<Proxy>? _proxyTask;
|
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())
|
if (WhenResolved.ReplacementTaskIsCompletedSuccessfully())
|
||||||
{
|
{
|
||||||
using var proxy = new Proxy(WhenResolved.Result);
|
using var proxy = new Proxy(WhenResolved.Result);
|
||||||
proxy.Export(endpoint, writer);
|
proxy.Export(endpoint, writer);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.ExportAsSenderPromise(endpoint, writer);
|
return this.ExportAsSenderPromise(endpoint, writer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ namespace Capnp.Rpc
|
|||||||
return new LocalAnswerCapabilityDeprecated(WhenReturned, access);
|
return new LocalAnswerCapabilityDeprecated(WhenReturned, access);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConsumedCapability Access(MemberAccessPath _, Task<IDisposable> task)
|
public ConsumedCapability Access(MemberAccessPath _, Task<IDisposable?> task)
|
||||||
{
|
{
|
||||||
return new LocalAnswerCapability(task.AsProxyTask());
|
return new LocalAnswerCapability(task.AsProxyTask());
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ namespace Capnp.Rpc
|
|||||||
|
|
||||||
public Task<ConsumedCapability?> WhenResolved { get; private set; }
|
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)
|
if (_whenResolvedProxy.IsCompleted)
|
||||||
{
|
{
|
||||||
@ -39,12 +39,14 @@ namespace Capnp.Rpc
|
|||||||
}
|
}
|
||||||
catch (AggregateException exception)
|
catch (AggregateException exception)
|
||||||
{
|
{
|
||||||
throw exception.InnerException;
|
throw exception.InnerException!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.ExportAsSenderPromise(endpoint, writer);
|
return this.ExportAsSenderPromise(endpoint, writer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ namespace Capnp.Rpc
|
|||||||
|
|
||||||
public Task<ConsumedCapability?> WhenResolved { get; private set; }
|
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)
|
if (_answer.IsCompleted)
|
||||||
{
|
{
|
||||||
@ -46,10 +46,11 @@ namespace Capnp.Rpc
|
|||||||
|
|
||||||
using var proxy = new Proxy(_access.Eval(result));
|
using var proxy = new Proxy(_access.Eval(result));
|
||||||
proxy.Export(endpoint, writer);
|
proxy.Export(endpoint, writer);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.ExportAsSenderPromise(endpoint, writer);
|
return this.ExportAsSenderPromise(endpoint, writer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,10 +54,11 @@ namespace Capnp.Rpc
|
|||||||
return new LocalAnswer(cts, AwaitAnswer(call));
|
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.which = CapDescriptor.WHICH.SenderHosted;
|
||||||
capDesc.SenderHosted = endpoint.AllocateExport(ProvidedCap, out bool _);
|
capDesc.SenderHosted = endpoint.AllocateExport(ProvidedCap, out bool _);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void Freeze(out IRpcEndpoint? boundEndpoint)
|
internal override void Freeze(out IRpcEndpoint? boundEndpoint)
|
||||||
|
@ -132,7 +132,7 @@ namespace Capnp.Rpc
|
|||||||
{
|
{
|
||||||
if (state.Kind == ObjectKind.Nil)
|
if (state.Kind == ObjectKind.Nil)
|
||||||
{
|
{
|
||||||
return default(DeserializerState);
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.Kind != ObjectKind.Struct)
|
if (state.Kind != ObjectKind.Struct)
|
||||||
|
@ -24,11 +24,35 @@ namespace Capnp.Rpc
|
|||||||
_cts = cts;
|
_cts = cts;
|
||||||
_cancelCompleter = new TaskCompletionSource<AnswerOrCounterquestion>();
|
_cancelCompleter = new TaskCompletionSource<AnswerOrCounterquestion>();
|
||||||
_answerTask = CancelableAwaitWhenReady();
|
_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 CancellationToken CancellationToken => _cts?.Token ?? CancellationToken.None;
|
||||||
|
|
||||||
public IReadOnlyList<CapDescriptor.WRITER> CapTable { get; set; }
|
public IReadOnlyList<CapDescriptor.WRITER>? CapTable { get; set; }
|
||||||
|
|
||||||
public void Cancel()
|
public void Cancel()
|
||||||
{
|
{
|
||||||
@ -99,7 +123,7 @@ namespace Capnp.Rpc
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var path = MemberAccessPath.Deserialize(rd);
|
var path = MemberAccessPath.Deserialize(rd);
|
||||||
var cap = new RemoteAnswerCapabilityDeprecated(aorcq.Counterquestion!, path);
|
var cap = new RemoteAnswerCapability(aorcq.Counterquestion!, path);
|
||||||
return new Proxy(cap);
|
return new Proxy(cap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,6 +135,31 @@ namespace Capnp.Rpc
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_cts?.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,17 +126,17 @@ namespace Capnp.Rpc
|
|||||||
lock (ReentrancyBlocker)
|
lock (ReentrancyBlocker)
|
||||||
{
|
{
|
||||||
SetReturned();
|
SetReturned();
|
||||||
}
|
|
||||||
|
|
||||||
if (StateFlags.HasFlag(State.TailCall))
|
if (StateFlags.HasFlag(State.TailCall))
|
||||||
{
|
|
||||||
_tcs.TrySetException(new RpcException(ReturnDespiteTailCallMessage));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!_tcs.TrySetResult(results))
|
|
||||||
{
|
{
|
||||||
ReleaseOutCaps(results);
|
_tcs.TrySetException(new RpcException(ReturnDespiteTailCallMessage));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!_tcs.TrySetResult(results))
|
||||||
|
{
|
||||||
|
ReleaseOutCaps(results);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -146,15 +146,15 @@ namespace Capnp.Rpc
|
|||||||
lock (ReentrancyBlocker)
|
lock (ReentrancyBlocker)
|
||||||
{
|
{
|
||||||
SetReturned();
|
SetReturned();
|
||||||
}
|
|
||||||
|
|
||||||
if (!StateFlags.HasFlag(State.TailCall))
|
if (!StateFlags.HasFlag(State.TailCall))
|
||||||
{
|
{
|
||||||
_tcs.TrySetException(new RpcException("Peer sent the results of this questions somewhere else. This was not expected and is a protocol error."));
|
_tcs.TrySetException(new RpcException("Peer sent the results of this questions somewhere else. This was not expected and is a protocol error."));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_tcs.TrySetResult(default);
|
_tcs.TrySetResult(default);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,9 +163,9 @@ namespace Capnp.Rpc
|
|||||||
lock (ReentrancyBlocker)
|
lock (ReentrancyBlocker)
|
||||||
{
|
{
|
||||||
SetReturned();
|
SetReturned();
|
||||||
}
|
|
||||||
|
|
||||||
_tcs.TrySetException(new RpcException(exception.Reason));
|
_tcs.TrySetException(new RpcException(exception.Reason));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void OnException(System.Exception exception)
|
internal void OnException(System.Exception exception)
|
||||||
@ -173,9 +173,9 @@ namespace Capnp.Rpc
|
|||||||
lock (ReentrancyBlocker)
|
lock (ReentrancyBlocker)
|
||||||
{
|
{
|
||||||
SetReturned();
|
SetReturned();
|
||||||
}
|
|
||||||
|
|
||||||
_tcs.TrySetException(exception);
|
_tcs.TrySetException(exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void OnCanceled()
|
internal void OnCanceled()
|
||||||
@ -183,9 +183,9 @@ namespace Capnp.Rpc
|
|||||||
lock (ReentrancyBlocker)
|
lock (ReentrancyBlocker)
|
||||||
{
|
{
|
||||||
SetReturned();
|
SetReturned();
|
||||||
}
|
|
||||||
|
|
||||||
_tcs.TrySetCanceled();
|
_tcs.TrySetCanceled();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeleteMyQuestion()
|
void DeleteMyQuestion()
|
||||||
@ -234,6 +234,7 @@ namespace Capnp.Rpc
|
|||||||
/// <param name="access">Access path</param>
|
/// <param name="access">Access path</param>
|
||||||
/// <returns>Low-level capability</returns>
|
/// <returns>Low-level capability</returns>
|
||||||
/// <exception cref="DeserializationException">The referenced member does not exist or does not resolve to a capability pointer.</exception>
|
/// <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)
|
public ConsumedCapability? Access(MemberAccessPath access)
|
||||||
{
|
{
|
||||||
lock (ReentrancyBlocker)
|
lock (ReentrancyBlocker)
|
||||||
@ -252,7 +253,7 @@ namespace Capnp.Rpc
|
|||||||
}
|
}
|
||||||
else
|
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
|
/// Refer to a (possibly nested) member of this question's (possibly future) result and return
|
||||||
/// it as a capability.
|
/// it as a capability.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="access">Access path</param>
|
||||||
/// <param name="task">promises the cap whose ownership is transferred to this object</param>
|
/// <param name="task">promises the cap whose ownership is transferred to this object</param>
|
||||||
/// <returns>Low-level capability</returns>
|
/// <returns>Low-level capability</returns>
|
||||||
/// <exception cref="DeserializationException">The referenced member does not exist or does not resolve to a capability pointer.</exception>
|
/// <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();
|
var proxyTask = task.AsProxyTask();
|
||||||
|
return new RemoteAnswerCapability(this, access, proxyTask);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ReleaseCaps(ConsumedCapability? target, SerializerState? inParams)
|
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)
|
lock (_reentrancyBlocker)
|
||||||
{
|
{
|
||||||
@ -98,7 +98,7 @@ namespace Capnp.Rpc
|
|||||||
Debug.Assert(!_released);
|
Debug.Assert(!_released);
|
||||||
++_pendingCallsOnPromise;
|
++_pendingCallsOnPromise;
|
||||||
|
|
||||||
_ep.RequestPostAction(() =>
|
return () =>
|
||||||
{
|
{
|
||||||
bool release = false;
|
bool release = false;
|
||||||
|
|
||||||
@ -115,7 +115,7 @@ namespace Capnp.Rpc
|
|||||||
{
|
{
|
||||||
_ep.ReleaseImport(_remoteId);
|
_ep.ReleaseImport(_remoteId);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -123,6 +123,8 @@ namespace Capnp.Rpc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async void TrackCall(Task call)
|
async void TrackCall(Task call)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@ -99,9 +100,12 @@ namespace Capnp.Rpc
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Proxy()
|
public Proxy()
|
||||||
{
|
{
|
||||||
|
#if DebugFinalizers
|
||||||
|
CreatorStackTrace = Environment.StackTrace;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Proxy(ConsumedCapability? cap)
|
internal Proxy(ConsumedCapability? cap): this()
|
||||||
{
|
{
|
||||||
Bind(cap);
|
Bind(cap);
|
||||||
}
|
}
|
||||||
@ -118,7 +122,7 @@ namespace Capnp.Rpc
|
|||||||
cap.AddRef();
|
cap.AddRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal IProvidedCapability? GetProvider()
|
internal Skeleton? GetProvider()
|
||||||
{
|
{
|
||||||
switch (ConsumedCap)
|
switch (ConsumedCap)
|
||||||
{
|
{
|
||||||
@ -163,7 +167,7 @@ namespace Capnp.Rpc
|
|||||||
~Proxy()
|
~Proxy()
|
||||||
{
|
{
|
||||||
#if DebugFinalizers
|
#if DebugFinalizers
|
||||||
Logger.LogWarning($"Caught orphaned Proxy, created from {CreatorMemberName} in {CreatorFilePath}, line {CreatorLineNumber}.");
|
Logger.LogWarning($"Caught orphaned Proxy, created from here: {CreatorStackTrace}.");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Dispose(false);
|
Dispose(false);
|
||||||
@ -230,9 +234,7 @@ namespace Capnp.Rpc
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if DebugFinalizers
|
#if DebugFinalizers
|
||||||
public string CreatorMemberName { get; set; }
|
string CreatorStackTrace { get; set; }
|
||||||
public string CreatorFilePath { get; set; }
|
|
||||||
public int CreatorLineNumber { get; set; }
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,16 +26,33 @@ namespace Capnp.Rpc
|
|||||||
// Value 0 has the special meaning of being in state C.
|
// Value 0 has the special meaning of being in state C.
|
||||||
long _refCount = 1;
|
long _refCount = 1;
|
||||||
|
|
||||||
#if DebugCapabilityLifecycle
|
#if DebugCapabilityLifecycle || DebugFinalizers
|
||||||
ILogger Logger { get; } = Logging.CreateLogger<RefCountingCapability>();
|
ILogger Logger { get; } = Logging.CreateLogger<RefCountingCapability>();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if DebugCapabilityLifecycle
|
||||||
string? _releasingMethodName;
|
string? _releasingMethodName;
|
||||||
string? _releasingFilePath;
|
string? _releasingFilePath;
|
||||||
int _releasingLineNumber;
|
int _releasingLineNumber;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if DebugFinalizers
|
||||||
|
string CreatorStackTrace { get; set; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public RefCountingCapability()
|
||||||
|
{
|
||||||
|
#if DebugFinalizers
|
||||||
|
CreatorStackTrace = Environment.StackTrace;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
~RefCountingCapability()
|
~RefCountingCapability()
|
||||||
{
|
{
|
||||||
|
#if DebugFinalizers
|
||||||
|
Logger.LogWarning($"Caught orphaned capability, created from here: {CreatorStackTrace}.");
|
||||||
|
#endif
|
||||||
|
|
||||||
Dispose(false);
|
Dispose(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +37,19 @@ namespace Capnp.Rpc
|
|||||||
WhenResolved = AwaitWhenResolved();
|
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)
|
async void ReAllowFinishWhenDone(Task task)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -64,7 +77,7 @@ namespace Capnp.Rpc
|
|||||||
{
|
{
|
||||||
lock (_question.ReentrancyBlocker)
|
lock (_question.ReentrancyBlocker)
|
||||||
{
|
{
|
||||||
if (!_question.IsTailCall && WhenResolved.IsCompleted)
|
if (!_question.IsTailCall && _question.StateFlags.HasFlag(PendingQuestion.State.Returned))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -96,8 +109,8 @@ namespace Capnp.Rpc
|
|||||||
{
|
{
|
||||||
lock (_question.ReentrancyBlocker)
|
lock (_question.ReentrancyBlocker)
|
||||||
{
|
{
|
||||||
if (_question.StateFlags.HasFlag(PendingQuestion.State.Returned) &&
|
if (!_question.StateFlags.HasFlag(PendingQuestion.State.TailCall) &&
|
||||||
!_question.StateFlags.HasFlag(PendingQuestion.State.TailCall))
|
_question.StateFlags.HasFlag(PendingQuestion.State.Returned))
|
||||||
{
|
{
|
||||||
if (ResolvedCap == null)
|
if (ResolvedCap == null)
|
||||||
{
|
{
|
||||||
@ -165,8 +178,9 @@ namespace Capnp.Rpc
|
|||||||
{
|
{
|
||||||
lock (_question.ReentrancyBlocker)
|
lock (_question.ReentrancyBlocker)
|
||||||
{
|
{
|
||||||
if (_question.StateFlags.HasFlag(PendingQuestion.State.Returned) &&
|
if ( _question.StateFlags.HasFlag(PendingQuestion.State.Returned) &&
|
||||||
_pendingCallsOnPromise == 0)
|
!_question.StateFlags.HasFlag(PendingQuestion.State.TailCall) &&
|
||||||
|
_pendingCallsOnPromise == 0)
|
||||||
{
|
{
|
||||||
if (ResolvedCap == null)
|
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)
|
lock (_question.ReentrancyBlocker)
|
||||||
{
|
{
|
||||||
@ -236,10 +250,12 @@ namespace Capnp.Rpc
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.ExportAsSenderPromise(endpoint, writer);
|
return this.ExportAsSenderPromise(endpoint, writer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async override void ReleaseRemotely()
|
protected async override void ReleaseRemotely()
|
||||||
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Capnp.Rpc
|
namespace Capnp.Rpc
|
||||||
{
|
{
|
||||||
|
#if false
|
||||||
|
|
||||||
class RemoteAnswerCapabilityDeprecated : RemoteResolvingCapability
|
class RemoteAnswerCapabilityDeprecated : RemoteResolvingCapability
|
||||||
{
|
{
|
||||||
@ -17,7 +18,6 @@ namespace Capnp.Rpc
|
|||||||
|
|
||||||
readonly PendingQuestion _question;
|
readonly PendingQuestion _question;
|
||||||
readonly MemberAccessPath _access;
|
readonly MemberAccessPath _access;
|
||||||
readonly Task<Proxy> _whenResolvedProxy;
|
|
||||||
ConsumedCapability? _resolvedCap;
|
ConsumedCapability? _resolvedCap;
|
||||||
|
|
||||||
public RemoteAnswerCapabilityDeprecated(PendingQuestion question, MemberAccessPath access): base(question.RpcEndpoint)
|
public RemoteAnswerCapabilityDeprecated(PendingQuestion question, MemberAccessPath access): base(question.RpcEndpoint)
|
||||||
@ -36,9 +36,6 @@ namespace Capnp.Rpc
|
|||||||
}
|
}
|
||||||
|
|
||||||
WhenResolved = AwaitWhenResolved();
|
WhenResolved = AwaitWhenResolved();
|
||||||
|
|
||||||
async Task<Proxy> AwaitProxy() => new Proxy(await WhenResolved);
|
|
||||||
_whenResolvedProxy = AwaitProxy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async void ReAllowFinishWhenDone(Task task)
|
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
|
protected override ConsumedCapability? ResolvedCap
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -258,8 +245,9 @@ namespace Capnp.Rpc
|
|||||||
|
|
||||||
protected async override void ReleaseRemotely()
|
protected async override void ReleaseRemotely()
|
||||||
{
|
{
|
||||||
try { using var _ = await _whenResolvedProxy; }
|
try { (await WhenResolved)?.Release(false); }
|
||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
@ -5,7 +5,7 @@ namespace Capnp.Rpc
|
|||||||
{
|
{
|
||||||
static class ResolvingCapabilityExtensions
|
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
|
where T: ConsumedCapability, IResolvingCapability
|
||||||
{
|
{
|
||||||
var vine = Vine.Create(cap);
|
var vine = Vine.Create(cap);
|
||||||
@ -16,7 +16,7 @@ namespace Capnp.Rpc
|
|||||||
|
|
||||||
if (first)
|
if (first)
|
||||||
{
|
{
|
||||||
endpoint.RequestPostAction(async () => {
|
return async () => {
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -28,14 +28,21 @@ namespace Capnp.Rpc
|
|||||||
endpoint.Resolve(preliminaryId, vine, () => throw exception);
|
endpoint.Resolve(preliminaryId, vine, () => throw exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<Proxy> AsProxyTask(this Task<IDisposable> task)
|
public static async Task<Proxy> AsProxyTask(this Task<IDisposable?> task)
|
||||||
{
|
{
|
||||||
var obj = await 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
|
Dismissed
|
||||||
}
|
}
|
||||||
|
|
||||||
static readonly ThreadLocal<Action?> _exportCapTablePostActions = new ThreadLocal<Action?>();
|
|
||||||
static readonly ThreadLocal<PendingQuestion?> _tailCall = new ThreadLocal<PendingQuestion?>();
|
static readonly ThreadLocal<PendingQuestion?> _tailCall = new ThreadLocal<PendingQuestion?>();
|
||||||
static readonly ThreadLocal<bool> _canDeferCalls = new ThreadLocal<bool>();
|
static readonly ThreadLocal<bool> _canDeferCalls = new ThreadLocal<bool>();
|
||||||
|
|
||||||
@ -386,7 +385,7 @@ namespace Capnp.Rpc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IProvidedCapability? cap;
|
Skeleton? callTargetCap;
|
||||||
PendingAnswer pendingAnswer;
|
PendingAnswer pendingAnswer;
|
||||||
bool releaseParamCaps = false;
|
bool releaseParamCaps = false;
|
||||||
|
|
||||||
@ -507,7 +506,7 @@ namespace Capnp.Rpc
|
|||||||
var inParams = req.Params.Content;
|
var inParams = req.Params.Content;
|
||||||
inParams.Caps = ImportCapTable(req.Params);
|
inParams.Caps = ImportCapTable(req.Params);
|
||||||
|
|
||||||
if (cap == null)
|
if (callTargetCap == null)
|
||||||
{
|
{
|
||||||
releaseParamCaps = true;
|
releaseParamCaps = true;
|
||||||
pendingAnswer = new PendingAnswer(
|
pendingAnswer = new PendingAnswer(
|
||||||
@ -519,7 +518,7 @@ namespace Capnp.Rpc
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var cts = new CancellationTokenSource();
|
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);
|
pendingAnswer = new PendingAnswer(callTask, cts);
|
||||||
}
|
}
|
||||||
catch (System.Exception exception)
|
catch (System.Exception exception)
|
||||||
@ -528,6 +527,10 @@ namespace Capnp.Rpc
|
|||||||
pendingAnswer = new PendingAnswer(
|
pendingAnswer = new PendingAnswer(
|
||||||
Task.FromException<AnswerOrCounterquestion>(exception), null);
|
Task.FromException<AnswerOrCounterquestion>(exception), null);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
callTargetCap.Relinquish();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AwaitAnswerAndReply();
|
AwaitAnswerAndReply();
|
||||||
@ -561,7 +564,8 @@ namespace Capnp.Rpc
|
|||||||
{
|
{
|
||||||
if (_exportTable.TryGetValue(req.Target.ImportedCap, out var rc))
|
if (_exportTable.TryGetValue(req.Target.ImportedCap, out var rc))
|
||||||
{
|
{
|
||||||
cap = rc.Cap;
|
callTargetCap = rc.Cap;
|
||||||
|
callTargetCap.Claim();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -594,7 +598,8 @@ namespace Capnp.Rpc
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var proxy = await t;
|
using var proxy = await t;
|
||||||
cap = proxy?.GetProvider();
|
callTargetCap = proxy?.GetProvider();
|
||||||
|
callTargetCap?.Claim();
|
||||||
CreateAnswerAwaitItAndReply();
|
CreateAnswerAwaitItAndReply();
|
||||||
}
|
}
|
||||||
catch (TaskCanceledException)
|
catch (TaskCanceledException)
|
||||||
@ -631,8 +636,9 @@ namespace Capnp.Rpc
|
|||||||
{
|
{
|
||||||
_canDeferCalls.Value = false;
|
_canDeferCalls.Value = false;
|
||||||
Impatient.AskingEndpoint = null;
|
Impatient.AskingEndpoint = null;
|
||||||
_tailCall.Value?.Send();
|
var call = _tailCall.Value;
|
||||||
_tailCall.Value = null;
|
_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
|
catch
|
||||||
{
|
{
|
||||||
@ -1105,7 +1103,7 @@ namespace Capnp.Rpc
|
|||||||
|
|
||||||
Tx(mb.Frame);
|
Tx(mb.Frame);
|
||||||
|
|
||||||
var main = new RemoteAnswerCapabilityDeprecated(
|
var main = new RemoteAnswerCapability(
|
||||||
pendingBootstrap,
|
pendingBootstrap,
|
||||||
MemberAccessPath.BootstrapAccess);
|
MemberAccessPath.BootstrapAccess);
|
||||||
|
|
||||||
@ -1340,7 +1338,9 @@ namespace Capnp.Rpc
|
|||||||
{
|
{
|
||||||
foreach (var capDesc in payload.CapTable)
|
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;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IRpcEndpoint.RequestPostAction(Action postAction)
|
|
||||||
{
|
|
||||||
_exportCapTablePostActions.Value += postAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExportCapTableAndSend(
|
void ExportCapTableAndSend(
|
||||||
SerializerState state,
|
SerializerState state,
|
||||||
Payload.WRITER payload)
|
Payload.WRITER payload)
|
||||||
{
|
{
|
||||||
Debug.Assert(_exportCapTablePostActions.Value == null);
|
|
||||||
_exportCapTablePostActions.Value = null;
|
|
||||||
|
|
||||||
payload.CapTable.Init(state.MsgBuilder!.Caps!.Count);
|
payload.CapTable.Init(state.MsgBuilder!.Caps!.Count);
|
||||||
|
|
||||||
|
Action? postAction = null;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
foreach (var cap in state.MsgBuilder.Caps)
|
foreach (var cap in state.MsgBuilder.Caps)
|
||||||
{
|
{
|
||||||
@ -1373,7 +1366,7 @@ namespace Capnp.Rpc
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cap.Export(this, capDesc);
|
postAction += cap.Export(this, capDesc);
|
||||||
cap.Release(false);
|
cap.Release(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1386,9 +1379,7 @@ namespace Capnp.Rpc
|
|||||||
// To avoid that situation, calls to "ReExportCapWhenResolved" are queued (and
|
// To avoid that situation, calls to "ReExportCapWhenResolved" are queued (and
|
||||||
// therefore deferred) to the postAction.
|
// therefore deferred) to the postAction.
|
||||||
|
|
||||||
var pa = _exportCapTablePostActions.Value;
|
postAction?.Invoke();
|
||||||
_exportCapTablePostActions.Value = null;
|
|
||||||
pa?.Invoke();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PendingQuestion IRpcEndpoint.BeginQuestion(ConsumedCapability target, SerializerState inParams)
|
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;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@ -17,8 +18,24 @@ namespace Capnp.Rpc
|
|||||||
Vine(ConsumedCapability consumedCap)
|
Vine(ConsumedCapability consumedCap)
|
||||||
{
|
{
|
||||||
Proxy = new Proxy(consumedCap ?? throw new ArgumentNullException(nameof(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)
|
internal override void Bind(object impl)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
|
@ -9,8 +9,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "capnpc-csharp", "capnpc-csh
|
|||||||
EndProject
|
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}"
|
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
|
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}"
|
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
|
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}"
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{9ED38750-F83F-4B10-B3A3-4FD6183F9E86}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
|
||||||
{B77AC567-E232-4072-85C3-8689566BF3D4}.Debug|Any CPU.Build.0 = 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
|
{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 PipeliningExtensionsClassFormat { get; }
|
||||||
public string ProxyClassFormat { get; }
|
public string ProxyClassFormat { get; }
|
||||||
public string SkeletonClassFormat { get; }
|
public string SkeletonClassFormat { get; }
|
||||||
|
public Name AwaitProxy { get; }
|
||||||
public bool NullableEnable { get; set; }
|
public bool NullableEnable { get; set; }
|
||||||
public GenNames(GeneratorOptions options)
|
public GenNames(GeneratorOptions options)
|
||||||
{
|
{
|
||||||
@ -110,6 +111,7 @@ namespace CapnpC.CSharp.Generator.CodeGen
|
|||||||
PipeliningExtensionsClassFormat = options.PipeliningExtensionsClassFormat;
|
PipeliningExtensionsClassFormat = options.PipeliningExtensionsClassFormat;
|
||||||
ProxyClassFormat = options.ProxyClassFormat;
|
ProxyClassFormat = options.ProxyClassFormat;
|
||||||
SkeletonClassFormat = options.SkeletonClassFormat;
|
SkeletonClassFormat = options.SkeletonClassFormat;
|
||||||
|
AwaitProxy = new Name(options.AwaitProxyName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Name MakeTypeName(TypeDefinition def, NameUsage usage = NameUsage.Default)
|
public Name MakeTypeName(TypeDefinition def, NameUsage usage = NameUsage.Default)
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
public string TaskParameterName { get; set; } = "task";
|
public string TaskParameterName { get; set; } = "task";
|
||||||
public string EagerMethodName { get; set; } = "Eager";
|
public string EagerMethodName { get; set; } = "Eager";
|
||||||
public string TypeIdFieldName { get; set; } = "typeId";
|
public string TypeIdFieldName { get; set; } = "typeId";
|
||||||
|
public string AwaitProxyName { get; set; } = "AwaitProxy";
|
||||||
public bool NullableEnableDefault { get; set; } = false;
|
public bool NullableEnableDefault { get; set; } = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -404,8 +404,11 @@ namespace CapnpC.CSharp.Generator.CodeGen
|
|||||||
Argument(SimpleLambdaExpression(
|
Argument(SimpleLambdaExpression(
|
||||||
Parameter(_names.DeserializerLocal.Identifier),
|
Parameter(_names.DeserializerLocal.Identifier),
|
||||||
Block(
|
Block(
|
||||||
MakeProxyCreateResult(method),
|
UsingStatement(
|
||||||
MakeProxyReturnResult(method)))));
|
Block(
|
||||||
|
MakeProxyCreateResult(method),
|
||||||
|
MakeProxyReturnResult(method)))
|
||||||
|
.WithExpression(_names.DeserializerLocal.IdentifierName)))));
|
||||||
|
|
||||||
bodyStmts.Add(ReturnStatement(pipelineAwareCall));
|
bodyStmts.Add(ReturnStatement(pipelineAwareCall));
|
||||||
}
|
}
|
||||||
@ -422,19 +425,18 @@ namespace CapnpC.CSharp.Generator.CodeGen
|
|||||||
call,
|
call,
|
||||||
IdentifierName(nameof(Capnp.Rpc.IPromisedAnswer.WhenReturned)));
|
IdentifierName(nameof(Capnp.Rpc.IPromisedAnswer.WhenReturned)));
|
||||||
|
|
||||||
var assignAwaited = LocalDeclarationStatement(
|
bodyStmts.Add(UsingStatement(
|
||||||
VariableDeclaration(
|
Block(
|
||||||
|
MakeProxyCreateResult(method),
|
||||||
|
MakeProxyReturnResult(method)))
|
||||||
|
.WithDeclaration(VariableDeclaration(
|
||||||
IdentifierName("var"))
|
IdentifierName("var"))
|
||||||
.AddVariables(
|
.AddVariables(
|
||||||
VariableDeclarator(
|
VariableDeclarator(
|
||||||
_names.DeserializerLocal.Identifier)
|
_names.DeserializerLocal.Identifier)
|
||||||
.WithInitializer(
|
.WithInitializer(
|
||||||
EqualsValueClause(
|
EqualsValueClause(
|
||||||
AwaitExpression(whenReturned)))));
|
AwaitExpression(whenReturned))))));
|
||||||
|
|
||||||
bodyStmts.Add(assignAwaited);
|
|
||||||
bodyStmts.Add(MakeProxyCreateResult(method));
|
|
||||||
bodyStmts.Add(MakeProxyReturnResult(method));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (method.GenericParameters.Count > 0)
|
if (method.GenericParameters.Count > 0)
|
||||||
@ -703,7 +705,10 @@ namespace CapnpC.CSharp.Generator.CodeGen
|
|||||||
Parameter(_names.CancellationTokenParameter.Identifier)
|
Parameter(_names.CancellationTokenParameter.Identifier)
|
||||||
.WithType(_names.Type<CancellationToken>(Nullability.NonNullable)))
|
.WithType(_names.Type<CancellationToken>(Nullability.NonNullable)))
|
||||||
.AddBodyStatements(
|
.AddBodyStatements(
|
||||||
MakeSkeletonMethodBody(method).ToArray());
|
UsingStatement(
|
||||||
|
Block(
|
||||||
|
MakeSkeletonMethodBody(method).ToArray()))
|
||||||
|
.WithExpression(_names.DeserializerLocal.IdentifierName));
|
||||||
|
|
||||||
if (method.Results.Count == 0)
|
if (method.Results.Count == 0)
|
||||||
{
|
{
|
||||||
@ -808,6 +813,59 @@ namespace CapnpC.CSharp.Generator.CodeGen
|
|||||||
|
|
||||||
readonly HashSet<(string, string)> _existingExtensionMethods = new HashSet<(string, string)>();
|
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)
|
public IEnumerable<MemberDeclarationSyntax> MakePipeliningSupport(TypeDefinition type)
|
||||||
{
|
{
|
||||||
foreach (var method in type.Methods)
|
foreach (var method in type.Methods)
|
||||||
@ -856,6 +914,7 @@ namespace CapnpC.CSharp.Generator.CodeGen
|
|||||||
.AddModifiers(This)
|
.AddModifiers(This)
|
||||||
.WithType(TransformReturnType(method)))
|
.WithType(TransformReturnType(method)))
|
||||||
.AddBodyStatements(
|
.AddBodyStatements(
|
||||||
|
MakeLocalAwaitProxyFunction(method, path),
|
||||||
ReturnStatement(
|
ReturnStatement(
|
||||||
CastExpression(
|
CastExpression(
|
||||||
capTypeSyntax,
|
capTypeSyntax,
|
||||||
@ -883,7 +942,10 @@ namespace CapnpC.CSharp.Generator.CodeGen
|
|||||||
IdentifierName(nameof(Capnp.Rpc.IPromisedAnswer.Access))))
|
IdentifierName(nameof(Capnp.Rpc.IPromisedAnswer.Access))))
|
||||||
.AddArgumentListArguments(
|
.AddArgumentListArguments(
|
||||||
Argument(
|
Argument(
|
||||||
accessPath.IdentifierName)))))));
|
accessPath.IdentifierName),
|
||||||
|
Argument(
|
||||||
|
InvocationExpression(
|
||||||
|
_names.AwaitProxy.IdentifierName))))))));
|
||||||
|
|
||||||
yield return pathDecl;
|
yield return pathDecl;
|
||||||
yield return methodDecl;
|
yield return methodDecl;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user