some refactoring. all tests green again

This commit is contained in:
Christian Köllner 2020-03-21 13:27:46 +01:00
parent f58464293b
commit e0d8f70cfc
49 changed files with 3418 additions and 2324 deletions

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net471</TargetFramework>
<TargetFrameworks>netcoreapp2.1;net471</TargetFrameworks>
<IsPackable>false</IsPackable>

View 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);
}
}
}

View File

@ -78,13 +78,13 @@ namespace Capnp.Net.Runtime.Tests
Assert.IsTrue(fcc.Wait(MediumNonDbgTimeout));
var cc = fcc.Result;
var pr = new Capnproto_test.Capnp.Test.TestInterface.Params_foo.READER(cc.InArgs);
var pr = new Capnproto_test.Capnp.Test.TestInterface.Params_Foo.READER(cc.InArgs);
Assert.AreEqual(123u, pr.I);
cc.ForwardToBob();
Assert.IsTrue(policy.Returns.ReceiveAsync().Wait(MediumNonDbgTimeout));
var rr = new Capnproto_test.Capnp.Test.TestInterface.Result_foo.READER(cc.OutArgs);
var rr = new Capnproto_test.Capnp.Test.TestInterface.Result_Foo.READER(cc.OutArgs);
Assert.AreEqual("foo", rr.X);
cc.ReturnToAlice();
@ -117,11 +117,11 @@ namespace Capnp.Net.Runtime.Tests
Assert.AreEqual(InterceptionState.RequestedFromAlice, cc.State);
var pr = new Capnproto_test.Capnp.Test.TestInterface.Params_foo.READER(cc.InArgs);
var pr = new Capnproto_test.Capnp.Test.TestInterface.Params_Foo.READER(cc.InArgs);
Assert.AreEqual(321u, pr.I);
Assert.AreEqual(false, pr.J);
var pw = cc.InArgs.Rewrap<Capnproto_test.Capnp.Test.TestInterface.Params_foo.WRITER>();
var pw = cc.InArgs.Rewrap<Capnproto_test.Capnp.Test.TestInterface.Params_Foo.WRITER>();
pw.I = 123u;
pw.J = true;
@ -133,12 +133,12 @@ namespace Capnp.Net.Runtime.Tests
Assert.IsTrue(cc.State == InterceptionState.ForwardedToBob || rx.IsCompleted);
Assert.IsTrue(rx.Wait(MediumNonDbgTimeout));
var rr = new Capnproto_test.Capnp.Test.TestInterface.Result_foo.READER(cc.OutArgs);
var rr = new Capnproto_test.Capnp.Test.TestInterface.Result_Foo.READER(cc.OutArgs);
Assert.AreEqual("foo", rr.X);
Assert.IsFalse(request1.IsCompleted);
var rw = ((DynamicSerializerState)cc.OutArgs).Rewrap<Capnproto_test.Capnp.Test.TestInterface.Result_foo.WRITER>();
var rw = ((DynamicSerializerState)cc.OutArgs).Rewrap<Capnproto_test.Capnp.Test.TestInterface.Result_Foo.WRITER>();
rw.X = "bar";
cc.OutArgs = rw;
@ -174,7 +174,7 @@ namespace Capnp.Net.Runtime.Tests
Assert.IsTrue(policy.Calls.TryReceive(out var cc));
Assert.IsFalse(request1.IsCompleted);
var rw = SerializerState.CreateForRpc<Capnproto_test.Capnp.Test.TestInterface.Result_foo.WRITER>();
var rw = SerializerState.CreateForRpc<Capnproto_test.Capnp.Test.TestInterface.Result_Foo.WRITER>();
rw.X = "bar";
cc.OutArgs = rw;

View File

@ -20,12 +20,14 @@ namespace Capnp.Net.Runtime.Tests
var tcs = new TaskCompletionSource<int>();
var impl = new TestPipelineImpl2(tcs.Task);
var bproxy = BareProxy.FromImpl(impl);
var proxy = bproxy.Cast<ITestPipeline>(true);
var cap = proxy.GetCap(0, null).OutBox_Cap();
var foo = cap.Foo(123, true);
tcs.SetResult(0);
Assert.IsTrue(foo.Wait(TestBase.MediumNonDbgTimeout));
Assert.AreEqual("bar", foo.Result);
using (var proxy = bproxy.Cast<ITestPipeline>(true))
using (var cap = proxy.GetCap(0, null).OutBox_Cap())
{
var foo = cap.Foo(123, true);
tcs.SetResult(0);
Assert.IsTrue(foo.Wait(TestBase.MediumNonDbgTimeout));
Assert.AreEqual("bar", foo.Result);
}
}
}
}

View File

@ -574,18 +574,17 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
class TestCallOrderImpl : ITestCallOrder
{
readonly object _lock = new object();
uint _counter;
ILogger Logger { get; } = Logging.CreateLogger<TestCallOrderImpl>();
public uint Count { get; set; }
public uint? CountToDispose { get; set; }
public void Dispose()
{
lock (_lock)
{
Assert.IsTrue(!CountToDispose.HasValue || Count == CountToDispose, "Must not dispose at this point");
{
Assert.IsTrue(!CountToDispose.HasValue || _counter == CountToDispose, $"Must not dispose at this point: {_counter} {Thread.CurrentThread.Name}");
}
}
@ -593,7 +592,18 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
{
lock (_lock)
{
return Task.FromResult(Count++);
return Task.FromResult(_counter++);
}
}
public uint Count
{
get
{
lock (_lock)
{
return _counter;
}
}
}
}
@ -749,7 +759,7 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
public Task<ITestInterface> GetHeld(CancellationToken cancellationToken_)
{
Interlocked.Increment(ref _counters.CallCount);
return Task.FromResult(ClientToHold);
return Task.FromResult(Proxy.Share(ClientToHold));
}
public Task<ITestMoreStuff> GetNull(CancellationToken cancellationToken_)

View File

@ -127,7 +127,7 @@ namespace Capnp.Net.Runtime.Tests
_.Call.Target.ImportedCap = bootCapId;
_.Call.InterfaceId = ((TypeIdAttribute)typeof(ITestInterface).GetCustomAttributes(typeof(TypeIdAttribute), false)[0]).Id;
_.Call.MethodId = 0;
_.Call.Params.Content.Rewrap<TestInterface.Params_foo.WRITER>();
_.Call.Params.Content.Rewrap<TestInterface.Params_Foo.WRITER>();
});
tester.ExpectAbort();
}
@ -154,7 +154,7 @@ namespace Capnp.Net.Runtime.Tests
_.Call.Target.ImportedCap = bootCapId;
_.Call.InterfaceId = ((TypeIdAttribute)typeof(ITestInterface).GetCustomAttributes(typeof(TypeIdAttribute), false)[0]).Id;
_.Call.MethodId = 0;
var wr = _.Call.Params.Content.Rewrap<TestInterface.Params_foo.WRITER>();
var wr = _.Call.Params.Content.Rewrap<TestInterface.Params_Foo.WRITER>();
wr.I = 123u;
wr.J = true;
});
@ -168,7 +168,7 @@ namespace Capnp.Net.Runtime.Tests
_.Call.Target.ImportedCap = bootCapId;
_.Call.InterfaceId = ((TypeIdAttribute)typeof(ITestInterface).GetCustomAttributes(typeof(TypeIdAttribute), false)[0]).Id;
_.Call.MethodId = 0;
_.Call.Params.Content.Rewrap<TestInterface.Params_foo.WRITER>();
_.Call.Params.Content.Rewrap<TestInterface.Params_Foo.WRITER>();
});
tester.ExpectAbort();
}
@ -565,7 +565,7 @@ namespace Capnp.Net.Runtime.Tests
_.Call.Target.ImportedCap = bootCapId;
_.Call.InterfaceId = new TestPipeline_Skeleton().InterfaceId;
_.Call.MethodId = 0;
var wr = _.Call.Params.Content.Rewrap<TestPipeline.Params_getCap.WRITER>();
var wr = _.Call.Params.Content.Rewrap<TestPipeline.Params_GetCap.WRITER>();
wr.InCap = null;
_.Call.Params.CapTable.Init(1);
_.Call.Params.CapTable[0].which = CapDescriptor.WHICH.ReceiverHosted;

View File

@ -642,7 +642,7 @@ namespace Capnp.Net.Runtime.Tests
});
}
[TestMethod, Timeout(10000)]
[TestMethod]
public void RetainAndReleaseClient()
{
using (var server = SetupServer())

View File

@ -12,6 +12,7 @@ using Microsoft.Extensions.Logging;
namespace Capnp.Net.Runtime.Tests
{
[TestClass]
[TestCategory("Coverage")]
public class TcpRpcPorted: TestBase
@ -19,99 +20,19 @@ namespace Capnp.Net.Runtime.Tests
[TestMethod]
public void Basic()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
client.WhenConnected.Wait();
var counters = new Counters();
server.Main = new TestInterfaceImpl(counters);
using (var main = client.GetMain<ITestInterface>())
{
var request1 = main.Foo(123, true, default);
var request3 = Assert.ThrowsExceptionAsync<RpcException>(() => main.Bar(default));
var s = new TestAllTypes();
Common.InitTestMessage(s);
var request2 = main.Baz(s, default);
Assert.IsTrue(request1.Wait(MediumNonDbgTimeout));
Assert.IsTrue(request2.Wait(MediumNonDbgTimeout));
Assert.IsTrue(request3.Wait(MediumNonDbgTimeout));
Assert.AreEqual("foo", request1.Result);
Assert.AreEqual(2, counters.CallCount);
}
}
NewLocalhostTcpTestbed().RunTest(Testsuite.Basic);
}
[TestMethod]
public void Pipeline()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
client.WhenConnected.Wait();
var counters = new Counters();
server.Main = new TestPipelineImpl(counters);
using (var main = client.GetMain<ITestPipeline>())
{
var chainedCallCount = new Counters();
var request = main.GetCap(234, new TestInterfaceImpl(chainedCallCount), default);
using (var outBox = request.OutBox_Cap())
{
var pipelineRequest = outBox.Foo(321, false, default);
var pipelineRequest2 = ((Proxy)outBox).Cast<ITestExtends>(false).Grault(default);
Assert.IsTrue(pipelineRequest.Wait(MediumNonDbgTimeout));
Assert.IsTrue(pipelineRequest2.Wait(MediumNonDbgTimeout));
Assert.AreEqual("bar", pipelineRequest.Result);
Common.CheckTestMessage(pipelineRequest2.Result);
Assert.AreEqual(3, counters.CallCount);
Assert.AreEqual(1, chainedCallCount.CallCount);
}
}
}
NewLocalhostTcpTestbed().RunTest(Testsuite.Pipeline);
}
[TestMethod]
public void Release()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
client.WhenConnected.Wait();
var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters);
using (var main = client.GetMain<ITestMoreStuff>())
{
var task1 = main.GetHandle(default);
var task2 = main.GetHandle(default);
Assert.IsTrue(task1.Wait(MediumNonDbgTimeout));
Assert.IsTrue(task2.Wait(MediumNonDbgTimeout));
Assert.AreEqual(2, counters.HandleCount);
task1.Result.Dispose();
Assert.IsTrue(SpinWait.SpinUntil(() => counters.HandleCount == 1, MediumNonDbgTimeout));
task2.Result.Dispose();
Assert.IsTrue(SpinWait.SpinUntil(() => counters.HandleCount == 0, MediumNonDbgTimeout));
}
}
NewLocalhostTcpTestbed().RunTest(Testsuite.Release);
}
[TestMethod]
@ -160,482 +81,63 @@ namespace Capnp.Net.Runtime.Tests
}
[TestMethod]
public void TestTailCall()
public void TailCall()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
client.WhenConnected.Wait();
var counters = new Counters();
server.Main = new TestTailCallerImpl(counters);
using (var main = client.GetMain<ITestTailCaller>())
{
var calleeCallCount = new Counters();
var callee = new TestTailCalleeImpl(calleeCallCount);
var promise = main.Foo(456, callee, default);
var dependentCall0 = promise.C().GetCallSequence(0, default);
Assert.IsTrue(promise.Wait(MediumNonDbgTimeout));
Assert.AreEqual(456u, promise.Result.I);
Assert.AreEqual("from TestTailCaller", promise.Result.T);
var dependentCall1 = promise.C().GetCallSequence(0, default);
var dependentCall2 = promise.C().GetCallSequence(0, default);
Assert.IsTrue(dependentCall0.Wait(MediumNonDbgTimeout));
Assert.IsTrue(dependentCall1.Wait(MediumNonDbgTimeout));
Assert.IsTrue(dependentCall2.Wait(MediumNonDbgTimeout));
Assert.AreEqual(1, counters.CallCount);
Assert.AreEqual(1, calleeCallCount.CallCount);
}
}
NewLocalhostTcpTestbed().RunTest(Testsuite.TailCall);
}
[TestMethod]
public void Cancelation()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
client.WhenConnected.Wait();
var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters);
using (var main = client.GetMain<ITestMoreStuff>())
{
var destroyed = new TaskCompletionSource<int>();
var impl = new TestInterfaceImpl(counters, destroyed);
var cts = new CancellationTokenSource();
var cancelTask = main.ExpectCancel(impl, cts.Token);
Assert.IsFalse(SpinWait.SpinUntil(() => destroyed.Task.IsCompleted || cancelTask.IsCompleted, ShortTimeout));
cts.Cancel();
Assert.IsTrue(destroyed.Task.Wait(MediumNonDbgTimeout));
Assert.IsFalse(cancelTask.IsCompleted && !cancelTask.IsCanceled);
}
}
NewLocalhostTcpTestbed().RunTest(Testsuite.Cancelation);
}
[TestMethod]
public void PromiseResolve()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
client.WhenConnected.Wait();
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
server.Main = impl;
using (var main = client.GetMain<ITestMoreStuff>())
{
var tcs = new TaskCompletionSource<ITestInterface>();
var eager = tcs.Task.Eager(true);
var request = main.CallFoo(eager, default);
var request2 = main.CallFooWhenResolved(eager, default);
var gcs = main.GetCallSequence(0, default);
Assert.IsTrue(gcs.Wait(MediumNonDbgTimeout));
Assert.AreEqual(2u, gcs.Result);
Assert.AreEqual(3, counters.CallCount);
var chainedCallCount = new Counters();
var tiimpl = new TestInterfaceImpl(chainedCallCount);
tcs.SetResult(tiimpl);
Assert.IsTrue(request.Wait(MediumNonDbgTimeout));
Assert.IsTrue(request2.Wait(MediumNonDbgTimeout));
Assert.AreEqual("bar", request.Result);
Assert.AreEqual("bar", request2.Result);
Assert.AreEqual(3, counters.CallCount);
Assert.AreEqual(2, chainedCallCount.CallCount);
}
}
NewLocalhostTcpTestbed().RunTest(Testsuite.PromiseResolve);
}
[TestMethod]
public void RetainAndRelease()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
client.WhenConnected.Wait();
var destructionPromise = new TaskCompletionSource<int>();
var destructionTask = destructionPromise.Task;
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
server.Main = impl;
using (var main = client.GetMain<ITestMoreStuff>())
{
var holdTask = main.Hold(new TestInterfaceImpl(new Counters(), destructionPromise), default);
Assert.IsTrue(holdTask.Wait(MediumNonDbgTimeout));
var cstask = main.GetCallSequence(0, default);
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
Assert.AreEqual(1u, cstask.Result);
Assert.IsFalse(destructionTask.IsCompleted);
var htask = main.CallHeld(default);
Assert.IsTrue(htask.Wait(MediumNonDbgTimeout));
Assert.AreEqual("bar", htask.Result);
var gtask = main.GetHeld(default);
Assert.IsTrue(gtask.Wait(MediumNonDbgTimeout));
// We can get the cap back from it.
using (var cap = gtask.Result)
{
// Wait for balanced state
WaitClientServerIdle(server, client);
// And call it, without any network communications.
long oldSendCount = client.SendCount;
var ftask = cap.Foo(123, true, default);
Assert.IsTrue(ftask.Wait(MediumNonDbgTimeout));
Assert.AreEqual("foo", ftask.Result);
Assert.AreEqual(oldSendCount, client.SendCount);
// We can send another copy of the same cap to another method, and it works.
var ctask = main.CallFoo(cap, default);
Assert.IsTrue(ctask.Wait(MediumNonDbgTimeout));
Assert.AreEqual("bar", ctask.Result);
// Give some time to settle.
cstask = main.GetCallSequence(0, default);
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
Assert.AreEqual(5u, cstask.Result);
cstask = main.GetCallSequence(0, default);
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
Assert.AreEqual(6u, cstask.Result);
cstask = main.GetCallSequence(0, default);
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
Assert.AreEqual(7u, cstask.Result);
// Can't be destroyed, we haven't released it.
Assert.IsFalse(destructionTask.IsCompleted);
}
// In deviation from original test, we have null the held capability on the main interface.
// This is because the main interface is the bootstrap capability and, as such, won't be disposed
// after disconnect.
var holdNullTask = main.Hold(null, default);
Assert.IsTrue(holdNullTask.Wait(MediumNonDbgTimeout));
}
Assert.IsTrue(destructionTask.Wait(MediumNonDbgTimeout));
}
NewLocalhostTcpTestbed().RunTest(Testsuite.RetainAndRelease);
}
[TestMethod]
public void Cancel()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
client.WhenConnected.Wait();
var destructionPromise = new TaskCompletionSource<int>();
var destructionTask = destructionPromise.Task;
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
server.Main = impl;
using (var main = client.GetMain<ITestMoreStuff>())
using (var cts = new CancellationTokenSource())
{
var ntask = main.NeverReturn(new TestInterfaceImpl(counters, destructionPromise), cts.Token);
// Allow some time to settle.
var cstask = main.GetCallSequence(0, default);
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
Assert.AreEqual(1u, cstask.Result);
cstask = main.GetCallSequence(0, default);
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
Assert.AreEqual(2u, cstask.Result);
// The cap shouldn't have been destroyed yet because the call never returned.
Assert.IsFalse(destructionTask.IsCompleted);
// There will be no automatic cancellation just because "ntask" goes of of scope or
// because the Proxy is disposed. Even ntask.Dispose() would not cancel the request.
// In .NET this needs to be done explicitly.
cts.Cancel();
}
// Now the cap should be released.
Assert.IsTrue(destructionTask.Wait(MediumNonDbgTimeout));
}
NewLocalhostTcpTestbed().RunTest(Testsuite.Cancel);
}
[TestMethod]
public void SendTwice()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
client.WhenConnected.Wait();
var destructionPromise = new TaskCompletionSource<int>();
var destructionTask = destructionPromise.Task;
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
server.Main = impl;
using (var main = client.GetMain<ITestMoreStuff>())
{
var cap = new TestInterfaceImpl(new Counters(), destructionPromise);
Task<string> ftask1, ftask2;
using (Skeleton.Claim(cap))
{
var ftask = main.CallFoo(cap, default);
Assert.IsTrue(ftask.Wait(MediumNonDbgTimeout));
Assert.AreEqual("bar", ftask.Result);
var ctask = main.GetCallSequence(0, default);
Assert.IsTrue(ctask.Wait(MediumNonDbgTimeout));
Assert.AreEqual(1u, ctask.Result);
ftask1 = main.CallFoo(cap, default);
ftask2 = main.CallFoo(cap, default);
}
Assert.IsTrue(ftask1.Wait(MediumNonDbgTimeout));
Assert.AreEqual("bar", ftask1.Result);
Assert.IsTrue(ftask2.Wait(MediumNonDbgTimeout));
Assert.AreEqual("bar", ftask2.Result);
// Now the cap should be released.
Assert.IsTrue(destructionTask.Wait(MediumNonDbgTimeout));
}
}
NewLocalhostTcpTestbed().RunTest(Testsuite.SendTwice);
}
[TestMethod]
public void Embargo()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
client.WhenConnected.Wait();
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
server.Main = impl;
using (var main = client.GetMain<ITestMoreStuff>())
{
var resolving = main as IResolvingCapability;
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout));
var cap = new TestCallOrderImpl();
cap.CountToDispose = 6;
var earlyCall = main.GetCallSequence(0, default);
var echo = main.Echo(cap, default);
using (var pipeline = echo.Eager())
{
var call0 = pipeline.GetCallSequence(0, default);
var call1 = pipeline.GetCallSequence(1, default);
Assert.IsTrue(earlyCall.Wait(MediumNonDbgTimeout));
impl.EnableEcho();
var call2 = pipeline.GetCallSequence(2, default);
Assert.IsTrue(echo.Wait(MediumNonDbgTimeout));
using (var resolved = echo.Result)
{
var call3 = pipeline.GetCallSequence(3, default);
var call4 = pipeline.GetCallSequence(4, default);
var call5 = pipeline.GetCallSequence(5, default);
try
{
bool flag = call0.Wait(MediumNonDbgTimeout);
Assert.IsTrue(flag);
}
catch (AggregateException exception) when (exception.InnerException is RpcException rpcException && rpcException.Message == "Cannot access a disposed object.")
{
Console.WriteLine($"Oops, object disposed. Counter = {cap.Count}, tx count = {client.SendCount}, rx count = {client.RecvCount}");
throw;
}
Assert.IsTrue(call1.Wait(MediumNonDbgTimeout));
Assert.IsTrue(call2.Wait(MediumNonDbgTimeout));
Assert.IsTrue(call3.Wait(MediumNonDbgTimeout));
Assert.IsTrue(call4.Wait(MediumNonDbgTimeout));
Assert.IsTrue(call5.Wait(MediumNonDbgTimeout));
Assert.AreEqual(0u, call0.Result);
Assert.AreEqual(1u, call1.Result);
Assert.AreEqual(2u, call2.Result);
Assert.AreEqual(3u, call3.Result);
Assert.AreEqual(4u, call4.Result);
Assert.AreEqual(5u, call5.Result);
}
}
}
}
NewLocalhostTcpTestbed().RunTest(Testsuite.Embargo);
}
[TestMethod]
public void EmbargoError()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
client.WhenConnected.Wait();
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
server.Main = impl;
using (var main = client.GetMain<ITestMoreStuff>())
{
var resolving = main as IResolvingCapability;
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout));
var cap = new TaskCompletionSource<ITestCallOrder>();
var earlyCall = main.GetCallSequence(0, default);
var echo = main.Echo(cap.Task.Eager(true), default);
var pipeline = echo.Eager();
var call0 = pipeline.GetCallSequence(0, default);
var call1 = pipeline.GetCallSequence(1, default);
Assert.IsTrue(earlyCall.Wait(MediumNonDbgTimeout));
impl.EnableEcho();
var call2 = pipeline.GetCallSequence(2, default);
Assert.IsTrue(echo.Wait(MediumNonDbgTimeout));
var resolved = echo.Result;
var call3 = pipeline.GetCallSequence(3, default);
var call4 = pipeline.GetCallSequence(4, default);
var call5 = pipeline.GetCallSequence(5, default);
cap.SetException(new InvalidOperationException("I'm annoying"));
ExpectPromiseThrows(call0);
ExpectPromiseThrows(call1);
ExpectPromiseThrows(call2);
ExpectPromiseThrows(call3);
ExpectPromiseThrows(call4);
ExpectPromiseThrows(call5);
// Verify that we're still connected (there were no protocol errors).
Assert.IsTrue(main.GetCallSequence(1, default).Wait(MediumNonDbgTimeout));
}
}
NewLocalhostTcpTestbed().RunTest(Testsuite.EmbargoError);
}
[TestMethod]
public void EmbargoNull()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
client.WhenConnected.Wait();
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
server.Main = impl;
using (var main = client.GetMain<ITestMoreStuff>())
{
var resolving = main as IResolvingCapability;
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout));
var promise = main.GetNull(default);
var cap = promise.Eager();
var call0 = cap.GetCallSequence(0, default);
Assert.IsTrue(promise.Wait(MediumNonDbgTimeout));
var call1 = cap.GetCallSequence(1, default);
ExpectPromiseThrows(call0);
ExpectPromiseThrows(call1);
// Verify that we're still connected (there were no protocol errors).
Assert.IsTrue(main.GetCallSequence(1, default).Wait(MediumNonDbgTimeout));
}
}
NewLocalhostTcpTestbed().RunTest(Testsuite.EmbargoNull);
}
[TestMethod]
public void CallBrokenPromise()
{
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
client.WhenConnected.Wait();
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
server.Main = impl;
using (var main = client.GetMain<ITestMoreStuff>())
{
var resolving = main as IResolvingCapability;
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout));
var tcs = new TaskCompletionSource<ITestInterface>();
var req = main.Hold(tcs.Task.Eager(true), default);
Assert.IsTrue(req.Wait(MediumNonDbgTimeout));
var req2 = main.CallHeld(default);
Assert.IsFalse(req2.Wait(ShortTimeout));
tcs.SetException(new InvalidOperationException("I'm a promise-breaker!"));
ExpectPromiseThrows(req2);
// Verify that we're still connected (there were no protocol errors).
Assert.IsTrue(main.GetCallSequence(1, default).Wait(MediumNonDbgTimeout));
}
}
NewLocalhostTcpTestbed().RunTest(Testsuite.CallBrokenPromise);
}
}
}

View File

@ -6,6 +6,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
@ -60,7 +61,11 @@ namespace Capnp.Net.Runtime.Tests
{
var t = new TcpRpcPorted();
Repeat(100, t.Embargo);
}
[TestMethod]
public void EmbargoServer()
{
var t2 = new TcpRpcInterop();
Repeat(100, t2.EmbargoServer);
}
@ -94,35 +99,45 @@ namespace Capnp.Net.Runtime.Tests
[TestMethod]
public void ScatteredTransfer()
{
using (var server = new TcpRpcServer(IPAddress.Any, TcpPort))
using (var client = new TcpRpcClient())
for (int retry = 0; retry < 10; retry++)
{
server.InjectMidlayer(s => new ScatteringStream(s, 7));
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>())
try
{
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);
var request3 = Assert.ThrowsExceptionAsync<RpcException>(() => main.Bar(default));
var s = new TestAllTypes();
Common.InitTestMessage(s);
var request2 = main.Baz(s, default);
server.InjectMidlayer(s => new ScatteringStream(s, 7));
client.InjectMidlayer(s => new ScatteringStream(s, 10));
client.Connect("localhost", TcpPort);
client.WhenConnected.Wait();
Assert.IsTrue(request1.Wait(MediumNonDbgTimeout));
Assert.IsTrue(request2.Wait(MediumNonDbgTimeout));
Assert.IsTrue(request3.Wait(MediumNonDbgTimeout));
var counters = new Counters();
server.Main = new TestInterfaceImpl(counters);
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.AreEqual(2, counters.CallCount);
counters.CallCount = 0;
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);
counters.CallCount = 0;
}
}
}
return;
}
catch (SocketException)
{
IncrementTcpPort();
}
}
}

View File

@ -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));
}
}
}

View 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);
}
}
}
}

View 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());
}
}
}

View 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));
}
}
}

View File

@ -29,7 +29,7 @@
</PropertyGroup>
<PropertyGroup>
<DefineConstants>DebugCapabilityLifecycle</DefineConstants>
<DefineConstants>DebugFinalizers</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.0|AnyCPU'">

View File

@ -160,6 +160,7 @@ namespace Capnp
/// <returns>The domain object instance. Nullability note: The returned reference may be null if
/// <paramref name="state"/> represents the nil object.</returns>
/// <exception cref="ArgumentException">Cannot construct object of type <typeparamref name="T"/></exception>
/// <remarks>Note that capability ownership is moved to the domain object</remarks>
public static T? Create<T>(DeserializerState state)
where T: class
{

View File

@ -9,7 +9,7 @@ namespace Capnp
/// Although it is public, you should not use it directly. Instead, use the reader, writer, and domain class adapters which are produced
/// by the code generator.
/// </summary>
public struct DeserializerState: IStructDeserializer
public struct DeserializerState: IStructDeserializer, IDisposable
{
/// <summary>
/// A wire message is essentially a collection of memory blocks.
@ -50,7 +50,7 @@ namespace Capnp
/// </summary>
public IList<Rpc.ConsumedCapability?>? Caps { get; set; }
/// <summary>
/// Current segment (essentially Segments[CurrentSegmentIndex]
/// Current segment (essentially Segments[CurrentSegmentIndex])
/// </summary>
public ReadOnlySpan<ulong> CurrentSegment => Segments != null ? Segments[(int)CurrentSegmentIndex].Span : default;
@ -106,11 +106,6 @@ namespace Capnp
case ObjectKind.ListOfStructs:
case ObjectKind.Nil:
case ObjectKind.Struct:
if (state.Caps != null)
{
foreach (var cap in state.Caps)
cap?.Release(true);
}
return new DeserializerState(state.Allocator!.Segments)
{
CurrentSegmentIndex = state.SegmentIndex,
@ -646,13 +641,10 @@ namespace Capnp
/// <exception cref="IndexOutOfRangeException">negative index</exception>
/// <exception cref="DeserializationException">state does not represent a struct, invalid pointer,
/// non-capability pointer, traversal limit exceeded</exception>
public T? ReadCap<T>(int index,
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0) where T: class
public T? ReadCap<T>(int index) where T: class
{
var cap = StructReadRawCap(index);
return Rpc.CapabilityReflection.CreateProxy<T>(cap, memberName, sourceFilePath, sourceLineNumber) as T;
return Rpc.CapabilityReflection.CreateProxy<T>(cap) as T;
}
/// <summary>
@ -690,5 +682,16 @@ namespace Capnp
return (Rpc.CapabilityReflection.CreateProxy<T>(Caps[(int)CapabilityIndex]) as T)!;
}
public void Dispose()
{
if (Caps != null)
{
foreach (var cap in Caps)
{
cap?.Release(false);
}
}
}
}
}

View File

@ -44,8 +44,6 @@ namespace Capnp
if (state.Caps != null)
{
mb.InitCapTable();
foreach (var cap in state.Caps)
cap?.AddRef();
}
var sstate = mb.CreateObject<DynamicSerializerState>();
Reserializing.DeepCopy(state, sstate);

View File

@ -262,25 +262,11 @@ namespace Capnp.Rpc
/// <exception cref="System.Reflection.TargetInvocationException">Problem with instatiating the Proxy (constructor threw exception).</exception>
/// <exception cref="MemberAccessException">Caller does not have permission to invoke the Proxy constructor.</exception>
/// <exception cref="TypeLoadException">Problem with building the Proxy type, or problem with loading some dependent class.</exception>
public static Proxy CreateProxy<TInterface>(ConsumedCapability? cap,
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
public static Proxy CreateProxy<TInterface>(ConsumedCapability? cap)
{
var factory = GetProxyFactory(typeof(TInterface));
var proxy = factory.NewProxy();
proxy.Bind(cap);
#if DebugFinalizers
proxy.CreatorMemberName = memberName;
proxy.CreatorFilePath = sourceFilePath;
proxy.CreatorLineNumber = sourceLineNumber;
if (cap != null)
{
cap.CreatorFilePath = proxy.CreatorFilePath;
cap.CreatorLineNumber = proxy.CreatorLineNumber;
cap.CreatorMemberName = proxy.CreatorMemberName;
}
#endif
return proxy;
}
}

View File

@ -1,4 +1,6 @@
namespace Capnp.Rpc
using System;
namespace Capnp.Rpc
{
/// <summary>
/// Base class for a low-level capability at consumer side. It is created by the <see cref="RpcEngine"/>. An application does not directly interact with it
@ -13,7 +15,7 @@
/// which usually also means to remove it from the remote peer's export table.
/// </summary>
protected abstract void ReleaseRemotely();
internal abstract void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer);
internal abstract Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer);
internal abstract void Freeze(out IRpcEndpoint? boundEndpoint);
internal abstract void Unfreeze();
@ -23,11 +25,5 @@
[System.Runtime.CompilerServices.CallerMemberName] string methodName = "",
[System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0);
#if DebugFinalizers
public string CreatorMemberName { get; set; }
public string CreatorFilePath { get; set; }
public int CreatorLineNumber { get; set; }
#endif
}
}

View File

@ -24,6 +24,6 @@ namespace Capnp.Rpc
/// <returns>Pipelined low-level capability</returns>
ConsumedCapability? Access(MemberAccessPath access);
ConsumedCapability? Access(MemberAccessPath access, Task<IDisposable> proxyTask);
ConsumedCapability? Access(MemberAccessPath access, Task<IDisposable?> proxyTask);
}
}

View File

@ -8,7 +8,6 @@ namespace Capnp.Rpc
PendingQuestion BeginQuestion(ConsumedCapability target, SerializerState inParams);
void SendQuestion(SerializerState inParams, Payload.WRITER payload);
uint AllocateExport(Skeleton providedCapability, out bool first);
void RequestPostAction(Action postAction);
void Finish(uint questionId);
void ReleaseImport(uint importId);
void Resolve(uint preliminaryId, Skeleton preliminaryCap, Func<ConsumedCapability> resolvedCapGetter);

View File

@ -94,7 +94,20 @@ namespace Capnp.Rpc
static async Task<Proxy> AwaitProxy<T>(Task<T> task) where T: class
{
var item = await task;
T item;
try
{
item = await task;
}
catch (TaskCanceledException exception)
{
return new Proxy(LazyCapability.CreateCanceledCap(exception.CancellationToken));
}
catch (System.Exception exception)
{
return new Proxy(LazyCapability.CreateBrokenCap(exception.Message));
}
switch (item)
{
@ -124,14 +137,11 @@ namespace Capnp.Rpc
/// <exception cref="InvalidCapabilityInterfaceException"><typeparamref name="TInterface"/> did not
/// quality as capability interface.</exception>
[Obsolete("Call Eager<TInterface>(task, true) instead")]
public static TInterface PseudoEager<TInterface>(this Task<TInterface> task,
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
public static TInterface PseudoEager<TInterface>(this Task<TInterface> task)
where TInterface : class
{
var lazyCap = new LazyCapability(AwaitProxy(task));
return (CapabilityReflection.CreateProxy<TInterface>(lazyCap, memberName, sourceFilePath, sourceLineNumber) as TInterface)!;
return (CapabilityReflection.CreateProxy<TInterface>(lazyCap) as TInterface)!;
}
static readonly MemberAccessPath Path_OneAndOnly = new MemberAccessPath(0U);
@ -157,7 +167,7 @@ namespace Capnp.Rpc
/// <exception cref="MemberAccessException">Caller does not have permission to invoke the Proxy constructor.</exception>
/// <exception cref="TypeLoadException">Problem with building the Proxy type, or problem with loading some dependent class.</exception>
public static TInterface Eager<TInterface>(this Task<TInterface> task, bool allowNoPipeliningFallback = false)
where TInterface : class
where TInterface : class, IDisposable
{
var answer = TryGetAnswer(task);
if (answer == null)
@ -172,7 +182,12 @@ namespace Capnp.Rpc
}
else
{
return (CapabilityReflection.CreateProxy<TInterface>(answer.Access(Path_OneAndOnly)) as TInterface)!;
async Task<IDisposable?> AsDisposableTask()
{
return await task;
}
return (CapabilityReflection.CreateProxy<TInterface>(answer.Access(Path_OneAndOnly, AsDisposableTask())) as TInterface)!;
}
}

View File

@ -1,4 +1,6 @@
namespace Capnp.Rpc
using System;
namespace Capnp.Rpc
{
/// <summary>
/// Low-level capability which as imported from a remote peer.
@ -35,7 +37,7 @@
{
}
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER capDesc)
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER capDesc)
{
if (endpoint == _ep)
{
@ -47,6 +49,7 @@
capDesc.which = CapDescriptor.WHICH.SenderHosted;
capDesc.SenderHosted = endpoint.AllocateExport(Vine.Create(this), out var _);
}
return null;
}
}
}

View File

@ -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();

View File

@ -1,4 +1,6 @@
namespace Capnp.Rpc.Interception
using System;
namespace Capnp.Rpc.Interception
{
class CensorCapability : RefCountingCapability
{
@ -26,10 +28,11 @@
return cc.Answer;
}
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
{
writer.which = CapDescriptor.WHICH.SenderHosted;
writer.SenderHosted = endpoint.AllocateExport(MyVine, out bool _);
return null;
}
internal override void Freeze(out IRpcEndpoint? boundEndpoint)

View File

@ -8,19 +8,15 @@ namespace Capnp.Rpc
{
public static LazyCapability CreateBrokenCap(string message)
{
var cap = new LazyCapability(Task.FromException<ConsumedCapability?>(new RpcException(message)));
cap.AddRef(); // Instance shall be persistent
return cap;
return new LazyCapability(Task.FromException<ConsumedCapability?>(new RpcException(message)));
}
public static LazyCapability CreateCanceledCap(CancellationToken token)
{
var cap = new LazyCapability(Task.FromCanceled<ConsumedCapability?>(token));
cap.AddRef(); // Instance shall be persistent
return cap;
return new LazyCapability(Task.FromCanceled<ConsumedCapability?>(token));
}
public static LazyCapability Null { get; } = CreateBrokenCap("Null capability");
public static LazyCapability Null => CreateBrokenCap("Null capability");
readonly Task<Proxy>? _proxyTask;
@ -63,16 +59,17 @@ namespace Capnp.Rpc
{
}
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
{
if (WhenResolved.ReplacementTaskIsCompletedSuccessfully())
{
using var proxy = new Proxy(WhenResolved.Result);
proxy.Export(endpoint, writer);
return null;
}
else
{
this.ExportAsSenderPromise(endpoint, writer);
return this.ExportAsSenderPromise(endpoint, writer);
}
}

View File

@ -30,7 +30,7 @@ namespace Capnp.Rpc
return new LocalAnswerCapabilityDeprecated(WhenReturned, access);
}
public ConsumedCapability Access(MemberAccessPath _, Task<IDisposable> task)
public ConsumedCapability Access(MemberAccessPath _, Task<IDisposable?> task)
{
return new LocalAnswerCapability(task.AsProxyTask());
}

View File

@ -29,7 +29,7 @@ namespace Capnp.Rpc
public Task<ConsumedCapability?> WhenResolved { get; private set; }
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
{
if (_whenResolvedProxy.IsCompleted)
{
@ -39,12 +39,14 @@ namespace Capnp.Rpc
}
catch (AggregateException exception)
{
throw exception.InnerException;
throw exception.InnerException!;
}
return null;
}
else
{
this.ExportAsSenderPromise(endpoint, writer);
return this.ExportAsSenderPromise(endpoint, writer);
}
}

View File

@ -30,7 +30,7 @@ namespace Capnp.Rpc
public Task<ConsumedCapability?> WhenResolved { get; private set; }
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
{
if (_answer.IsCompleted)
{
@ -46,10 +46,11 @@ namespace Capnp.Rpc
using var proxy = new Proxy(_access.Eval(result));
proxy.Export(endpoint, writer);
return null;
}
else
{
this.ExportAsSenderPromise(endpoint, writer);
return this.ExportAsSenderPromise(endpoint, writer);
}
}

View File

@ -54,10 +54,11 @@ namespace Capnp.Rpc
return new LocalAnswer(cts, AwaitAnswer(call));
}
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER capDesc)
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER capDesc)
{
capDesc.which = CapDescriptor.WHICH.SenderHosted;
capDesc.SenderHosted = endpoint.AllocateExport(ProvidedCap, out bool _);
return null;
}
internal override void Freeze(out IRpcEndpoint? boundEndpoint)

View File

@ -132,7 +132,7 @@ namespace Capnp.Rpc
{
if (state.Kind == ObjectKind.Nil)
{
return default(DeserializerState);
return default;
}
if (state.Kind != ObjectKind.Struct)

View File

@ -24,11 +24,35 @@ namespace Capnp.Rpc
_cts = cts;
_cancelCompleter = new TaskCompletionSource<AnswerOrCounterquestion>();
_answerTask = CancelableAwaitWhenReady();
Chain(async t =>
{
var aorcq = default(AnswerOrCounterquestion);
try
{
aorcq = await t;
}
catch
{
}
if (aorcq.Answer != null)
{
if (aorcq.Answer.Caps != null)
{
foreach (var cap in aorcq.Answer.Caps)
{
cap?.AddRef();
}
}
}
});
}
public CancellationToken CancellationToken => _cts?.Token ?? CancellationToken.None;
public IReadOnlyList<CapDescriptor.WRITER> CapTable { get; set; }
public IReadOnlyList<CapDescriptor.WRITER>? CapTable { get; set; }
public void Cancel()
{
@ -99,7 +123,7 @@ namespace Capnp.Rpc
else
{
var path = MemberAccessPath.Deserialize(rd);
var cap = new RemoteAnswerCapabilityDeprecated(aorcq.Counterquestion!, path);
var cap = new RemoteAnswerCapability(aorcq.Counterquestion!, path);
return new Proxy(cap);
}
}
@ -111,6 +135,31 @@ namespace Capnp.Rpc
public void Dispose()
{
_cts?.Dispose();
Chain(async t =>
{
AnswerOrCounterquestion aorcq;
try
{
aorcq = await t;
}
catch
{
return;
}
if (aorcq.Answer != null)
{
if (aorcq.Answer.Caps != null)
{
foreach (var cap in aorcq.Answer.Caps)
{
cap?.Release(false);
}
}
}
});
}
}
}

View File

@ -126,17 +126,17 @@ namespace Capnp.Rpc
lock (ReentrancyBlocker)
{
SetReturned();
}
if (StateFlags.HasFlag(State.TailCall))
{
_tcs.TrySetException(new RpcException(ReturnDespiteTailCallMessage));
}
else
{
if (!_tcs.TrySetResult(results))
if (StateFlags.HasFlag(State.TailCall))
{
ReleaseOutCaps(results);
_tcs.TrySetException(new RpcException(ReturnDespiteTailCallMessage));
}
else
{
if (!_tcs.TrySetResult(results))
{
ReleaseOutCaps(results);
}
}
}
}
@ -146,15 +146,15 @@ namespace Capnp.Rpc
lock (ReentrancyBlocker)
{
SetReturned();
}
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."));
}
else
{
_tcs.TrySetResult(default);
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."));
}
else
{
_tcs.TrySetResult(default);
}
}
}
@ -163,9 +163,9 @@ namespace Capnp.Rpc
lock (ReentrancyBlocker)
{
SetReturned();
}
_tcs.TrySetException(new RpcException(exception.Reason));
_tcs.TrySetException(new RpcException(exception.Reason));
}
}
internal void OnException(System.Exception exception)
@ -173,9 +173,9 @@ namespace Capnp.Rpc
lock (ReentrancyBlocker)
{
SetReturned();
}
_tcs.TrySetException(exception);
_tcs.TrySetException(exception);
}
}
internal void OnCanceled()
@ -183,9 +183,9 @@ namespace Capnp.Rpc
lock (ReentrancyBlocker)
{
SetReturned();
}
_tcs.TrySetCanceled();
_tcs.TrySetCanceled();
}
}
void DeleteMyQuestion()
@ -234,6 +234,7 @@ namespace Capnp.Rpc
/// <param name="access">Access path</param>
/// <returns>Low-level capability</returns>
/// <exception cref="DeserializationException">The referenced member does not exist or does not resolve to a capability pointer.</exception>
[Obsolete("Please re-generate. Replaced by Access(MemberAccessPath access, Task<IDisposable> task)")]
public ConsumedCapability? Access(MemberAccessPath access)
{
lock (ReentrancyBlocker)
@ -252,7 +253,7 @@ namespace Capnp.Rpc
}
else
{
return new RemoteAnswerCapabilityDeprecated(this, access);
return new RemoteAnswerCapability(this, access);
}
}
}
@ -261,32 +262,14 @@ namespace Capnp.Rpc
/// Refer to a (possibly nested) member of this question's (possibly future) result and return
/// it as a capability.
/// </summary>
/// <param name="access">Access path</param>
/// <param name="task">promises the cap whose ownership is transferred to this object</param>
/// <returns>Low-level capability</returns>
/// <exception cref="DeserializationException">The referenced member does not exist or does not resolve to a capability pointer.</exception>
public ConsumedCapability? Access(MemberAccessPath access, Task<IDisposable> task)
public ConsumedCapability? Access(MemberAccessPath access, Task<IDisposable?> task)
{
var proxyTask = task.AsProxyTask();
lock (ReentrancyBlocker)
{
if (proxyTask.IsCompleted && !StateFlags.HasFlag(State.TailCall))
{
try
{
using var proxy = proxyTask.Result;
return proxy.ConsumedCap;
}
catch (AggregateException exception)
{
throw exception.InnerException!;
}
}
else
{
return new RemoteAnswerCapabilityDeprecated(this, access);
}
}
return new RemoteAnswerCapability(this, access, proxyTask);
}
static void ReleaseCaps(ConsumedCapability? target, SerializerState? inParams)

View File

@ -78,7 +78,7 @@ namespace Capnp.Rpc
}
}
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
{
lock (_reentrancyBlocker)
{
@ -98,7 +98,7 @@ namespace Capnp.Rpc
Debug.Assert(!_released);
++_pendingCallsOnPromise;
_ep.RequestPostAction(() =>
return () =>
{
bool release = false;
@ -115,7 +115,7 @@ namespace Capnp.Rpc
{
_ep.ReleaseImport(_remoteId);
}
});
};
}
else
{
@ -123,6 +123,8 @@ namespace Capnp.Rpc
}
}
}
return null;
}
async void TrackCall(Task call)

View File

@ -1,4 +1,5 @@
using System;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
@ -99,9 +100,12 @@ namespace Capnp.Rpc
/// </summary>
public Proxy()
{
#if DebugFinalizers
CreatorStackTrace = Environment.StackTrace;
#endif
}
internal Proxy(ConsumedCapability? cap)
internal Proxy(ConsumedCapability? cap): this()
{
Bind(cap);
}
@ -118,7 +122,7 @@ namespace Capnp.Rpc
cap.AddRef();
}
internal IProvidedCapability? GetProvider()
internal Skeleton? GetProvider()
{
switch (ConsumedCap)
{
@ -163,7 +167,7 @@ namespace Capnp.Rpc
~Proxy()
{
#if DebugFinalizers
Logger.LogWarning($"Caught orphaned Proxy, created from {CreatorMemberName} in {CreatorFilePath}, line {CreatorLineNumber}.");
Logger.LogWarning($"Caught orphaned Proxy, created from here: {CreatorStackTrace}.");
#endif
Dispose(false);
@ -230,9 +234,7 @@ namespace Capnp.Rpc
}
#if DebugFinalizers
public string CreatorMemberName { get; set; }
public string CreatorFilePath { get; set; }
public int CreatorLineNumber { get; set; }
string CreatorStackTrace { get; set; }
#endif
}
}

View File

@ -26,16 +26,33 @@ namespace Capnp.Rpc
// Value 0 has the special meaning of being in state C.
long _refCount = 1;
#if DebugCapabilityLifecycle
#if DebugCapabilityLifecycle || DebugFinalizers
ILogger Logger { get; } = Logging.CreateLogger<RefCountingCapability>();
#endif
#if DebugCapabilityLifecycle
string? _releasingMethodName;
string? _releasingFilePath;
int _releasingLineNumber;
#endif
#if DebugFinalizers
string CreatorStackTrace { get; set; }
#endif
public RefCountingCapability()
{
#if DebugFinalizers
CreatorStackTrace = Environment.StackTrace;
#endif
}
~RefCountingCapability()
{
#if DebugFinalizers
Logger.LogWarning($"Caught orphaned capability, created from here: {CreatorStackTrace}.");
#endif
Dispose(false);
}

View File

@ -37,6 +37,19 @@ namespace Capnp.Rpc
WhenResolved = AwaitWhenResolved();
}
static async Task<Proxy> TransferOwnershipToDummyProxy(PendingQuestion question, MemberAccessPath access)
{
var result = await question.WhenReturned;
var cap = access.Eval(result);
var proxy = new Proxy(cap);
cap?.Release(false);
return proxy;
}
public RemoteAnswerCapability(PendingQuestion question, MemberAccessPath access) : this(question, access, TransferOwnershipToDummyProxy(question, access))
{
}
async void ReAllowFinishWhenDone(Task task)
{
try
@ -64,7 +77,7 @@ namespace Capnp.Rpc
{
lock (_question.ReentrancyBlocker)
{
if (!_question.IsTailCall && WhenResolved.IsCompleted)
if (!_question.IsTailCall && _question.StateFlags.HasFlag(PendingQuestion.State.Returned))
{
try
{
@ -96,8 +109,8 @@ namespace Capnp.Rpc
{
lock (_question.ReentrancyBlocker)
{
if (_question.StateFlags.HasFlag(PendingQuestion.State.Returned) &&
!_question.StateFlags.HasFlag(PendingQuestion.State.TailCall))
if (!_question.StateFlags.HasFlag(PendingQuestion.State.TailCall) &&
_question.StateFlags.HasFlag(PendingQuestion.State.Returned))
{
if (ResolvedCap == null)
{
@ -165,8 +178,9 @@ namespace Capnp.Rpc
{
lock (_question.ReentrancyBlocker)
{
if (_question.StateFlags.HasFlag(PendingQuestion.State.Returned) &&
_pendingCallsOnPromise == 0)
if ( _question.StateFlags.HasFlag(PendingQuestion.State.Returned) &&
!_question.StateFlags.HasFlag(PendingQuestion.State.TailCall) &&
_pendingCallsOnPromise == 0)
{
if (ResolvedCap == null)
{
@ -200,7 +214,7 @@ namespace Capnp.Rpc
}
}
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
{
lock (_question.ReentrancyBlocker)
{
@ -236,10 +250,12 @@ namespace Capnp.Rpc
}
else
{
this.ExportAsSenderPromise(endpoint, writer);
return this.ExportAsSenderPromise(endpoint, writer);
}
}
}
return null;
}
protected async override void ReleaseRemotely()

View File

@ -3,6 +3,7 @@ using System.Threading.Tasks;
namespace Capnp.Rpc
{
#if false
class RemoteAnswerCapabilityDeprecated : RemoteResolvingCapability
{
@ -17,7 +18,6 @@ namespace Capnp.Rpc
readonly PendingQuestion _question;
readonly MemberAccessPath _access;
readonly Task<Proxy> _whenResolvedProxy;
ConsumedCapability? _resolvedCap;
public RemoteAnswerCapabilityDeprecated(PendingQuestion question, MemberAccessPath access): base(question.RpcEndpoint)
@ -36,9 +36,6 @@ namespace Capnp.Rpc
}
WhenResolved = AwaitWhenResolved();
async Task<Proxy> AwaitProxy() => new Proxy(await WhenResolved);
_whenResolvedProxy = AwaitProxy();
}
async void ReAllowFinishWhenDone(Task task)
@ -62,16 +59,6 @@ namespace Capnp.Rpc
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
lock (_question.ReentrancyBlocker)
{
using var _ = new Proxy(_resolvedCap);
}
}
protected override ConsumedCapability? ResolvedCap
{
get
@ -258,8 +245,9 @@ namespace Capnp.Rpc
protected async override void ReleaseRemotely()
{
try { using var _ = await _whenResolvedProxy; }
try { (await WhenResolved)?.Release(false); }
catch { }
}
}
#endif
}

View File

@ -5,7 +5,7 @@ namespace Capnp.Rpc
{
static class ResolvingCapabilityExtensions
{
public static void ExportAsSenderPromise<T>(this T cap, IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
public static Action? ExportAsSenderPromise<T>(this T cap, IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
where T: ConsumedCapability, IResolvingCapability
{
var vine = Vine.Create(cap);
@ -16,7 +16,7 @@ namespace Capnp.Rpc
if (first)
{
endpoint.RequestPostAction(async () => {
return async () => {
try
{
@ -28,14 +28,21 @@ namespace Capnp.Rpc
endpoint.Resolve(preliminaryId, vine, () => throw exception);
}
});
};
}
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;
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);
}
}
}
}

View File

@ -68,7 +68,6 @@ namespace Capnp.Rpc
Dismissed
}
static readonly ThreadLocal<Action?> _exportCapTablePostActions = new ThreadLocal<Action?>();
static readonly ThreadLocal<PendingQuestion?> _tailCall = new ThreadLocal<PendingQuestion?>();
static readonly ThreadLocal<bool> _canDeferCalls = new ThreadLocal<bool>();
@ -386,7 +385,7 @@ namespace Capnp.Rpc
}
}
IProvidedCapability? cap;
Skeleton? callTargetCap;
PendingAnswer pendingAnswer;
bool releaseParamCaps = false;
@ -507,7 +506,7 @@ namespace Capnp.Rpc
var inParams = req.Params.Content;
inParams.Caps = ImportCapTable(req.Params);
if (cap == null)
if (callTargetCap == null)
{
releaseParamCaps = true;
pendingAnswer = new PendingAnswer(
@ -519,7 +518,7 @@ namespace Capnp.Rpc
try
{
var cts = new CancellationTokenSource();
var callTask = cap.Invoke(req.InterfaceId, req.MethodId, inParams, cts.Token);
var callTask = callTargetCap.Invoke(req.InterfaceId, req.MethodId, inParams, cts.Token);
pendingAnswer = new PendingAnswer(callTask, cts);
}
catch (System.Exception exception)
@ -528,6 +527,10 @@ namespace Capnp.Rpc
pendingAnswer = new PendingAnswer(
Task.FromException<AnswerOrCounterquestion>(exception), null);
}
finally
{
callTargetCap.Relinquish();
}
}
AwaitAnswerAndReply();
@ -561,7 +564,8 @@ namespace Capnp.Rpc
{
if (_exportTable.TryGetValue(req.Target.ImportedCap, out var rc))
{
cap = rc.Cap;
callTargetCap = rc.Cap;
callTargetCap.Claim();
}
else
{
@ -594,7 +598,8 @@ namespace Capnp.Rpc
try
{
using var proxy = await t;
cap = proxy?.GetProvider();
callTargetCap = proxy?.GetProvider();
callTargetCap?.Claim();
CreateAnswerAwaitItAndReply();
}
catch (TaskCanceledException)
@ -631,8 +636,9 @@ namespace Capnp.Rpc
{
_canDeferCalls.Value = false;
Impatient.AskingEndpoint = null;
_tailCall.Value?.Send();
var call = _tailCall.Value;
_tailCall.Value = null;
call?.Send();
}
}
@ -948,14 +954,6 @@ namespace Capnp.Rpc
}
}
}
//if (results != null && results.Caps != null)
//{
// foreach (var cap in results.Caps)
// {
// cap?.Release();
// }
//}
}
catch
{
@ -1105,7 +1103,7 @@ namespace Capnp.Rpc
Tx(mb.Frame);
var main = new RemoteAnswerCapabilityDeprecated(
var main = new RemoteAnswerCapability(
pendingBootstrap,
MemberAccessPath.BootstrapAccess);
@ -1340,7 +1338,9 @@ namespace Capnp.Rpc
{
foreach (var capDesc in payload.CapTable)
{
list.Add(ImportCap(capDesc));
var cap = ImportCap(capDesc);
cap.AddRef();
list.Add(cap);
}
}
}
@ -1348,20 +1348,13 @@ namespace Capnp.Rpc
return list;
}
void IRpcEndpoint.RequestPostAction(Action postAction)
{
_exportCapTablePostActions.Value += postAction;
}
void ExportCapTableAndSend(
SerializerState state,
Payload.WRITER payload)
{
Debug.Assert(_exportCapTablePostActions.Value == null);
_exportCapTablePostActions.Value = null;
payload.CapTable.Init(state.MsgBuilder!.Caps!.Count);
Action? postAction = null;
int i = 0;
foreach (var cap in state.MsgBuilder.Caps)
{
@ -1373,7 +1366,7 @@ namespace Capnp.Rpc
}
else
{
cap.Export(this, capDesc);
postAction += cap.Export(this, capDesc);
cap.Release(false);
}
}
@ -1386,9 +1379,7 @@ namespace Capnp.Rpc
// To avoid that situation, calls to "ReExportCapWhenResolved" are queued (and
// therefore deferred) to the postAction.
var pa = _exportCapTablePostActions.Value;
_exportCapTablePostActions.Value = null;
pa?.Invoke();
postAction?.Invoke();
}
PendingQuestion IRpcEndpoint.BeginQuestion(ConsumedCapability target, SerializerState inParams)

View File

@ -1,4 +1,5 @@
using System;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
@ -17,8 +18,24 @@ namespace Capnp.Rpc
Vine(ConsumedCapability consumedCap)
{
Proxy = new Proxy(consumedCap ?? throw new ArgumentNullException(nameof(consumedCap)));
#if DebugFinalizers
CreatorStackTrace = Environment.StackTrace;
#endif
}
#if DebugFinalizers
~Vine()
{
Logger.LogWarning($"Caught orphaned Vine, created from here: {CreatorStackTrace}.");
Dispose(false);
}
ILogger Logger { get; } = Logging.CreateLogger<Vine>();
string CreatorStackTrace { get; }
#endif
internal override void Bind(object impl)
{
throw new NotImplementedException();

View File

@ -9,8 +9,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "capnpc-csharp", "capnpc-csh
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Capnp.Net.Runtime.Tests.Std20", "Capnp.Net.Runtime.Tests\Capnp.Net.Runtime.Tests.Std20.csproj", "{9ED38750-F83F-4B10-B3A3-4FD6183F9E86}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Capnp.Net.Runtime.Tests.Core21", "Capnp.Net.Runtime.Tests.Core21\Capnp.Net.Runtime.Tests.Core21.csproj", "{58E8FFC8-D207-4B0F-842A-58ED9D3D9EEF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CapnpC.CSharp.Generator.Tests", "CapnpC.CSharp.Generator.Tests\CapnpC.CSharp.Generator.Tests.csproj", "{B77AC567-E232-4072-85C3-8689566BF3D4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CapnpC.CSharp.MsBuild.Generation", "CapnpC.CSharp.MsBuild.Generation\CapnpC.CSharp.MsBuild.Generation.csproj", "{1EFC1F20-C7BB-4F44-8BF9-DBB123AACCF4}"
@ -37,10 +35,6 @@ Global
{9ED38750-F83F-4B10-B3A3-4FD6183F9E86}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9ED38750-F83F-4B10-B3A3-4FD6183F9E86}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9ED38750-F83F-4B10-B3A3-4FD6183F9E86}.Release|Any CPU.Build.0 = Release|Any CPU
{58E8FFC8-D207-4B0F-842A-58ED9D3D9EEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{58E8FFC8-D207-4B0F-842A-58ED9D3D9EEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{58E8FFC8-D207-4B0F-842A-58ED9D3D9EEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{58E8FFC8-D207-4B0F-842A-58ED9D3D9EEF}.Release|Any CPU.Build.0 = Release|Any CPU
{B77AC567-E232-4072-85C3-8689566BF3D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B77AC567-E232-4072-85C3-8689566BF3D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B77AC567-E232-4072-85C3-8689566BF3D4}.Release|Any CPU.ActiveCfg = Release|Any CPU

File diff suppressed because it is too large Load Diff

View File

@ -72,6 +72,7 @@ namespace CapnpC.CSharp.Generator.CodeGen
public string PipeliningExtensionsClassFormat { get; }
public string ProxyClassFormat { get; }
public string SkeletonClassFormat { get; }
public Name AwaitProxy { get; }
public bool NullableEnable { get; set; }
public GenNames(GeneratorOptions options)
{
@ -110,6 +111,7 @@ namespace CapnpC.CSharp.Generator.CodeGen
PipeliningExtensionsClassFormat = options.PipeliningExtensionsClassFormat;
ProxyClassFormat = options.ProxyClassFormat;
SkeletonClassFormat = options.SkeletonClassFormat;
AwaitProxy = new Name(options.AwaitProxyName);
}
public Name MakeTypeName(TypeDefinition def, NameUsage usage = NameUsage.Default)

View File

@ -37,6 +37,7 @@
public string TaskParameterName { get; set; } = "task";
public string EagerMethodName { get; set; } = "Eager";
public string TypeIdFieldName { get; set; } = "typeId";
public string AwaitProxyName { get; set; } = "AwaitProxy";
public bool NullableEnableDefault { get; set; } = false;
}
}

View File

@ -404,8 +404,11 @@ namespace CapnpC.CSharp.Generator.CodeGen
Argument(SimpleLambdaExpression(
Parameter(_names.DeserializerLocal.Identifier),
Block(
MakeProxyCreateResult(method),
MakeProxyReturnResult(method)))));
UsingStatement(
Block(
MakeProxyCreateResult(method),
MakeProxyReturnResult(method)))
.WithExpression(_names.DeserializerLocal.IdentifierName)))));
bodyStmts.Add(ReturnStatement(pipelineAwareCall));
}
@ -422,19 +425,18 @@ namespace CapnpC.CSharp.Generator.CodeGen
call,
IdentifierName(nameof(Capnp.Rpc.IPromisedAnswer.WhenReturned)));
var assignAwaited = LocalDeclarationStatement(
VariableDeclaration(
bodyStmts.Add(UsingStatement(
Block(
MakeProxyCreateResult(method),
MakeProxyReturnResult(method)))
.WithDeclaration(VariableDeclaration(
IdentifierName("var"))
.AddVariables(
VariableDeclarator(
_names.DeserializerLocal.Identifier)
.WithInitializer(
EqualsValueClause(
AwaitExpression(whenReturned)))));
bodyStmts.Add(assignAwaited);
bodyStmts.Add(MakeProxyCreateResult(method));
bodyStmts.Add(MakeProxyReturnResult(method));
.AddVariables(
VariableDeclarator(
_names.DeserializerLocal.Identifier)
.WithInitializer(
EqualsValueClause(
AwaitExpression(whenReturned))))));
}
if (method.GenericParameters.Count > 0)
@ -703,7 +705,10 @@ namespace CapnpC.CSharp.Generator.CodeGen
Parameter(_names.CancellationTokenParameter.Identifier)
.WithType(_names.Type<CancellationToken>(Nullability.NonNullable)))
.AddBodyStatements(
MakeSkeletonMethodBody(method).ToArray());
UsingStatement(
Block(
MakeSkeletonMethodBody(method).ToArray()))
.WithExpression(_names.DeserializerLocal.IdentifierName));
if (method.Results.Count == 0)
{
@ -808,6 +813,59 @@ namespace CapnpC.CSharp.Generator.CodeGen
readonly HashSet<(string, string)> _existingExtensionMethods = new HashSet<(string, string)>();
LocalFunctionStatementSyntax MakeLocalAwaitProxyFunction(Method method, IReadOnlyList<Field> path)
{
var members = new List<Name>();
IEnumerable<Field> fields = path;
if (method.Results.Count >= 2)
{
int index = Array.IndexOf(method.ResultStruct.Fields.ToArray(), path[0]) + 1;
members.Add(new Name($"Item{index}"));
fields = path.Skip(1);
}
foreach (var field in fields)
{
members.Add(_names.GetCodeIdentifier(field));
}
ExpressionSyntax memberAccess =
ParenthesizedExpression(
AwaitExpression(
_names.TaskParameter.IdentifierName));
memberAccess = MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
memberAccess,
members.First().IdentifierName);
foreach (var member in members.Skip(1))
{
memberAccess = ConditionalAccessExpression(
memberAccess,
MemberBindingExpression(member.IdentifierName));
}
var idisposable = _names.MakeNullableRefType(IdentifierName(nameof(IDisposable)));
return LocalFunctionStatement(
GenericName(
Identifier(nameof(Task)))
.WithTypeArgumentList(
TypeArgumentList(
SingletonSeparatedList<TypeSyntax>(
idisposable))),
_names.AwaitProxy.Identifier)
.WithModifiers(
TokenList(
Token(SyntaxKind.AsyncKeyword)))
.WithExpressionBody(
ArrowExpressionClause(memberAccess))
.WithSemicolonToken(
Token(SyntaxKind.SemicolonToken));
}
public IEnumerable<MemberDeclarationSyntax> MakePipeliningSupport(TypeDefinition type)
{
foreach (var method in type.Methods)
@ -856,6 +914,7 @@ namespace CapnpC.CSharp.Generator.CodeGen
.AddModifiers(This)
.WithType(TransformReturnType(method)))
.AddBodyStatements(
MakeLocalAwaitProxyFunction(method, path),
ReturnStatement(
CastExpression(
capTypeSyntax,
@ -883,7 +942,10 @@ namespace CapnpC.CSharp.Generator.CodeGen
IdentifierName(nameof(Capnp.Rpc.IPromisedAnswer.Access))))
.AddArgumentListArguments(
Argument(
accessPath.IdentifierName)))))));
accessPath.IdentifierName),
Argument(
InvocationExpression(
_names.AwaitProxy.IdentifierName))))))));
yield return pathDecl;
yield return methodDecl;