Merge pull request #49 from c80k/coverage

Coverage
This commit is contained in:
c80k 2020-04-24 13:27:57 +02:00 committed by GitHub
commit 5b903584ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
183 changed files with 20177 additions and 7109 deletions

3
.gitignore vendored
View File

@ -336,3 +336,6 @@ ASALocalRun/
# Capnp code behind # Capnp code behind
*.capnp.cs *.capnp.cs
/globalPackages /globalPackages
# Coverage results folder
coverage/

View File

@ -7,8 +7,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.0" /> <PackageReference Include="BenchmarkDotNet" Version="0.12.0" />
<PackageReference Include="Capnp.Net.Runtime" Version="1.3.34-g409e517587" /> <PackageReference Include="Capnp.Net.Runtime" Version="1.3.97-g4f0abaac73" />
<PackageReference Include="CapnpC.CSharp.MsBuild.Generation" Version="1.2.138" /> <PackageReference Include="CapnpC.CSharp.MsBuild.Generation" Version="1.3.97-g4f0abaac73" />
<PackageReference Include="Google.Protobuf" Version="3.11.3" /> <PackageReference Include="Google.Protobuf" Version="3.11.3" />
<PackageReference Include="Grpc.Net.Client" Version="2.27.0" /> <PackageReference Include="Grpc.Net.Client" Version="2.27.0" />
<PackageReference Include="Grpc.Tools" Version="2.27.0"> <PackageReference Include="Grpc.Tools" Version="2.27.0">

View File

@ -11,8 +11,11 @@ namespace Benchmark
{ {
static void Main(string[] args) static void Main(string[] args)
{ {
BenchmarkRunner.Run<GrpcBenchmark>(); if (args.Length == 0 || args[0] == "grpc")
BenchmarkRunner.Run<CapnpBenchmark>(); BenchmarkRunner.Run<GrpcBenchmark>();
if (args.Length == 0 || args[0] == "capnp")
BenchmarkRunner.Run<CapnpBenchmark>();
} }
} }
} }

View File

@ -3,11 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 16
VisualStudioVersion = 16.0.29728.190 VisualStudioVersion = 16.0.29728.190
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EchoServiceGrpc", "EchoServiceGrpc\EchoServiceGrpc.csproj", "{D59C7B71-3887-426B-A636-2DBDA0549817}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmark", "Benchmark\Benchmark.csproj", "{7F7580CA-CCF0-4650-87BF-502D51A8F435}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmark", "Benchmark\Benchmark.csproj", "{7F7580CA-CCF0-4650-87BF-502D51A8F435}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EchoServiceCapnp", "EchoServiceCapnp\EchoServiceCapnp.csproj", "{309A4A26-F29E-4F49-AB49-76BAE0FD7D62}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EchoServiceCapnp", "EchoServiceCapnp\EchoServiceCapnp.csproj", "{309A4A26-F29E-4F49-AB49-76BAE0FD7D62}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EchoServiceGrpc", "EchoServiceGrpc\EchoServiceGrpc.csproj", "{C9CEE2AD-AC6F-4CBD-A83D-2784832C1E37}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -15,10 +15,6 @@ Global
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D59C7B71-3887-426B-A636-2DBDA0549817}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D59C7B71-3887-426B-A636-2DBDA0549817}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D59C7B71-3887-426B-A636-2DBDA0549817}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D59C7B71-3887-426B-A636-2DBDA0549817}.Release|Any CPU.Build.0 = Release|Any CPU
{7F7580CA-CCF0-4650-87BF-502D51A8F435}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7F7580CA-CCF0-4650-87BF-502D51A8F435}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7F7580CA-CCF0-4650-87BF-502D51A8F435}.Debug|Any CPU.Build.0 = Debug|Any CPU {7F7580CA-CCF0-4650-87BF-502D51A8F435}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F7580CA-CCF0-4650-87BF-502D51A8F435}.Release|Any CPU.ActiveCfg = Release|Any CPU {7F7580CA-CCF0-4650-87BF-502D51A8F435}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -27,6 +23,10 @@ Global
{309A4A26-F29E-4F49-AB49-76BAE0FD7D62}.Debug|Any CPU.Build.0 = Debug|Any CPU {309A4A26-F29E-4F49-AB49-76BAE0FD7D62}.Debug|Any CPU.Build.0 = Debug|Any CPU
{309A4A26-F29E-4F49-AB49-76BAE0FD7D62}.Release|Any CPU.ActiveCfg = Release|Any CPU {309A4A26-F29E-4F49-AB49-76BAE0FD7D62}.Release|Any CPU.ActiveCfg = Release|Any CPU
{309A4A26-F29E-4F49-AB49-76BAE0FD7D62}.Release|Any CPU.Build.0 = Release|Any CPU {309A4A26-F29E-4F49-AB49-76BAE0FD7D62}.Release|Any CPU.Build.0 = Release|Any CPU
{C9CEE2AD-AC6F-4CBD-A83D-2784832C1E37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C9CEE2AD-AC6F-4CBD-A83D-2784832C1E37}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C9CEE2AD-AC6F-4CBD-A83D-2784832C1E37}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C9CEE2AD-AC6F-4CBD-A83D-2784832C1E37}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -9,11 +9,12 @@
<DebugType>full</DebugType> <DebugType>full</DebugType>
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<DefineConstants></DefineConstants>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Capnp.Net.Runtime" Version="1.3.34-g409e517587" /> <PackageReference Include="Capnp.Net.Runtime" Version="1.3.90-g65e87e5aa9" />
<PackageReference Include="CapnpC.CSharp.MsBuild.Generation" Version="1.3.29-g6d711b8579" /> <PackageReference Include="CapnpC.CSharp.MsBuild.Generation" Version="1.3.90-g65e87e5aa9" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,63 @@
using Capnp;
using Capnp.Rpc;
using System.Collections.Generic;
namespace CapnpProfile
{
class EnginePair
{
class EngineChannel : IEndpoint
{
readonly Queue<WireFrame> _frameBuffer = new Queue<WireFrame>();
bool _dismissed;
public EngineChannel()
{
}
public RpcEngine.RpcEndpoint OtherEndpoint { get; set; }
public bool HasBufferedFrames => _frameBuffer.Count > 0;
public int FrameCounter { get; private set; }
public void Dismiss()
{
if (!_dismissed)
{
_dismissed = true;
OtherEndpoint.Dismiss();
}
}
public void Forward(WireFrame frame)
{
if (_dismissed)
return;
OtherEndpoint.Forward(frame);
}
}
readonly EngineChannel _channel1, _channel2;
public RpcEngine Engine1 { get; }
public RpcEngine Engine2 { get; }
public RpcEngine.RpcEndpoint Endpoint1 { get; }
public RpcEngine.RpcEndpoint Endpoint2 { get; }
public EnginePair()
{
Engine1 = new RpcEngine();
Engine2 = new RpcEngine();
_channel1 = new EngineChannel();
Endpoint1 = Engine1.AddEndpoint(_channel1);
_channel2 = new EngineChannel();
Endpoint2 = Engine2.AddEndpoint(_channel2);
_channel1.OtherEndpoint = Endpoint2;
_channel2.OtherEndpoint = Endpoint1;
}
public int Channel1SendCount => _channel1.FrameCounter;
public int Channel2SendCount => _channel2.FrameCounter;
}
}

View File

@ -7,25 +7,62 @@ using System.Threading.Tasks;
namespace CapnpProfile namespace CapnpProfile
{ {
class Program class Program
{ {
static async Task Main(string[] args) static async Task Run(IEchoer echoer)
{ {
using var server = new TcpRpcServer();
server.Main = new CapnpEchoService();
server.AddBuffering();
server.StartAccepting(IPAddress.Any, 5002);
using var client = new TcpRpcClient("localhost", 5002);
await client.WhenConnected;
using var echoer = client.GetMain<IEchoer>();
var payload = new byte[20]; var payload = new byte[20];
new Random().NextBytes(payload); new Random().NextBytes(payload);
#if SOTASK_PERF
int counter = 0;
#endif
while (true) while (true)
{ {
var result = await echoer.Echo(payload); var result = await echoer.Echo(payload);
if (result.Count != payload.Length) if (result.Count != payload.Length)
throw new InvalidOperationException("Echo server malfunction"); throw new InvalidOperationException("Echo server malfunction");
#if SOTASK_PERF
if (++counter == 10000)
{
counter = 0;
Console.WriteLine($"StrictlyOrderedTask performance statistics:");
Console.WriteLine($"AwaitInternal: max. {Capnp.Util.StrictlyOrderedTaskExtensions.Stats.AwaitInternalMaxOuterIterations} outer iterations");
Console.WriteLine($"AwaitInternal: max. {Capnp.Util.StrictlyOrderedTaskExtensions.Stats.AwaitInternalMaxInnerIterations} inner iterations");
Console.WriteLine($"OnCompleted: max. {Capnp.Util.StrictlyOrderedTaskExtensions.Stats.OnCompletedMaxSpins} iterations");
}
#endif
}
}
static async Task Main(string[] args)
{
if (args.Length > 0)
{
var pair = new EnginePair();
pair.Engine1.Main = new CapnpEchoService();
var echoer = (CapabilityReflection.CreateProxy<IEchoer>(pair.Endpoint2.QueryMain()) as IEchoer);
await Run(echoer);
}
else
{
using var server = new TcpRpcServer();
server.Main = new CapnpEchoService();
server.AddBuffering();
server.StartAccepting(IPAddress.Any, 5002);
using var client = new TcpRpcClient();
client.AddBuffering();
client.Connect("localhost", 5002);
await client.WhenConnected;
using var echoer = client.GetMain<IEchoer>();
await Run(echoer);
} }
} }
} }

View File

@ -6,8 +6,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Capnp.Net.Runtime" Version="1.3.34-g409e517587" /> <PackageReference Include="Capnp.Net.Runtime" Version="1.3.97-g4f0abaac73" />
<PackageReference Include="CapnpC.CSharp.MsBuild.Generation" Version="1.2.138" /> <PackageReference Include="CapnpC.CSharp.MsBuild.Generation" Version="1.3.97-g4f0abaac73" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -5,7 +5,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Protobuf Include="Protos\Echo.proto" GrpcServices="Server" /> <Protobuf Include="..\Benchmark\Protos\Echo.proto" GrpcServices="Server">
<Link>Protos\Echo.proto</Link>
</Protobuf>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -6,7 +6,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
namespace EchoService namespace EchoServiceGrpc2
{ {
public class Program public class Program
{ {

View File

@ -1,13 +0,0 @@
syntax = "proto3";
service Echoer {
rpc Echo (EchoRequest) returns (EchoReply);
}
message EchoRequest {
bytes payload = 1;
}
message EchoReply {
bytes payload = 1;
}

View File

@ -2,13 +2,14 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using EchoService;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
namespace EchoService namespace EchoServiceGrpc2
{ {
public class Startup public class Startup
{ {

View File

@ -1,10 +1,10 @@
{ {
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Warning", "Default": "Debug",
"System": "Warning", "System": "Information",
"Grpc": "Warning", "Grpc": "Information",
"Microsoft": "Warning" "Microsoft": "Information"
} }
} }
} }

View File

@ -2,5 +2,6 @@
<configuration> <configuration>
<packageSources> <packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" /> <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="local" value="..\GeneratedNuGetPackages\"/>
</packageSources> </packageSources>
</configuration> </configuration>

View File

@ -2,5 +2,6 @@
<configuration> <configuration>
<packageSources> <packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" /> <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="local" value="..\GeneratedNuGetPackages\"/>
</packageSources> </packageSources>
</configuration> </configuration>

View File

@ -1,113 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>Capnp.Net.Runtime</PackageId>
<Authors>Christian Köllner and contributors</Authors>
<Company />
<Product>capnproto-dotnetcore</Product>
<Description>A Cap'n Proto implementation for .NET Core, runtime assembly for .NET Core 2.1</Description>
<Copyright>Christian Köllner and contributors</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageLicenseFile></PackageLicenseFile>
<PackageProjectUrl>https://github.com/c80k/capnproto-dotnetcore</PackageProjectUrl>
<RepositoryUrl>https://github.com/c80k/capnproto-dotnetcore</RepositoryUrl>
<AssemblyName>Capnp.Net.Runtime</AssemblyName>
<RootNamespace>Capnp</RootNamespace>
<RepositoryType>Git</RepositoryType>
<PackageTags>capnp "Cap'n Proto" RPC serialization cerealization</PackageTags>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Capnp.Net.Runtime\AnyPointer.cs" Link="AnyPointer.cs" />
<Compile Include="..\Capnp.Net.Runtime\CapnpSerializable.cs" Link="CapnpSerializable.cs" />
<Compile Include="..\Capnp.Net.Runtime\DeserializationException.cs" Link="DeserializationException.cs" />
<Compile Include="..\Capnp.Net.Runtime\DeserializerState.cs" Link="DeserializerState.cs" />
<Compile Include="..\Capnp.Net.Runtime\DynamicSerializerState.cs" Link="DynamicSerializerState.cs" />
<Compile Include="..\Capnp.Net.Runtime\EmptyList.cs" Link="EmptyList.cs" />
<Compile Include="..\Capnp.Net.Runtime\EmptyListDeserializer.cs" Link="EmptyListDeserializer.cs" />
<Compile Include="..\Capnp.Net.Runtime\FramePump.cs" Link="FramePump.cs" />
<Compile Include="..\Capnp.Net.Runtime\Framing.cs" Link="Framing.cs" />
<Compile Include="..\Capnp.Net.Runtime\ICapnpSerializable.cs" Link="ICapnpSerializable.cs" />
<Compile Include="..\Capnp.Net.Runtime\ISegmentAllocator.cs" Link="ISegmentAllocator.cs" />
<Compile Include="..\Capnp.Net.Runtime\IStructDeserializer.cs" Link="IStructDeserializer.cs" />
<Compile Include="..\Capnp.Net.Runtime\IStructSerializer.cs" Link="IStructSerializer.cs" />
<Compile Include="..\Capnp.Net.Runtime\ListDeserializer.cs" Link="ListDeserializer.cs" />
<Compile Include="..\Capnp.Net.Runtime\ListKind.cs" Link="ListKind.cs" />
<Compile Include="..\Capnp.Net.Runtime\ListOfBitsDeserializer.cs" Link="ListOfBitsDeserializer.cs" />
<Compile Include="..\Capnp.Net.Runtime\ListOfBitsSerializer.cs" Link="ListOfBitsSerializer.cs" />
<Compile Include="..\Capnp.Net.Runtime\ListOfCapsDeserializer.cs" Link="ListOfCapsDeserializer.cs" />
<Compile Include="..\Capnp.Net.Runtime\ListOfCapsSerializer.cs" Link="ListOfCapsSerializer.cs" />
<Compile Include="..\Capnp.Net.Runtime\ListOfEmptyDeserializer.cs" Link="ListOfEmptyDeserializer.cs" />
<Compile Include="..\Capnp.Net.Runtime\ListOfEmptySerializer.cs" Link="ListOfEmptySerializer.cs" />
<Compile Include="..\Capnp.Net.Runtime\ListOfPointersDeserializer.cs" Link="ListOfPointersDeserializer.cs" />
<Compile Include="..\Capnp.Net.Runtime\ListOfPointersSerializer.cs" Link="ListOfPointersSerializer.cs" />
<Compile Include="..\Capnp.Net.Runtime\ListOfPrimitivesDeserializer.cs" Link="ListOfPrimitivesDeserializer.cs" />
<Compile Include="..\Capnp.Net.Runtime\ListOfPrimitivesSerializer.cs" Link="ListOfPrimitivesSerializer.cs" />
<Compile Include="..\Capnp.Net.Runtime\ListOfStructsDeserializer.cs" Link="ListOfStructsDeserializer.cs" />
<Compile Include="..\Capnp.Net.Runtime\ListOfStructsSerializer.cs" Link="ListOfStructsSerializer.cs" />
<Compile Include="..\Capnp.Net.Runtime\ListOfTextSerializer.cs" Link="ListOfTextSerializer.cs" />
<Compile Include="..\Capnp.Net.Runtime\Logging.cs" Link="Logging.cs" />
<Compile Include="..\Capnp.Net.Runtime\MessageBuilder.cs" Link="MessageBuilder.cs" />
<Compile Include="..\Capnp.Net.Runtime\ObjectKind.cs" Link="ObjectKind.cs" />
<Compile Include="..\Capnp.Net.Runtime\PrimitiveCoder.cs" Link="PrimitiveCoder.cs" />
<Compile Include="..\Capnp.Net.Runtime\ReadOnlyListExtensions.cs" Link="ReadOnlyListExtensions.cs" />
<Compile Include="..\Capnp.Net.Runtime\Reserializing.cs" Link="Reserializing.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\AnswerOrCounterquestion.cs" Link="Rpc\AnswerOrCounterquestion.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\BareProxy.cs" Link="Rpc\BareProxy.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\CapabilityReflection.cs" Link="Rpc\CapabilityReflection.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\ConsumedCapability.cs" Link="Rpc\ConsumedCapability.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\IEndpoint.cs" Link="Rpc\IEndpoint.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\IMonoSkeleton.cs" Link="Rpc\IMonoSkeleton.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\Impatient.cs" Link="Rpc\Impatient.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\ImportedCapability.cs" Link="Rpc\ImportedCapability.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\InvalidCapabilityInterfaceException.cs" Link="Rpc\InvalidCapabilityInterfaceException.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\IPromisedAnswer.cs" Link="Rpc\IPromisedAnswer.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\IProvidedCapability.cs" Link="Rpc\IProvidedCapability.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\IResolvingCapability.cs" Link="Rpc\IResolvingCapability.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\IRpcEndpoint.cs" Link="Rpc\IRpcEndpoint.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\LazyCapability.cs" Link="Rpc\LazyCapability.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\LocalAnswer.cs" Link="Rpc\LocalAnswer.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\LocalAnswerCapability.cs" Link="Rpc\LocalAnswerCapability.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\LocalCapability.cs" Link="Rpc\LocalCapability.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\MemberAccessPath.cs" Link="Rpc\MemberAccessPath.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\PendingAnswer.cs" Link="Rpc\PendingAnswer.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\PendingQuestion.cs" Link="Rpc\PendingQuestion.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\PolySkeleton.cs" Link="Rpc\PolySkeleton.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\PromisedCapability.cs" Link="Rpc\PromisedCapability.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\Proxy.cs" Link="Rpc\Proxy.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\ProxyAttribute.cs" Link="Rpc\ProxyAttribute.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\RefCountingCapability.cs" Link="Rpc\RefCountingCapability.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\RemoteAnswerCapability.cs" Link="Rpc\RemoteAnswerCapability.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\RemoteCapability.cs" Link="Rpc\RemoteCapability.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\RemoteResolvingCapability.cs" Link="Rpc\RemoteResolvingCapability.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\ResolvingCapabilityExtensions.cs" Link="Rpc\ResolvingCapabilityExtensions.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\rpc.cs" Link="Rpc\rpc.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\RpcEngine.cs" Link="Rpc\RpcEngine.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\RpcException.cs" Link="Rpc\RpcException.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\RpcUnimplementedException.cs" Link="Rpc\RpcUnimplementedException.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\Skeleton.cs" Link="Rpc\Skeleton.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\SkeletonAttribute.cs" Link="Rpc\SkeletonAttribute.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\TcpRpcClient.cs" Link="Rpc\TcpRpcClient.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\TcpRpcServer.cs" Link="Rpc\TcpRpcServer.cs" />
<Compile Include="..\Capnp.Net.Runtime\Rpc\Vine.cs" Link="Rpc\Vine.cs" />
<Compile Include="..\Capnp.Net.Runtime\SecurityOptions.cs" Link="SecurityOptions.cs" />
<Compile Include="..\Capnp.Net.Runtime\SegmentAllocator.cs" Link="SegmentAllocator.cs" />
<Compile Include="..\Capnp.Net.Runtime\SegmentSlice.cs" Link="SegmentSlice.cs" />
<Compile Include="..\Capnp.Net.Runtime\SerializerExtensions.cs" Link="SerializerExtensions.cs" />
<Compile Include="..\Capnp.Net.Runtime\SerializerState.cs" Link="SerializerState.cs" />
<Compile Include="..\Capnp.Net.Runtime\UtilityExtensions.cs" Link="UtilityExtensions.cs" />
<Compile Include="..\Capnp.Net.Runtime\WireFrame.cs" Link="WireFrame.cs" />
<Compile Include="..\Capnp.Net.Runtime\WirePointer.cs" Link="WirePointer.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="Rpc\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
</ItemGroup>
</Project>

View File

@ -1,52 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<IsPackable>false</IsPackable>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Capnp.Net.Runtime.Tests\DeserializationTests.cs" Link="DeserializationTests.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\DynamicSerializerStateTests.cs" Link="DynamicSerializerStateTests.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\FramePumpTests.cs" Link="FramePumpTests.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\General.cs" Link="General.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\Interception.cs" Link="Interception.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\Issue19.cs" Link="Issue19.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\Issue20.cs" Link="Issue20.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\Issue25.cs" Link="Issue25.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\JobUtil.cs" Link="JobUtil.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\MessageBuilderTests.cs" Link="MessageBuilderTests.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\ProvidedCapabilityMock.cs" Link="ProvidedCapabilityMock.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\ProvidedCapabilityMultiCallMock.cs" Link="ProvidedCapabilityMultiCallMock.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\RpcSchemaTests.cs" Link="RpcSchemaTests.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\ScatteringStream.cs" Link="ScatteringStream.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\SegmentAllocatorTests.cs" Link="SegmentAllocatorTests.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\TcpRpc.cs" Link="TcpRpc.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\TcpRpcAdvancedStuff.cs" Link="TcpRpcAdvancedStuff.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\TcpRpcInterop.cs" Link="TcpRpcInterop.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\TcpRpcPorted.cs" Link="TcpRpcPorted.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\TcpRpcStress.cs" Link="TcpRpcStress.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\test.cs" Link="test.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\TestBase.cs" Link="TestBase.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\TestCallContext.cs" Link="TestCallContext.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\TestCapImplementations.cs" Link="TestCapImplementations.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\TestInterfaces.cs" Link="TestInterfaces.cs" />
<Compile Include="..\Capnp.Net.Runtime.Tests\WirePointerTests.cs" Link="WirePointerTests.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Capnp.Net.Runtime\Capnp.Net.Runtime.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,30 @@
using Capnp.Rpc;
using Capnproto_test.Capnp.Test;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Text;
namespace Capnp.Net.Runtime.Tests
{
[TestClass]
[TestCategory("Coverage")]
public class CapabilityReflectionTests
{
[TestMethod]
public void ValidateCapabilityInterface()
{
Assert.ThrowsException<ArgumentNullException>(() => CapabilityReflection.ValidateCapabilityInterface(null));
CapabilityReflection.ValidateCapabilityInterface(typeof(ITestInterface));
Assert.ThrowsException<InvalidCapabilityInterfaceException>(() => CapabilityReflection.ValidateCapabilityInterface(typeof(CapabilityReflectionTests)));
}
[TestMethod]
public void IsValidCapabilityInterface()
{
Assert.ThrowsException<ArgumentNullException>(() => CapabilityReflection.IsValidCapabilityInterface(null));
Assert.IsTrue(CapabilityReflection.IsValidCapabilityInterface(typeof(ITestInterface)));
Assert.IsFalse(CapabilityReflection.IsValidCapabilityInterface(typeof(CapabilityReflectionTests)));
}
}
}

View File

@ -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>
@ -10,18 +10,25 @@
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<RootNamespace>Capnp.Net.Runtime.Tests</RootNamespace>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netcoreapp2.1|AnyCPU'">
<DefineConstants>TRACE</DefineConstants>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" /> <PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" /> <PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
<PackageReference Include="System.IO.Pipelines" Version="4.7.0" />
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.10.0" /> <PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.10.0" />
</ItemGroup> </ItemGroup>

View File

@ -1,8 +1,15 @@
using Microsoft.VisualStudio.TestTools.UnitTesting; using Capnp.Net.Runtime.Tests.GenImpls;
using Capnproto_test.Capnp.Test;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using static Capnproto_test.Capnp.Test.TestStructUnion;
namespace Capnp.Net.Runtime.Tests namespace Capnp.Net.Runtime.Tests
{ {
[TestClass] [TestClass]
[TestCategory("Coverage")]
public class DeserializationTests public class DeserializationTests
{ {
[TestMethod] [TestMethod]
@ -144,6 +151,9 @@ namespace Capnp.Net.Runtime.Tests
Assert.AreEqual(2, asListOfStructs.Count); Assert.AreEqual(2, asListOfStructs.Count);
Assert.AreEqual(0ul, asListOfStructs[0].ReadDataULong(0)); Assert.AreEqual(0ul, asListOfStructs[0].ReadDataULong(0));
Assert.AreEqual(ulong.MaxValue, asListOfStructs[1].ReadDataULong(0)); Assert.AreEqual(ulong.MaxValue, asListOfStructs[1].ReadDataULong(0));
Assert.ThrowsException<IndexOutOfRangeException>(() => asListOfStructs[-1].ReadDataUShort(0));
Assert.ThrowsException<IndexOutOfRangeException>(() => asListOfStructs[3].ReadDataUShort(0));
CollectionAssert.AreEqual(new ulong[] { 0, ulong.MaxValue }, asListOfStructs.Select(_ => _.ReadDataULong(0)).ToArray());
} }
[TestMethod] [TestMethod]
@ -207,6 +217,22 @@ namespace Capnp.Net.Runtime.Tests
Assert.AreEqual(double.PositiveInfinity, asListOfStructs[5].ReadDataDouble(0)); Assert.AreEqual(double.PositiveInfinity, asListOfStructs[5].ReadDataDouble(0));
} }
[TestMethod]
public void ListOfStructsAsListOfBools()
{
var ds = new DynamicSerializerState(MessageBuilder.Create(128));
ds.SetListOfStructs(3, 1, 0);
ds.ListBuildStruct(1).WriteData(0, false);
ds.ListBuildStruct(2).WriteData(0, true);
DeserializerState d = ds;
var asListOfBools = d.RequireList().CastBool();
Assert.AreEqual(3, asListOfBools.Count);
Assert.AreEqual(false, asListOfBools[0]);
Assert.AreEqual(false, asListOfBools[1]);
Assert.AreEqual(true, asListOfBools[2]);
}
[TestMethod] [TestMethod]
public void ListOfStructsAsListOfBytes() public void ListOfStructsAsListOfBytes()
{ {
@ -353,5 +379,599 @@ namespace Capnp.Net.Runtime.Tests
Assert.AreEqual(double.NegativeInfinity, asListOfDoubles[0]); Assert.AreEqual(double.NegativeInfinity, asListOfDoubles[0]);
Assert.AreEqual(double.MaxValue, asListOfDoubles[1]); Assert.AreEqual(double.MaxValue, asListOfDoubles[1]);
} }
[TestMethod]
public void NestedLists()
{
var expected = new int[][] {
new int[] { 1, 2, 3 },
new int[] { 4, 5 },
new int[] { 6 } };
var b = MessageBuilder.Create();
var dss = b.CreateObject<DynamicSerializerState>();
dss.SetObject(expected);
DeserializerState d = dss;
var ld = d.RequireList();
Assert.ThrowsException<NotSupportedException>(() => ld.CastText());
var result = ld.Cast2D<int>();
Assert.AreEqual(3, result.Count);
for (int i = 0; i < result.Count; i++)
{
CollectionAssert.AreEqual(expected[i], result[i].ToArray());
}
Assert.ThrowsException<NotSupportedException>(() => ld.Cast2D<decimal>());
}
[TestMethod]
public void LinearListWrongUse()
{
var b = MessageBuilder.Create();
var dss = b.CreateObject<DynamicSerializerState>();
dss.SetObject(new int[] { 1, 2, 3 });
DeserializerState d = dss;
var ld = d.RequireList();
Assert.ThrowsException<NotSupportedException>(() => ld.CastList());
Assert.ThrowsException<NotSupportedException>(() => ld.CastCapList<ITestInterface>());
}
[TestMethod]
public void NestedLists3D()
{
var expected = new int[][][] {
new int[][]
{
new int[] { 1, 2, 3 },
new int[] { 4, 5 },
new int[] { 6 }
},
new int[][]
{
new int[] { 1, 2, 3 },
new int[0]
},
new int[0][]
};
var b = MessageBuilder.Create();
var dss = b.CreateObject<DynamicSerializerState>();
dss.SetObject(expected);
DeserializerState d = dss;
var ld = d.RequireList();
var result = ld.Cast3D<int>();
Assert.AreEqual(3, result.Count);
for (int i = 0; i < result.Count; i++)
{
var inner = result[i];
Assert.AreEqual(expected[i].Length, inner.Count);
for (int j = 0; j < expected[i].Length; j++)
{
var inner2 = inner[j];
CollectionAssert.AreEqual(expected[i][j], inner2.ToArray());
}
}
}
[TestMethod]
public void NestedListsND()
{
var expected = new int[][][] {
new int[][]
{
new int[] { 1, 2, 3 },
new int[] { 4, 5 },
new int[] { 6 }
},
new int[][]
{
new int[] { 1, 2, 3 },
new int[0]
},
new int[0][]
};
var b = MessageBuilder.Create();
var dss = b.CreateObject<DynamicSerializerState>();
dss.SetObject(expected);
DeserializerState d = dss;
var ld = d.RequireList();
var result = (IReadOnlyList<object>)ld.CastND<int>(3);
Assert.AreEqual(3, result.Count);
for (int i = 0; i < result.Count; i++)
{
var inner = (IReadOnlyList<object>)result[i];
Assert.AreEqual(expected[i].Length, inner.Count);
for (int j = 0; j < expected[i].Length; j++)
{
var inner2 = (IReadOnlyList<int>)inner[j];
CollectionAssert.AreEqual(expected[i][j], inner2.ToArray());
}
}
}
[TestMethod]
public void NestedLists3DStruct()
{
var expected = new List<List<List<SomeStruct.WRITER>>>();
var b = MessageBuilder.Create();
var dss = b.CreateObject<DynamicSerializerState>();
for (int i = 0; i < 3; i++)
{
expected.Add(new List<List<SomeStruct.WRITER>>());
for (int j = 0; j <= i; j++)
{
expected[i].Add(new List<SomeStruct.WRITER>());
for (int k = 0; k <= j; k++)
{
var x = b.CreateObject<SomeStruct.WRITER>();
x.SomeText = $"{i}, {j}, {k}";
expected[i][j].Add(x);
}
}
}
dss.SetObject(expected);
DeserializerState d = dss;
var ld = d.RequireList();
var result = ld.Cast3D<SomeStruct.READER>(_ => new SomeStruct.READER(_));
Assert.AreEqual(3, result.Count);
for (int i = 0; i < result.Count; i++)
{
var inner = result[i];
Assert.AreEqual(i + 1, inner.Count);
for (int j = 0; j < inner.Count; j++)
{
var inner2 = inner[j];
Assert.AreEqual(j + 1, inner2.Count);
for (int k = 0; k < inner2.Count; k++)
{
Assert.AreEqual($"{i}, {j}, {k}", inner2[k].SomeText);
}
}
}
}
[TestMethod]
public void NestedListsNDStruct()
{
var expected = new List<List<List<SomeStruct.WRITER>>>();
var b = MessageBuilder.Create();
var dss = b.CreateObject<DynamicSerializerState>();
for (int i = 0; i < 3; i++)
{
expected.Add(new List<List<SomeStruct.WRITER>>());
for (int j = 0; j <= i; j++)
{
expected[i].Add(new List<SomeStruct.WRITER>());
for (int k = 0; k <= j; k++)
{
var x = b.CreateObject<SomeStruct.WRITER>();
x.SomeText = $"{i}, {j}, {k}";
expected[i][j].Add(x);
}
}
}
dss.SetObject(expected);
DeserializerState d = dss;
var ld = d.RequireList();
var result = (IReadOnlyList<object>)ld.CastND<SomeStruct.READER>(3, _ => new SomeStruct.READER(_));
Assert.AreEqual(3, result.Count);
for (int i = 0; i < result.Count; i++)
{
var inner = (IReadOnlyList<object>)result[i];
Assert.AreEqual(i + 1, inner.Count);
for (int j = 0; j < inner.Count; j++)
{
var inner2 = (IReadOnlyList<SomeStruct.READER>)inner[j];
Assert.AreEqual(j + 1, inner2.Count);
for (int k = 0; k < inner2.Count; k++)
{
Assert.AreEqual($"{i}, {j}, {k}", inner2[k].SomeText);
}
}
}
}
[TestMethod]
public void NestedLists2DStruct()
{
var expected = new List<List<SomeStruct.WRITER>>();
var b = MessageBuilder.Create();
var dss = b.CreateObject<DynamicSerializerState>();
for (int i = 0; i < 3; i++)
{
expected.Add(new List<SomeStruct.WRITER>());
for (int j = 0; j <= i; j++)
{
var x = b.CreateObject<SomeStruct.WRITER>();
x.SomeText = $"{i}, {j}";
expected[i].Add(x);
}
}
dss.SetObject(expected);
DeserializerState d = dss;
var ld = d.RequireList();
var result = ld.Cast2D(_ => new SomeStruct.READER(_));
Assert.AreEqual(3, result.Count);
for (int i = 0; i < result.Count; i++)
{
var inner = result[i];
Assert.AreEqual(i + 1, inner.Count);
for (int j = 0; j < inner.Count; j++)
{
Assert.AreEqual($"{i}, {j}", inner[j].SomeText);
}
}
}
[TestMethod]
public void ListOfEnums()
{
var expected = new TestEnum[] { TestEnum.bar, TestEnum.baz, TestEnum.corge };
var b = MessageBuilder.Create();
var dss = b.CreateObject<DynamicSerializerState>();
dss.SetObject(expected);
DeserializerState d = dss;
var ld = d.RequireList();
var result = ld.CastEnums(_ => (TestEnum)_);
CollectionAssert.AreEqual(expected, result.ToArray());
}
[TestMethod]
public void NestedLists2DEnum()
{
var expected = new TestEnum[][]
{
new TestEnum[] { TestEnum.bar, TestEnum.baz, TestEnum.corge },
new TestEnum[] { TestEnum.corge, TestEnum.foo, TestEnum.garply }
};
var b = MessageBuilder.Create();
var dss = b.CreateObject<DynamicSerializerState>();
dss.SetObject(expected);
DeserializerState d = dss;
var ld = d.RequireList();
var result = ld.CastEnums2D(_ => (TestEnum)_);
Assert.AreEqual(expected.Length, result.Count);
for (int i = 0; i < result.Count; i++)
{
CollectionAssert.AreEqual(expected[i], result[i].ToArray());
}
}
[TestMethod]
public void NestedLists3DEnum()
{
var expected = new TestEnum[][][] {
new TestEnum[][]
{
new TestEnum[] { TestEnum.qux, TestEnum.quux, TestEnum.grault },
new TestEnum[] { TestEnum.garply, TestEnum.foo },
new TestEnum[] { TestEnum.corge }
},
new TestEnum[][]
{
new TestEnum[] { TestEnum.baz, TestEnum.bar },
new TestEnum[0]
},
new TestEnum[0][]
};
var b = MessageBuilder.Create();
var dss = b.CreateObject<DynamicSerializerState>();
dss.SetObject(expected);
DeserializerState d = dss;
var ld = d.RequireList();
var result = ld.CastEnums3D(_ => (TestEnum)_);
Assert.AreEqual(3, result.Count);
for (int i = 0; i < result.Count; i++)
{
var inner = result[i];
Assert.AreEqual(expected[i].Length, inner.Count);
for (int j = 0; j < expected[i].Length; j++)
{
var inner2 = inner[j];
CollectionAssert.AreEqual(expected[i][j], inner2.ToArray());
}
}
}
[TestMethod]
public void NestedListsNDEnum()
{
var expected = new TestEnum[][][] {
new TestEnum[][]
{
new TestEnum[] { TestEnum.qux, TestEnum.quux, TestEnum.grault },
new TestEnum[] { TestEnum.garply, TestEnum.foo },
new TestEnum[] { TestEnum.corge }
},
new TestEnum[][]
{
new TestEnum[] { TestEnum.baz, TestEnum.bar },
new TestEnum[0]
},
new TestEnum[0][]
};
var b = MessageBuilder.Create();
var dss = b.CreateObject<DynamicSerializerState>();
dss.SetObject(expected);
DeserializerState d = dss;
var ld = d.RequireList();
var result = (IReadOnlyList<object>)ld.CastEnumsND(3, _ => (TestEnum)_);
Assert.AreEqual(3, result.Count);
for (int i = 0; i < result.Count; i++)
{
var inner = (IReadOnlyList<object>)result[i];
Assert.AreEqual(expected[i].Length, inner.Count);
for (int j = 0; j < expected[i].Length; j++)
{
var inner2 = (IReadOnlyList<TestEnum>)inner[j];
CollectionAssert.AreEqual(expected[i][j], inner2.ToArray());
}
}
}
[TestMethod]
public void NestedLists2DVoid()
{
var b = MessageBuilder.Create();
var s = b.CreateObject<ListOfPointersSerializer<ListOfEmptySerializer>>();
s.Init(3);
s[0].Init(4);
s[1].Init(5);
s[2].Init(6);
DeserializerState d = s;
var voids = d.RequireList().CastVoid2D();
CollectionAssert.AreEqual(new int[] { 4, 5, 6 }, voids.ToArray());
}
[TestMethod]
public void NestedLists3DVoid()
{
var expected = new int[][] {
new int[] { 1, 2, 3 },
new int[] { 4, 5 },
new int[] { 6 } };
var b = MessageBuilder.Create();
var s = b.CreateObject<ListOfPointersSerializer<ListOfPointersSerializer<ListOfEmptySerializer>>>();
s.Init(expected.Length);
for (int i = 0; i < expected.Length; i++)
{
s[i].Init(expected[i], (l, j) => l.Init(j));
}
DeserializerState d = s;
var voids = d.RequireList().CastVoid3D();
Assert.AreEqual(expected.Length, voids.Count);
for (int i = 0; i < expected.Length; i++)
{
CollectionAssert.AreEqual(expected[i], voids[i].ToArray());
}
}
[TestMethod]
public void ListOfEmpty()
{
var expected = new TestEnum[] { TestEnum.bar, TestEnum.baz, TestEnum.corge };
var b = MessageBuilder.Create();
var loes = b.CreateObject<ListOfEmptySerializer>();
loes.Init(12345678);
DeserializerState d = loes;
var ld = d.RequireList();
Assert.AreEqual(ListKind.ListOfEmpty, ld.Kind);
if (!(ld is ListOfEmptyDeserializer loed))
{
Assert.Fail("List did not deserialize back to ListOfEmptyDeserializer");
return;
}
Assert.AreEqual(12345678, loed.Count);
Assert.ThrowsException<IndexOutOfRangeException>(() => { var _ = loed[-1]; });
Assert.ThrowsException<IndexOutOfRangeException>(() => { var _ = loed[12345678]; });
_ = loed[12345677];
var kind = loed.Cast(_ => _.Kind).Take(1).Single();
Assert.AreEqual(ObjectKind.Nil, kind);
}
[TestMethod]
public void DeserializerStateBadConv()
{
SerializerState s = null;
Assert.ThrowsException<ArgumentNullException>(() => (DeserializerState)s);
s = new DynamicSerializerState();
Assert.ThrowsException<InvalidOperationException>(() => (DeserializerState)s);
}
[TestMethod]
public void BadPointers()
{
var data = new ulong[1];
var wf = new WireFrame(new Memory<ulong>[] { new Memory<ulong>(data) });
var d0 = DeserializerState.CreateRoot(wf);
Assert.AreEqual(ObjectKind.Nil, d0.Kind);
WirePointer p = default;
p.BeginStruct(1, 0);
p.Offset = 0;
data[0] = p;
Assert.ThrowsException<DeserializationException>(() => DeserializerState.CreateRoot(wf));
p.BeginList(ListKind.ListOfBits, 64);
data[0] = p;
Assert.ThrowsException<DeserializationException>(() => DeserializerState.CreateRoot(wf));
p.BeginList(ListKind.ListOfBytes, 8);
data[0] = p;
Assert.ThrowsException<DeserializationException>(() => DeserializerState.CreateRoot(wf));
p.BeginList(ListKind.ListOfEmpty, 6400);
data[0] = p;
var d1 = DeserializerState.CreateRoot(wf);
p.BeginList(ListKind.ListOfInts, 2);
data[0] = p;
Assert.ThrowsException<DeserializationException>(() => DeserializerState.CreateRoot(wf));
p.BeginList(ListKind.ListOfLongs, 1);
data[0] = p;
Assert.ThrowsException<DeserializationException>(() => DeserializerState.CreateRoot(wf));
p.BeginList(ListKind.ListOfPointers, 1);
data[0] = p;
Assert.ThrowsException<DeserializationException>(() => DeserializerState.CreateRoot(wf));
p.BeginList(ListKind.ListOfShorts, 4);
data[0] = p;
Assert.ThrowsException<DeserializationException>(() => DeserializerState.CreateRoot(wf));
p.BeginList(ListKind.ListOfStructs, 1);
data[0] = p;
Assert.ThrowsException<DeserializationException>(() => DeserializerState.CreateRoot(wf));
p.SetFarPointer(0, 0, false);
data[0] = p;
Assert.ThrowsException<DeserializationException>(() => DeserializerState.CreateRoot(wf));
p.SetFarPointer(1, 0, false);
data[0] = p;
Assert.ThrowsException<DeserializationException>(() => DeserializerState.CreateRoot(wf));
p.SetFarPointer(0, 1, false);
data[0] = p;
Assert.ThrowsException<DeserializationException>(() => DeserializerState.CreateRoot(wf));
p.SetFarPointer(0, 0, true);
data[0] = p;
Assert.ThrowsException<DeserializationException>(() => DeserializerState.CreateRoot(wf));
var data2 = new ulong[3];
var wf2 = new WireFrame(new Memory<ulong>[] { new Memory<ulong>(data2) });
p.BeginList(ListKind.ListOfStructs, 1);
data2[0] = p;
data2[1] = p;
Assert.ThrowsException<DeserializationException>(() => DeserializerState.CreateRoot(wf2));
p.SetFarPointer(0, 1, true);
data2[0] = p;
data2[1] = 0;
Assert.ThrowsException<DeserializationException>(() => DeserializerState.CreateRoot(wf2));
p.SetFarPointer(0, 1, false);
data2[1] = p;
data2[2] = p;
Assert.ThrowsException<DeserializationException>(() => DeserializerState.CreateRoot(wf2));
p.SetFarPointer(0, 2, true);
data2[0] = p;
Assert.ThrowsException<DeserializationException>(() => DeserializerState.CreateRoot(wf2));
}
[TestMethod]
public void ReadCap1()
{
var mb = MessageBuilder.Create();
var dss = mb.CreateObject<DynamicSerializerState>();
dss.SetStruct(0, 1);
DeserializerState ds = dss;
Assert.ThrowsException<ArgumentOutOfRangeException>(() => ds.ReadCap(-1));
Assert.ThrowsException<InvalidOperationException>(() => ds.ReadCap(0));
}
[TestMethod]
public void ReadCap2()
{
var mb = MessageBuilder.Create();
mb.InitCapTable();
var dss1 = mb.CreateObject<DynamicSerializerState>();
var dss2 = mb.CreateObject<DynamicSerializerState>();
dss2.SetStruct(1, 1);
dss1.SetStruct(0, 2);
dss1.Link(0, dss2);
dss1.LinkToCapability(1, 7);
var d = (DeserializerState)dss1;
Assert.ThrowsException<Rpc.RpcException>(() => d.ReadCap(0));
Assert.ThrowsException<Rpc.RpcException>(() => d.ReadCap(1));
}
[TestMethod]
public void Read1()
{
var mb = MessageBuilder.Create();
var dss = mb.CreateObject<DynamicSerializerState>();
dss.SetStruct(1, 0);
dss.WriteData(0, ulong.MaxValue);
var d = (DeserializerState)dss;
Assert.AreEqual(ushort.MaxValue, d.ReadDataUShort(48));
Assert.ThrowsException<ArgumentOutOfRangeException>(() => d.ReadDataUShort(49));
Assert.AreEqual((ushort)0, d.ReadDataUShort(64));
Assert.IsNotNull(d.StructReadPointer(7));
Assert.ThrowsException<DeserializationException>(() => d.RequireCapList<ITestInterface>());
}
[TestMethod]
public void Read2()
{
var mb = MessageBuilder.Create();
var dss = mb.CreateObject<ListOfBitsSerializer>();
dss.Init(50);
var d = (DeserializerState)dss;
Assert.ThrowsException<DeserializationException>(() => d.ReadDataUInt(0));
Assert.ThrowsException<DeserializationException>(() => d.StructReadPointer(0));
}
[TestMethod]
public void ReadCapList()
{
var mb = MessageBuilder.Create();
mb.InitCapTable();
var dss = mb.CreateObject<DynamicSerializerState>();
dss.SetStruct(0, 1);
var loc = mb.CreateObject<ListOfCapsSerializer<ITestInterface>>();
loc.Init(1);
loc[0] = new TestInterfaceImpl2();
dss.LinkObject(0, loc);
var d = (DeserializerState)dss;
var cl = d.ReadCapList<ITestInterface>(0);
Assert.AreEqual(1, cl.Count);
Assert.IsNotNull(cl[0]);
}
[TestMethod]
public void ReadCap()
{
var dss = DynamicSerializerState.CreateForRpc();
dss.SetStruct(0, 1);
dss.LinkObject<ITestInterface>(0, new TestInterfaceImpl2());
var d = (DeserializerState)dss;
Assert.IsNotNull(d.ReadCap(0));
Assert.IsNotNull(d.ReadCap<ITestInterface>(0));
}
[TestMethod]
public void RequireCap1()
{
var dss = DynamicSerializerState.CreateForRpc();
dss.SetStruct(1, 1);
var d = (DeserializerState)dss;
Assert.ThrowsException<DeserializationException>(() => d.RequireCap<ITestInterface>());
}
[TestMethod]
public void RequireCap2()
{
DeserializerState d = default;
d.Kind = ObjectKind.Capability;
Assert.ThrowsException<InvalidOperationException>(() => d.RequireCap<ITestInterface>());
}
} }
} }

View File

@ -0,0 +1,198 @@
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 EmbargoOnPromisedAnswer()
{
NewDtbdctTestbed().RunTest(Testsuite.EmbargoOnPromisedAnswer);
}
[TestMethod]
public void EmbargoOnImportedCap()
{
NewDtbdctTestbed().RunTest(Testsuite.EmbargoOnImportedCap);
}
[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 PromiseResolveLate()
{
NewDtbdctTestbed().RunTest(Testsuite.PromiseResolveLate);
}
[TestMethod]
public void PromiseResolveError()
{
NewDtbdctTestbed().RunTest(Testsuite.PromiseResolveError);
}
[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);
}
[TestMethod]
public void BootstrapReuse()
{
NewDtbdctTestbed().RunTest(Testsuite.BootstrapReuse);
}
[TestMethod]
public void Ownership1()
{
NewDtbdctTestbed().RunTest(Testsuite.Ownership1);
}
[TestMethod]
public void Ownership2()
{
NewDtbdctTestbed().RunTest(Testsuite.Ownership2);
}
[TestMethod]
public void Ownership3()
{
NewDtbdctTestbed().RunTest(Testsuite.Ownership3);
}
[TestMethod]
public void SillySkeleton()
{
NewDtbdctTestbed().RunTest(Testsuite.SillySkeleton);
}
[TestMethod]
public void ImportReceiverAnswer()
{
NewDtbdctTestbed().RunTest(Testsuite.ImportReceiverAnswer);
}
[TestMethod]
public void ImportReceiverAnswerError()
{
NewDtbdctTestbed().RunTest(Testsuite.ImportReceiverAnswerError);
}
[TestMethod]
public void ImportReceiverAnswerCanceled()
{
NewDtbdctTestbed().RunTest(Testsuite.ImportReceiverCanceled);
}
[TestMethod]
public void ButNoTailCall()
{
NewDtbdctTestbed().RunTest(Testsuite.ButNoTailCall);
}
[TestMethod]
public void SecondIsTailCall()
{
NewDtbdctTestbed().RunTest(Testsuite.SecondIsTailCall);
}
[TestMethod]
public void ReexportSenderPromise()
{
NewDtbdctTestbed().RunTest(Testsuite.ReexportSenderPromise);
}
[TestMethod]
public void CallAfterFinish1()
{
NewDtbdctTestbed().RunTest(Testsuite.CallAfterFinish1);
}
[TestMethod]
public void CallAfterFinish2()
{
NewDtbdctTestbed().RunTest(Testsuite.CallAfterFinish2);
}
[TestMethod]
public void LegacyAccess()
{
NewDtbdctTestbed().RunTest(Testsuite.LegacyAccess);
}
}
}

View File

@ -7,6 +7,7 @@ using System.IO;
namespace Capnp.Net.Runtime.Tests namespace Capnp.Net.Runtime.Tests
{ {
[TestClass] [TestClass]
[TestCategory("Coverage")]
public class DynamicSerializerStateTests public class DynamicSerializerStateTests
{ {
[TestMethod] [TestMethod]

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@ using System.Threading.Tasks;
namespace Capnp.Net.Runtime.Tests namespace Capnp.Net.Runtime.Tests
{ {
[TestClass] [TestClass]
[TestCategory("Coverage")]
public class FramePumpTests public class FramePumpTests
{ {
class MyStruct : SerializerState class MyStruct : SerializerState

View File

@ -1,15 +1,18 @@
using Microsoft.VisualStudio.TestTools.UnitTesting; using Capnp.Rpc;
using Capnp.Util;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
namespace Capnp.Net.Runtime.Tests namespace Capnp.Net.Runtime.Tests
{ {
[TestClass] [TestClass]
public class General public class General: TestBase
{ {
[TestMethod] [TestMethod]
public void AwaitOrderTest() public void AwaitOrderTest()
@ -41,31 +44,174 @@ namespace Capnp.Net.Runtime.Tests
Task.WhenAll(tasks).Wait(); Task.WhenAll(tasks).Wait();
} }
[TestMethod] class PromisedAnswerMock : IPromisedAnswer
public void AwaitOrderTest2()
{ {
int returnCounter = 0; readonly TaskCompletionSource<DeserializerState> _tcs = new TaskCompletionSource<DeserializerState>();
async Task ExpectCount(Task task, int count) public PromisedAnswerMock()
{ {
await task; WhenReturned = _tcs.Task.EnforceAwaitOrder();
Assert.AreEqual(count, returnCounter++);
} }
var tcs = new TaskCompletionSource<int>(); public StrictlyOrderedAwaitTask<DeserializerState> WhenReturned { get; }
var cts = new CancellationTokenSource();
var tasks = public void Return()
from i in Enumerable.Range(0, 100) {
select ExpectCount(tcs.Task.ContinueWith( _tcs.SetResult(default);
t => t, }
cts.Token,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Current), i);
tcs.SetResult(0); public void Cancel()
{
_tcs.SetCanceled();
}
Task.WhenAll(tasks).Wait(); public void Fault()
{
_tcs.SetException(new InvalidOperationException("test fault"));
}
public ConsumedCapability Access(MemberAccessPath access)
{
throw new NotImplementedException();
}
public ConsumedCapability Access(MemberAccessPath access, Task<IDisposable> proxyTask)
{
throw new NotImplementedException();
}
public bool IsTailCall => false;
public void Dispose()
{
}
}
[TestMethod]
public void MakePipelineAwareOnFastPath()
{
var mock = new PromisedAnswerMock();
mock.Return();
for (int i = 0; i < 100; i++)
{
var t = Impatient.MakePipelineAware(mock, _ => (object)null);
Assert.IsTrue(t.IsCompleted);
};
}
[TestMethod]
[TestCategory("Coverage")]
public void SafeJoinCompletedThread()
{
var thread = new Thread(() =>
{
});
thread.Start();
thread.SafeJoin(null, 200);
}
[TestMethod]
[TestCategory("Coverage")]
public void SafeJoinBusyThread()
{
var thread = new Thread(() =>
{
try
{
while (true) ;
}
catch (ThreadInterruptedException)
{
Console.WriteLine("Interrupted");
}
catch (ThreadAbortException)
{
Console.WriteLine("Aborted");
}
});
thread.Start();
thread.SafeJoin(null, 5);
}
[TestMethod]
[TestCategory("Coverage")]
public void SafeJoinSleepingThread()
{
var thread = new Thread(() =>
{
try
{
Thread.Sleep(Timeout.Infinite);
}
catch (ThreadInterruptedException)
{
Console.WriteLine("Interrupted");
}
catch (ThreadAbortException)
{
Console.WriteLine("Aborted");
}
});
thread.Start();
thread.SafeJoin(null, 5);
}
[TestMethod]
[TestCategory("Coverage")]
public void SafeJoinDeadlockedThread()
{
var lk = new object();
lock (lk)
{
var thread = new Thread(() =>
{
try
{
lock (lk)
{
}
}
catch (ThreadInterruptedException)
{
Console.WriteLine("Interrupted");
}
catch (ThreadAbortException)
{
Console.WriteLine("Aborted");
}
});
thread.Start();
thread.SafeJoin(null, 5);
}
}
[TestMethod]
[TestCategory("Coverage")]
public void SafeJoinDefensiveThread()
{
var thread = new Thread(() =>
{
for (; ; )
{
try
{
Thread.Sleep(Timeout.Infinite);
}
catch (ThreadInterruptedException)
{
Console.WriteLine("Interrupted");
}
catch (ThreadAbortException)
{
Console.WriteLine("Aborted");
}
}
});
thread.Start();
thread.SafeJoin(null, 5);
} }
} }
} }

View File

@ -0,0 +1,208 @@
using Capnp.Net.Runtime.Tests.GenImpls;
using Capnp.Rpc;
using Capnp.Util;
using Capnproto_test.Capnp.Test;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Capnp.Net.Runtime.Tests
{
[TestClass]
[TestCategory("Coverage")]
public class ImpatientTests
{
[TestMethod]
public async Task Unwrap()
{
var impl = new TestInterfaceImpl2();
Assert.AreEqual(impl, await impl.Unwrap<ITestInterface>());
using (var proxy = Proxy.Share<ITestInterface>(impl))
using (var reso = ((Proxy)proxy).GetResolvedCapability<ITestInterface>())
{
Assert.AreEqual(((Proxy)proxy).ConsumedCap, ((Proxy)reso).ConsumedCap);
}
Assert.IsNull(await default(ITestInterface).Unwrap());
var tcs = new TaskCompletionSource<ITestInterface>();
tcs.SetResult(null);
Assert.IsNull(await tcs.Task.Eager(true).Unwrap());
var excepted = Task.FromException<ITestInterface>(new InvalidTimeZoneException("So annoying"));
await Assert.ThrowsExceptionAsync<RpcException>(async () => await excepted.Eager(true).Unwrap());
}
[TestMethod]
public async Task MaybeTailCall3()
{
bool flag = false;
SerializerState Fn(int a, int b, int c)
{
Assert.AreEqual(0, a);
Assert.AreEqual(1, b);
Assert.AreEqual(2, c);
flag = true;
return null;
}
var t = Task.FromResult((0, 1, 2));
await Impatient.MaybeTailCall(t, Fn);
Assert.IsTrue(flag);
}
[TestMethod]
public async Task MaybeTailCall4()
{
bool flag = false;
SerializerState Fn(int a, int b, int c, int d)
{
Assert.AreEqual(0, a);
Assert.AreEqual(1, b);
Assert.AreEqual(2, c);
Assert.AreEqual(3, d);
flag = true;
return null;
}
var t = Task.FromResult((0, 1, 2, 3));
await Impatient.MaybeTailCall(t, Fn);
Assert.IsTrue(flag);
}
[TestMethod]
public async Task MaybeTailCall5()
{
bool flag = false;
SerializerState Fn(int a, int b, int c, int d, int e)
{
Assert.AreEqual(0, a);
Assert.AreEqual(1, b);
Assert.AreEqual(2, c);
Assert.AreEqual(3, d);
Assert.AreEqual(4, e);
flag = true;
return null;
}
var t = Task.FromResult((0, 1, 2, 3, 4));
await Impatient.MaybeTailCall(t, Fn);
Assert.IsTrue(flag);
}
[TestMethod]
public async Task MaybeTailCall6()
{
bool flag = false;
SerializerState Fn(int a, int b, int c, int d, int e, int f)
{
Assert.AreEqual(0, a);
Assert.AreEqual(1, b);
Assert.AreEqual(2, c);
Assert.AreEqual(3, d);
Assert.AreEqual(4, e);
Assert.AreEqual(5, f);
flag = true;
return null;
}
var t = Task.FromResult((0, 1, 2, 3, 4, 5));
await Impatient.MaybeTailCall(t, Fn);
Assert.IsTrue(flag);
}
[TestMethod]
public async Task MaybeTailCall7()
{
bool flag = false;
SerializerState Fn(int a, int b, int c, int d, int e, int f, int g)
{
Assert.AreEqual(0, a);
Assert.AreEqual(1, b);
Assert.AreEqual(2, c);
Assert.AreEqual(3, d);
Assert.AreEqual(4, e);
Assert.AreEqual(5, f);
Assert.AreEqual(6, g);
flag = true;
return null;
}
var t = Task.FromResult((0, 1, 2, 3, 4, 5, 6));
await Impatient.MaybeTailCall(t, Fn);
Assert.IsTrue(flag);
}
class PromisedAnswerMock : IPromisedAnswer
{
readonly TaskCompletionSource<DeserializerState> _tcs = new TaskCompletionSource<DeserializerState>();
public StrictlyOrderedAwaitTask<DeserializerState> WhenReturned { get; }
public PromisedAnswerMock()
{
WhenReturned = _tcs.Task.EnforceAwaitOrder();
}
public bool IsTailCall => false;
public ConsumedCapability Access(MemberAccessPath access)
{
throw new NotImplementedException();
}
public ConsumedCapability Access(MemberAccessPath access, Task<IDisposable> proxyTask)
{
throw new NotImplementedException();
}
public void Dispose()
{
}
}
[TestMethod]
public void ObsoleteGetAnswer()
{
#pragma warning disable CS0618
var answer = new PromisedAnswerMock();
Assert.ThrowsException<ArgumentException>(() => Impatient.GetAnswer(Task.FromResult(new object())));
var t = Impatient.MakePipelineAware(answer, _ => _);
Assert.AreEqual(answer, Impatient.GetAnswer(t));
#pragma warning restore CS0618
}
[TestMethod]
public async Task Access()
{
var answer = new PromisedAnswerMock();
async Task AwaitReturn() => await answer.WhenReturned;
var cap = Impatient.Access(AwaitReturn(), new MemberAccessPath(), Task.FromResult<IDisposable>(new TestInterfaceImpl2()));
using (var proxy = new BareProxy(cap))
{
await proxy.WhenResolved;
}
}
[TestMethod]
public void ObsoletePseudoEager()
{
#pragma warning disable CS0618
var task = Task.FromResult<ITestInterface>(new TestInterfaceImpl2());
Assert.IsTrue(task.PseudoEager() is Proxy proxy && proxy.WhenResolved.IsCompleted);
#pragma warning restore CS0618
}
[TestMethod]
public void Eager()
{
var task = Task.FromResult<ITestInterface>(new TestInterfaceImpl2());
Assert.ThrowsException<ArgumentException>(() => task.Eager(false));
Assert.ThrowsException<ArgumentException>(() => task.Eager());
}
}
}

View File

@ -14,6 +14,7 @@ using System.Threading.Tasks.Dataflow;
namespace Capnp.Net.Runtime.Tests namespace Capnp.Net.Runtime.Tests
{ {
[TestClass] [TestClass]
[TestCategory("Coverage")]
public class Interception: TestBase public class Interception: TestBase
{ {
class MyPolicy : IInterceptionPolicy class MyPolicy : IInterceptionPolicy
@ -66,9 +67,8 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait();
var counters = new Counters(); var counters = new Counters();
server.Main = policy.Attach<ITestInterface>(new TestInterfaceImpl(counters)); server.Main = policy.Attach<ITestInterface>(new TestInterfaceImpl(counters));
using (var main = client.GetMain<ITestInterface>()) using (var main = client.GetMain<ITestInterface>())
{ {
@ -77,13 +77,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();
@ -95,6 +95,46 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod]
public void InterceptServerSideRedirectCall()
{
var policy = new MyPolicy("a");
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
//client.WhenConnected.Wait();
var counters = new Counters();
server.Main = policy.Attach<ITestInterface>(new TestInterfaceImpl(counters));
var redirTarget = new TestInterfaceImpl2();
using (var main = client.GetMain<ITestInterface>())
using (redirTarget)
{
var request1 = main.Foo(123, true, default);
var fcc = policy.Calls.ReceiveAsync();
Assert.IsTrue(fcc.Wait(MediumNonDbgTimeout));
var cc = fcc.Result;
Assert.ThrowsException<ArgumentNullException>(() => cc.Bob = null);
cc.Bob = redirTarget;
cc.ForwardToBob();
Assert.IsTrue(policy.Returns.ReceiveAsync().Wait(MediumNonDbgTimeout));
cc.ReturnToAlice();
Assert.IsTrue(request1.Wait(MediumNonDbgTimeout));
Assert.AreEqual("bar", request1.Result);
}
Assert.IsTrue(redirTarget.IsDisposed);
Assert.AreEqual(0, counters.CallCount);
}
}
[TestMethod] [TestMethod]
public void InterceptClientSideModifyCall() public void InterceptClientSideModifyCall()
{ {
@ -105,7 +145,7 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
var counters = new Counters(); var counters = new Counters();
server.Main = new TestInterfaceImpl(counters); server.Main = new TestInterfaceImpl(counters);
@ -116,11 +156,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;
@ -129,15 +169,15 @@ namespace Capnp.Net.Runtime.Tests
var rx = policy.Returns.ReceiveAsync(); var rx = policy.Returns.ReceiveAsync();
// Racing against Bob's answer // Racing against Bob's answer
Assert.IsTrue(cc.State == InterceptionState.ForwardedToBob || rx.IsCompleted); Assert.IsTrue(cc.State == InterceptionState.ForwardedToBob || cc.State == InterceptionState.ReturnedFromBob);
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;
@ -163,7 +203,7 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
var counters = new Counters(); var counters = new Counters();
server.Main = new TestInterfaceImpl(counters); server.Main = new TestInterfaceImpl(counters);
@ -173,7 +213,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;
@ -196,7 +236,7 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
var counters = new Counters(); var counters = new Counters();
server.Main = new TestInterfaceImpl(counters); server.Main = new TestInterfaceImpl(counters);
@ -227,7 +267,7 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
var counters = new Counters(); var counters = new Counters();
server.Main = new TestInterfaceImpl(counters); server.Main = new TestInterfaceImpl(counters);
@ -261,17 +301,17 @@ namespace Capnp.Net.Runtime.Tests
client.WhenConnected.Wait(); client.WhenConnected.Wait();
var counters = new Counters(); var counters = new Counters();
server.Main = new TestInterfaceImpl(counters); server.Main = new TestMoreStuffImpl(counters);
using (var main = policy.Attach(client.GetMain<ITestInterface>())) using (var main = policy.Attach(client.GetMain<ITestMoreStuff>()))
{ {
var request1 = main.Foo(321, false, new CancellationToken(true)); var request1 = main.NeverReturn(new TestInterfaceImpl(new Counters()), new CancellationToken(true));
Assert.IsTrue(policy.Calls.TryReceive(out var cc)); Assert.IsTrue(policy.Calls.TryReceive(out var cc));
Assert.IsFalse(request1.IsCompleted); Assert.IsFalse(request1.IsCompleted);
Assert.IsTrue(cc.CancelFromAlice.IsCancellationRequested); Assert.IsTrue(cc.CancelFromAlice.IsCancellationRequested);
cc.ForwardToBob(); cc.ForwardToBob();
Assert.IsTrue(policy.Returns.ReceiveAsync().Wait(MediumNonDbgTimeout)); Assert.IsTrue(policy.Returns.ReceiveAsync().Wait(MediumNonDbgTimeout), "must return");
Assert.IsTrue(cc.ReturnCanceled); Assert.IsTrue(cc.ReturnCanceled, "must be canceled");
cc.ReturnCanceled = false; cc.ReturnCanceled = false;
cc.Exception = "Cancelled"; cc.Exception = "Cancelled";
@ -283,6 +323,41 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod]
public void InterceptClientSideOverrideFaultedCall()
{
var policy = new MyPolicy("a");
(var server, var client) = SetupClientServerPair();
using (server)
using (client)
{
//client.WhenConnected.Wait();
var counters = new Counters();
server.Main = new TestInterfaceImpl(counters);
using (var main = policy.Attach(client.GetMain<ITestInterface>()))
{
var request1 = main.Bar();
Assert.IsTrue(policy.Calls.TryReceive(out var cc));
Assert.IsFalse(request1.IsCompleted);
cc.ForwardToBob();
Assert.IsTrue(policy.Returns.ReceiveAsync().Wait(MediumNonDbgTimeout));
Assert.IsNotNull(cc.Exception);
cc.ReturnCanceled = false;
cc.Exception = null;
cc.ReturnToAlice();
Assert.ThrowsException<InvalidOperationException>(() => cc.ReturnToAlice());
Assert.IsTrue(request1.IsCompleted);
Assert.IsFalse(request1.IsFaulted);
}
}
}
[TestMethod] [TestMethod]
public void InterceptClientSideRedirectCall() public void InterceptClientSideRedirectCall()
{ {
@ -293,7 +368,7 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
var counters = new Counters(); var counters = new Counters();
server.Main = new TestInterfaceImpl(counters); server.Main = new TestInterfaceImpl(counters);
@ -320,6 +395,44 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod]
public void InterceptClientSideModifyPipelinedCall()
{
var policy = new MyPolicy("a");
(var server, var client) = SetupClientServerPair(TcpRpcTestOptions.ClientTracer);
using (server)
using (client)
{
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
server.Main = impl;
using (var main = policy.Attach(client.GetMain<ITestMoreStuff>()))
{
var req = main.GetNull().Eager().GetCallSequence(0);
Assert.IsTrue(policy.Calls.TryReceive(out var ccGetNull));
Assert.IsTrue(policy.Calls.TryReceive(out var ccGetCallSequence));
ccGetNull.ForwardToBob();
Assert.IsTrue(policy.Returns.ReceiveAsync().Wait(MediumNonDbgTimeout));
ccGetNull.ReturnToAlice();
ccGetCallSequence.Bob = Proxy.Share<ITestMoreStuff>(impl);
ccGetCallSequence.ForwardToBob();
Assert.IsTrue(policy.Returns.ReceiveAsync().Wait(MediumNonDbgTimeout));
ccGetCallSequence.ReturnToAlice();
Assert.IsTrue(req.IsCompleted && !req.IsFaulted && !req.IsCanceled);
Assert.AreEqual(0u, req.Result);
}
}
}
[TestMethod] [TestMethod]
public void InterfaceAndMethodId() public void InterfaceAndMethodId()
{ {
@ -330,7 +443,7 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
var counters = new Counters(); var counters = new Counters();
server.Main = new TestInterfaceImpl(counters); server.Main = new TestInterfaceImpl(counters);
@ -355,7 +468,7 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
var counters = new Counters(); var counters = new Counters();
server.Main = new TestTailCallerImpl(counters); server.Main = new TestTailCallerImpl(counters);
@ -387,7 +500,7 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
var counters = new Counters(); var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters); server.Main = new TestMoreStuffImpl(counters);
@ -427,7 +540,7 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
var counters = new Counters(); var counters = new Counters();
server.Main = policy.Attach<ITestMoreStuff>(new TestMoreStuffImpl(counters)); server.Main = policy.Attach<ITestMoreStuff>(new TestMoreStuffImpl(counters));
@ -484,7 +597,7 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
var counters = new Counters(); var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters); server.Main = new TestMoreStuffImpl(counters);
@ -552,7 +665,7 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
var counters = new Counters(); var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters); server.Main = new TestMoreStuffImpl(counters);
@ -592,7 +705,7 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
server.Main = implAc; server.Main = implAc;
using (var main = client.GetMain<ITestInterface>()) using (var main = client.GetMain<ITestInterface>())
@ -620,5 +733,35 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod]
public void AttachWrongUse()
{
var impl = new TestInterfaceImpl2();
Assert.ThrowsException<ArgumentNullException>(() => default(IInterceptionPolicy).Attach(impl));
Assert.ThrowsException<ArgumentNullException>(() => new MyPolicy("x").Attach(default(ITestInterface)));
}
[TestMethod]
public void DetachWrongUse()
{
var impl = new TestInterfaceImpl2();
Assert.ThrowsException<ArgumentNullException>(() => default(IInterceptionPolicy).Detach(impl));
Assert.ThrowsException<ArgumentNullException>(() => new MyPolicy("x").Detach(default(ITestInterface)));
}
[TestMethod]
public void AttachDetach()
{
ConsumedCapability GetCap(object obj) => ((Proxy)obj).ConsumedCap;
var a = new MyPolicy("a");
var b = new MyPolicy("b");
var c = new MyPolicy("c");
var proxy = Proxy.Share<ITestInterface>(new TestInterfaceImpl2());
var attached = a.Attach(b.Attach(c.Attach(proxy)));
Assert.AreEqual(GetCap(attached), GetCap(b.Attach(attached)));
var detached = c.Detach(a.Detach(b.Detach(attached)));
Assert.AreEqual(GetCap(proxy), GetCap(detached));
}
} }
} }

View File

@ -0,0 +1,253 @@
using Capnp.Net.Runtime.Tests.GenImpls;
using Capnp.Rpc;
using Capnproto_test.Capnp.Test;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
namespace Capnp.Net.Runtime.Tests
{
[TestClass]
[TestCategory("Coverage")]
public class LocalRpc: TestBase
{
[TestMethod]
public void DeferredLocalAnswer()
{
var tcs = new TaskCompletionSource<int>();
var impl = new TestPipelineImpl2(tcs.Task);
var bproxy = BareProxy.FromImpl(impl);
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);
}
}
[TestMethod]
public void Embargo()
{
NewLocalTestbed().RunTest(Testsuite.EmbargoOnPromisedAnswer);
}
[TestMethod]
public void EmbargoError()
{
NewLocalTestbed().RunTest(Testsuite.EmbargoError);
}
[TestMethod]
public void EmbargoNull()
{
NewLocalTestbed().RunTest(Testsuite.EmbargoNull);
}
[TestMethod]
public void CallBrokenPromise()
{
NewLocalTestbed().RunTest(Testsuite.CallBrokenPromise);
}
[TestMethod]
public void TailCall()
{
NewLocalTestbed().RunTest(Testsuite.TailCall);
}
[TestMethod]
public void SendTwice()
{
NewLocalTestbed().RunTest(Testsuite.SendTwice);
}
[TestMethod]
public void Cancel()
{
NewLocalTestbed().RunTest(Testsuite.Cancel);
}
[TestMethod]
public void RetainAndRelease()
{
NewLocalTestbed().RunTest(Testsuite.RetainAndRelease);
}
[TestMethod]
public void PromiseResolve()
{
NewLocalTestbed().RunTest(Testsuite.PromiseResolve);
}
[TestMethod]
public void Cancelation()
{
NewLocalTestbed().RunTest(Testsuite.Cancelation);
}
[TestMethod]
public void ReleaseOnCancel()
{
NewLocalTestbed().RunTest(Testsuite.ReleaseOnCancel);
}
[TestMethod]
public void Release()
{
NewLocalTestbed().RunTest(Testsuite.Release);
}
[TestMethod]
public void Pipeline()
{
NewLocalTestbed().RunTest(Testsuite.Pipeline);
}
[TestMethod]
public void Basic()
{
NewLocalTestbed().RunTest(Testsuite.Basic);
}
[TestMethod]
public void Ownership1()
{
NewLocalTestbed().RunTest(Testsuite.Ownership1);
}
[TestMethod]
public void Ownership2()
{
NewLocalTestbed().RunTest(Testsuite.Ownership2);
}
[TestMethod]
public void Ownership3()
{
NewLocalTestbed().RunTest(Testsuite.Ownership3);
}
[TestMethod]
public void ImportReceiverAnswer()
{
NewLocalTestbed().RunTest(Testsuite.Ownership3);
}
[TestMethod]
public void LegacyAccess()
{
NewLocalTestbed().RunTest(Testsuite.LegacyAccess);
}
[TestMethod]
public void EagerRace()
{
var impl = new TestMoreStuffImpl(new Counters());
var tcs = new TaskCompletionSource<ITestMoreStuff>();
using (var promise = tcs.Task.Eager(true))
using (var cts = new CancellationTokenSource())
{
var bb = new BufferBlock<Task<uint>>();
int counter = 0;
void Generator()
{
while (!cts.IsCancellationRequested)
{
bb.Post(promise.GetCallSequence((uint)Volatile.Read(ref counter)));
Interlocked.Increment(ref counter);
}
bb.Complete();
}
async Task Verifier()
{
uint i = 0;
while (true)
{
Task<uint> t;
try
{
t = await bb.ReceiveAsync();
}
catch (InvalidOperationException)
{
break;
}
uint j = await t;
Assert.AreEqual(i, j);
i++;
}
}
var genTask = Task.Run(() => Generator());
var verTask = Verifier();
SpinWait.SpinUntil(() => Volatile.Read(ref counter) >= 100);
Task.Run(() => tcs.SetResult(impl));
cts.Cancel();
Assert.IsTrue(genTask.Wait(MediumNonDbgTimeout));
Assert.IsTrue(verTask.Wait(MediumNonDbgTimeout));
}
}
[TestMethod]
public void AwaitNoDeadlock()
{
for (int i = 0; i < 100; i++)
{
var tcs1 = new TaskCompletionSource<int>();
var tcs2 = new TaskCompletionSource<int>();
var t1 = Capnp.Util.StrictlyOrderedTaskExtensions.EnforceAwaitOrder(tcs1.Task);
var t2 = Capnp.Util.StrictlyOrderedTaskExtensions.EnforceAwaitOrder(tcs2.Task);
async Task Wait1()
{
await t1;
await t2;
}
async Task Wait2()
{
await t2;
await t1;
}
var w1 = Wait1();
var w2 = Wait2();
Task.Run(() => tcs1.SetResult(0));
Task.Run(() => tcs2.SetResult(0));
Assert.IsTrue(Task.WaitAll(new Task[] { w1, w2 }, MediumNonDbgTimeout));
}
}
[TestMethod]
public void DisposedProxy()
{
var b = new BareProxy();
Assert.ThrowsException<ArgumentNullException>(() => b.Bind(null));
var impl = new TestInterfaceImpl2();
var proxy = Proxy.Share<ITestInterface>(impl);
var p = (Proxy)proxy;
Assert.ThrowsException<InvalidOperationException>(() => p.Bind(p.ConsumedCap));
Assert.IsFalse(p.IsDisposed);
proxy.Dispose();
Assert.IsTrue(p.IsDisposed);
Assert.ThrowsException<ObjectDisposedException>(() => p.ConsumedCap);
var t = proxy.Foo(123, true);
Assert.IsTrue(Assert.ThrowsExceptionAsync<ObjectDisposedException>(() => t).Wait(MediumNonDbgTimeout));
}
}
}

View File

@ -3,6 +3,7 @@
namespace Capnp.Net.Runtime.Tests namespace Capnp.Net.Runtime.Tests
{ {
[TestClass] [TestClass]
[TestCategory("Coverage")]
public class MessageBuilderTests public class MessageBuilderTests
{ {
class Struct2D0P : SerializerState class Struct2D0P : SerializerState

View File

@ -420,18 +420,53 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
public void Dispose() public void Dispose()
{ {
_tcs?.SetResult(0); _tcs?.TrySetResult(0);
Assert.IsFalse(IsDisposed);
IsDisposed = true;
DisposeCallStack = Environment.StackTrace;
} }
public string DisposeCallStack { get; private set; }
public bool IsDisposed { get; private set; }
public virtual Task<string> Foo(uint i, bool j, CancellationToken cancellationToken) public virtual Task<string> Foo(uint i, bool j, CancellationToken cancellationToken)
{ {
Interlocked.Increment(ref _counters.CallCount); Interlocked.Increment(ref _counters.CallCount);
cancellationToken.ThrowIfCancellationRequested();
Assert.AreEqual(123u, i); Assert.AreEqual(123u, i);
Assert.IsTrue(j); Assert.IsTrue(j);
return Task.FromResult("foo"); return Task.FromResult("foo");
} }
} }
class TestInterfaceImpl2 : ITestInterface
{
public Task Bar(CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task Baz(TestAllTypes s, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public void Dispose()
{
Assert.IsFalse(IsDisposed);
IsDisposed = true;
}
public bool IsDisposed { get; private set; }
public Task<string> Foo(uint i, bool j, CancellationToken cancellationToken_ = default)
{
Assert.AreEqual(123u, i);
Assert.IsTrue(j);
return Task.FromResult("bar");
}
}
#endregion TestInterface #endregion TestInterface
#region TestExtends #region TestExtends
@ -485,20 +520,26 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
public async Task<(string, TestPipeline.AnyBox)> GetAnyCap(uint n, BareProxy inCap, CancellationToken cancellationToken_) public async Task<(string, TestPipeline.AnyBox)> GetAnyCap(uint n, BareProxy inCap, CancellationToken cancellationToken_)
{ {
Interlocked.Increment(ref _counters.CallCount); using (inCap)
Assert.AreEqual(234u, n); {
var s = await inCap.Cast<ITestInterface>(true).Foo(123, true, cancellationToken_); Interlocked.Increment(ref _counters.CallCount);
Assert.AreEqual("foo", s); Assert.AreEqual(234u, n);
return ("bar", new TestPipeline.AnyBox() { Cap = BareProxy.FromImpl(new TestExtendsImpl(_counters)) }); var s = await inCap.Cast<ITestInterface>(true).Foo(123, true, cancellationToken_);
Assert.AreEqual("foo", s);
return ("bar", new TestPipeline.AnyBox() { Cap = BareProxy.FromImpl(new TestExtendsImpl(_counters)) });
}
} }
public async Task<(string, TestPipeline.Box)> GetCap(uint n, ITestInterface inCap, CancellationToken cancellationToken_) public async Task<(string, TestPipeline.Box)> GetCap(uint n, ITestInterface inCap, CancellationToken cancellationToken_)
{ {
Interlocked.Increment(ref _counters.CallCount); using (inCap)
Assert.AreEqual(234u, n); {
var s = await inCap.Foo(123, true, cancellationToken_); Interlocked.Increment(ref _counters.CallCount);
Assert.AreEqual("foo", s); Assert.AreEqual(234u, n);
return ("bar", new TestPipeline.Box() { Cap = new TestExtendsImpl(_counters) }); var s = await inCap.Foo(123, true, cancellationToken_);
Assert.AreEqual("foo", s);
return ("bar", new TestPipeline.Box() { Cap = new TestExtendsImpl(_counters) });
}
} }
public Task TestPointers(ITestInterface cap, object obj, IReadOnlyList<ITestInterface> list, CancellationToken cancellationToken_) public Task TestPointers(ITestInterface cap, object obj, IReadOnlyList<ITestInterface> list, CancellationToken cancellationToken_)
@ -506,24 +547,61 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
throw new NotImplementedException(); throw new NotImplementedException();
} }
} }
class TestPipelineImpl2 : ITestPipeline
{
readonly Task _deblock;
readonly TestInterfaceImpl2 _timpl2;
public TestPipelineImpl2(Task deblock)
{
_deblock = deblock;
_timpl2 = new TestInterfaceImpl2();
}
public void Dispose()
{
}
public bool IsChildCapDisposed => _timpl2.IsDisposed;
public Task<(string, TestPipeline.AnyBox)> GetAnyCap(uint n, BareProxy inCap, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public async Task<(string, TestPipeline.Box)> GetCap(uint n, ITestInterface inCap, CancellationToken cancellationToken_ = default)
{
using (inCap)
{
await _deblock;
return ("hello", new TestPipeline.Box() { Cap = _timpl2 });
}
}
public Task TestPointers(ITestInterface cap, object obj, IReadOnlyList<ITestInterface> list, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
}
#endregion TestPipeline #endregion TestPipeline
#region TestCallOrder #region TestCallOrder
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}");
} }
} }
@ -531,26 +609,24 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
{ {
lock (_lock) lock (_lock)
{ {
return Task.FromResult(Count++); Assert.AreEqual(expected, _counter);
return Task.FromResult(_counter++);
}
}
public uint Count
{
get
{
lock (_lock)
{
return _counter;
}
} }
} }
} }
#endregion TestCallOrder #endregion TestCallOrder
#region TestTailCaller
class TestTailCaller : ITestTailCaller
{
public void Dispose()
{
}
public Task<TestTailCallee.TailResult> Foo(int i, ITestTailCallee callee, CancellationToken cancellationToken_)
{
return callee.Foo(i, "from TestTailCaller", cancellationToken_);
}
}
#endregion TestTailCaller
#region TestTailCaller #region TestTailCaller
class TestTailCallerImpl : ITestTailCaller class TestTailCallerImpl : ITestTailCaller
{ {
@ -575,6 +651,107 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
} }
} }
} }
class TestTailCallerImpl2 : ITestTailCaller
{
ITestCallOrder _keeper;
public TestTailCallerImpl2()
{
}
public void Dispose()
{
_keeper?.Dispose();
}
public Task<TestTailCallee.TailResult> Foo(int i, ITestTailCallee callee, CancellationToken cancellationToken_)
{
using (callee)
{
if (_keeper == null)
{
var task = callee.Foo(i, "from TestTailCaller", cancellationToken_);
_keeper = task.C();
return task;
}
else
{
return Task.FromResult(
new TestTailCallee.TailResult()
{
C = _keeper
});
}
}
}
}
class TestTailCallerImpl3 : ITestTailCaller
{
public TestTailCallerImpl3()
{
}
public void Dispose()
{
}
public Task<TestTailCallee.TailResult> Foo(int i, ITestTailCallee callee, CancellationToken cancellationToken_)
{
using (callee)
{
var task1 = callee.Foo(i, "from TestTailCaller 1", cancellationToken_);
async void FinishTask()
{
var r = await task1;
r.C.Dispose();
}
FinishTask();
var task2 = callee.Foo(i, "from TestTailCaller 2", cancellationToken_);
async void AssertIsTailCall()
{
try
{
await task2;
Assert.Fail("Not a tail call");
}
catch (NoResultsException)
{
}
}
AssertIsTailCall();
return task2;
}
}
}
class TestTailCallerImpl4 : ITestTailCaller
{
public TestTailCallerImpl4()
{
}
public void Dispose()
{
}
public async Task<TestTailCallee.TailResult> Foo(int i, ITestTailCallee callee, CancellationToken cancellationToken_)
{
await Task.Yield();
using (callee)
{
return await callee.Foo(i, "from TestTailCaller", cancellationToken_);
}
}
}
#endregion TestTailCaller #endregion TestTailCaller
#region TestTailCallee #region TestTailCallee
@ -589,10 +766,15 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
public void Dispose() public void Dispose()
{ {
IsDisposed = true;
} }
public bool IsDisposed { get; private set; }
public Task<TestTailCallee.TailResult> Foo(int i, string t, CancellationToken cancellationToken_) public Task<TestTailCallee.TailResult> Foo(int i, string t, CancellationToken cancellationToken_)
{ {
Assert.IsFalse(IsDisposed);
Interlocked.Increment(ref _counters.CallCount); Interlocked.Increment(ref _counters.CallCount);
var result = new TestTailCallee.TailResult() var result = new TestTailCallee.TailResult()
@ -638,9 +820,12 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
public async Task<string> CallFooWhenResolved(ITestInterface cap, CancellationToken cancellationToken_) public async Task<string> CallFooWhenResolved(ITestInterface cap, CancellationToken cancellationToken_)
{ {
Interlocked.Increment(ref _counters.CallCount); Interlocked.Increment(ref _counters.CallCount);
await ((Proxy)cap).WhenResolved; using (cap)
string s = await cap.Foo(123, true, cancellationToken_); {
Assert.AreEqual("foo", s); await ((Proxy)cap).WhenResolved;
string s = await cap.Foo(123, true, cancellationToken_);
Assert.AreEqual("foo", s);
}
return "bar"; return "bar";
} }
@ -656,6 +841,7 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
public void Dispose() public void Dispose()
{ {
ClientToHold?.Dispose(); ClientToHold?.Dispose();
ClientToHold = null;
} }
public Task<ITestCallOrder> Echo(ITestCallOrder cap, CancellationToken cancellationToken_) public Task<ITestCallOrder> Echo(ITestCallOrder cap, CancellationToken cancellationToken_)
@ -687,7 +873,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_)
@ -735,6 +921,399 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
} }
} }
class TestMoreStuffImpl2 : ITestMoreStuff
{
readonly TaskCompletionSource<ITestCallOrder> _echo = new TaskCompletionSource<ITestCallOrder>();
readonly TaskCompletionSource<ITestInterface> _held = new TaskCompletionSource<ITestInterface>();
ITestCallOrder _cap;
int _callCount;
public TestMoreStuffImpl2()
{
}
public async Task<string> CallFoo(ITestInterface cap, CancellationToken cancellationToken_)
{
using (cap)
{
string s = await cap.Foo(123, true, cancellationToken_);
Assert.AreEqual("foo", s);
}
return "bar";
}
public Task<string> CallFooWhenResolved(ITestInterface cap, CancellationToken cancellationToken_)
{
throw new NotImplementedException();
}
public Task<string> CallHeld(CancellationToken cancellationToken_)
{
throw new NotImplementedException();
}
public void Dispose()
{
}
public Task<ITestCallOrder> Echo(ITestCallOrder cap, CancellationToken cancellationToken_)
{
_cap = cap;
return Task.FromResult(_echo.Task.Eager(true));
}
public void EnableEcho()
{
_echo.SetResult(_cap);
}
public Task ExpectCancel(ITestInterface cap, CancellationToken cancellationToken_)
{
throw new NotImplementedException();
}
public Task<uint> GetCallSequence(uint expected, CancellationToken cancellationToken_)
{
return Task.FromResult((uint)(Interlocked.Increment(ref _callCount) - 1));
}
public Task<string> GetEnormousString(CancellationToken cancellationToken_)
{
return Task.FromResult(new string(new char[100000000]));
}
public Task<ITestHandle> GetHandle(CancellationToken cancellationToken_)
{
throw new NotImplementedException();
}
public Task<ITestInterface> GetHeld(CancellationToken cancellationToken_)
{
return _held.Task;
}
public Task<ITestMoreStuff> GetNull(CancellationToken cancellationToken_)
{
return Task.FromResult(default(ITestMoreStuff));
}
public async Task Hold(ITestInterface cap, CancellationToken cancellationToken_)
{
try
{
var unwrapped = await cap.Unwrap();
_held.SetResult(unwrapped);
}
catch (System.Exception exception) when (exception.Message == new TaskCanceledException().Message)
{
_held.SetCanceled();
}
catch (System.Exception exception)
{
_held.SetException(exception);
}
}
public Task<(string, string)> MethodWithDefaults(string a, uint b, string c, CancellationToken cancellationToken_)
{
throw new NotImplementedException();
}
public Task MethodWithNullDefault(string a, ITestInterface b, CancellationToken cancellationToken_)
{
throw new NotImplementedException();
}
public Task<ITestInterface> NeverReturn(ITestInterface cap, CancellationToken cancellationToken_)
{
throw new NotImplementedException();
}
}
class TestMoreStuffImpl3 : ITestMoreStuff, ITestCallOrder
{
readonly TaskCompletionSource<ITestInterface> _heldCap = new TaskCompletionSource<ITestInterface>();
public Task<string> CallFoo(ITestInterface cap, CancellationToken cancellationToken_ = default)
{
using (cap)
{
return cap.Foo(123, true);
}
}
public Task<string> CallFooWhenResolved(ITestInterface Cap, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task<string> CallHeld(CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public async void Dispose()
{
using (var cap = await _heldCap.Task)
{
}
}
int _echoCounter;
public Task<ITestCallOrder> Echo(ITestCallOrder cap, CancellationToken cancellationToken_ = default)
{
if (_echoCounter++ < 20)
{
return Task.FromResult(((Proxy)cap).Cast<ITestMoreStuff>(false).Echo(cap).Eager());
}
else
{
return Task.FromResult(cap);
}
}
public Task ExpectCancel(ITestInterface Cap, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
uint _counter;
public Task<uint> GetCallSequence(uint expected, CancellationToken cancellationToken_ = default)
{
Assert.AreEqual(_counter, expected);
return Task.FromResult(_counter++);
}
public Task<string> GetEnormousString(CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task<ITestHandle> GetHandle(CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public async Task<ITestInterface> GetHeld(CancellationToken cancellationToken_ = default)
{
return await _heldCap.Task;
}
public Task<ITestMoreStuff> GetNull(CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task Hold(ITestInterface Cap, CancellationToken cancellationToken_ = default)
{
_heldCap.SetResult(Cap);
return Task.CompletedTask;
}
public Task<(string, string)> MethodWithDefaults(string A, uint B, string C, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task MethodWithNullDefault(string A, ITestInterface B, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task<ITestInterface> NeverReturn(ITestInterface Cap, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
}
class TestMoreStuffImpl4 : ITestMoreStuff, ITestCallOrder
{
readonly TaskCompletionSource<ITestInterface> _heldCap = new TaskCompletionSource<ITestInterface>();
public Task<string> CallFoo(ITestInterface cap, CancellationToken cancellationToken_ = default)
{
using (cap)
{
return cap.Foo(123, true);
}
}
public Task<string> CallFooWhenResolved(ITestInterface Cap, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task<string> CallHeld(CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public async void Dispose()
{
using (var cap = await _heldCap.Task)
{
}
}
public Task<ITestCallOrder> Echo(ITestCallOrder cap, CancellationToken cancellationToken_ = default)
{
using (var target = ((Proxy)cap).Cast<ITestMoreStuff>(false))
{
return Task.FromResult(target.Echo(cap).Eager());
}
}
public Task ExpectCancel(ITestInterface Cap, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
uint _counter;
public Task<uint> GetCallSequence(uint expected, CancellationToken cancellationToken_ = default)
{
Assert.AreEqual(_counter, expected);
return Task.FromResult(_counter++);
}
public Task<string> GetEnormousString(CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task<ITestHandle> GetHandle(CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task<ITestInterface> GetHeld(CancellationToken cancellationToken_ = default)
{
return Task.FromResult(_heldCap.Task.Eager(true));
}
public Task<ITestMoreStuff> GetNull(CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task Hold(ITestInterface Cap, CancellationToken cancellationToken_ = default)
{
_heldCap.SetResult(Cap);
return Task.CompletedTask;
}
public Task<(string, string)> MethodWithDefaults(string A, uint B, string C, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task MethodWithNullDefault(string A, ITestInterface B, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task<ITestInterface> NeverReturn(ITestInterface Cap, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
}
class TestMoreStuffImpl5 : ITestMoreStuff, ITestCallOrder
{
readonly TaskCompletionSource<ITestInterface> _heldCap = new TaskCompletionSource<ITestInterface>();
public Task<string> CallFoo(ITestInterface cap, CancellationToken cancellationToken_ = default)
{
using (cap)
{
return cap.Foo(123, true);
}
}
public Task<string> CallFooWhenResolved(ITestInterface Cap, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task<string> CallHeld(CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public async void Dispose()
{
using (var cap = await _heldCap.Task)
{
}
}
TaskCompletionSource<int> _echoEnabled = new TaskCompletionSource<int>();
public void EnableEcho() => _echoEnabled.SetResult(0);
public async Task<ITestCallOrder> Echo(ITestCallOrder cap, CancellationToken cancellationToken_ = default)
{
await _echoEnabled.Task;
return cap;
}
public Task ExpectCancel(ITestInterface Cap, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
uint _counter;
public Task<uint> GetCallSequence(uint expected, CancellationToken cancellationToken_ = default)
{
Assert.AreEqual(_counter, expected);
return Task.FromResult(_counter++);
}
public Task<string> GetEnormousString(CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task<ITestHandle> GetHandle(CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task<ITestInterface> GetHeld(CancellationToken cancellationToken_ = default)
{
return Task.FromResult(_heldCap.Task.Eager(true));
}
public Task<ITestMoreStuff> GetNull(CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task Hold(ITestInterface Cap, CancellationToken cancellationToken_ = default)
{
_heldCap.SetResult(Cap);
return Task.CompletedTask;
}
public Task<(string, string)> MethodWithDefaults(string A, uint B, string C, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task MethodWithNullDefault(string A, ITestInterface B, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
public Task<ITestInterface> NeverReturn(ITestInterface Cap, CancellationToken cancellationToken_ = default)
{
throw new NotImplementedException();
}
}
#endregion TestMoreStuff #endregion TestMoreStuff
#region TestHandle #region TestHandle

View File

@ -4,7 +4,7 @@ using Capnp.Rpc;
namespace Capnp.Net.Runtime.Tests namespace Capnp.Net.Runtime.Tests
{ {
class ProvidedCapabilityMock : Skeleton class ProvidedCapabilityMock : RefCountingSkeleton
{ {
readonly TaskCompletionSource<(ulong, ushort, DeserializerState, CancellationToken)> readonly TaskCompletionSource<(ulong, ushort, DeserializerState, CancellationToken)>
_call = new TaskCompletionSource<(ulong, ushort, DeserializerState, CancellationToken)>(); _call = new TaskCompletionSource<(ulong, ushort, DeserializerState, CancellationToken)>();

View File

@ -6,7 +6,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Capnp.Net.Runtime.Tests namespace Capnp.Net.Runtime.Tests
{ {
class ProvidedCapabilityMultiCallMock : Skeleton class ProvidedCapabilityMultiCallMock : RefCountingSkeleton
{ {
readonly BufferBlock<TestCallContext> _ccs = new BufferBlock<TestCallContext>(); readonly BufferBlock<TestCallContext> _ccs = new BufferBlock<TestCallContext>();

View File

@ -1,9 +1,12 @@
using Capnp.Rpc; using Capnp.Rpc;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
namespace Capnp.Net.Runtime.Tests namespace Capnp.Net.Runtime.Tests
{ {
[TestClass] [TestClass]
[TestCategory("Coverage")]
public class RpcSchemaTests public class RpcSchemaTests
{ {
[TestMethod] [TestMethod]
@ -365,5 +368,406 @@ namespace Capnp.Net.Runtime.Tests
Assert.AreEqual("reason", r.Unimplemented.Resolve.Exception.Reason); Assert.AreEqual("reason", r.Unimplemented.Resolve.Exception.Reason);
} }
} }
void ConstructReconstructTest<TD, TW>(Func<TD> construct, Action<TD> verify)
where TD : class, ICapnpSerializable, new()
where TW: SerializerState, new()
{
var obj = construct();
var mb = MessageBuilder.Create();
var root = mb.BuildRoot<TW>();
obj.Serialize(root);
using (var tr = new FrameTracing.RpcFrameTracer(Console.Out))
{
tr.TraceFrame(FrameTracing.FrameDirection.Tx, mb.Frame);
}
var d = (DeserializerState)root;
var obj2 = new TD();
obj2.Deserialize(d);
verify(obj2);
}
[TestMethod]
public void DcMessageAbort()
{
ConstructReconstructTest<Message, Message.WRITER>(
() => new Message()
{
Abort = new Rpc.Exception()
{
Reason = "problem"
}
},
msg =>
{
Assert.IsNotNull(msg.Abort);
Assert.IsNull(msg.Accept);
Assert.AreEqual("problem", msg.Abort.Reason);
}
);
}
[TestMethod]
public void DcMessageAccept()
{
ConstructReconstructTest<Message, Message.WRITER>(
() => new Message()
{
Accept = new Accept()
{
QuestionId = 123u,
Embargo = true
}
},
msg =>
{
Assert.IsNotNull(msg.Accept);
Assert.AreEqual(123u, msg.Accept.QuestionId);
Assert.IsTrue(msg.Accept.Embargo);
}
);
}
[TestMethod]
public void DcMessageUnimplementedJoin()
{
ConstructReconstructTest<Message, Message.WRITER>(
() => new Message()
{
Unimplemented = new Message()
{
Join = new Join()
{
QuestionId = 456u,
Target = new MessageTarget()
{
ImportedCap = 789u
}
}
}
},
msg =>
{
Assert.IsNotNull(msg.Unimplemented);
Assert.IsNotNull(msg.Unimplemented.Join);
Assert.AreEqual(456u, msg.Unimplemented.Join.QuestionId);
Assert.IsNotNull(msg.Unimplemented.Join.Target);
Assert.AreEqual(789u, msg.Unimplemented.Join.Target.ImportedCap);
}
);
}
[TestMethod]
public void DcMessageCall()
{
ConstructReconstructTest<Message, Message.WRITER>(
() => new Message()
{
Call = new Call()
{
AllowThirdPartyTailCall = true,
InterfaceId = 0x12345678abcdef,
MethodId = 0x5555,
Params = new Payload()
{
CapTable = new List<CapDescriptor>()
{
new CapDescriptor()
{
ReceiverAnswer = new PromisedAnswer()
{
QuestionId = 42u,
Transform = new List<PromisedAnswer.Op>()
{
new PromisedAnswer.Op()
{
GetPointerField = 3
}
}
},
},
new CapDescriptor()
{
ReceiverHosted = 7u
},
new CapDescriptor()
{
SenderHosted = 8u
},
new CapDescriptor()
{
SenderPromise = 9u
},
new CapDescriptor()
{
ThirdPartyHosted = new ThirdPartyCapDescriptor()
{
VineId = 10u
}
}
}
},
QuestionId = 57u,
SendResultsTo = new Call.sendResultsTo()
{
which = Call.sendResultsTo.WHICH.Caller
},
Target = new MessageTarget()
{
PromisedAnswer = new PromisedAnswer()
{
QuestionId = 12u
}
}
}
},
msg =>
{
Assert.IsNotNull(msg.Call);
Assert.IsTrue(msg.Call.AllowThirdPartyTailCall);
Assert.AreEqual(0x12345678abcdeful, msg.Call.InterfaceId);
Assert.AreEqual(0x5555u, msg.Call.MethodId);
Assert.IsNotNull(msg.Call.Params);
Assert.IsNotNull(msg.Call.Params.CapTable);
Assert.AreEqual(5, msg.Call.Params.CapTable.Count);
Assert.IsNotNull(msg.Call.Params.CapTable[0].ReceiverAnswer);
Assert.AreEqual(42u, msg.Call.Params.CapTable[0].ReceiverAnswer.QuestionId);
Assert.IsNotNull(msg.Call.Params.CapTable[0].ReceiverAnswer.Transform);
Assert.AreEqual(1, msg.Call.Params.CapTable[0].ReceiverAnswer.Transform.Count);
Assert.AreEqual((ushort)3, msg.Call.Params.CapTable[0].ReceiverAnswer.Transform[0].GetPointerField);
Assert.AreEqual(7u, msg.Call.Params.CapTable[1].ReceiverHosted);
Assert.AreEqual(8u, msg.Call.Params.CapTable[2].SenderHosted);
Assert.AreEqual(9u, msg.Call.Params.CapTable[3].SenderPromise);
Assert.IsNotNull(msg.Call.Params.CapTable[4].ThirdPartyHosted);
Assert.AreEqual(10u, msg.Call.Params.CapTable[4].ThirdPartyHosted.VineId);
Assert.AreEqual(57u, msg.Call.QuestionId);
Assert.IsNotNull(msg.Call.SendResultsTo);
Assert.AreEqual(Call.sendResultsTo.WHICH.Caller, msg.Call.SendResultsTo.which);
Assert.IsNotNull(msg.Call.Target);
Assert.IsNotNull(msg.Call.Target.PromisedAnswer);
Assert.AreEqual(12u, msg.Call.Target.PromisedAnswer.QuestionId);
}
);
}
[TestMethod]
public void DcMessageReturnResults()
{
ConstructReconstructTest<Message, Message.WRITER>(
() => new Message()
{
Return = new Return()
{
AnswerId = 123u,
ReleaseParamCaps = false,
Results = new Payload()
{
CapTable = new List<CapDescriptor>()
}
}
},
msg =>
{
Assert.IsNotNull(msg.Return);
Assert.AreEqual(123u, msg.Return.AnswerId);
Assert.IsFalse(msg.Return.ReleaseParamCaps);
Assert.IsNotNull(msg.Return.Results);
Assert.IsNotNull(msg.Return.Results.CapTable);
Assert.AreEqual(0, msg.Return.Results.CapTable.Count);
}
);
}
[TestMethod]
public void DcMessageReturnException()
{
ConstructReconstructTest<Message, Message.WRITER>(
() => new Message()
{
Return = new Return()
{
AnswerId = 123u,
ReleaseParamCaps = true,
Exception = new Rpc.Exception()
{
Reason = "bad",
TheType = Rpc.Exception.Type.overloaded
}
}
},
msg =>
{
Assert.IsNotNull(msg.Return);
Assert.AreEqual(123u, msg.Return.AnswerId);
Assert.IsTrue(msg.Return.ReleaseParamCaps);
Assert.IsNotNull(msg.Return.Exception);
Assert.AreEqual("bad", msg.Return.Exception.Reason);
Assert.AreEqual(Rpc.Exception.Type.overloaded, msg.Return.Exception.TheType);
}
);
}
[TestMethod]
public void DcMessageReturnTakeFromOtherQuestion()
{
ConstructReconstructTest<Message, Message.WRITER>(
() => new Message()
{
Return = new Return()
{
AnswerId = 123u,
ReleaseParamCaps = true,
TakeFromOtherQuestion = 321u
}
},
msg =>
{
Assert.IsNotNull(msg.Return);
Assert.AreEqual(123u, msg.Return.AnswerId);
Assert.IsTrue(msg.Return.ReleaseParamCaps);
Assert.AreEqual(321u, msg.Return.TakeFromOtherQuestion);
}
);
}
[TestMethod]
public void DcMessageFinish()
{
ConstructReconstructTest<Message, Message.WRITER>(
() => new Message()
{
Finish = new Finish()
{
QuestionId = 321u,
ReleaseResultCaps = true
}
},
msg =>
{
Assert.IsNotNull(msg.Finish);
Assert.AreEqual(321u, msg.Finish.QuestionId);
Assert.IsTrue(msg.Finish.ReleaseResultCaps);
}
);
}
[TestMethod]
public void DcMessageResolve()
{
ConstructReconstructTest<Message, Message.WRITER>(
() => new Message()
{
Resolve = new Resolve()
{
PromiseId = 555u,
Exception = new Rpc.Exception()
{
Reason = "error",
TheType = Rpc.Exception.Type.failed
}
}
},
msg =>
{
Assert.IsNotNull(msg.Resolve);
Assert.AreEqual(555u, msg.Resolve.PromiseId);
Assert.AreEqual("error", msg.Resolve.Exception.Reason);
Assert.AreEqual(Rpc.Exception.Type.failed, msg.Resolve.Exception.TheType);
}
);
}
[TestMethod]
public void DcMessageRelease()
{
ConstructReconstructTest<Message, Message.WRITER>(
() => new Message()
{
Release = new Release()
{
Id = 6000u,
ReferenceCount = 6u
}
},
msg =>
{
Assert.IsNotNull(msg.Release);
Assert.AreEqual(6000u, msg.Release.Id);
Assert.AreEqual(6u, msg.Release.ReferenceCount);
}
);
}
[TestMethod]
public void DcMessageBootstrap()
{
ConstructReconstructTest<Message, Message.WRITER>(
() => new Message()
{
Bootstrap = new Bootstrap()
{
QuestionId = 99u
}
},
msg =>
{
Assert.IsNotNull(msg.Bootstrap);
Assert.AreEqual(99u, msg.Bootstrap.QuestionId);
}
);
}
[TestMethod]
public void DcMessageProvide()
{
ConstructReconstructTest<Message, Message.WRITER>(
() => new Message()
{
Provide = new Provide()
{
Target = new MessageTarget()
{
ImportedCap = 7u
}
}
},
msg =>
{
Assert.IsNotNull(msg.Provide);
Assert.IsNotNull(msg.Provide.Target);
Assert.AreEqual(7u, msg.Provide.Target.ImportedCap);
}
);
}
[TestMethod]
public void DcMessageDisembargo()
{
ConstructReconstructTest<Message, Message.WRITER>(
() => new Message()
{
Disembargo = new Disembargo()
{
Context = new Disembargo.context()
{
ReceiverLoopback = 900u
},
Target = new MessageTarget()
}
},
msg =>
{
Assert.IsNotNull(msg.Disembargo);
Assert.IsNotNull(msg.Disembargo.Context);
Assert.AreEqual(900u, msg.Disembargo.Context.ReceiverLoopback);
Assert.IsNotNull(msg.Disembargo.Target);
Assert.IsNull(msg.Disembargo.Target.ImportedCap);
Assert.IsNull(msg.Disembargo.Target.PromisedAnswer);
Assert.AreEqual(MessageTarget.WHICH.undefined, msg.Disembargo.Target.which);
}
);
}
} }
} }

View File

@ -3,6 +3,7 @@
namespace Capnp.Net.Runtime.Tests namespace Capnp.Net.Runtime.Tests
{ {
[TestClass] [TestClass]
[TestCategory("Coverage")]
public class SegmentAllocatorTests public class SegmentAllocatorTests
{ {
[TestMethod] [TestMethod]

File diff suppressed because it is too large Load Diff

View File

@ -7,43 +7,21 @@ using Capnp.Rpc;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks.Dataflow;
using Capnp.Net.Runtime.Tests.GenImpls;
using Capnproto_test.Capnp.Test;
using Capnp.Net.Runtime.Tests.Util;
namespace Capnp.Net.Runtime.Tests namespace Capnp.Net.Runtime.Tests
{ {
[TestClass] [TestClass]
public class TcpRpc [TestCategory("Coverage")]
public class TcpRpc: TestBase
{ {
public static int TcpPort = 49153; bool ExpectingLogOutput { get; set; } = true;
(TcpRpcServer, TcpRpcClient) SetupClientServerPair() [TestMethod]
{
var server = new TcpRpcServer(IPAddress.Any, TcpPort);
var client = new TcpRpcClient("localhost", TcpPort);
return (server, client);
}
bool ExpectingLogOutput { get; set; }
[TestInitialize]
public void InitConsoleLogging()
{
ExpectingLogOutput = true;
Logging.LoggerFactory?.Dispose();
Logging.LoggerFactory = new LoggerFactory().AddConsole((msg, level) =>
{
if (!ExpectingLogOutput && level != LogLevel.Debug)
{
Assert.Fail("Did not expect any logging output, but got this: " + msg);
}
return true;
});
}
int MediumTimeout => Debugger.IsAttached ? Timeout.Infinite : 2000;
[TestMethod, Timeout(10000)]
public void CreateAndDispose() public void CreateAndDispose()
{ {
(var server, var client) = SetupClientServerPair(); (var server, var client) = SetupClientServerPair();
@ -54,7 +32,7 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void ConnectAndDispose() public void ConnectAndDispose()
{ {
(var server, var client) = SetupClientServerPair(); (var server, var client) = SetupClientServerPair();
@ -64,8 +42,8 @@ namespace Capnp.Net.Runtime.Tests
{ {
try try
{ {
client.WhenConnected.Wait(); Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout); SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
Assert.AreEqual(1, server.ConnectionCount); Assert.AreEqual(1, server.ConnectionCount);
} }
catch (System.Exception e) catch (System.Exception e)
@ -76,16 +54,17 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void ConnectNoServer() public void ConnectNoServer()
{ {
using (var client = new TcpRpcClient("localhost", TcpPort)) (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var client = new TcpRpcClient(addr.ToString(), port))
{ {
Assert.IsTrue(Assert.ThrowsExceptionAsync<RpcException>(() => client.WhenConnected).Wait(10000)); Assert.IsTrue(Assert.ThrowsExceptionAsync<RpcException>(() => client.WhenConnected).Wait(10000));
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void ConnectAndBootstrap() public void ConnectAndBootstrap()
{ {
(var server, var client) = SetupClientServerPair(); (var server, var client) = SetupClientServerPair();
@ -93,18 +72,18 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout); SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
Assert.AreEqual(1, server.ConnectionCount); Assert.AreEqual(1, server.ConnectionCount);
server.Main = new ProvidedCapabilityMock(); server.Main = new ProvidedCapabilityMock();
var main = client.GetMain<BareProxy>(); var main = client.GetMain<BareProxy>();
var resolving = main as IResolvingCapability; var resolving = main as IResolvingCapability;
Assert.IsTrue(resolving.WhenResolved.Wait(MediumTimeout)); Assert.IsTrue(resolving.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void ConnectNoBootstrap() public void ConnectNoBootstrap()
{ {
(var server, var client) = SetupClientServerPair(); (var server, var client) = SetupClientServerPair();
@ -112,17 +91,17 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout); SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
Assert.AreEqual(1, server.ConnectionCount); Assert.AreEqual(1, server.ConnectionCount);
var main = client.GetMain<BareProxy>(); var main = client.GetMain<BareProxy>();
var resolving = main as IResolvingCapability; var resolving = main as IResolvingCapability;
Assert.IsTrue(Assert.ThrowsExceptionAsync<RpcException>(() => resolving.WhenResolved).Wait(MediumTimeout)); Assert.IsTrue(Assert.ThrowsExceptionAsync<RpcException>(() => resolving.WhenResolved.WrappedTask).Wait(MediumNonDbgTimeout));
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void CallReturn() public void CallReturn()
{ {
(var server, var client) = SetupClientServerPair(); (var server, var client) = SetupClientServerPair();
@ -130,20 +109,20 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout); SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
Assert.AreEqual(1, server.ConnectionCount); Assert.AreEqual(1, server.ConnectionCount);
var mock = new ProvidedCapabilityMock(); var mock = new ProvidedCapabilityMock();
server.Main = mock; server.Main = mock;
var main = client.GetMain<BareProxy>(); var main = client.GetMain<BareProxy>();
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout)); Assert.IsTrue(main.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
var args = DynamicSerializerState.CreateForRpc(); var args = DynamicSerializerState.CreateForRpc();
args.SetStruct(1, 0); args.SetStruct(1, 0);
args.WriteData(0, 123456); args.WriteData(0, 123456);
using (var answer = main.Call(0x1234567812345678, 0x3333, args)) using (var answer = main.Call(0x1234567812345678, 0x3333, args))
{ {
Assert.IsTrue(mock.WhenCalled.Wait(MediumTimeout)); Assert.IsTrue(mock.WhenCalled.Wait(MediumNonDbgTimeout));
(var interfaceId, var methodId, var inargs, var ct) = mock.WhenCalled.Result; (var interfaceId, var methodId, var inargs, var ct) = mock.WhenCalled.Result;
Assert.AreEqual<ulong>(0x1234567812345678, interfaceId); Assert.AreEqual<ulong>(0x1234567812345678, interfaceId);
Assert.AreEqual<ushort>(0x3333, methodId); Assert.AreEqual<ushort>(0x3333, methodId);
@ -155,7 +134,7 @@ namespace Capnp.Net.Runtime.Tests
result.WriteData(0, 654321); result.WriteData(0, 654321);
mock.Return.SetResult(result); mock.Return.SetResult(result);
Assert.IsTrue(answer.WhenReturned.Wait(MediumTimeout)); Assert.IsTrue(answer.WhenReturned.WrappedTask.Wait(MediumNonDbgTimeout));
var outresult = answer.WhenReturned.Result; var outresult = answer.WhenReturned.Result;
Assert.AreEqual(ObjectKind.Struct, outresult.Kind); Assert.AreEqual(ObjectKind.Struct, outresult.Kind);
Assert.AreEqual(654321, outresult.ReadDataInt(0)); Assert.AreEqual(654321, outresult.ReadDataInt(0));
@ -163,7 +142,7 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void CallCancelOnServer() public void CallCancelOnServer()
{ {
(var server, var client) = SetupClientServerPair(); (var server, var client) = SetupClientServerPair();
@ -171,20 +150,20 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout); SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
Assert.AreEqual(1, server.ConnectionCount); Assert.AreEqual(1, server.ConnectionCount);
var mock = new ProvidedCapabilityMock(); var mock = new ProvidedCapabilityMock();
server.Main = mock; server.Main = mock;
var main = client.GetMain<BareProxy>(); var main = client.GetMain<BareProxy>();
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout)); Assert.IsTrue(main.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
var args = DynamicSerializerState.CreateForRpc(); var args = DynamicSerializerState.CreateForRpc();
args.SetStruct(1, 0); args.SetStruct(1, 0);
args.WriteData(0, 123456); args.WriteData(0, 123456);
using (var answer = main.Call(0x1234567812345678, 0x3333, args)) using (var answer = main.Call(0x1234567812345678, 0x3333, args))
{ {
Assert.IsTrue(mock.WhenCalled.Wait(MediumTimeout)); Assert.IsTrue(mock.WhenCalled.Wait(MediumNonDbgTimeout));
(var interfaceId, var methodId, var inargs, var ct) = mock.WhenCalled.Result; (var interfaceId, var methodId, var inargs, var ct) = mock.WhenCalled.Result;
Assert.AreEqual<ulong>(0x1234567812345678, interfaceId); Assert.AreEqual<ulong>(0x1234567812345678, interfaceId);
Assert.AreEqual<ushort>(0x3333, methodId); Assert.AreEqual<ushort>(0x3333, methodId);
@ -193,12 +172,12 @@ namespace Capnp.Net.Runtime.Tests
mock.Return.SetCanceled(); mock.Return.SetCanceled();
Assert.IsTrue(Assert.ThrowsExceptionAsync<TaskCanceledException>(() => answer.WhenReturned).Wait(MediumTimeout)); Assert.IsTrue(Assert.ThrowsExceptionAsync<TaskCanceledException>(async () => await answer.WhenReturned).Wait(MediumNonDbgTimeout));
} }
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void CallCancelOnClient() public void CallCancelOnClient()
{ {
ExpectingLogOutput = false; ExpectingLogOutput = false;
@ -210,22 +189,22 @@ namespace Capnp.Net.Runtime.Tests
{ {
try try
{ {
client.WhenConnected.Wait(); Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout); SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
Assert.AreEqual(1, server.ConnectionCount); Assert.AreEqual(1, server.ConnectionCount);
var mock = new ProvidedCapabilityMock(); var mock = new ProvidedCapabilityMock();
server.Main = mock; server.Main = mock;
var main = client.GetMain<BareProxy>(); var main = client.GetMain<BareProxy>();
var resolving = main as IResolvingCapability; var resolving = main as IResolvingCapability;
Assert.IsTrue(resolving.WhenResolved.Wait(MediumTimeout)); Assert.IsTrue(resolving.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
var args = DynamicSerializerState.CreateForRpc(); var args = DynamicSerializerState.CreateForRpc();
args.SetStruct(1, 0); args.SetStruct(1, 0);
args.WriteData(0, 123456); args.WriteData(0, 123456);
CancellationToken ctx; CancellationToken ctx;
using (var answer = main.Call(0x1234567812345678, 0x3333, args)) using (var answer = main.Call(0x1234567812345678, 0x3333, args))
{ {
Assert.IsTrue(mock.WhenCalled.Wait(MediumTimeout)); Assert.IsTrue(mock.WhenCalled.Wait(MediumNonDbgTimeout));
(var interfaceId, var methodId, var inargs, var ct) = mock.WhenCalled.Result; (var interfaceId, var methodId, var inargs, var ct) = mock.WhenCalled.Result;
Assert.AreEqual<ulong>(0x1234567812345678, interfaceId); Assert.AreEqual<ulong>(0x1234567812345678, interfaceId);
Assert.AreEqual<ushort>(0x3333, methodId); Assert.AreEqual<ushort>(0x3333, methodId);
@ -234,7 +213,7 @@ namespace Capnp.Net.Runtime.Tests
ctx = ct; ctx = ct;
} }
Assert.IsTrue(SpinWait.SpinUntil(() => ctx.IsCancellationRequested, MediumTimeout)); Assert.IsTrue(SpinWait.SpinUntil(() => ctx.IsCancellationRequested, MediumNonDbgTimeout));
} }
finally finally
{ {
@ -243,7 +222,7 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void CallReturnAfterClientSideCancel() public void CallReturnAfterClientSideCancel()
{ {
ExpectingLogOutput = false; ExpectingLogOutput = false;
@ -254,14 +233,14 @@ namespace Capnp.Net.Runtime.Tests
{ {
try try
{ {
client.WhenConnected.Wait(); Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout); SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
Assert.AreEqual(1, server.ConnectionCount); Assert.AreEqual(1, server.ConnectionCount);
var mock = new ProvidedCapabilityMock(); var mock = new ProvidedCapabilityMock();
server.Main = mock; server.Main = mock;
var main = client.GetMain<BareProxy>(); var main = client.GetMain<BareProxy>();
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout)); Assert.IsTrue(main.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
var args = DynamicSerializerState.CreateForRpc(); var args = DynamicSerializerState.CreateForRpc();
args.SetStruct(1, 0); args.SetStruct(1, 0);
args.WriteData(0, 123456); args.WriteData(0, 123456);
@ -269,7 +248,7 @@ namespace Capnp.Net.Runtime.Tests
IPromisedAnswer answer; IPromisedAnswer answer;
using (answer = main.Call(0x1234567812345678, 0x3333, args)) using (answer = main.Call(0x1234567812345678, 0x3333, args))
{ {
Assert.IsTrue(mock.WhenCalled.Wait(MediumTimeout)); Assert.IsTrue(mock.WhenCalled.Wait(MediumNonDbgTimeout));
(var interfaceId, var methodId, var inargs, var ct) = mock.WhenCalled.Result; (var interfaceId, var methodId, var inargs, var ct) = mock.WhenCalled.Result;
Assert.AreEqual<ulong>(0x1234567812345678, interfaceId); Assert.AreEqual<ulong>(0x1234567812345678, interfaceId);
Assert.AreEqual<ushort>(0x3333, methodId); Assert.AreEqual<ushort>(0x3333, methodId);
@ -278,7 +257,7 @@ namespace Capnp.Net.Runtime.Tests
ctx = ct; ctx = ct;
} }
Assert.IsTrue(SpinWait.SpinUntil(() => ctx.IsCancellationRequested, MediumTimeout)); Assert.IsTrue(SpinWait.SpinUntil(() => ctx.IsCancellationRequested, MediumNonDbgTimeout));
var mbr = MessageBuilder.Create(); var mbr = MessageBuilder.Create();
mbr.InitCapTable(); mbr.InitCapTable();
@ -289,7 +268,8 @@ namespace Capnp.Net.Runtime.Tests
// Even after the client cancelled the call, the server must still send // Even after the client cancelled the call, the server must still send
// a response. // a response.
Assert.IsTrue(answer.WhenReturned.ContinueWith(t => { }).Wait(MediumTimeout)); async Task AwaitWhenReturned() => await answer.WhenReturned;
Assert.IsTrue(AwaitWhenReturned().ContinueWith(t => { }).Wait(MediumNonDbgTimeout));
} }
finally finally
{ {
@ -305,7 +285,7 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void CallServerSideException() public void CallServerSideException()
{ {
(var server, var client) = SetupClientServerPair(); (var server, var client) = SetupClientServerPair();
@ -313,20 +293,20 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout); SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
Assert.AreEqual(1, server.ConnectionCount); Assert.AreEqual(1, server.ConnectionCount);
var mock = new ProvidedCapabilityMock(); var mock = new ProvidedCapabilityMock();
server.Main = mock; server.Main = mock;
var main = client.GetMain<BareProxy>(); var main = client.GetMain<BareProxy>();
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout)); Assert.IsTrue(main.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
var args = DynamicSerializerState.CreateForRpc(); var args = DynamicSerializerState.CreateForRpc();
args.SetStruct(1, 0); args.SetStruct(1, 0);
args.WriteData(0, 123456); args.WriteData(0, 123456);
using (var answer = main.Call(0x1234567812345678, 0x3333, args)) using (var answer = main.Call(0x1234567812345678, 0x3333, args))
{ {
Assert.IsTrue(mock.WhenCalled.Wait(MediumTimeout)); Assert.IsTrue(mock.WhenCalled.Wait(MediumNonDbgTimeout));
(var interfaceId, var methodId, var inargs, var ct) = mock.WhenCalled.Result; (var interfaceId, var methodId, var inargs, var ct) = mock.WhenCalled.Result;
Assert.AreEqual<ulong>(0x1234567812345678, interfaceId); Assert.AreEqual<ulong>(0x1234567812345678, interfaceId);
Assert.AreEqual<ushort>(0x3333, methodId); Assert.AreEqual<ushort>(0x3333, methodId);
@ -335,14 +315,14 @@ namespace Capnp.Net.Runtime.Tests
mock.Return.SetException(new MyTestException()); mock.Return.SetException(new MyTestException());
var exTask = Assert.ThrowsExceptionAsync<RpcException>(() => answer.WhenReturned); var exTask = Assert.ThrowsExceptionAsync<RpcException>(async () => await answer.WhenReturned);
Assert.IsTrue(exTask.Wait(MediumTimeout)); Assert.IsTrue(exTask.Wait(MediumNonDbgTimeout));
Assert.IsTrue(exTask.Result.Message.Contains(new MyTestException().Message)); Assert.IsTrue(exTask.Result.Message.Contains(new MyTestException().Message));
} }
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void PipelineBeforeReturn() public void PipelineBeforeReturn()
{ {
(var server, var client) = SetupClientServerPair(); (var server, var client) = SetupClientServerPair();
@ -350,20 +330,20 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout); SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
Assert.AreEqual(1, server.ConnectionCount); Assert.AreEqual(1, server.ConnectionCount);
var mock = new ProvidedCapabilityMock(); var mock = new ProvidedCapabilityMock();
server.Main = mock; server.Main = mock;
var main = client.GetMain<BareProxy>(); var main = client.GetMain<BareProxy>();
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout)); Assert.IsTrue(main.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
var args = DynamicSerializerState.CreateForRpc(); var args = DynamicSerializerState.CreateForRpc();
args.SetStruct(1, 0); args.SetStruct(1, 0);
args.WriteData(0, 123456); args.WriteData(0, 123456);
using (var answer = main.Call(0x1234567812345678, 0x3333, args)) using (var answer = main.Call(0x1234567812345678, 0x3333, args))
{ {
Assert.IsTrue(mock.WhenCalled.Wait(MediumTimeout)); Assert.IsTrue(mock.WhenCalled.Wait(MediumNonDbgTimeout));
var pipelined = (BareProxy)CapabilityReflection.CreateProxy<BareProxy>(answer.Access( var pipelined = (BareProxy)CapabilityReflection.CreateProxy<BareProxy>(answer.Access(
new MemberAccessPath(new MemberAccessPath.MemberAccess[] { new MemberAccessPath.StructMemberAccess(1) }))); new MemberAccessPath(new MemberAccessPath.MemberAccess[] { new MemberAccessPath.StructMemberAccess(1) })));
@ -385,15 +365,15 @@ namespace Capnp.Net.Runtime.Tests
var result = DynamicSerializerState.CreateForRpc(); var result = DynamicSerializerState.CreateForRpc();
result.SetStruct(1, 2); result.SetStruct(1, 2);
result.WriteData(0, 654321); result.WriteData(0, 654321);
uint id = result.ProvideCapability(mock2); uint id = result.ProvideCapability(mock2).Value;
result.LinkToCapability(1, id); result.LinkToCapability(1, id);
mock.Return.SetResult(result); mock.Return.SetResult(result);
Assert.IsTrue(answer.WhenReturned.Wait(MediumTimeout)); Assert.IsTrue(answer.WhenReturned.WrappedTask.Wait(MediumNonDbgTimeout));
Assert.IsFalse(ct.IsCancellationRequested); Assert.IsFalse(ct.IsCancellationRequested);
Assert.IsTrue(mock2.WhenCalled.Wait(MediumTimeout)); Assert.IsTrue(mock2.WhenCalled.Wait(MediumNonDbgTimeout));
(var interfaceId2, var methodId2, var inargs2, var ct2) = mock2.WhenCalled.Result; (var interfaceId2, var methodId2, var inargs2, var ct2) = mock2.WhenCalled.Result;
Assert.AreEqual<ulong>(0x8765432187654321, interfaceId2); Assert.AreEqual<ulong>(0x8765432187654321, interfaceId2);
@ -406,7 +386,7 @@ namespace Capnp.Net.Runtime.Tests
result2.WriteData(0, 222222); result2.WriteData(0, 222222);
mock2.Return.SetResult(result2); mock2.Return.SetResult(result2);
Assert.IsTrue(answer2.WhenReturned.Wait(MediumTimeout)); Assert.IsTrue(answer2.WhenReturned.WrappedTask.Wait(MediumNonDbgTimeout));
var outresult2 = answer2.WhenReturned.Result; var outresult2 = answer2.WhenReturned.Result;
Assert.AreEqual(ObjectKind.Struct, outresult2.Kind); Assert.AreEqual(ObjectKind.Struct, outresult2.Kind);
Assert.AreEqual(222222, outresult2.ReadDataInt(0)); Assert.AreEqual(222222, outresult2.ReadDataInt(0));
@ -415,7 +395,7 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void PipelineAfterReturn() public void PipelineAfterReturn()
{ {
(var server, var client) = SetupClientServerPair(); (var server, var client) = SetupClientServerPair();
@ -423,20 +403,20 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout); SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
Assert.AreEqual(1, server.ConnectionCount); Assert.AreEqual(1, server.ConnectionCount);
var mock = new ProvidedCapabilityMock(); var mock = new ProvidedCapabilityMock();
server.Main = mock; server.Main = mock;
var main = client.GetMain<BareProxy>(); var main = client.GetMain<BareProxy>();
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout)); Assert.IsTrue(main.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
var args = DynamicSerializerState.CreateForRpc(); var args = DynamicSerializerState.CreateForRpc();
args.SetStruct(1, 0); args.SetStruct(1, 0);
args.WriteData(0, 123456); args.WriteData(0, 123456);
using (var answer = main.Call(0x1234567812345678, 0x3333, args)) using (var answer = main.Call(0x1234567812345678, 0x3333, args))
{ {
Assert.IsTrue(mock.WhenCalled.Wait(MediumTimeout)); Assert.IsTrue(mock.WhenCalled.Wait(MediumNonDbgTimeout));
(var interfaceId, var methodId, var inargs, var ct) = mock.WhenCalled.Result; (var interfaceId, var methodId, var inargs, var ct) = mock.WhenCalled.Result;
Assert.AreEqual<ulong>(0x1234567812345678, interfaceId); Assert.AreEqual<ulong>(0x1234567812345678, interfaceId);
@ -449,7 +429,7 @@ namespace Capnp.Net.Runtime.Tests
var result = DynamicSerializerState.CreateForRpc(); var result = DynamicSerializerState.CreateForRpc();
result.SetStruct(1, 2); result.SetStruct(1, 2);
result.WriteData(0, 654321); result.WriteData(0, 654321);
uint id = result.ProvideCapability(mock2); uint id = result.ProvideCapability(mock2).Value;
result.LinkToCapability(1, id); result.LinkToCapability(1, id);
mock.Return.SetResult(result); mock.Return.SetResult(result);
@ -466,8 +446,8 @@ namespace Capnp.Net.Runtime.Tests
using (var answer2 = pipelined.Call(0x8765432187654321, 0x4444, args2)) using (var answer2 = pipelined.Call(0x8765432187654321, 0x4444, args2))
{ {
Assert.IsTrue(answer.WhenReturned.Wait(MediumTimeout)); Assert.IsTrue(answer.WhenReturned.WrappedTask.Wait(MediumNonDbgTimeout));
Assert.IsTrue(mock2.WhenCalled.Wait(MediumTimeout)); Assert.IsTrue(mock2.WhenCalled.Wait(MediumNonDbgTimeout));
(var interfaceId2, var methodId2, var inargs2, var ct2) = mock2.WhenCalled.Result; (var interfaceId2, var methodId2, var inargs2, var ct2) = mock2.WhenCalled.Result;
Assert.AreEqual<ulong>(0x8765432187654321, interfaceId2); Assert.AreEqual<ulong>(0x8765432187654321, interfaceId2);
@ -480,7 +460,7 @@ namespace Capnp.Net.Runtime.Tests
result2.WriteData(0, 222222); result2.WriteData(0, 222222);
mock2.Return.SetResult(result2); mock2.Return.SetResult(result2);
Assert.IsTrue(answer2.WhenReturned.Wait(MediumTimeout)); Assert.IsTrue(answer2.WhenReturned.WrappedTask.Wait(MediumNonDbgTimeout));
var outresult2 = answer2.WhenReturned.Result; var outresult2 = answer2.WhenReturned.Result;
Assert.AreEqual(ObjectKind.Struct, outresult2.Kind); Assert.AreEqual(ObjectKind.Struct, outresult2.Kind);
Assert.AreEqual(222222, outresult2.ReadDataInt(0)); Assert.AreEqual(222222, outresult2.ReadDataInt(0));
@ -491,7 +471,7 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void PipelineMultiple() public void PipelineMultiple()
{ {
(var server, var client) = SetupClientServerPair(); (var server, var client) = SetupClientServerPair();
@ -499,20 +479,20 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout); SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
Assert.AreEqual(1, server.ConnectionCount); Assert.AreEqual(1, server.ConnectionCount);
var mock = new ProvidedCapabilityMock(); var mock = new ProvidedCapabilityMock();
server.Main = mock; server.Main = mock;
var main = client.GetMain<BareProxy>(); var main = client.GetMain<BareProxy>();
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout)); Assert.IsTrue(main.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
var args = DynamicSerializerState.CreateForRpc(); var args = DynamicSerializerState.CreateForRpc();
args.SetStruct(1, 0); args.SetStruct(1, 0);
args.WriteData(0, 123456); args.WriteData(0, 123456);
using (var answer = main.Call(0x1234567812345678, 0x3333, args)) using (var answer = main.Call(0x1234567812345678, 0x3333, args))
{ {
Assert.IsTrue(mock.WhenCalled.Wait(MediumTimeout)); Assert.IsTrue(mock.WhenCalled.Wait(MediumNonDbgTimeout));
var pipelined = (BareProxy)CapabilityReflection.CreateProxy<BareProxy>(answer.Access(new MemberAccessPath(new MemberAccessPath.MemberAccess[] { new MemberAccessPath.StructMemberAccess(1) }))); var pipelined = (BareProxy)CapabilityReflection.CreateProxy<BareProxy>(answer.Access(new MemberAccessPath(new MemberAccessPath.MemberAccess[] { new MemberAccessPath.StructMemberAccess(1) })));
@ -539,12 +519,12 @@ namespace Capnp.Net.Runtime.Tests
var result = DynamicSerializerState.CreateForRpc(); var result = DynamicSerializerState.CreateForRpc();
result.SetStruct(1, 2); result.SetStruct(1, 2);
result.WriteData(0, 654321); result.WriteData(0, 654321);
uint id = result.ProvideCapability(mock2); uint id = result.ProvideCapability(mock2).Value;
result.LinkToCapability(1, id); result.LinkToCapability(1, id);
mock.Return.SetResult(result); mock.Return.SetResult(result);
Assert.IsTrue(answer.WhenReturned.Wait(MediumTimeout)); Assert.IsTrue(answer.WhenReturned.WrappedTask.Wait(MediumNonDbgTimeout));
Assert.IsFalse(ct.IsCancellationRequested); Assert.IsFalse(ct.IsCancellationRequested);
var args4 = DynamicSerializerState.CreateForRpc(); var args4 = DynamicSerializerState.CreateForRpc();
@ -563,10 +543,10 @@ namespace Capnp.Net.Runtime.Tests
var call4 = mock2.WhenCalled; var call4 = mock2.WhenCalled;
var call5 = mock2.WhenCalled; var call5 = mock2.WhenCalled;
Assert.IsTrue(call2.Wait(MediumTimeout)); Assert.IsTrue(call2.Wait(MediumNonDbgTimeout));
Assert.IsTrue(call3.Wait(MediumTimeout)); Assert.IsTrue(call3.Wait(MediumNonDbgTimeout));
Assert.IsTrue(call4.Wait(MediumTimeout)); Assert.IsTrue(call4.Wait(MediumNonDbgTimeout));
Assert.IsTrue(call5.Wait(MediumTimeout)); Assert.IsTrue(call5.Wait(MediumNonDbgTimeout));
Assert.AreEqual<ulong>(0x1111111111111111, call2.Result.InterfaceId); Assert.AreEqual<ulong>(0x1111111111111111, call2.Result.InterfaceId);
Assert.AreEqual<ulong>(0x2222222222222222, call3.Result.InterfaceId); Assert.AreEqual<ulong>(0x2222222222222222, call3.Result.InterfaceId);
@ -593,10 +573,10 @@ namespace Capnp.Net.Runtime.Tests
ret5.WriteData(0, -4); ret5.WriteData(0, -4);
call5.Result.Result.SetResult(ret5); call5.Result.Result.SetResult(ret5);
Assert.IsTrue(answer2.WhenReturned.Wait(MediumTimeout)); Assert.IsTrue(answer2.WhenReturned.WrappedTask.Wait(MediumNonDbgTimeout));
Assert.IsTrue(answer3.WhenReturned.Wait(MediumTimeout)); Assert.IsTrue(answer3.WhenReturned.WrappedTask.Wait(MediumNonDbgTimeout));
Assert.IsTrue(answer4.WhenReturned.Wait(MediumTimeout)); Assert.IsTrue(answer4.WhenReturned.WrappedTask.Wait(MediumNonDbgTimeout));
Assert.IsTrue(answer5.WhenReturned.Wait(MediumTimeout)); Assert.IsTrue(answer5.WhenReturned.WrappedTask.Wait(MediumNonDbgTimeout));
Assert.AreEqual(-1, answer2.WhenReturned.Result.ReadDataInt(0)); Assert.AreEqual(-1, answer2.WhenReturned.Result.ReadDataInt(0));
Assert.AreEqual(-2, answer3.WhenReturned.Result.ReadDataInt(0)); Assert.AreEqual(-2, answer3.WhenReturned.Result.ReadDataInt(0));
@ -608,7 +588,7 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void PipelineCallAfterDisposal() public void PipelineCallAfterDisposal()
{ {
(var server, var client) = SetupClientServerPair(); (var server, var client) = SetupClientServerPair();
@ -616,21 +596,21 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout); SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
Assert.AreEqual(1, server.ConnectionCount); Assert.AreEqual(1, server.ConnectionCount);
var mock = new ProvidedCapabilityMock(); var mock = new ProvidedCapabilityMock();
server.Main = mock; server.Main = mock;
var main = client.GetMain<BareProxy>(); var main = client.GetMain<BareProxy>();
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout)); Assert.IsTrue(main.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
var args = DynamicSerializerState.CreateForRpc(); var args = DynamicSerializerState.CreateForRpc();
args.SetStruct(1, 0); args.SetStruct(1, 0);
args.WriteData(0, 123456); args.WriteData(0, 123456);
BareProxy pipelined; BareProxy pipelined;
using (var answer = main.Call(0x1234567812345678, 0x3333, args)) using (var answer = main.Call(0x1234567812345678, 0x3333, args))
{ {
Assert.IsTrue(mock.WhenCalled.Wait(MediumTimeout)); Assert.IsTrue(mock.WhenCalled.Wait(MediumNonDbgTimeout));
pipelined = (BareProxy)CapabilityReflection.CreateProxy<BareProxy>( pipelined = (BareProxy)CapabilityReflection.CreateProxy<BareProxy>(
answer.Access(new MemberAccessPath(new MemberAccessPath.MemberAccess[] { new MemberAccessPath.StructMemberAccess(1) }))); answer.Access(new MemberAccessPath(new MemberAccessPath.MemberAccess[] { new MemberAccessPath.StructMemberAccess(1) })));
@ -655,7 +635,7 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void PipelineCallDuringDisposal() public void PipelineCallDuringDisposal()
{ {
(var server, var client) = SetupClientServerPair(); (var server, var client) = SetupClientServerPair();
@ -663,21 +643,21 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout); SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
Assert.AreEqual(1, server.ConnectionCount); Assert.AreEqual(1, server.ConnectionCount);
var mock = new ProvidedCapabilityMock(); var mock = new ProvidedCapabilityMock();
server.Main = mock; server.Main = mock;
var main = client.GetMain<BareProxy>(); var main = client.GetMain<BareProxy>();
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout)); Assert.IsTrue(main.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
var args = DynamicSerializerState.CreateForRpc(); var args = DynamicSerializerState.CreateForRpc();
args.SetStruct(1, 0); args.SetStruct(1, 0);
args.WriteData(0, 123456); args.WriteData(0, 123456);
IPromisedAnswer answer2; IPromisedAnswer answer2;
using (var answer = main.Call(0x1234567812345678, 0x3333, args)) using (var answer = main.Call(0x1234567812345678, 0x3333, args))
{ {
Assert.IsTrue(mock.WhenCalled.Wait(MediumTimeout)); Assert.IsTrue(mock.WhenCalled.Wait(MediumNonDbgTimeout));
var pipelined = (BareProxy)CapabilityReflection.CreateProxy<BareProxy>(answer.Access(new MemberAccessPath(new MemberAccessPath.MemberAccess[] { new MemberAccessPath.StructMemberAccess(1) }))); var pipelined = (BareProxy)CapabilityReflection.CreateProxy<BareProxy>(answer.Access(new MemberAccessPath(new MemberAccessPath.MemberAccess[] { new MemberAccessPath.StructMemberAccess(1) })));
@ -695,7 +675,7 @@ namespace Capnp.Net.Runtime.Tests
var tcs = new TaskCompletionSource<int>(); var tcs = new TaskCompletionSource<int>();
using (ct.Register(() => tcs.SetResult(0))) using (ct.Register(() => tcs.SetResult(0)))
{ {
Assert.IsTrue(tcs.Task.Wait(MediumTimeout)); Assert.IsTrue(tcs.Task.Wait(MediumNonDbgTimeout));
} }
var mock2 = new ProvidedCapabilityMock(); var mock2 = new ProvidedCapabilityMock();
@ -703,15 +683,178 @@ namespace Capnp.Net.Runtime.Tests
var result = DynamicSerializerState.CreateForRpc(); var result = DynamicSerializerState.CreateForRpc();
result.SetStruct(1, 2); result.SetStruct(1, 2);
result.WriteData(0, 654321); result.WriteData(0, 654321);
uint id = result.ProvideCapability(mock2); uint id = result.ProvideCapability(mock2).Value;
result.LinkToCapability(1, id); result.LinkToCapability(1, id);
mock.Return.SetResult(result); mock.Return.SetResult(result);
Assert.IsTrue(Assert.ThrowsExceptionAsync<TaskCanceledException>( Assert.IsTrue(Assert.ThrowsExceptionAsync<TaskCanceledException>(
() => answer2.WhenReturned).Wait(MediumTimeout)); async () => await answer2.WhenReturned).Wait(MediumNonDbgTimeout));
} }
} }
} }
[TestMethod]
public void Server1()
{
var cbb = new BufferBlock<IConnection>();
var server = new TcpRpcServer();
server.Main = new TestInterfaceImpl2();
bool init = true;
var tracer = new FrameTracing.RpcFrameTracer(Console.Out);
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
server.OnConnectionChanged += (s, a) =>
{
var c = a.Connection;
if (init)
{
Assert.ThrowsException<ArgumentNullException>(() => c.AttachTracer(null));
c.AttachTracer(tracer);
Assert.ThrowsException<ArgumentNullException>(() => c.InjectMidlayer(null));
c.InjectMidlayer(_ => _);
Assert.IsFalse(c.IsComputing);
Assert.IsFalse(c.IsWaitingForData);
Assert.AreEqual(ConnectionState.Initializing, c.State);
Assert.IsNotNull(c.RemotePort);
Assert.AreEqual(port, c.LocalPort);
Assert.AreEqual(0L, c.RecvCount);
Assert.AreEqual(0L, c.SendCount);
}
else
{
Assert.ThrowsException<InvalidOperationException>(() => c.AttachTracer(tracer));
Assert.ThrowsException<InvalidOperationException>(() => c.InjectMidlayer(_ => _));
Assert.AreEqual(ConnectionState.Down, c.State);
}
cbb.Post(c);
};
Assert.ThrowsException<InvalidOperationException>(() => server.StopListening());
server.StartAccepting(addr, port);
Assert.IsTrue(server.IsAlive);
Assert.ThrowsException<InvalidOperationException>(() => server.StartAccepting(addr, port));
var server2 = new TcpRpcServer();
Assert.ThrowsException<SocketException>(() => server2.StartAccepting(addr, port));
var client1 = new TcpRpcClient(addr.ToString(), port);
var c1 = cbb.Receive(TimeSpan.FromMilliseconds(MediumNonDbgTimeout));
Assert.IsNotNull(c1);
Assert.AreEqual(1, server.ConnectionCount);
Assert.AreEqual(c1, server.Connections[0]);
Assert.AreEqual(ConnectionState.Active, c1.State);
var proxy = client1.GetMain<ITestInterface>();
Assert.IsTrue(proxy is IResolvingCapability r && r.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
Assert.IsTrue(c1.RecvCount > 0);
Assert.IsTrue(c1.SendCount > 0);
var client2 = new TcpRpcClient(addr.ToString(), port);
var c2 = cbb.Receive(TimeSpan.FromMilliseconds(MediumNonDbgTimeout));
Assert.IsNotNull(c2);
Assert.AreEqual(2, server.ConnectionCount);
Assert.AreEqual(c2, server.Connections[1]);
init = false;
client1.Dispose();
var c1d = cbb.Receive(TimeSpan.FromMilliseconds(MediumNonDbgTimeout));
Assert.IsNotNull(c1d);
Assert.AreEqual(1, server.ConnectionCount);
Assert.AreEqual(c2, server.Connections[0]);
Assert.IsTrue(SpinWait.SpinUntil(() => c1d.State == ConnectionState.Down, MediumNonDbgTimeout));
client2.Dispose();
var c2d = cbb.Receive(TimeSpan.FromMilliseconds(MediumNonDbgTimeout));
Assert.IsNotNull(c2d);
Assert.AreEqual(0, server.ConnectionCount);
Assert.IsTrue(SpinWait.SpinUntil(() => c2d.State == ConnectionState.Down, MediumNonDbgTimeout));
server.StopListening();
Assert.IsFalse(server.IsAlive);
Assert.ThrowsException<InvalidOperationException>(() => server.StopListening());
for (int i = 0; i < 100; i++)
{
server.StartAccepting(addr, port);
Assert.IsTrue(server.IsAlive);
server.StopListening();
Assert.IsFalse(server.IsAlive);
}
server.Dispose();
}
[TestMethod]
public void Server2()
{
var server = new TcpRpcServer();
server.Main = new TestInterfaceImpl2();
var tracer = new FrameTracing.RpcFrameTracer(Console.Out);
server.OnConnectionChanged += (s, a) =>
{
server.Dispose();
};
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
server.StartAccepting(addr, port);
var client1 = new TcpRpcClient(addr.ToString(), port);
Assert.IsTrue(client1.WhenConnected.Wait(MediumNonDbgTimeout), "Did not connect");
Assert.IsTrue(SpinWait.SpinUntil(() => client1.State == ConnectionState.Down, MediumNonDbgTimeout),
$"Connection did not go down: {client1.State}");
}
[TestMethod]
public void Server3()
{
using (var server = new TcpRpcServer())
{
server.Main = new TestInterfaceImpl2();
var tracer = new FrameTracing.RpcFrameTracer(Console.Out);
server.OnConnectionChanged += (s, a) =>
{
a.Connection.Close();
};
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
server.StartAccepting(addr, port);
var client1 = new TcpRpcClient(addr.ToString(), port);
Assert.IsTrue(client1.WhenConnected.Wait(MediumNonDbgTimeout));
Assert.IsTrue(SpinWait.SpinUntil(() => client1.State == ConnectionState.Down, MediumNonDbgTimeout));
}
}
[TestMethod]
public void Client()
{
using (var server = new TcpRpcServer())
using (var client = new TcpRpcClient())
{
Assert.IsFalse(client.IsComputing);
Assert.IsFalse(client.IsWaitingForData);
Assert.AreEqual(0L, client.SendCount);
Assert.AreEqual(0L, client.RecvCount);
Assert.ThrowsException<InvalidOperationException>(() => client.GetMain<ITestInterface>());
Assert.ThrowsException<ArgumentNullException>(() => client.AttachTracer(null));
Assert.ThrowsException<ArgumentNullException>(() => client.InjectMidlayer(null));
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
server.StartAccepting(addr, port);
client.Connect(addr.ToString(), port);
Assert.ThrowsException<InvalidOperationException>(() => client.Connect(addr.ToString(), port));
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
Assert.ThrowsException<InvalidOperationException>(() => client.AttachTracer(new FrameTracing.RpcFrameTracer(Console.Out, false)));
Assert.ThrowsException<InvalidOperationException>(() => client.InjectMidlayer(_ => _));
Assert.AreEqual(port, client.RemotePort);
Assert.IsTrue(client.LocalPort != 0);
Assert.AreEqual(0L, client.SendCount);
Assert.AreEqual(0L, client.RecvCount);
Assert.IsTrue(SpinWait.SpinUntil(() => client.IsWaitingForData, MediumNonDbgTimeout));
((IConnection)client).Close();
Assert.IsTrue(SpinWait.SpinUntil(() => client.State == ConnectionState.Down, MediumNonDbgTimeout));
}
}
} }
} }

View File

@ -1,4 +1,5 @@
using Capnp.Net.Runtime.Tests.GenImpls; using Capnp.Net.Runtime.Tests.GenImpls;
using Capnp.Net.Runtime.Tests.Util;
using Capnp.Rpc; using Capnp.Rpc;
using Capnproto_test.Capnp.Test; using Capnproto_test.Capnp.Test;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
@ -10,12 +11,14 @@ using System.Threading.Tasks;
namespace Capnp.Net.Runtime.Tests namespace Capnp.Net.Runtime.Tests
{ {
[TestClass] [TestClass]
[TestCategory("Coverage")]
public class TcpRpcAdvancedStuff : TestBase public class TcpRpcAdvancedStuff : TestBase
{ {
[TestMethod, Timeout(10000)] [TestMethod]
public void MultiConnect() public void MultiConnect()
{ {
using (var server = SetupServer()) (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = SetupServer(addr, port))
{ {
var counters = new Counters(); var counters = new Counters();
var tcs = new TaskCompletionSource<int>(); var tcs = new TaskCompletionSource<int>();
@ -23,9 +26,9 @@ namespace Capnp.Net.Runtime.Tests
for (int i = 1; i <= 10; i++) for (int i = 1; i <= 10; i++)
{ {
using (var client = SetupClient()) using (var client = SetupClient(addr, port))
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
using (var main = client.GetMain<ITestInterface>()) using (var main = client.GetMain<ITestInterface>())
{ {
@ -50,19 +53,20 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void TwoClients() public void TwoClients()
{ {
using (var server = SetupServer()) (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = SetupServer(addr, port))
{ {
var counters = new Counters(); var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters); server.Main = new TestMoreStuffImpl(counters);
using (var client1 = SetupClient()) using (var client1 = SetupClient(addr, port))
using (var client2 = SetupClient()) using (var client2 = SetupClient(addr, port))
{ {
Assert.IsTrue(client1.WhenConnected.Wait(MediumNonDbgTimeout)); //Assert.IsTrue(client1.WhenConnected.Wait(MediumNonDbgTimeout));
Assert.IsTrue(client2.WhenConnected.Wait(MediumNonDbgTimeout)); //Assert.IsTrue(client2.WhenConnected.Wait(MediumNonDbgTimeout));
using (var main = client1.GetMain<ITestMoreStuff>()) using (var main = client1.GetMain<ITestMoreStuff>())
{ {
@ -89,17 +93,18 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void ClosingServerWhileRequestingBootstrap() public void ClosingServerWhileRequestingBootstrap()
{ {
for (int i = 0; i < 100; i++) for (int i = 0; i < 100; i++)
{ {
var server = SetupServer(); (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
var server = SetupServer(addr, port);
var counters = new Counters(); var counters = new Counters();
var tcs = new TaskCompletionSource<int>(); var tcs = new TaskCompletionSource<int>();
server.Main = new TestInterfaceImpl(counters, tcs); server.Main = new TestInterfaceImpl(counters, tcs);
using (var client = SetupClient()) using (var client = SetupClient(addr, port))
{ {
client.WhenConnected.Wait(); client.WhenConnected.Wait();
@ -111,7 +116,7 @@ namespace Capnp.Net.Runtime.Tests
try try
{ {
Assert.IsTrue(((IResolvingCapability)main).WhenResolved.Wait(MediumNonDbgTimeout)); Assert.IsTrue(((IResolvingCapability)main).WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
} }
catch (AggregateException) catch (AggregateException)
{ {
@ -124,14 +129,15 @@ namespace Capnp.Net.Runtime.Tests
[TestMethod] [TestMethod]
public void InheritFromGenericInterface() public void InheritFromGenericInterface()
{ {
using (var server = SetupServer()) (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = SetupServer(addr, port))
{ {
var counters = new Counters(); var counters = new Counters();
server.Main = new B2Impl(); server.Main = new B2Impl();
using (var client = SetupClient()) using (var client = SetupClient(addr, port))
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
using (var main = client.GetMain<CapnpGen.IB2>()) using (var main = client.GetMain<CapnpGen.IB2>())
{ {
@ -147,13 +153,14 @@ namespace Capnp.Net.Runtime.Tests
[TestMethod] [TestMethod]
public void Issue25() public void Issue25()
{ {
using (var server = SetupServer()) (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = SetupServer(addr, port))
{ {
server.Main = new Issue25BImpl(); server.Main = new Issue25BImpl();
using (var client = SetupClient()) using (var client = SetupClient(addr, port))
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
using (var main = client.GetMain<CapnpGen.IIssue25B>()) using (var main = client.GetMain<CapnpGen.IIssue25B>())
{ {
@ -172,5 +179,134 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
} }
[TestMethod]
public void ExportCapToThirdParty()
{
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = SetupServer(addr, port))
{
var counters = new Counters();
server.Main = new TestMoreStuffImpl3();
using (var client = SetupClient(addr, port))
{
//Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
using (var main = client.GetMain<ITestMoreStuff>())
{
var held = main.GetHeld().Eager();
(addr, port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server2 = SetupServer(addr, port))
{
server2.Main = new TestMoreStuffImpl2();
using (var client2 = SetupClient(addr, port))
{
//Assert.IsTrue(client2.WhenConnected.Wait(MediumNonDbgTimeout));
using (var main2 = client2.GetMain<ITestMoreStuff>())
{
var fooTask = main2.CallFoo(held);
Assert.IsTrue(main.Hold(new TestInterfaceImpl(new Counters())).Wait(MediumNonDbgTimeout));
Assert.IsTrue(fooTask.Wait(MediumNonDbgTimeout));
Assert.AreEqual("bar", fooTask.Result);
}
}
}
}
}
}
}
[TestMethod]
public void ExportTailCallCapToThirdParty()
{
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = SetupServer(addr, port))
{
server.Main = new TestTailCallerImpl2();
using (var client = SetupClient(addr, port))
{
//Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
using (var main = client.GetMain<ITestTailCaller>())
{
var callee = new TestTailCalleeImpl(new Counters());
var fooTask = main.Foo(123, callee);
Assert.IsTrue(fooTask.Wait(MediumNonDbgTimeout));
using (var c = fooTask.Result.C)
using (var client2 = SetupClient(addr, port))
{
//Assert.IsTrue(client2.WhenConnected.Wait(MediumNonDbgTimeout));
using (var main2 = client2.GetMain<ITestTailCaller>())
{
var fooTask2 = main2.Foo(123, null);
Assert.IsTrue(fooTask2.Wait(MediumNonDbgTimeout));
Assert.IsTrue(fooTask2.C().GetCallSequence(0).Wait(MediumNonDbgTimeout));
}
}
}
}
}
}
[TestMethod]
public void SalamiTactics()
{
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = SetupServer(addr, port))
{
server.Main = new TestMoreStuffImpl3();
using (var client = SetupClient(addr, port))
{
//client.WhenConnected.Wait();
using (var main = client.GetMain<ITestMoreStuff>())
{
var echoTask = main.Echo(Proxy.Share<ITestCallOrder>(main));
Assert.IsTrue(echoTask.Wait(MediumNonDbgTimeout));
using (var echo = echoTask.Result)
{
var list = new Task<uint>[200];
for (uint i = 0; i < list.Length; i++)
{
list[i] = echo.GetCallSequence(i);
}
Assert.IsTrue(Task.WaitAll(list, MediumNonDbgTimeout));
for (uint i = 0; i < list.Length; i++)
{
Assert.AreEqual(i, list[i].Result);
}
}
}
}
}
}
[TestMethod]
public void NoTailCallMt()
{
NewLocalhostTcpTestbed().RunTest(Testsuite.NoTailCallMt);
}
[TestMethod]
public void CallAfterFinish1()
{
NewLocalhostTcpTestbed().RunTest(Testsuite.CallAfterFinish1);
}
[TestMethod]
public void CallAfterFinish2()
{
NewLocalhostTcpTestbed().RunTest(Testsuite.CallAfterFinish2);
}
} }
} }

View File

@ -1,13 +1,16 @@
using Capnp.FrameTracing; using Capnp.FrameTracing;
using Capnp.Net.Runtime.Tests.GenImpls; using Capnp.Net.Runtime.Tests.GenImpls;
using Capnp.Net.Runtime.Tests.Util;
using Capnp.Rpc; using Capnp.Rpc;
using Capnproto_test.Capnp.Test; using Capnproto_test.Capnp.Test;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Net;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -36,7 +39,7 @@ namespace Capnp.Net.Runtime.Tests
Process _currentProcess; Process _currentProcess;
bool TryLaunchCompatTestProcess(string whichTest, Action<StreamReader> test) bool TryLaunchCompatTestProcess(IPAddress addr, int port, string whichTest, Action<StreamReader> test)
{ {
string myPath = Path.GetDirectoryName(typeof(TcpRpcInterop).Assembly.Location); string myPath = Path.GetDirectoryName(typeof(TcpRpcInterop).Assembly.Location);
string config; string config;
@ -47,7 +50,7 @@ namespace Capnp.Net.Runtime.Tests
#endif #endif
string path = Path.Combine(myPath, $@"..\..\..\..\{config}\CapnpCompatTest.exe"); string path = Path.Combine(myPath, $@"..\..\..\..\{config}\CapnpCompatTest.exe");
path = Path.GetFullPath(path); path = Path.GetFullPath(path);
string arguments = $"{whichTest} 127.0.0.1:{TcpPort}"; string arguments = $"{whichTest} {addr}:{port}";
var startInfo = new ProcessStartInfo(path, arguments) var startInfo = new ProcessStartInfo(path, arguments)
{ {
UseShellExecute = false, UseShellExecute = false,
@ -63,21 +66,25 @@ namespace Capnp.Net.Runtime.Tests
try try
{ {
_currentProcess.StandardError.ReadToEndAsync().ContinueWith(t => Console.Error.WriteLine(t.Result)); try
var firstLine = _currentProcess.StandardOutput.ReadLineAsync(); {
Assert.IsTrue(firstLine.Wait(MediumNonDbgTimeout), "Problem after launching test process"); _currentProcess.StandardError.ReadToEndAsync().ContinueWith(t => Console.Error.WriteLine(t.Result));
Assert.IsNotNull(firstLine.Result, "Problem after launching test process"); var firstLine = _currentProcess.StandardOutput.ReadLineAsync();
Assert.IsTrue(firstLine.Result.StartsWith("Listening") || firstLine.Result.StartsWith("Connecting"), Assert.IsTrue(firstLine.Wait(MediumNonDbgTimeout), "Problem after launching test process");
"Problem after launching test process"); Assert.IsNotNull(firstLine.Result, "Problem after launching test process");
Assert.IsTrue(firstLine.Result.StartsWith("Listening") || firstLine.Result.StartsWith("Connecting"),
"Problem after launching test process");
}
catch (AssertFailedException exception)
{
Logger.LogError(exception.Message);
return false;
}
test(_currentProcess.StandardOutput); test(_currentProcess.StandardOutput);
return true; return true;
} }
catch (AssertFailedException)
{
return false;
}
finally finally
{ {
try try
@ -91,15 +98,13 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
void LaunchCompatTestProcess(string whichTest, Action<StreamReader> test) void LaunchCompatTestProcess(string whichTest, IPAddress addr, int port, Action<StreamReader> test)
{ {
for (int retry = 0; retry < 5; retry++) for (int retry = 0; retry < 5; retry++)
{ {
if (TryLaunchCompatTestProcess(whichTest, test)) if (TryLaunchCompatTestProcess(addr, port, whichTest, test))
return; return;
if (whichTest.StartsWith("server:"))
PrepareNextTest();
} }
Assert.Fail("Problem after launching test process"); Assert.Fail("Problem after launching test process");
@ -117,20 +122,15 @@ namespace Capnp.Net.Runtime.Tests
Assert.AreEqual(expected, line.Result); Assert.AreEqual(expected, line.Result);
} }
[TestInitialize] [TestMethod]
public void PrepareNextTest()
{
IncrementTcpPort();
}
[TestMethod, Timeout(10000)]
public void BasicClient() public void BasicClient()
{ {
LaunchCompatTestProcess("server:Interface", stdout => (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
LaunchCompatTestProcess("server:Interface", addr, port, stdout =>
{ {
using (var client = new TcpRpcClient("localhost", TcpPort)) using (var client = new TcpRpcClient(addr.ToString(), port))
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
using (var main = client.GetMain<ITestInterface>()) using (var main = client.GetMain<ITestInterface>())
{ {
@ -154,15 +154,16 @@ namespace Capnp.Net.Runtime.Tests
}); });
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void BasicServer() public void BasicServer()
{ {
using (var server = SetupServer()) (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = SetupServer(addr, port))
{ {
var counters = new Counters(); var counters = new Counters();
server.Main = new TestInterfaceImpl(counters); server.Main = new TestInterfaceImpl(counters);
LaunchCompatTestProcess("client:Basic", stdout => LaunchCompatTestProcess("client:Basic", addr, port, stdout =>
{ {
AssertOutput(stdout, "Basic test start"); AssertOutput(stdout, "Basic test start");
AssertOutput(stdout, "Basic test end"); AssertOutput(stdout, "Basic test end");
@ -171,16 +172,17 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void PipelineClient() public void PipelineClient()
{ {
LaunchCompatTestProcess("server:Pipeline", stdout => (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
LaunchCompatTestProcess("server:Pipeline", addr, port, stdout =>
{ {
stdout.ReadToEndAsync().ContinueWith(t => Console.WriteLine(t.Result)); stdout.ReadToEndAsync().ContinueWith(t => Console.WriteLine(t.Result));
using (var client = new TcpRpcClient("localhost", TcpPort)) using (var client = new TcpRpcClient(addr.ToString(), port))
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
using (var main = client.GetMain<ITestPipeline>()) using (var main = client.GetMain<ITestPipeline>())
{ {
@ -208,32 +210,35 @@ namespace Capnp.Net.Runtime.Tests
}); });
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void PipelineServer() public void PipelineServer()
{ {
using (var server = SetupServer()) (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = SetupServer(addr, port))
{ {
var counters = new Counters(); var counters = new Counters();
server.Main = new TestPipelineImpl(counters); server.Main = new TestPipelineImpl(counters);
LaunchCompatTestProcess("client:Pipelining", stdout => LaunchCompatTestProcess("client:Pipelining", addr, port, stdout =>
{ {
AssertOutput(stdout, "Pipelining test start"); AssertOutput(stdout, "Pipelining test start");
AssertOutput(stdout, "foo 123 1"); AssertOutput(stdout, "foo 123 1");
AssertOutput(stdout, "~");
AssertOutput(stdout, "Pipelining test end"); AssertOutput(stdout, "Pipelining test end");
Assert.AreEqual(3, counters.CallCount); Assert.AreEqual(3, counters.CallCount);
}); });
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void ReleaseClient() public void ReleaseClient()
{ {
LaunchCompatTestProcess("server:MoreStuff", stdout => (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
LaunchCompatTestProcess("server:MoreStuff", addr, port, stdout =>
{ {
using (var client = new TcpRpcClient("localhost", TcpPort)) using (var client = new TcpRpcClient(addr.ToString(), port))
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
using (var main = client.GetMain<ITestMoreStuff>()) using (var main = client.GetMain<ITestMoreStuff>())
{ {
@ -259,15 +264,16 @@ namespace Capnp.Net.Runtime.Tests
}); });
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void ReleaseServer() public void ReleaseServer()
{ {
using (var server = SetupServer()) (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = SetupServer(addr, port))
{ {
var counters = new Counters(); var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters); server.Main = new TestMoreStuffImpl(counters);
LaunchCompatTestProcess("client:Release", stdout => LaunchCompatTestProcess("client:Release", addr, port, stdout =>
{ {
AssertOutput(stdout, "Release test start"); AssertOutput(stdout, "Release test start");
AssertOutput(stdout, "sync"); AssertOutput(stdout, "sync");
@ -295,15 +301,16 @@ namespace Capnp.Net.Runtime.Tests
// later on verify that the handle count is 0. // later on verify that the handle count is 0.
int iterationCount = 5000; int iterationCount = 5000;
LaunchCompatTestProcess("server:MoreStuff", stdout => (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
LaunchCompatTestProcess("server:MoreStuff", addr, port, stdout =>
{ {
using (var client = new TcpRpcClient("localhost", TcpPort)) using (var client = new TcpRpcClient(addr.ToString(), port))
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
using (var main = client.GetMain<ITestMoreStuff>()) using (var main = client.GetMain<ITestMoreStuff>())
{ {
((Proxy)main).WhenResolved.Wait(MediumNonDbgTimeout); ((Proxy)main).WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout);
async Task VerifyOutput() async Task VerifyOutput()
{ {
@ -342,17 +349,19 @@ namespace Capnp.Net.Runtime.Tests
for (int i = 0; i < iterationCount; i++) for (int i = 0; i < iterationCount; i++)
{ {
var task = main.GetHandle(default); var task = main.GetHandle(default);
taskList.Add(task.ContinueWith(t => async Task TerminateAnswer()
{ {
try try
{ {
t.Result.Dispose(); using (var proxy = await task)
{
}
} }
catch (AggregateException ex) when (ex.InnerException is TaskCanceledException) catch (TaskCanceledException)
{ {
} }
})); }
Impatient.GetAnswer(task).Dispose(); taskList.Add(TerminateAnswer());
} }
// Ensure that all answers return (probably in canceled state) // Ensure that all answers return (probably in canceled state)
@ -369,15 +378,16 @@ namespace Capnp.Net.Runtime.Tests
}); });
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void ReleaseOnCancelServer() public void ReleaseOnCancelServer()
{ {
using (var server = SetupServer()) (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = SetupServer(addr, port))
{ {
var counters = new Counters(); var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters); server.Main = new TestMoreStuffImpl(counters);
LaunchCompatTestProcess("client:ReleaseOnCancel", stdout => LaunchCompatTestProcess("client:ReleaseOnCancel", addr, port, stdout =>
{ {
AssertOutput(stdout, "ReleaseOnCancel test start"); AssertOutput(stdout, "ReleaseOnCancel test start");
AssertOutput(stdout, "ReleaseOnCancel test end"); AssertOutput(stdout, "ReleaseOnCancel test end");
@ -387,18 +397,20 @@ namespace Capnp.Net.Runtime.Tests
} }
[TestMethod] [TestMethod]
[TestCategory("Coverage")]
public void TestTailCallClient() public void TestTailCallClient()
{ {
LaunchCompatTestProcess("server:TailCaller", stdout => (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
LaunchCompatTestProcess("server:TailCaller", addr, port, stdout =>
{ {
using (var client = new TcpRpcClient()) using (var client = new TcpRpcClient())
{ {
var tracer = new RpcFrameTracer(Console.Out); var tracer = new RpcFrameTracer(Console.Out);
client.AttachTracer(tracer); client.AttachTracer(tracer);
client.Connect("localhost", TcpPort); client.Connect(addr.ToString(), port);
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
using (var main = client.GetMain<ITestTailCaller>()) using (var main = client.GetMain<ITestTailCaller>())
{ {
@ -406,20 +418,22 @@ namespace Capnp.Net.Runtime.Tests
var callee = new TestTailCalleeImpl(calleeCallCount); var callee = new TestTailCalleeImpl(calleeCallCount);
var promise = main.Foo(456, callee, default); var promise = main.Foo(456, callee, default);
var dependentCall0 = promise.C().GetCallSequence(0, default); using (var c = promise.C())
{
var dependentCall0 = c.GetCallSequence(0, default);
Assert.IsTrue(promise.Wait(MediumNonDbgTimeout)); Assert.IsTrue(promise.Wait(MediumNonDbgTimeout));
Assert.AreEqual(456u, promise.Result.I); Assert.AreEqual(456u, promise.Result.I);
Assert.AreEqual("from TestTailCaller", promise.Result.T); Assert.AreEqual("from TestTailCaller", promise.Result.T);
var dependentCall1 = promise.C().GetCallSequence(0, default); var dependentCall1 = c.GetCallSequence(1, default);
var dependentCall2 = promise.C().GetCallSequence(0, default); var dependentCall2 = c.GetCallSequence(2, default);
AssertOutput(stdout, "foo");
Assert.IsTrue(dependentCall0.Wait(MediumNonDbgTimeout));
Assert.IsTrue(dependentCall1.Wait(MediumNonDbgTimeout));
Assert.IsTrue(dependentCall2.Wait(MediumNonDbgTimeout));
AssertOutput(stdout, "foo");
Assert.IsTrue(dependentCall0.Wait(MediumNonDbgTimeout));
Assert.IsTrue(dependentCall1.Wait(MediumNonDbgTimeout));
Assert.IsTrue(dependentCall2.Wait(MediumNonDbgTimeout));
}
Assert.AreEqual(1, calleeCallCount.CallCount); Assert.AreEqual(1, calleeCallCount.CallCount);
} }
} }
@ -430,8 +444,8 @@ namespace Capnp.Net.Runtime.Tests
// For details on why this test is ignored, see https://github.com/capnproto/capnproto/issues/876 // For details on why this test is ignored, see https://github.com/capnproto/capnproto/issues/876
public void TestTailCallServer() public void TestTailCallServer()
{ {
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = SetupServer()) using (var server = SetupServer(addr, port))
{ {
var tracer = new RpcFrameTracer(Console.Out); var tracer = new RpcFrameTracer(Console.Out);
@ -444,7 +458,7 @@ namespace Capnp.Net.Runtime.Tests
var counters = new Counters(); var counters = new Counters();
server.Main = new TestTailCallerImpl(counters); server.Main = new TestTailCallerImpl(counters);
LaunchCompatTestProcess("client:TailCall", stdout => LaunchCompatTestProcess("client:TailCall", addr, port, stdout =>
{ {
AssertOutput(stdout, "TailCall test start"); AssertOutput(stdout, "TailCall test start");
AssertOutput(stdout, "foo"); AssertOutput(stdout, "foo");
@ -455,16 +469,17 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void CancelationServer() public void CancelationServer()
{ {
LaunchCompatTestProcess("server:MoreStuff", stdout => (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
LaunchCompatTestProcess("server:MoreStuff", addr, port, stdout =>
{ {
stdout.ReadToEndAsync().ContinueWith(t => Console.WriteLine(t.Result)); stdout.ReadToEndAsync().ContinueWith(t => Console.WriteLine(t.Result));
using (var client = new TcpRpcClient("localhost", TcpPort)) using (var client = new TcpRpcClient(addr.ToString(), port))
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
using (var main = client.GetMain<ITestMoreStuff>()) using (var main = client.GetMain<ITestMoreStuff>())
{ {
@ -485,15 +500,16 @@ namespace Capnp.Net.Runtime.Tests
}); });
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void CancelationClient() public void CancelationClient()
{ {
using (var server = SetupServer()) (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = SetupServer(addr, port))
{ {
var counters = new Counters(); var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters); server.Main = new TestMoreStuffImpl(counters);
LaunchCompatTestProcess("client:Cancelation", stdout => LaunchCompatTestProcess("client:Cancelation", addr, port, stdout =>
{ {
AssertOutput(stdout, "Cancelation test start"); AssertOutput(stdout, "Cancelation test start");
AssertOutput(stdout, "~"); AssertOutput(stdout, "~");
@ -502,40 +518,43 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void PromiseResolveServer() public void PromiseResolveServer()
{ {
LaunchCompatTestProcess("server:MoreStuff", stdout => (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
LaunchCompatTestProcess("server:MoreStuff", addr, port, stdout =>
{ {
using (var client = new TcpRpcClient("localhost", TcpPort)) using (var client = new TcpRpcClient(addr.ToString(), port))
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
using (var main = client.GetMain<ITestMoreStuff>()) using (var main = client.GetMain<ITestMoreStuff>())
{ {
var tcs = new TaskCompletionSource<ITestInterface>(); var tcs = new TaskCompletionSource<ITestInterface>();
var eager = tcs.Task.Eager(true); using (var eager = tcs.Task.Eager(true))
{
var request = main.CallFoo(Proxy.Share(eager), default);
AssertOutput(stdout, "callFoo");
var request2 = main.CallFooWhenResolved(Proxy.Share(eager), default);
AssertOutput(stdout, "callFooWhenResolved");
var request = main.CallFoo(eager, default); var gcs = main.GetCallSequence(0, default);
AssertOutput(stdout, "callFoo"); AssertOutput(stdout, "getCallSequence");
var request2 = main.CallFooWhenResolved(eager, default); Assert.IsTrue(gcs.Wait(MediumNonDbgTimeout));
AssertOutput(stdout, "callFooWhenResolved"); Assert.AreEqual(2u, gcs.Result);
var gcs = main.GetCallSequence(0, default); var chainedCallCount = new Counters();
AssertOutput(stdout, "getCallSequence"); var tiimpl = new TestInterfaceImpl(chainedCallCount);
Assert.IsTrue(gcs.Wait(MediumNonDbgTimeout)); tcs.SetResult(tiimpl);
Assert.AreEqual(2u, gcs.Result);
var chainedCallCount = new Counters(); Assert.IsTrue(request.Wait(MediumNonDbgTimeout));
var tiimpl = new TestInterfaceImpl(chainedCallCount); Assert.IsTrue(request2.Wait(MediumNonDbgTimeout));
tcs.SetResult(tiimpl);
Assert.IsTrue(request.Wait(MediumNonDbgTimeout)); Assert.AreEqual("bar", request.Result);
Assert.IsTrue(request2.Wait(MediumNonDbgTimeout)); Assert.AreEqual("bar", request2.Result);
Assert.AreEqual(2, chainedCallCount.CallCount);
Assert.AreEqual("bar", request.Result); }
Assert.AreEqual("bar", request2.Result);
Assert.AreEqual(2, chainedCallCount.CallCount);
AssertOutput(stdout, "fin"); AssertOutput(stdout, "fin");
AssertOutput(stdout, "fin"); AssertOutput(stdout, "fin");
@ -544,38 +563,41 @@ namespace Capnp.Net.Runtime.Tests
}); });
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void PromiseResolveClient() public void PromiseResolveClient()
{ {
using (var server = SetupServer()) (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = SetupServer(addr, port))
{ {
var counters = new Counters(); var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters); server.Main = new TestMoreStuffImpl(counters);
LaunchCompatTestProcess("client:PromiseResolve", stdout => LaunchCompatTestProcess("client:PromiseResolve", addr, port, stdout =>
{ {
AssertOutput(stdout, "PromiseResolve test start"); AssertOutput(stdout, "PromiseResolve test start");
AssertOutput(stdout, "foo 123 1"); AssertOutput(stdout, "foo 123 1");
AssertOutput(stdout, "foo 123 1"); AssertOutput(stdout, "foo 123 1");
AssertOutput(stdout, "~");
AssertOutput(stdout, "PromiseResolve test end"); AssertOutput(stdout, "PromiseResolve test end");
Assert.AreEqual(3, counters.CallCount); Assert.AreEqual(3, counters.CallCount);
}); });
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void RetainAndReleaseServer() public void RetainAndReleaseServer()
{ {
var destructionPromise = new TaskCompletionSource<int>(); var destructionPromise = new TaskCompletionSource<int>();
var destructionTask = destructionPromise.Task; var destructionTask = destructionPromise.Task;
LaunchCompatTestProcess("server:MoreStuff", stdout => (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
LaunchCompatTestProcess("server:MoreStuff", addr, port, stdout =>
{ {
stdout.ReadToEndAsync().ContinueWith(t => Console.WriteLine(t.Result)); stdout.ReadToEndAsync().ContinueWith(t => Console.WriteLine(t.Result));
using (var client = new TcpRpcClient("localhost", TcpPort)) using (var client = new TcpRpcClient(addr.ToString(), port))
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
using (var main = client.GetMain<ITestMoreStuff>()) using (var main = client.GetMain<ITestMoreStuff>())
{ {
@ -641,15 +663,16 @@ namespace Capnp.Net.Runtime.Tests
}); });
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void RetainAndReleaseClient() public void RetainAndReleaseClient()
{ {
using (var server = SetupServer()) (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = SetupServer(addr, port))
{ {
var counters = new Counters(); var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters); server.Main = new TestMoreStuffImpl(counters);
LaunchCompatTestProcess("client:RetainAndRelease", stdout => LaunchCompatTestProcess("client:RetainAndRelease", addr, port, stdout =>
{ {
AssertOutput(stdout, "RetainAndRelease test start"); AssertOutput(stdout, "RetainAndRelease test start");
AssertOutput(stdout, "foo 123 1"); AssertOutput(stdout, "foo 123 1");
@ -661,14 +684,15 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void CancelServer() public void CancelServer()
{ {
LaunchCompatTestProcess("server:MoreStuff", stdout => (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
LaunchCompatTestProcess("server:MoreStuff", addr, port, stdout =>
{ {
using (var client = new TcpRpcClient("localhost", TcpPort)) using (var client = new TcpRpcClient(addr.ToString(), port))
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
var destructionPromise = new TaskCompletionSource<int>(); var destructionPromise = new TaskCompletionSource<int>();
var destructionTask = destructionPromise.Task; var destructionTask = destructionPromise.Task;
@ -702,15 +726,16 @@ namespace Capnp.Net.Runtime.Tests
}); });
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void CancelClient() public void CancelClient()
{ {
using (var server = SetupServer()) (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = SetupServer(addr, port))
{ {
var counters = new Counters(); var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters); server.Main = new TestMoreStuffImpl(counters);
LaunchCompatTestProcess("client:Cancel", stdout => LaunchCompatTestProcess("client:Cancel", addr, port, stdout =>
{ {
AssertOutput(stdout, "Cancel test start"); AssertOutput(stdout, "Cancel test start");
AssertOutput(stdout, "~"); AssertOutput(stdout, "~");
@ -719,14 +744,15 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void SendTwiceServer() public void SendTwiceServer()
{ {
LaunchCompatTestProcess("server:MoreStuff", stdout => (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
LaunchCompatTestProcess("server:MoreStuff", addr, port, stdout =>
{ {
using (var client = new TcpRpcClient("localhost", TcpPort)) using (var client = new TcpRpcClient(addr.ToString(), port))
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
using (var main = client.GetMain<ITestMoreStuff>()) using (var main = client.GetMain<ITestMoreStuff>())
{ {
@ -763,15 +789,16 @@ namespace Capnp.Net.Runtime.Tests
}); });
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void SendTwiceClient() public void SendTwiceClient()
{ {
using (var server = SetupServer()) (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = SetupServer(addr, port))
{ {
var counters = new Counters(); var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters); server.Main = new TestMoreStuffImpl(counters);
LaunchCompatTestProcess("client:SendTwice", stdout => LaunchCompatTestProcess("client:SendTwice", addr, port, stdout =>
{ {
AssertOutput(stdout, "SendTwice test start"); AssertOutput(stdout, "SendTwice test start");
AssertOutput(stdout, "foo 123 1"); AssertOutput(stdout, "foo 123 1");
@ -783,41 +810,21 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void EmbargoServer() public void EmbargoServer()
{ {
LaunchCompatTestProcess("server:MoreStuff", stdout => (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
LaunchCompatTestProcess("server:MoreStuff", addr, port, stdout =>
{ {
int retry = 0; using (var client = new TcpRpcClient(addr.ToString(), port))
label:
using (var client = new TcpRpcClient("localhost", TcpPort))
{ {
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout), "client connect"); //Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout), "client connect");
using (var main = client.GetMain<ITestMoreStuff>()) using (var wrapped = client.GetMain<ITestMoreStuff>())
{ {
var resolving = main as IResolvingCapability; var unwrap = wrapped.Unwrap();
Assert.IsTrue(unwrap.Wait(MediumNonDbgTimeout));
bool success; var main = unwrap.Result;
try
{
success = resolving.WhenResolved.Wait(MediumNonDbgTimeout);
}
catch
{
success = false;
}
if (!success)
{
if (++retry == 5)
{
Assert.Fail("Attempting to obtain bootstrap interface failed. Bailing out.");
}
goto label;
}
var cap = new TestCallOrderImpl(); var cap = new TestCallOrderImpl();
cap.CountToDispose = 6; cap.CountToDispose = 6;
@ -868,17 +875,18 @@ namespace Capnp.Net.Runtime.Tests
}); });
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void EmbargoServer2() public void EmbargoServer2()
{ {
LaunchCompatTestProcess("server:MoreStuff", stdout => (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
LaunchCompatTestProcess("server:MoreStuff", addr, port, stdout =>
{ {
int retry = 0; int retry = 0;
label: label:
using (var client = new TcpRpcClient("localhost", TcpPort)) using (var client = new TcpRpcClient(addr.ToString(), port))
{ {
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout), "client connect"); //Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout), "client connect");
using (var main = client.GetMain<ITestMoreStuff>()) using (var main = client.GetMain<ITestMoreStuff>())
{ {
@ -888,7 +896,7 @@ namespace Capnp.Net.Runtime.Tests
try try
{ {
success = resolving.WhenResolved.Wait(MediumNonDbgTimeout); success = resolving.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout);
} }
catch catch
{ {
@ -951,15 +959,16 @@ namespace Capnp.Net.Runtime.Tests
}); });
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void EmbargoClient() public void EmbargoClient()
{ {
using (var server = SetupServer()) (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = SetupServer(addr, port))
{ {
var counters = new Counters(); var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters); server.Main = new TestMoreStuffImpl(counters);
LaunchCompatTestProcess("client:Embargo", stdout => LaunchCompatTestProcess("client:Embargo", addr, port, stdout =>
{ {
AssertOutput(stdout, "Embargo test start"); AssertOutput(stdout, "Embargo test start");
AssertOutput(stdout, "Embargo test end"); AssertOutput(stdout, "Embargo test end");
@ -967,17 +976,17 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
public void EmbargoErrorImpl(StreamReader stdout) public void EmbargoErrorImpl(IPAddress addr, int port, StreamReader stdout)
{ {
using (var client = new TcpRpcClient("localhost", TcpPort)) using (var client = new TcpRpcClient(addr.ToString(), port))
{ {
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout)); //Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
using (var main = client.GetMain<ITestMoreStuff>()) using (var main = client.GetMain<ITestMoreStuff>())
{ {
var resolving = main as IResolvingCapability; var resolving = main as IResolvingCapability;
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout)); Assert.IsTrue(resolving.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
var cap = new TaskCompletionSource<ITestCallOrder>(); var cap = new TaskCompletionSource<ITestCallOrder>();
@ -1021,33 +1030,36 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void EmbargoErrorServer() public void EmbargoErrorServer()
{ {
LaunchCompatTestProcess("server:MoreStuff", EmbargoErrorImpl); (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
LaunchCompatTestProcess("server:MoreStuff", addr, port, r => EmbargoErrorImpl(addr, port, r));
} }
[TestMethod, Timeout(240000)] [TestMethod, Timeout(240000)]
public void RepeatedEmbargoError() public void RepeatedEmbargoError()
{ {
LaunchCompatTestProcess("server:MoreStuff", stdout => (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
LaunchCompatTestProcess("server:MoreStuff", addr, port, stdout =>
{ {
for (int i = 0; i < 100; i++) for (int i = 0; i < 20; i++)
{ {
EmbargoErrorImpl(stdout); EmbargoErrorImpl(addr, port, stdout);
} }
}); });
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void EmbargoErrorClient() public void EmbargoErrorClient()
{ {
using (var server = SetupServer()) (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = SetupServer(addr, port))
{ {
var counters = new Counters(); var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters); server.Main = new TestMoreStuffImpl(counters);
LaunchCompatTestProcess("client:EmbargoError", stdout => LaunchCompatTestProcess("client:EmbargoError", addr, port, stdout =>
{ {
AssertOutput(stdout, "EmbargoError test start"); AssertOutput(stdout, "EmbargoError test start");
AssertOutput(stdout, "EmbargoError test end"); AssertOutput(stdout, "EmbargoError test end");
@ -1055,17 +1067,18 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void EmbargoNullServer() public void EmbargoNullServer()
{ {
LaunchCompatTestProcess("server:MoreStuff", stdout => (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
LaunchCompatTestProcess("server:MoreStuff", addr, port, stdout =>
{ {
int retry = 0; int retry = 0;
label: label:
using (var client = new TcpRpcClient("localhost", TcpPort)) using (var client = new TcpRpcClient(addr.ToString(), port))
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
using (var main = client.GetMain<ITestMoreStuff>()) using (var main = client.GetMain<ITestMoreStuff>())
{ {
@ -1075,7 +1088,7 @@ namespace Capnp.Net.Runtime.Tests
try try
{ {
success = resolving.WhenResolved.Wait(MediumNonDbgTimeout); success = resolving.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout);
} }
catch catch
{ {
@ -1115,15 +1128,16 @@ namespace Capnp.Net.Runtime.Tests
}); });
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void EmbargoNullClient() public void EmbargoNullClient()
{ {
using (var server = SetupServer()) (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = SetupServer(addr, port))
{ {
var counters = new Counters(); var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters); server.Main = new TestMoreStuffImpl(counters);
LaunchCompatTestProcess("client:EmbargoNull", stdout => LaunchCompatTestProcess("client:EmbargoNull", addr, port, stdout =>
{ {
AssertOutput(stdout, "EmbargoNull test start"); AssertOutput(stdout, "EmbargoNull test start");
AssertOutput(stdout, "EmbargoNull test end"); AssertOutput(stdout, "EmbargoNull test end");
@ -1131,19 +1145,20 @@ namespace Capnp.Net.Runtime.Tests
} }
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void CallBrokenPromiseServer() public void CallBrokenPromiseServer()
{ {
LaunchCompatTestProcess("server:MoreStuff", stdout => (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
LaunchCompatTestProcess("server:MoreStuff", addr, port, stdout =>
{ {
using (var client = new TcpRpcClient("localhost", TcpPort)) using (var client = new TcpRpcClient(addr.ToString(), port))
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
using (var main = client.GetMain<ITestMoreStuff>()) using (var main = client.GetMain<ITestMoreStuff>())
{ {
var resolving = main as IResolvingCapability; var resolving = main as IResolvingCapability;
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout)); Assert.IsTrue(resolving.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
var tcs = new TaskCompletionSource<ITestInterface>(); var tcs = new TaskCompletionSource<ITestInterface>();
@ -1168,15 +1183,16 @@ namespace Capnp.Net.Runtime.Tests
}); });
} }
[TestMethod, Timeout(10000)] [TestMethod]
public void CallBrokenPromiseClient() public void CallBrokenPromiseClient()
{ {
using (var server = SetupServer()) (var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = SetupServer(addr, port))
{ {
var counters = new Counters(); var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters); server.Main = new TestMoreStuffImpl(counters);
LaunchCompatTestProcess("client:CallBrokenPromise", stdout => LaunchCompatTestProcess("client:CallBrokenPromise", addr, port, stdout =>
{ {
AssertOutput(stdout, "CallBrokenPromise test start"); AssertOutput(stdout, "CallBrokenPromise test start");
AssertOutput(stdout, "CallBrokenPromise test end"); AssertOutput(stdout, "CallBrokenPromise test end");

View File

@ -12,105 +12,27 @@ using Microsoft.Extensions.Logging;
namespace Capnp.Net.Runtime.Tests namespace Capnp.Net.Runtime.Tests
{ {
[TestClass] [TestClass]
[TestCategory("Coverage")]
public class TcpRpcPorted: TestBase public class TcpRpcPorted: TestBase
{ {
[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(TcpRpcTestOptions.ClientTracer).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]
@ -121,13 +43,13 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
var counters = new Counters(); var counters = new Counters();
server.Main = new TestMoreStuffImpl(counters); server.Main = new TestMoreStuffImpl(counters);
using (var main = client.GetMain<ITestMoreStuff>()) using (var main = client.GetMain<ITestMoreStuff>())
{ {
((Proxy)main).WhenResolved.Wait(MediumNonDbgTimeout); ((Proxy)main).WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout);
// Since we have a threaded model, there is no way to deterministically provoke the situation // Since we have a threaded model, there is no way to deterministically provoke the situation
// where Cancel and Finish message cross paths. Instead, we'll do a lot of such requests and // where Cancel and Finish message cross paths. Instead, we'll do a lot of such requests and
@ -159,482 +81,108 @@ 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.EmbargoOnPromisedAnswer);
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()
{
NewLocalhostTcpTestbed().RunTest(Testsuite.CallBrokenPromise);
}
[TestMethod]
public void BootstrapReuse()
{ {
(var server, var client) = SetupClientServerPair(); (var server, var client) = SetupClientServerPair();
var counters = new Counters();
var impl = new TestInterfaceImpl(counters);
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
server.Main = impl; server.Main = impl;
using (var main = client.GetMain<ITestMoreStuff>()) for (int i = 0; i < 10; i++)
{ {
var resolving = main as IResolvingCapability; using (var main = client.GetMain<ITestMoreStuff>())
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout)); {
((Proxy)main).WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout);
var tcs = new TaskCompletionSource<ITestInterface>(); }
Assert.IsFalse(impl.IsDisposed);
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));
} }
} }
Assert.IsTrue(impl.IsDisposed);
}
[TestMethod]
public void Ownership1()
{
NewLocalhostTcpTestbed().RunTest(Testsuite.Ownership1);
}
[TestMethod]
public void Ownership2()
{
NewLocalhostTcpTestbed().RunTest(Testsuite.Ownership2);
}
[TestMethod]
public void Ownership3()
{
NewLocalhostTcpTestbed().RunTest(Testsuite.Ownership3);
} }
} }
} }

View File

@ -1,4 +1,5 @@
using Capnp.Net.Runtime.Tests.GenImpls; using Capnp.Net.Runtime.Tests.GenImpls;
using Capnp.Net.Runtime.Tests.Util;
using Capnp.Rpc; using Capnp.Rpc;
using Capnproto_test.Capnp.Test; using Capnproto_test.Capnp.Test;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -6,6 +7,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;
@ -19,7 +21,6 @@ namespace Capnp.Net.Runtime.Tests
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
Logger.LogTrace("Repetition {0}", i); Logger.LogTrace("Repetition {0}", i);
IncrementTcpPort();
action(); action();
} }
} }
@ -34,7 +35,7 @@ namespace Capnp.Net.Runtime.Tests
using (server) using (server)
using (client) using (client)
{ {
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
var counters = new Counters(); var counters = new Counters();
var impl = new TestMoreStuffImpl(counters); var impl = new TestMoreStuffImpl(counters);
@ -42,7 +43,7 @@ namespace Capnp.Net.Runtime.Tests
using (var main = client.GetMain<ITestMoreStuff>()) using (var main = client.GetMain<ITestMoreStuff>())
{ {
var resolving = main as IResolvingCapability; var resolving = main as IResolvingCapability;
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout)); Assert.IsTrue(resolving.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
} }
} }
}); });
@ -59,10 +60,17 @@ namespace Capnp.Net.Runtime.Tests
public void Embargo() public void Embargo()
{ {
var t = new TcpRpcPorted(); var t = new TcpRpcPorted();
Repeat(100, t.Embargo); Repeat(100,
() =>
NewLocalhostTcpTestbed(TcpRpcTestOptions.ClientTracer | TcpRpcTestOptions.ClientFluctStream)
.RunTest(Testsuite.EmbargoOnPromisedAnswer));
}
[TestMethod]
public void EmbargoServer()
{
var t2 = new TcpRpcInterop(); var t2 = new TcpRpcInterop();
Repeat(100, t2.EmbargoServer); Repeat(20, t2.EmbargoServer);
} }
[TestMethod] [TestMethod]
@ -94,14 +102,15 @@ namespace Capnp.Net.Runtime.Tests
[TestMethod] [TestMethod]
public void ScatteredTransfer() public void ScatteredTransfer()
{ {
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
using (var server = new TcpRpcServer(IPAddress.Any, TcpPort)) using (var server = new TcpRpcServer(addr, port))
using (var client = new TcpRpcClient()) using (var client = new TcpRpcClient())
{ {
server.InjectMidlayer(s => new ScatteringStream(s, 7)); server.InjectMidlayer(s => new ScatteringStream(s, 7));
client.InjectMidlayer(s => new ScatteringStream(s, 10)); client.InjectMidlayer(s => new ScatteringStream(s, 10));
client.Connect("localhost", TcpPort); client.Connect(addr.ToString(), port);
client.WhenConnected.Wait(); //client.WhenConnected.Wait();
var counters = new Counters(); var counters = new Counters();
server.Main = new TestInterfaceImpl(counters); server.Main = new TestInterfaceImpl(counters);

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

@ -1,102 +0,0 @@
using Capnp.Rpc;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Net.Runtime.Tests.ManualImpls
{
// [Skeleton(typeof(TestInterfaceSkeleton))]
// [Proxy(typeof(TestInterfaceProxy))]
// interface ITestInterface: IDisposable
// {
// Task<string> Foo(uint i, bool j);
// Task Bar();
// Task<int> Baz(int s);
// }
// [Skeleton(typeof(TestExtendsSkeleton))]
// [Proxy(typeof(TestExtendsProxy))]
// interface ITestExtends: ITestInterface, IDisposable
// {
// void Qux();
// Task Corge(int x);
// Task<int> Grault();
// }
// interface ITestExtends2: ITestExtends, IDisposable
// {
// }
// struct Box
// {
// public ITestExtends Cap { get; set; }
// }
// struct AnyBox
// {
// public object Cap { get; set; }
// }
// [Skeleton(typeof(TestPipelineSkeleton))]
// [Proxy(typeof(TestPipelineProxy))]
// interface ITestPipeline: IDisposable
// {
// Task<(string, Box)> GetCap(uint n, ITestInterface inCap);
// Task TestPointers(ITestExtends cap, DeserializerState obj, IReadOnlyList<ITestExtends> list);
// Task<(string, AnyBox)> GetAnyCap(uint n, object inCap);
// }
// [Skeleton(typeof(TestCallOrderSkeleton))]
// [Proxy(typeof(TestCallOrderProxy))]
// interface ITestCallOrder : IDisposable
// {
// Task<uint> GetCallSequence(uint expected);
// }
// struct TailResult
// {
// public uint I { get; set; }
// public string T { get; set; }
// public ITestCallOrder C { get; set; }
//}
// [Skeleton(typeof(TestTailCalleeSkeleton))]
// [Proxy(typeof(TestTailCalleeProxy))]
// interface ITestTailCallee: IDisposable
// {
// Task<TailResult> Foo(int i, string t);
// }
// [Skeleton(typeof(TestTailCallerSkeleton))]
// [Proxy(typeof(TestTailCallerProxy))]
// interface ITestTailCaller: IDisposable
// {
// Task<TailResult> Foo(int i, ITestTailCallee c);
// }
// [Skeleton(typeof(TestHandleSkeleton))]
// [Proxy(typeof(TestHandleProxy))]
// interface ITestHandle: IDisposable { }
// [Skeleton(typeof(TestMoreStuffSkeleton))]
// [Proxy(typeof(TestMoreStuffProxy))]
// interface ITestMoreStuff: ITestCallOrder
// {
// Task<string> CallFoo(ITestInterface cap);
// Task<string> CallFooWhenResolved(ITestInterface cap);
// Task<ITestInterface> NeverReturn(ITestInterface cap, CancellationToken ct);
// Task Hold(ITestInterface cap);
// Task<string> CallHeld();
// Task<ITestInterface> GetHeld();
// Task<ITestCallOrder> Echo(ITestCallOrder cap);
// Task ExpectCancel(ITestInterface cap, CancellationToken ct);
// Task<(string, string)> MethodWithDefaults(string a, uint b, string c);
// void MethodWithNullDefault(string a, ITestInterface b);
// Task<ITestHandle> GetHandle();
// Task<ITestMoreStuff> GetNull();
// Task<string> GetEnormousString();
// }
}

View File

@ -0,0 +1,870 @@
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 (TaskCanceledException)
{
// Also used in some test
}
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 EmbargoOnPromisedAnswer(ITestbed testbed)
{
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
{
if (main is IResolvingCapability resolving)
testbed.MustComplete(resolving.WhenResolved.WrappedTask);
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(true))
{
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);
testbed.MustComplete(call0);
testbed.MustComplete(call1);
testbed.MustComplete(call2);
testbed.MustComplete(call3);
testbed.MustComplete(call4);
testbed.MustComplete(call5);
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);
Assert.AreEqual(cap.Count, cap.CountToDispose, "counter must have reached number of calls");
}
}
}
}
public static void EmbargoOnImportedCap(ITestbed testbed)
{
var impl = new TestMoreStuffImpl2();
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
{
var cap = new TestCallOrderImpl();
cap.CountToDispose = 6;
var earlyCall = main.GetCallSequence(0, default);
var echo = main.Echo(cap, default);
testbed.MustComplete(echo);
using (var pipeline = echo.Result)
{
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))
{
if (main is IResolvingCapability resolving)
testbed.MustComplete(resolving.WhenResolved.WrappedTask);
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(true))
{
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))
{
if (main is IResolvingCapability resolving)
testbed.MustComplete(resolving.WhenResolved.WrappedTask);
var promise = main.GetNull(default);
using (var cap = promise.Eager(true))
{
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();
using (var impl = new TestMoreStuffImpl(counters))
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
{
if (main is IResolvingCapability resolving)
testbed.MustComplete(resolving.WhenResolved.WrappedTask);
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.IsTrue(callee.IsDisposed);
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(Proxy.Share<ITestInterface>(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(Proxy.Share<ITestInterface>(cap), default);
ftask2 = main.CallFoo(Proxy.Share<ITestInterface>(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();
using (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);
Assert.IsFalse(((Proxy)copy).IsDisposed);
var ctask = main.CallFoo(copy, default);
Assert.IsTrue(((Proxy)copy).IsDisposed);
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);
}
}
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>();
using (var eager = tcs.Task.Eager(true))
{
var request = main.CallFoo(Proxy.Share(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 PromiseResolveLate(ITestbed testbed)
{
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
{
var tcs = new TaskCompletionSource<ITestInterface>();
var disposed = new TaskCompletionSource<int>();
using (var eager = tcs.Task.Eager(true))
{
var request = main.NeverReturn(Proxy.Share(eager), new CancellationToken(true));
testbed.MustComplete(request);
var tiimpl = new TestInterfaceImpl(new Counters(), disposed);
tcs.SetResult(tiimpl);
Assert.IsFalse(tiimpl.IsDisposed);
}
testbed.MustComplete(disposed.Task);
}
}
public static void PromiseResolveError(ITestbed testbed)
{
var counters = new Counters();
var impl = new TestMoreStuffImpl(counters);
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
{
var tcs = new TaskCompletionSource<ITestInterface>();
using (var eager = tcs.Task.Eager(true))
{
var request = main.CallFoo(Proxy.Share(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);
tcs.SetException(new System.Exception("too bad"));
testbed.MustComplete(request, request2);
Assert.IsTrue(request.IsFaulted);
Assert.IsTrue(request2.IsFaulted);
}
}
}
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.IsTrue(SpinWait.SpinUntil(() => counters.HandleCount == 1, TestBase.ShortTimeout));
task2.Result.Dispose();
testbed.FlushCommunication();
Assert.IsTrue(SpinWait.SpinUntil(() => counters.HandleCount == 0, TestBase.ShortTimeout));
}
}
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);
using (var testx = ((Proxy)outBox).Cast<ITestExtends>(false))
{
var pipelineRequest2 = testx.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);
}
}
public static void BootstrapReuse(ITestbed testbed)
{
var counters = new Counters();
var impl = new TestInterfaceImpl(counters);
for (int i = 0; i < 10; i++)
{
using (var main = testbed.ConnectMain<ITestInterface>(impl))
{
}
Assert.IsFalse(impl.IsDisposed);
}
}
public static void Ownership1(ITestbed testbed)
{
var impl = new TestMoreStuffImpl(new Counters());
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
{
var tcs = new TaskCompletionSource<int>();
var ti = new TestInterfaceImpl(new Counters(), tcs);
testbed.MustComplete(main.CallFoo(ti, default));
testbed.MustComplete(tcs.Task);
}
}
public static void Ownership2(ITestbed testbed)
{
var impl = new TestMoreStuffImpl(new Counters());
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
using (var nullProxy = main.GetNull().Eager(true))
{
var tcs = new TaskCompletionSource<int>();
var ti = new TestInterfaceImpl(new Counters(), tcs);
testbed.MustComplete(nullProxy.CallFoo(ti, default));
testbed.MustComplete(tcs.Task);
}
}
public static void Ownership3(ITestbed testbed)
{
var impl = new TestMoreStuffImpl(new Counters());
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
using (var nullProxy = main.GetNull(new CancellationToken(true)).Eager(true))
{
var tcs = new TaskCompletionSource<int>();
var ti = new TestInterfaceImpl(new Counters(), tcs);
testbed.MustComplete(nullProxy.CallFoo(ti, default));
testbed.MustComplete(tcs.Task);
}
}
class ThrowingSkeleton : RefCountingSkeleton
{
public bool WasCalled { get; private set; }
public override Task<AnswerOrCounterquestion> Invoke(ulong interfaceId, ushort methodId, DeserializerState args, CancellationToken cancellationToken = default)
{
WasCalled = true;
throw new NotImplementedException();
}
}
public static void SillySkeleton(ITestbed testbed)
{
var impl = new ThrowingSkeleton();
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
{
var tcs = new TaskCompletionSource<int>();
var ti = new TestInterfaceImpl(new Counters(), tcs);
testbed.ExpectPromiseThrows(main.CallFoo(ti));
Assert.IsTrue(impl.WasCalled);
testbed.MustComplete(tcs.Task);
}
}
public static void ImportReceiverAnswer(ITestbed testbed)
{
var impl = new TestMoreStuffImpl2();
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
{
var held = main.GetHeld().Eager();
var foo = main.CallFoo(held);
testbed.MustNotComplete(foo);
var tcs = new TaskCompletionSource<int>();
testbed.MustComplete(
main.Hold(new TestInterfaceImpl(new Counters(), tcs)),
foo,
tcs.Task);
}
}
public static void ImportReceiverAnswerError(ITestbed testbed)
{
var impl = new TestMoreStuffImpl2();
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
using (var held = main.GetHeld().Eager())
{
var foo = main.CallFoo(held);
testbed.MustNotComplete(foo);
var faulted = Task.FromException<ITestInterface>(
new InvalidOperationException("I faulted")).Eager(true);
testbed.MustComplete(
main.Hold(faulted),
foo);
Assert.IsTrue(foo.IsFaulted);
}
}
public static void ImportReceiverCanceled(ITestbed testbed)
{
var impl = new TestMoreStuffImpl2();
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
using (var held = main.GetHeld().Eager())
{
var foo = main.CallFoo(held);
testbed.MustNotComplete(foo);
var canceled = Task.FromCanceled<ITestInterface>(new CancellationToken(true)).Eager(true);
testbed.MustComplete(
main.Hold(canceled),
foo);
Assert.IsTrue(foo.IsCanceled);
}
}
public static void ButNoTailCall(ITestbed testbed)
{
var impl = new TestMoreStuffImpl4();
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
{
var peer = new TestMoreStuffImpl5();
var heldTask = main.Echo(peer);
testbed.MustComplete(heldTask);
var r = heldTask.Result as IResolvingCapability;
peer.EnableEcho();
testbed.MustComplete(r.WhenResolved.WrappedTask);
heldTask.Result.Dispose();
}
}
public static void SecondIsTailCall(ITestbed testbed)
{
var impl = new TestTailCallerImpl3();
using (var main = testbed.ConnectMain<ITestTailCaller>(impl))
{
var callee = new TestTailCalleeImpl(new Counters());
var task = main.Foo(123, callee);
testbed.MustComplete(task);
Assert.AreEqual("from TestTailCaller 2", task.Result.T);
}
}
public static void NoTailCallMt(ITestbed testbed)
{
var impl = new TestTailCallerImpl4();
using (var main = testbed.ConnectMain<ITestTailCaller>(impl))
using (var callee = Proxy.Share<ITestTailCallee>(new TestTailCalleeImpl(new Counters())))
{
var tasks = ParallelEnumerable
.Range(0, 200)
.Select(async i =>
{
var r = await main.Foo(i, Proxy.Share(callee));
Assert.AreEqual((uint)i, r.I);
})
.ToArray();
testbed.MustComplete(tasks);
Assert.IsFalse(tasks.Any(t => t.IsCanceled || t.IsFaulted));
}
}
public static void ReexportSenderPromise(ITestbed testbed)
{
var impl = new TestMoreStuffImpl(new Counters());
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
{
var tcs = new TaskCompletionSource<ITestInterface>();
var tcsd = new TaskCompletionSource<int>();
using (var promise = tcs.Task.Eager(true))
{
var task1 = main.CallFooWhenResolved(Proxy.Share(promise));
var task2 = main.CallFooWhenResolved(Proxy.Share(promise));
var callee = new TestInterfaceImpl(new Counters(), tcsd);
tcs.SetResult(callee);
testbed.MustComplete(task1, task2);
}
testbed.MustComplete(tcsd.Task);
}
}
public static void CallAfterFinish1(ITestbed testbed)
{
var counters = new Counters();
var impl = new TestMoreStuffImpl3();
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
{
using (var held = main.GetHeld().Eager())
{
testbed.CloseClient();
testbed.ExpectPromiseThrows(held.Foo(123, true));
}
}
}
public static void CallAfterFinish2(ITestbed testbed)
{
var counters = new Counters();
var impl = new TestMoreStuffImpl3();
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
{
using (var cts = new CancellationTokenSource())
using (var held = main.GetHeld(cts.Token).Eager())
{
cts.Cancel();
testbed.ExpectPromiseThrows(held.Foo(123, true));
}
}
}
public static void LegacyAccess(ITestbed testbed)
{
var impl = new TestMoreStuffImpl(new Counters());
using (var main = testbed.ConnectMain<ITestMoreStuff>(impl))
{
var task = main.Echo(new TestCallOrderImpl());
var answer = Impatient.TryGetAnswer(task);
Assert.IsNotNull(answer);
var cap = answer.Access(new MemberAccessPath(0));
using (var proxy = (ITestCallOrder)CapabilityReflection.CreateProxy<ITestCallOrder>(cap))
{
var seq = proxy.GetCallSequence(0);
testbed.MustComplete(seq);
Assert.AreEqual(0u, seq.Result);
}
}
}
}
}

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,50 @@
using System;
using System.IO;
using System.Threading;
namespace Capnp.Net.Runtime.Tests
{
class FluctStream : Stream
{
readonly Stream _baseStream;
readonly Random _rng = new Random();
public FluctStream(Stream baseStream)
{
_baseStream = baseStream;
}
public override bool CanRead => _baseStream.CanRead;
public override bool CanSeek => false;
public override bool CanWrite => _baseStream.CanWrite;
public override long Length => _baseStream.Length;
public override long Position
{
get => _baseStream.Position;
set => throw new NotImplementedException();
}
public override void Flush() => _baseStream.Flush();
public override int Read(byte[] buffer, int offset, int count)
{
int n = _rng.Next(0, 8);
if (n >= 7)
Thread.Sleep(n - 7);
return _baseStream.Read(buffer, offset, count);
}
public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException();
public override void SetLength(long value) => throw new NotImplementedException();
public override void Write(byte[] buffer, int offset, int count)
{
_baseStream.Write(buffer, offset, count);
}
}
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Capnp.Net.Runtime.Tests namespace Capnp.Net.Runtime.Tests

View File

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
namespace Capnp.Net.Runtime.Tests.Util
{
class TcpManager
{
public static readonly TcpManager Instance = new TcpManager();
readonly byte[] _nextAddress;
int _nextPort = 50005;
public TcpManager()
{
_nextAddress = new byte[] { 127, 0, 0, 1 };
}
public (IPAddress, int) GetLocalAddressAndPort()
{
if (++_nextAddress[2] == 0 &&
++_nextAddress[1] == 0 &&
++_nextAddress[0] == 0)
{
_nextAddress[0] = 2;
}
return (new IPAddress(_nextAddress), _nextPort);
}
}
}

View File

@ -0,0 +1,485 @@
using Capnp.Net.Runtime.Tests.Util;
using Capnp.Rpc;
using Capnp.Util;
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>(object main) where T : class, IDisposable;
void MustComplete(params Task[] tasks);
void MustNotComplete(params Task[] tasks);
void FlushCommunication();
void CloseClient();
void CloseServer();
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()
{
if (!_dismissed)
{
_dismissed = true;
OtherEndpoint.Dismiss();
}
}
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;
try
{
OtherEndpoint.Forward(frame);
}
catch (InvalidOperationException)
{
}
_recursion = false;
return true;
}
else
{
return false;
}
}
void IEndpoint.Flush()
{
}
}
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 LocalTestbed : ITestbed, ITestController
{
long ITestbed.ClientSendCount => 0;
public void RunTest(Action<ITestbed> action)
{
action(this);
}
T ITestbed.ConnectMain<T>(object main)
{
return Proxy.Share<T>((T)main);
}
void ITestbed.FlushCommunication()
{
}
void ITestbed.MustComplete(params Task[] tasks)
{
Assert.IsTrue(tasks.All(t => t.IsCompleted));
}
void ITestbed.MustNotComplete(params Task[] tasks)
{
Assert.IsFalse(tasks.Any(t => t.IsCompleted));
}
void ITestbed.CloseClient()
{
throw new NotSupportedException();
}
void ITestbed.CloseServer()
{
throw new NotSupportedException();
}
}
protected class DtbdctTestbed : ITestbed, ITestController
{
readonly DecisionTree _decisionTree = new DecisionTree();
EnginePair _enginePair;
public void RunTest(Action<ITestbed> action)
{
_decisionTree.Iterate(() => {
action(this);
_enginePair.FlushChannels(() => false);
Assert.AreEqual(0, _enginePair.Endpoint1.ExportedCapabilityCount);
Assert.AreEqual(0, _enginePair.Endpoint1.ImportedCapabilityCount);
Assert.AreEqual(0, _enginePair.Endpoint1.PendingQuestionCount);
Assert.AreEqual(0, _enginePair.Endpoint1.PendingAnswerCount);
Assert.AreEqual(0, _enginePair.Endpoint2.ExportedCapabilityCount);
Assert.AreEqual(0, _enginePair.Endpoint2.ImportedCapabilityCount);
Assert.AreEqual(0, _enginePair.Endpoint2.PendingQuestionCount);
Assert.AreEqual(0, _enginePair.Endpoint2.PendingAnswerCount);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
});
}
T ITestbed.ConnectMain<T>(object 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;
void ITestbed.CloseClient()
{
_enginePair.Endpoint1.Dismiss();
}
void ITestbed.CloseServer()
{
_enginePair.Endpoint2.Dismiss();
}
}
protected class LocalhostTcpTestbed : ITestbed, ITestController
{
readonly TcpRpcTestOptions _options;
TcpRpcServer _server;
TcpRpcClient _client;
bool _prematurelyClosed;
public LocalhostTcpTestbed(TcpRpcTestOptions options)
{
_options = options;
}
public void RunTest(Action<ITestbed> action)
{
(_server, _client) = SetupClientServerPair(_options);
Assert.IsTrue(SpinWait.SpinUntil(() => _server.ConnectionCount > 0, LargeNonDbgTimeout));
var conn = _server.Connections[0];
using (_server)
using (_client)
{
action(this);
if (!_prematurelyClosed)
{
Assert.IsTrue(SpinWait.SpinUntil(() => _client.SendCount == conn.RecvCount, MediumNonDbgTimeout));
Assert.IsTrue(SpinWait.SpinUntil(() => conn.SendCount == _client.RecvCount, MediumNonDbgTimeout));
}
}
}
T ITestbed.ConnectMain<T>(object main)
{
_server.Main = main;
return _client.GetMain<T>();
}
static Task[] GulpExceptions(Task[] tasks)
{
async Task Gulp(Task t)
{
try
{
await t;
}
catch
{
}
}
return tasks.Select(Gulp).ToArray();
}
void ITestbed.MustComplete(params Task[] tasks)
{
Assert.IsTrue(Task.WaitAll(GulpExceptions(tasks), MediumNonDbgTimeout));
}
void ITestbed.MustNotComplete(params Task[] tasks)
{
Assert.AreEqual(-1, Task.WaitAny(GulpExceptions(tasks), ShortTimeout));
}
void ITestbed.FlushCommunication()
{
WaitClientServerIdle(_server, _client);
}
long ITestbed.ClientSendCount => _client.SendCount;
void ITestbed.CloseClient()
{
_prematurelyClosed = true;
_client.Dispose();
}
void ITestbed.CloseServer()
{
_prematurelyClosed = true;
_server.Dispose();
}
}
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 TestBase()
{
Logging.LoggerFactory?.Dispose();
#pragma warning disable CS0618
Logging.LoggerFactory = new LoggerFactory().AddConsole((msg, level) => true);
#pragma warning restore CS0618
Logger = Logging.CreateLogger<TcpRpcStress>();
if (Thread.CurrentThread.Name == null)
Thread.CurrentThread.Name = $"Test Thread {Thread.CurrentThread.ManagedThreadId}";
#if SOTASK_PERF
StrictlyOrderedTaskExtensions.Stats.Reset();
#endif
}
[TestCleanup]
public void TestCleanup()
{
#if SOTASK_PERF
Console.WriteLine($"StrictlyOrderedTask performance statistics:");
Console.WriteLine($"AwaitInternal: max. {StrictlyOrderedTaskExtensions.Stats.AwaitInternalMaxOuterIterations} outer iterations");
Console.WriteLine($"AwaitInternal: max. {StrictlyOrderedTaskExtensions.Stats.AwaitInternalMaxInnerIterations} inner iterations");
Console.WriteLine($"OnCompleted: max. {StrictlyOrderedTaskExtensions.Stats.OnCompletedMaxSpins} iterations");
#endif
}
protected static TcpRpcClient SetupClient(IPAddress addr, int port, TcpRpcTestOptions options = TcpRpcTestOptions.None)
{
var client = new TcpRpcClient();
client.AddBuffering();
if (options.HasFlag(TcpRpcTestOptions.ClientTracer))
client.AttachTracer(new FrameTracing.RpcFrameTracer(Console.Out, false));
if (options.HasFlag(TcpRpcTestOptions.ClientFluctStream))
client.InjectMidlayer(s => new FluctStream(s));
if (!options.HasFlag(TcpRpcTestOptions.ClientNoConnect))
client.Connect(addr.ToString(), port);
return client;
}
[Flags]
public enum TcpRpcTestOptions
{
None = 0,
ClientTracer = 1,
ClientFluctStream = 2,
ClientNoConnect = 4
}
protected static TcpRpcServer SetupServer(IPAddress addr, int port)
{
var server = new TcpRpcServer();
server.AddBuffering();
server.StartAccepting(addr, port);
return server;
}
protected static (TcpRpcServer, TcpRpcClient) SetupClientServerPair(TcpRpcTestOptions options = TcpRpcTestOptions.None)
{
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
var server = SetupServer(addr, port);
var client = SetupClient(addr, port, options);
return (server, client);
}
protected static T SetupEnginePair<T>(object 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);
}
protected static DtbdctTestbed NewDtbdctTestbed() => new DtbdctTestbed();
protected static LocalhostTcpTestbed NewLocalhostTcpTestbed(TcpRpcTestOptions options = TcpRpcTestOptions.None) =>
new LocalhostTcpTestbed(options);
protected static LocalTestbed NewLocalTestbed() => new LocalTestbed();
/// <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

@ -4,6 +4,7 @@ using System;
namespace Capnp.Net.Runtime.Tests namespace Capnp.Net.Runtime.Tests
{ {
[TestClass] [TestClass]
[TestCategory("Coverage")]
public class WirePointerTests public class WirePointerTests
{ {
[TestMethod] [TestMethod]

View File

@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Capnp.Net.Runtime.Tests")]

View File

@ -22,14 +22,18 @@
<PackageTags>capnp "Cap'n Proto" RPC serialization cerealization</PackageTags> <PackageTags>capnp "Cap'n Proto" RPC serialization cerealization</PackageTags>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'"> <PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>TRACE;DEBUG</DefineConstants> <DefineConstants>TRACE;DEBUG</DefineConstants>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<DefineConstants>DebugCapabilityLifecycle</DefineConstants> <DefineConstants></DefineConstants>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.0|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.0|AnyCPU'">
@ -39,6 +43,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -36,7 +36,8 @@ namespace Capnp
public FromList() public FromList()
{ {
_elementSerializer = (Func<DeserializerState, T>)GetSerializer(typeof(T)); var deser = GetSerializer(typeof(T));
_elementSerializer = d => (deser(d) as T)!;
} }
public object Create(DeserializerState state) public object Create(DeserializerState state)
@ -54,11 +55,22 @@ namespace Capnp
} }
} }
static object? CreateFromAny(DeserializerState state)
{
switch (state.Kind)
{
case ObjectKind.Capability: return state.RequireCap<Rpc.BareProxy>();
case ObjectKind.Nil: return null;
default: return state;
}
}
static readonly ConditionalWeakTable<Type, Func<DeserializerState, object?>> _typeMap = static readonly ConditionalWeakTable<Type, Func<DeserializerState, object?>> _typeMap =
new ConditionalWeakTable<Type, Func<DeserializerState, object?>>(); new ConditionalWeakTable<Type, Func<DeserializerState, object?>>();
static CapnpSerializable() static CapnpSerializable()
{ {
_typeMap.Add(typeof(object), CreateFromAny);
_typeMap.Add(typeof(string), d => d.RequireList().CastText()); _typeMap.Add(typeof(string), d => d.RequireList().CastText());
_typeMap.Add(typeof(IReadOnlyList<bool>), d => d.RequireList().CastBool()); _typeMap.Add(typeof(IReadOnlyList<bool>), d => d.RequireList().CastBool());
_typeMap.Add(typeof(IReadOnlyList<sbyte>), d => d.RequireList().CastSByte()); _typeMap.Add(typeof(IReadOnlyList<sbyte>), d => d.RequireList().CastSByte());
@ -147,29 +159,30 @@ namespace Capnp
/// <list type="bullet"> /// <list type="bullet">
/// <item><description>Type implementing <see cref="ICapnpSerializable"/>. The type must must have a public parameterless constructor.</description></item> /// <item><description>Type implementing <see cref="ICapnpSerializable"/>. The type must must have a public parameterless constructor.</description></item>
/// <item><description>A capability interface (<seealso cref="Rpc.InvalidCapabilityInterfaceException"/> for further explanation)</description></item> /// <item><description>A capability interface (<seealso cref="Rpc.InvalidCapabilityInterfaceException"/> for further explanation)</description></item>
/// <item><description><see cref="String"/></description></item> /// <item><description><see cref="string"/></description></item>
/// <item><description><code>IReadOnlyList{Boolean}</code></description></item> /// <item><description>IReadOnlyList&lt;bool&gt;, IReadOnlyList&lt;sbyte&gt;, IReadOnlyList&lt;byte&gt;</description></item>
/// <item><description><code>IReadOnlyList{SByte}"</code></description></item> /// <item><description>IReadOnlyList&lt;short&gt;, IReadOnlyList&lt;ushort&gt;, IReadOnlyList&lt;int&gt;</description></item>
/// <item><description><code>IReadOnlyList{Byte}"</code></description></item> /// <item><description>IReadOnlyList&lt;uint&gt;, IReadOnlyList&lt;long&gt;, IReadOnlyList&lt;ulong&gt;</description></item>
/// <item><description><code>IReadOnlyList{Int16}"</code></description></item> /// <item><description>IReadOnlyList&lt;float&gt;, IReadOnlyList&lt;double&gt;</description></item>
/// <item><description><code>IReadOnlyList{UInt16}"</code></description></item> /// <item><description>IReadOnlyList&lt;T&gt; whereby T is one of the things listed here.</description></item>
/// <item><description><code>IReadOnlyList{Int32}"</code></description></item>
/// <item><description><code>IReadOnlyList{UInt32}"</code></description></item>
/// <item><description><code>IReadOnlyList{Int64}"</code></description></item>
/// <item><description><code>IReadOnlyList{UInt64}"</code></description></item>
/// <item><description><code>IReadOnlyList{Single}"</code></description></item>
/// <item><description><code>IReadOnlyList{Double}"</code></description></item>
/// <item><description><code>IReadOnlyList{T}</code> whereby T is one of the things listed here.</description></item>
/// </list> /// </list>
/// </typeparam> /// </typeparam>
/// <param name="state">deserializer state to construct from</param> /// <param name="state">deserializer state to construct from</param>
/// <returns>The domain object instance. Nullability note: The returned reference will be null if (and only if) <typeparamref name="T"/> is a capability interface and /// <returns>The domain object instance. Nullability note: The returned reference may be null if
/// <paramref name="state"/> represents the nil object (obtained from a null pointer). For all other types, when the state is nil, /// <paramref name="state"/> represents the nil object.</returns>
/// the method still constructs a valid but "empty" object instance (such as domain object without any properties set, empty string, empty list etc.)</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) public static T? Create<T>(DeserializerState state)
where T: class where T: class
{ {
return (T?)GetSerializer(typeof(T))(state); try
{
return (T?)GetSerializer(typeof(T))(state);
}
catch (TargetInvocationException ex)
{
throw new ArgumentException("Failed to construct domain object", ex);
}
} }
} }
} }

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 /// 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.
@ -45,12 +45,13 @@ namespace Capnp
/// The kind of object this state currently represents. /// The kind of object this state currently represents.
/// </summary> /// </summary>
public ObjectKind Kind { get; set; } public ObjectKind Kind { get; set; }
bool _disposed;
/// <summary> /// <summary>
/// The capabilities imported from the capability table. Only valid in RPC context. /// The capabilities imported from the capability table. Only valid in RPC context.
/// </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;
@ -65,6 +66,7 @@ namespace Capnp
StructPtrCount = 1; StructPtrCount = 1;
Kind = ObjectKind.Struct; Kind = ObjectKind.Struct;
Caps = null; Caps = null;
_disposed = false;
} }
/// <summary> /// <summary>
@ -158,7 +160,6 @@ namespace Capnp
/// Memory span which represents this struct's data section (given this state actually represents a struct) /// Memory span which represents this struct's data section (given this state actually represents a struct)
/// </summary> /// </summary>
public ReadOnlySpan<ulong> StructDataSection => CurrentSegment.Slice(Offset, StructDataCount); public ReadOnlySpan<ulong> StructDataSection => CurrentSegment.Slice(Offset, StructDataCount);
ReadOnlySpan<ulong> StructPtrSection => CurrentSegment.Slice(Offset + StructDataCount, StructPtrCount);
ReadOnlySpan<ulong> GetRawBits() => CurrentSegment.Slice(Offset, (ListElementCount + 63) / 64); ReadOnlySpan<ulong> GetRawBits() => CurrentSegment.Slice(Offset, (ListElementCount + 63) / 64);
ReadOnlySpan<ulong> GetRawBytes() => CurrentSegment.Slice(Offset, (ListElementCount + 7) / 8); ReadOnlySpan<ulong> GetRawBytes() => CurrentSegment.Slice(Offset, (ListElementCount + 7) / 8);
@ -172,26 +173,15 @@ namespace Capnp
{ {
get get
{ {
switch (Kind) return Kind switch
{ {
case ObjectKind.ListOfBits: ObjectKind.ListOfBits => GetRawBits(),
return GetRawBits(); ObjectKind.ListOfBytes => GetRawBytes(),
ObjectKind.ListOfShorts => GetRawShorts(),
case ObjectKind.ListOfBytes: ObjectKind.ListOfInts => GetRawInts(),
return GetRawBytes(); ObjectKind.ListOfLongs => GetRawLongs(),
_ => default,
case ObjectKind.ListOfShorts: };
return GetRawShorts();
case ObjectKind.ListOfInts:
return GetRawInts();
case ObjectKind.ListOfLongs:
return GetRawLongs();
default:
return default;
}
} }
} }
@ -213,6 +203,10 @@ namespace Capnp
GetRawBytes(); GetRawBytes();
break; break;
case ObjectKind.ListOfShorts:
GetRawShorts();
break;
case ObjectKind.ListOfInts: case ObjectKind.ListOfInts:
GetRawInts(); GetRawInts();
break; break;
@ -313,6 +307,8 @@ namespace Capnp
case ListKind.ListOfStructs: case ListKind.ListOfStructs:
{ {
if (Offset >= CurrentSegment.Length)
throw new DeserializationException("List of composites pointer exceeds segment bounds");
WirePointer tag = CurrentSegment[Offset]; WirePointer tag = CurrentSegment[Offset];
if (tag.Kind != PointerKind.Struct) if (tag.Kind != PointerKind.Struct)
throw new DeserializationException("Unexpected: List of composites with non-struct type tag"); throw new DeserializationException("Unexpected: List of composites with non-struct type tag");
@ -333,9 +329,16 @@ namespace Capnp
case PointerKind.Far: case PointerKind.Far:
if (pointer.TargetSegmentIndex >= Segments.Count)
throw new DeserializationException("Error decoding pointer: Invalid target segment index");
CurrentSegmentIndex = pointer.TargetSegmentIndex;
if (pointer.IsDoubleFar) if (pointer.IsDoubleFar)
{ {
CurrentSegmentIndex = pointer.TargetSegmentIndex; if (pointer.LandingPadOffset >= CurrentSegment.Length - 1)
throw new DeserializationException("Error decoding double-far pointer: exceeds segment bounds");
Offset = 0; Offset = 0;
WirePointer pointer1 = CurrentSegment[pointer.LandingPadOffset]; WirePointer pointer1 = CurrentSegment[pointer.LandingPadOffset];
@ -347,15 +350,18 @@ namespace Capnp
throw new DeserializationException("Error decoding double-far pointer: not followed by intra-segment pointer"); throw new DeserializationException("Error decoding double-far pointer: not followed by intra-segment pointer");
CurrentSegmentIndex = pointer1.TargetSegmentIndex; CurrentSegmentIndex = pointer1.TargetSegmentIndex;
Offset = 0; Offset = pointer1.LandingPadOffset;
pointer = pointer2; pointer = pointer2;
offset = -1; offset = -1;
} }
else else
{ {
CurrentSegmentIndex = pointer.TargetSegmentIndex;
Offset = 0; Offset = 0;
offset = pointer.LandingPadOffset; offset = pointer.LandingPadOffset;
if (pointer.LandingPadOffset >= CurrentSegment.Length)
throw new DeserializationException("Error decoding pointer: exceeds segment bounds");
pointer = CurrentSegment[pointer.LandingPadOffset]; pointer = CurrentSegment[pointer.LandingPadOffset];
} }
continue; continue;
@ -383,14 +389,14 @@ namespace Capnp
/// </summary> /// </summary>
/// <param name="offset">Offset relative to this.Offset within current segment</param> /// <param name="offset">Offset relative to this.Offset within current segment</param>
/// <returns>the low-level capability object, or null if it is a null pointer</returns> /// <returns>the low-level capability object, or null if it is a null pointer</returns>
/// <exception cref="IndexOutOfRangeException">offset negative or out of range</exception> /// <exception cref="ArgumentOutOfRangeException">offset negative or out of range</exception>
/// <exception cref="InvalidOperationException">capability table not set</exception> /// <exception cref="InvalidOperationException">capability table not set</exception>
/// <exception cref="Rpc.RpcException">not a capability pointer or invalid capability index</exception> /// <exception cref="Rpc.RpcException">not a capability pointer or invalid capability index</exception>
internal Rpc.ConsumedCapability? DecodeCapPointer(int offset) internal Rpc.ConsumedCapability DecodeCapPointer(int offset)
{ {
if (offset < 0) if (offset < 0)
{ {
throw new IndexOutOfRangeException(nameof(offset)); throw new ArgumentOutOfRangeException(nameof(offset));
} }
if (Caps == null) if (Caps == null)
@ -404,7 +410,7 @@ namespace Capnp
{ {
// Despite this behavior is not officially specified, // Despite this behavior is not officially specified,
// the official C++ implementation seems to send null pointers for null caps. // the official C++ implementation seems to send null pointers for null caps.
return null; return Rpc.NullCapability.Instance;
} }
if (pointer.Kind != PointerKind.Other) if (pointer.Kind != PointerKind.Other)
@ -496,13 +502,13 @@ namespace Capnp
return state; return state;
} }
internal Rpc.ConsumedCapability? StructReadRawCap(int index) internal Rpc.ConsumedCapability StructReadRawCap(int index)
{ {
if (Kind != ObjectKind.Struct && Kind != ObjectKind.Nil) if (Kind != ObjectKind.Struct && Kind != ObjectKind.Nil)
throw new InvalidOperationException("Allowed on structs only"); throw new InvalidOperationException("Allowed on structs only");
if (index >= StructPtrCount) if (index >= StructPtrCount)
return null; return Rpc.NullCapability.Instance;
return DecodeCapPointer(index + StructDataCount); return DecodeCapPointer(index + StructDataCount);
} }
@ -646,20 +652,14 @@ namespace Capnp
/// </summary> /// </summary>
/// <typeparam name="T">Capability interface</typeparam> /// <typeparam name="T">Capability interface</typeparam>
/// <param name="index">index within this struct's pointer table</param> /// <param name="index">index within this struct's pointer table</param>
/// <param name="memberName">debugging aid</param>
/// <param name="sourceFilePath">debugging aid</param>
/// <param name="sourceLineNumber">debugging aid</param>
/// <returns>capability instance or null if pointer was null</returns> /// <returns>capability instance or null if pointer was null</returns>
/// <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>
@ -693,9 +693,26 @@ namespace Capnp
throw new DeserializationException("Expected a capability"); throw new DeserializationException("Expected a capability");
if (Caps == null) if (Caps == null)
throw new InvalidOperationException("Capability table not set. This is a bug."); throw new InvalidOperationException("Capability table not set");
return (Rpc.CapabilityReflection.CreateProxy<T>(Caps[(int)CapabilityIndex]) as T)!; return (Rpc.CapabilityReflection.CreateProxy<T>(Caps[(int)CapabilityIndex]) as T)!;
} }
/// <summary>
/// Releases the capability table
/// </summary>
public void Dispose()
{
if (Caps != null && !_disposed)
{
foreach (var cap in Caps)
{
cap.Release();
}
Caps = null;
_disposed = true;
}
}
} }
} }

View File

@ -42,7 +42,9 @@ namespace Capnp
{ {
var mb = MessageBuilder.Create(); var mb = MessageBuilder.Create();
if (state.Caps != null) if (state.Caps != null)
{
mb.InitCapTable(); mb.InitCapTable();
}
var sstate = mb.CreateObject<DynamicSerializerState>(); var sstate = mb.CreateObject<DynamicSerializerState>();
Reserializing.DeepCopy(state, sstate); Reserializing.DeepCopy(state, sstate);
@ -77,7 +79,7 @@ namespace Capnp
/// <item><description>This state does neither describe a struct, nor a list of pointers</description></item> /// <item><description>This state does neither describe a struct, nor a list of pointers</description></item>
/// <item><description>Another state is already linked to the specified position (sorry, no overwrite allowed)</description></item></list> /// <item><description>Another state is already linked to the specified position (sorry, no overwrite allowed)</description></item></list>
/// </exception> /// </exception>
public new void LinkToCapability(int slot, uint capabilityIndex) => base.LinkToCapability(slot, capabilityIndex); public new void LinkToCapability(int slot, uint? capabilityIndex) => base.LinkToCapability(slot, capabilityIndex);
/// <summary> /// <summary>
/// Determines the underlying object to be a struct. /// Determines the underlying object to be a struct.
@ -87,6 +89,13 @@ namespace Capnp
/// <exception cref="InvalidOperationException">The object type was already set to something different</exception> /// <exception cref="InvalidOperationException">The object type was already set to something different</exception>
public new void SetStruct(ushort dataCount, ushort ptrCount) => base.SetStruct(dataCount, ptrCount); public new void SetStruct(ushort dataCount, ushort ptrCount) => base.SetStruct(dataCount, ptrCount);
/// <summary>
/// Determines the underyling object to be a capability.
/// </summary>
/// <param name="capabilityIndex">Capability table index, or null to encode a null pointer</param>
/// <exception cref="InvalidOperationException">The object type was already set to something different</exception>
public new void SetCapability(uint? capabilityIndex) => base.SetCapability(capabilityIndex);
/// <summary> /// <summary>
/// Determines the underlying object to be a list of (primitive) values. /// Determines the underlying object to be a list of (primitive) values.
/// </summary> /// </summary>
@ -121,31 +130,27 @@ namespace Capnp
/// <param name="obj">Object representation. Must be one of the following: /// <param name="obj">Object representation. Must be one of the following:
/// <list type="bullet"> /// <list type="bullet">
/// <item><description>An instance implementing <see cref="ICapnpSerializable"/></description></item> /// <item><description>An instance implementing <see cref="ICapnpSerializable"/></description></item>
/// <item><description>null</description></item> /// <item><description>null</description>, <see cref="String"/></item>
/// <item><description>A <see cref="String"/></description></item> /// <item><description><c>IReadOnlyList&lt;byte&gt;</c>, <c>IReadOnlyList&lt;sbyte&gt;</c>, <c>IReadOnlyList&lt;ushort&gt;</c>, <c>IReadOnlyList&lt;short&gt;</c></description></item>
/// <item><description>A <code><![CDATA[IReadOnlyList<byte>]]></code></description></item> /// <item><description><c>IReadOnlyList&lt;int&gt;</c>, <c>IReadOnlyList&lt;uint&gt;</c>, <c>IReadOnlyList&lt;long&gt;</c>, <c>IReadOnlyList&lt;ulong&gt;</c></description></item>
/// <item><description>A <code><![CDATA[IReadOnlyList<sbyte>]]></code></description></item> /// <item><description><c>IReadOnlyList&lt;float&gt;</c>, <c>IReadOnlyList&lt;double&gt;</c>, <c>IReadOnlyList&lt;bool&gt;</c>, <c>IReadOnlyList&lt;string&gt;</c></description></item>
/// <item><description>A <code><![CDATA[IReadOnlyList<ushort>]]></code></description></item> /// <item><description>Another <see cref="DeserializerState"/> or <see cref="SerializerState"/></description></item>
/// <item><description>A <code><![CDATA[IReadOnlyList<short>]]></code></description></item>
/// <item><description>A <code><![CDATA[IReadOnlyList<int>]]></code></description></item>
/// <item><description>A <code><![CDATA[IReadOnlyList<uint>]]></code></description></item>
/// <item><description>A <code><![CDATA[IReadOnlyList<long>]]></code></description></item>
/// <item><description>A <code><![CDATA[IReadOnlyList<ulong>]]></code></description></item>
/// <item><description>A <code><![CDATA[IReadOnlyList<float>]]></code></description></item>
/// <item><description>A <code><![CDATA[IReadOnlyList<double>]]></code></description></item>
/// <item><description>A <code><![CDATA[IReadOnlyList<bool>]]></code></description></item>
/// <item><description>A <code><![CDATA[IReadOnlyList<string>]]></code></description></item>
/// <item><description>Another <see cref="DeserializerState"/></description></item>
/// <item><description>Another <see cref="SerializerState"/></description></item>
/// <item><description>Low-level capability object (<see cref="Rpc.ConsumedCapability"/>)</description></item> /// <item><description>Low-level capability object (<see cref="Rpc.ConsumedCapability"/>)</description></item>
/// <item><description>Proxy object (<see cref="Rpc.Proxy"/>)</description></item> /// <item><description>Proxy object (<see cref="Rpc.Proxy"/>)</description></item>
/// <item><description>Skeleton object (<see cref="Rpc.Skeleton"/>)</description></item> /// <item><description>Skeleton object (<see cref="Rpc.Skeleton"/>)</description></item>
/// <item><description>Capability interface implementation</description></item> /// <item><description>Capability interface implementation</description></item>
/// <item><description>A <code><![CDATA[IReadOnlyList<object>]]></code> whereby each list item is one of the things listed here.</description></item> /// <item><description><c>IReadOnlyList&lt;object&gt;</c>, whereby each list item is one of the things listed here.</description></item>
/// </list> /// </list>
/// </param> /// </param>
public void SetObject(object? obj) public void SetObject(object? obj)
{ {
void RewrapAndInheritBack<T>(Action<T> init) where T : SerializerState, new()
{
var r = Rewrap<T>();
init(r);
InheritFrom(r);
}
switch (obj) switch (obj)
{ {
case ICapnpSerializable serializable: case ICapnpSerializable serializable:
@ -157,55 +162,55 @@ namespace Capnp
break; break;
case IReadOnlyList<byte> bytes: case IReadOnlyList<byte> bytes:
Rewrap<ListOfPrimitivesSerializer<byte>>().Init(bytes); RewrapAndInheritBack<ListOfPrimitivesSerializer<byte>>(_ => _.Init(bytes));
break; break;
case IReadOnlyList<sbyte> sbytes: case IReadOnlyList<sbyte> sbytes:
Rewrap<ListOfPrimitivesSerializer<sbyte>>().Init(sbytes); RewrapAndInheritBack<ListOfPrimitivesSerializer<sbyte>>(_ => _.Init(sbytes));
break; break;
case IReadOnlyList<ushort> ushorts: case IReadOnlyList<ushort> ushorts:
Rewrap<ListOfPrimitivesSerializer<ushort>>().Init(ushorts); RewrapAndInheritBack<ListOfPrimitivesSerializer<ushort>>(_ => _.Init(ushorts));
break; break;
case IReadOnlyList<short> shorts: case IReadOnlyList<short> shorts:
Rewrap<ListOfPrimitivesSerializer<short>>().Init(shorts); RewrapAndInheritBack<ListOfPrimitivesSerializer<short>>(_ => _.Init(shorts));
break; break;
case IReadOnlyList<uint> uints: case IReadOnlyList<uint> uints:
Rewrap<ListOfPrimitivesSerializer<uint>>().Init(uints); RewrapAndInheritBack<ListOfPrimitivesSerializer<uint>>(_ => _.Init(uints));
break; break;
case IReadOnlyList<int> ints: case IReadOnlyList<int> ints:
Rewrap<ListOfPrimitivesSerializer<int>>().Init(ints); RewrapAndInheritBack<ListOfPrimitivesSerializer<int>>(_ => _.Init(ints));
break; break;
case IReadOnlyList<ulong> ulongs: case IReadOnlyList<ulong> ulongs:
Rewrap<ListOfPrimitivesSerializer<ulong>>().Init(ulongs); RewrapAndInheritBack<ListOfPrimitivesSerializer<ulong>>(_ => _.Init(ulongs));
break; break;
case IReadOnlyList<long> longs: case IReadOnlyList<long> longs:
Rewrap<ListOfPrimitivesSerializer<long>>().Init(longs); RewrapAndInheritBack<ListOfPrimitivesSerializer<long>>(_ => _.Init(longs));
break; break;
case IReadOnlyList<float> floats: case IReadOnlyList<float> floats:
Rewrap<ListOfPrimitivesSerializer<float>>().Init(floats); RewrapAndInheritBack<ListOfPrimitivesSerializer<float>>(_ => _.Init(floats));
break; break;
case IReadOnlyList<double> doubles: case IReadOnlyList<double> doubles:
Rewrap<ListOfPrimitivesSerializer<double>>().Init(doubles); RewrapAndInheritBack<ListOfPrimitivesSerializer<double>>(_ => _.Init(doubles));
break; break;
case IReadOnlyList<bool> bools: case IReadOnlyList<bool> bools:
Rewrap<ListOfBitsSerializer>().Init(bools); RewrapAndInheritBack<ListOfBitsSerializer>(_ => _.Init(bools));
break; break;
case IReadOnlyList<string> strings: case IReadOnlyList<string> strings:
Rewrap<ListOfTextSerializer>().Init(strings); RewrapAndInheritBack<ListOfTextSerializer>(_ => _.Init(strings));
break; break;
case IReadOnlyList<object> objects: case IReadOnlyList<object> objects:
Rewrap<ListOfPointersSerializer<DynamicSerializerState>>().Init(objects, (s, o) => s.SetObject(o)); RewrapAndInheritBack<ListOfPointersSerializer<DynamicSerializerState>>(_ => _.Init(objects, (s, o) => s.SetObject(o)));
break; break;
case DeserializerState ds: case DeserializerState ds:

View File

@ -8,14 +8,14 @@ namespace Capnp
/// <summary> /// <summary>
/// Implements an empty <see cref="IReadOnlyList{T}"/>. /// Implements an empty <see cref="IReadOnlyList{T}"/>.
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T">list element type</typeparam>
public class EmptyList<T> : IReadOnlyList<T> public class EmptyList<T> : IReadOnlyList<T>
{ {
/// <summary> /// <summary>
/// Always throws an <see cref="ArgumentOutOfRangeException"/>. /// Always throws an <see cref="IndexOutOfRangeException"/>.
/// </summary> /// </summary>
/// <param name="index">Ignored</param> /// <param name="index">Ignored</param>
public T this[int index] => throw new ArgumentOutOfRangeException(nameof(index)); public T this[int index] => throw new IndexOutOfRangeException(nameof(index));
/// <summary> /// <summary>
/// Always 0. /// Always 0.

View File

@ -68,7 +68,7 @@ namespace Capnp
/// <summary> /// <summary>
/// Returns an empty string. /// Returns an empty string.
/// </summary> /// </summary>
public override string CastText() => string.Empty; public override string? CastText() => null;
/// <summary> /// <summary>
/// Returns an empty <code><![CDATA[IReadOnlyList<uint>]]></code>. /// Returns an empty <code><![CDATA[IReadOnlyList<uint>]]></code>.

View File

@ -2,7 +2,9 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Net.Sockets;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@ -116,8 +118,24 @@ namespace Capnp
#endif #endif
_writer.Write(bytes); _writer.Write(bytes);
} }
}
}
_writer.Flush(); /// <summary>
/// Flushes all buffered frames.
/// </summary>
public void Flush()
{
if (Monitor.TryEnter(_writeLock))
{
try
{
_writer?.Flush();
}
finally
{
Monitor.Exit(_writeLock);
}
} }
} }
@ -160,9 +178,12 @@ namespace Capnp
} }
} }
} }
catch (EndOfStreamException) catch (Exception exception) when (exception is EndOfStreamException ||
exception is IOException ioex &&
ioex.InnerException is SocketException sockex &&
sockex.SocketErrorCode == SocketError.Interrupted)
{ {
Logger.LogWarning("Encountered End of Stream"); Logger.LogInformation("Encountered end of stream");
} }
catch (InvalidDataException e) catch (InvalidDataException e)
{ {

View File

@ -18,15 +18,26 @@ namespace Capnp.FrameTracing
readonly Stopwatch _timer = new Stopwatch(); readonly Stopwatch _timer = new Stopwatch();
readonly TextWriter _traceWriter; readonly TextWriter _traceWriter;
readonly bool _disposeWriter;
/// <summary> /// <summary>
/// Constructs an instance /// Constructs an instance
/// </summary> /// </summary>
/// <param name="traceWriter">textual logging target</param> /// <param name="traceWriter">textual logging target</param>
public RpcFrameTracer(TextWriter traceWriter) public RpcFrameTracer(TextWriter traceWriter): this(traceWriter, true)
{
}
/// <summary>
/// Constructs an instance
/// </summary>
/// <param name="traceWriter">textual logging target</param>
/// <param name="dispose">whether to dispose the writer when tracing is finished</param>
public RpcFrameTracer(TextWriter traceWriter, bool dispose)
{ {
_traceWriter = traceWriter ?? throw new ArgumentNullException(nameof(traceWriter)); _traceWriter = traceWriter ?? throw new ArgumentNullException(nameof(traceWriter));
_traceWriter.WriteLine(Header); _traceWriter.WriteLine(Header);
_disposeWriter = dispose;
} }
/// <summary> /// <summary>
@ -35,7 +46,8 @@ namespace Capnp.FrameTracing
public void Dispose() public void Dispose()
{ {
_traceWriter.WriteLine("<end of trace>"); _traceWriter.WriteLine("<end of trace>");
_traceWriter.Dispose(); if (_disposeWriter)
_traceWriter.Dispose();
} }
void RenderMessageTarget(MessageTarget.READER target, FrameDirection dir) void RenderMessageTarget(MessageTarget.READER target, FrameDirection dir)

View File

@ -11,6 +11,9 @@ namespace Capnp
static class GenericCasts<T> static class GenericCasts<T>
{ {
public static Func<ListDeserializer, T>? CastFunc; public static Func<ListDeserializer, T>? CastFunc;
public static Func<ListDeserializer, T> GetCastFunc() => CastFunc ??
throw new NotSupportedException("Requested cast is not supported");
} }
static ListDeserializer() static ListDeserializer()
@ -26,7 +29,7 @@ namespace Capnp
GenericCasts<IReadOnlyList<ulong>>.CastFunc = _ => _.CastULong(); GenericCasts<IReadOnlyList<ulong>>.CastFunc = _ => _.CastULong();
GenericCasts<IReadOnlyList<float>>.CastFunc = _ => _.CastFloat(); GenericCasts<IReadOnlyList<float>>.CastFunc = _ => _.CastFloat();
GenericCasts<IReadOnlyList<double>>.CastFunc = _ => _.CastDouble(); GenericCasts<IReadOnlyList<double>>.CastFunc = _ => _.CastDouble();
GenericCasts<string>.CastFunc = _ => _.CastText(); GenericCasts<string>.CastFunc = _ => _.CastText()!; // it *may* return null, but how to express this syntactically correct?
} }
/// <summary> /// <summary>
@ -45,12 +48,7 @@ namespace Capnp
T Cast<T>() T Cast<T>()
{ {
var func = GenericCasts<T>.CastFunc; return GenericCasts<T>.GetCastFunc()(this);
if (func == null)
throw new NotSupportedException("Requested cast is not supported");
return func(this);
} }
/// <summary> /// <summary>
@ -89,9 +87,9 @@ namespace Capnp
/// <returns>Capability list representation</returns> /// <returns>Capability list representation</returns>
/// <exception cref="NotSupportedException">If this kind of list cannot be represented as list of capabilities (because it is a list of non-pointers)</exception> /// <exception cref="NotSupportedException">If this kind of list cannot be represented as list of capabilities (because it is a list of non-pointers)</exception>
/// <exception cref="Rpc.InvalidCapabilityInterfaceException">If <typeparamref name="T"/> does not qualify as capability interface.</exception> /// <exception cref="Rpc.InvalidCapabilityInterfaceException">If <typeparamref name="T"/> does not qualify as capability interface.</exception>
public virtual IReadOnlyList<ListOfCapsDeserializer<T>> CastCapList<T>() where T: class public virtual IReadOnlyList<T> CastCapList<T>() where T: class
{ {
throw new NotSupportedException("This kind of list does not contain nested lists"); throw new NotSupportedException("This kind of list cannot be represented as list of capabilities");
} }
object CastND(int n, Func<ListDeserializer, object> func) object CastND(int n, Func<ListDeserializer, object> func)
@ -158,6 +156,7 @@ namespace Capnp
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception> /// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public IReadOnlyList<IReadOnlyList<T>> Cast2D<T>() public IReadOnlyList<IReadOnlyList<T>> Cast2D<T>()
{ {
GenericCasts<IReadOnlyList<T>>.GetCastFunc(); // Probe to avoid lazy NotSupportedException
return CastList().LazyListSelect(ld => ld.Cast<IReadOnlyList<T>>()); return CastList().LazyListSelect(ld => ld.Cast<IReadOnlyList<T>>());
} }
@ -269,7 +268,7 @@ namespace Capnp
/// </summary> /// </summary>
/// <returns>The desired representation</returns> /// <returns>The desired representation</returns>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception> /// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public IReadOnlyList<string> CastText2() => CastList().LazyListSelect(ld => ld.CastText()); public IReadOnlyList<string?> CastText2() => CastList().LazyListSelect(ld => ld.CastText());
/// <summary> /// <summary>
/// Represents this list as Text. For representing it as List(Text), use <seealso cref="CastText2"/>. /// Represents this list as Text. For representing it as List(Text), use <seealso cref="CastText2"/>.
@ -285,7 +284,7 @@ namespace Capnp
/// </remarks> /// </remarks>
/// <returns>The decoded text</returns> /// <returns>The decoded text</returns>
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception> /// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
public virtual string CastText() public virtual string? CastText()
{ {
throw new NotSupportedException("This kind of list does not represent text"); throw new NotSupportedException("This kind of list does not represent text");
} }

View File

@ -2,6 +2,7 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
namespace Capnp namespace Capnp
{ {
@ -10,7 +11,6 @@ namespace Capnp
/// </summary> /// </summary>
public class ListOfBitsSerializer: SerializerState, IReadOnlyList<bool> public class ListOfBitsSerializer: SerializerState, IReadOnlyList<bool>
{ {
/// <summary> /// <summary>
/// Gets or sets the element at given index. /// Gets or sets the element at given index.
/// </summary> /// </summary>
@ -22,6 +22,8 @@ namespace Capnp
{ {
get get
{ {
ListSerializerHelper.EnsureAllocated(this);
if (index < 0 || index >= Count) if (index < 0 || index >= Count)
throw new IndexOutOfRangeException(); throw new IndexOutOfRangeException();
@ -32,6 +34,8 @@ namespace Capnp
} }
set set
{ {
ListSerializerHelper.EnsureAllocated(this);
if (index < 0 || index >= Count) if (index < 0 || index >= Count)
throw new IndexOutOfRangeException(); throw new IndexOutOfRangeException();
@ -88,11 +92,17 @@ namespace Capnp
} }
} }
IEnumerable<bool> Enumerate()
{
for (int i = 0; i < Count; i++)
yield return this[i];
}
/// <summary> /// <summary>
/// Implements <see cref="IEnumerable{Boolean}"/> /// Implements <see cref="IEnumerable{Boolean}"/>
/// </summary> /// </summary>
public IEnumerator<bool> GetEnumerator() => (IEnumerator<bool>)this.ToArray().GetEnumerator(); public IEnumerator<bool> GetEnumerator() => Enumerate().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => this.ToArray().GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
} }
} }

View File

@ -35,16 +35,29 @@ namespace Capnp
[AllowNull] [AllowNull]
public T this[int index] public T this[int index]
{ {
get => (Rpc.CapabilityReflection.CreateProxy<T>(DecodeCapPointer(index)) as T)!; get
{
ListSerializerHelper.EnsureAllocated(this);
try
{
return (Rpc.CapabilityReflection.CreateProxy<T>(DecodeCapPointer(index)) as T)!;
}
catch (ArgumentOutOfRangeException)
{
throw new IndexOutOfRangeException();
}
}
set set
{ {
if (!IsAllocated) ListSerializerHelper.EnsureAllocated(this);
throw new InvalidOperationException("Call Init() first");
if (index < 0 || index >= RawData.Length) if (index < 0 || index >= RawData.Length)
throw new IndexOutOfRangeException("index out of range"); throw new IndexOutOfRangeException("index out of range");
RawData[index] = ProvideCapability(value); var p = default(WirePointer);
p.SetCapability(ProvideCapability(value));
RawData[index] = p;
} }
} }

View File

@ -83,9 +83,9 @@ namespace Capnp
/// </summary> /// </summary>
/// <typeparam name="T">Capability interface</typeparam> /// <typeparam name="T">Capability interface</typeparam>
/// <returns>The desired representation. Since it is evaluated lazily, type conflicts will not happen before accessing the resulting list's elements.</returns> /// <returns>The desired representation. Since it is evaluated lazily, type conflicts will not happen before accessing the resulting list's elements.</returns>
public override IReadOnlyList<ListOfCapsDeserializer<T>> CastCapList<T>() public override IReadOnlyList<T> CastCapList<T>()
{ {
return this.LazyListSelect(d => d.RequireCapList<T>()); return State.RequireCapList<T>();
} }
} }
} }

View File

@ -31,7 +31,7 @@ namespace Capnp
var state = _lpd.State; var state = _lpd.State;
if (index < 0 || index >= _lpd.Count) if (index < 0 || index >= _lpd.Count)
throw new ArgumentOutOfRangeException(nameof(index)); throw new IndexOutOfRangeException();
state.Offset += index; state.Offset += index;
state.Kind = ObjectKind.Struct; state.Kind = ObjectKind.Struct;

View File

@ -29,9 +29,9 @@ namespace Capnp
} }
/// <summary> /// <summary>
/// Retrieves the underlying memory span of the represented items. /// The list's data
/// </summary> /// </summary>
public Span<T> Span => MemoryMarshal.Cast<ulong, T>(RawData); public Span<T> Data => MemoryMarshal.Cast<ulong, T>(RawData).Slice(0, Count);
/// <summary> /// <summary>
/// Gets or sets the value at given index. /// Gets or sets the value at given index.
@ -40,8 +40,16 @@ namespace Capnp
/// <returns>Element value</returns> /// <returns>Element value</returns>
public T this[int index] public T this[int index]
{ {
get => Span[index]; get
set => Span[index] = value; {
ListSerializerHelper.EnsureAllocated(this);
return Data[index];
}
set
{
ListSerializerHelper.EnsureAllocated(this);
Data[index] = value;
}
} }
/// <summary> /// <summary>
@ -84,19 +92,19 @@ namespace Capnp
switch (items) switch (items)
{ {
case T[] array: case T[] array:
array.CopyTo(Span); array.CopyTo(Data);
break; break;
case ArraySegment<T> segment: case ArraySegment<T> segment:
segment.AsSpan().CopyTo(Span); segment.AsSpan().CopyTo(Data);
break; break;
case ListOfPrimitivesDeserializer<T> deser: case ListOfPrimitivesDeserializer<T> deser:
deser.Span.CopyTo(Span); deser.Span.CopyTo(Data);
break; break;
case ListOfPrimitivesSerializer<T> ser: case ListOfPrimitivesSerializer<T> ser:
ser.Span.CopyTo(Span); ser.Data.CopyTo(Data);
break; break;
default: default:
@ -108,11 +116,18 @@ namespace Capnp
} }
} }
IEnumerable<T> Enumerate()
{
for (int i = 0; i < Count; i++)
yield return Data[i];
}
/// <summary> /// <summary>
/// Implements <see cref="IEnumerable{T}"/>. /// Implements <see cref="IEnumerable{T}"/>.
/// </summary> /// </summary>
public IEnumerator<T> GetEnumerator() => (IEnumerator<T>)Span.ToArray().GetEnumerator(); /// <returns></returns>
public IEnumerator<T> GetEnumerator() => Enumerate().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => Span.ToArray().GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
} }
} }

View File

@ -22,8 +22,7 @@ namespace Capnp
{ {
get get
{ {
if (!IsAllocated) ListSerializerHelper.EnsureAllocated(this);
throw new InvalidOperationException("Not initialized");
if (index < 0 || index >= Count) if (index < 0 || index >= Count)
throw new IndexOutOfRangeException(); throw new IndexOutOfRangeException();
@ -32,8 +31,7 @@ namespace Capnp
} }
set set
{ {
if (!IsAllocated) ListSerializerHelper.EnsureAllocated(this);
throw new InvalidOperationException("Not initialized");
if (index < 0 || index >= Count) if (index < 0 || index >= Count)
throw new IndexOutOfRangeException(); throw new IndexOutOfRangeException();
@ -53,7 +51,7 @@ namespace Capnp
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
yield return TryGetPointer<SerializerState>(i)?.ListReadAsText(); yield return this[i];
} }
} }

View File

@ -0,0 +1,13 @@
using System;
namespace Capnp
{
static class ListSerializerHelper
{
public static void EnsureAllocated(SerializerState serializer)
{
if (!serializer.IsAllocated)
throw new InvalidOperationException("Call Init() first");
}
}
}

View File

@ -10,7 +10,7 @@ namespace Capnp
{ {
readonly ISegmentAllocator _allocator; readonly ISegmentAllocator _allocator;
readonly DynamicSerializerState _rootPtrBuilder; readonly DynamicSerializerState _rootPtrBuilder;
List<Rpc.ConsumedCapability?>? _capTable; List<Rpc.ConsumedCapability>? _capTable;
MessageBuilder(ISegmentAllocator allocator) MessageBuilder(ISegmentAllocator allocator)
{ {
@ -64,7 +64,7 @@ namespace Capnp
/// <summary> /// <summary>
/// Creates an object and sets it as root object. /// Creates an object and sets it as root object.
/// </summary> /// </summary>
/// <typeparam name="TS">Serializer state specialization</typeparam> /// <typeparam name="TS">Serializer state specialization (must be a struct)</typeparam>
/// <returns>Serializer state instance representing the new object</returns> /// <returns>Serializer state instance representing the new object</returns>
public TS BuildRoot<TS>() where TS: SerializerState, new() public TS BuildRoot<TS>() where TS: SerializerState, new()
{ {
@ -72,6 +72,9 @@ namespace Capnp
throw new InvalidOperationException("Root already set"); throw new InvalidOperationException("Root already set");
var root = CreateObject<TS>(); var root = CreateObject<TS>();
if (root.Kind != ObjectKind.Struct)
throw new InvalidOperationException("Root object must be a struct");
Root = root; Root = root;
return root; return root;
} }
@ -89,13 +92,13 @@ namespace Capnp
if (_capTable != null) if (_capTable != null)
throw new InvalidOperationException("Capability table was already initialized"); throw new InvalidOperationException("Capability table was already initialized");
_capTable = new List<Rpc.ConsumedCapability?>(); _capTable = new List<Rpc.ConsumedCapability>();
} }
/// <summary> /// <summary>
/// Returns this message builder's segment allocator. /// Returns this message builder's segment allocator.
/// </summary> /// </summary>
public ISegmentAllocator Allocator => _allocator; public ISegmentAllocator Allocator => _allocator;
internal List<Rpc.ConsumedCapability?>? Caps => _capTable; internal List<Rpc.ConsumedCapability>? Caps => _capTable;
} }
} }

View File

@ -4,9 +4,7 @@ namespace Capnp
{ {
/// <summary> /// <summary>
/// The different kinds of Cap'n Proto objects. /// The different kinds of Cap'n Proto objects.
/// Despite this is a [Flags] enum, it does not make sense to mutually combine literals.
/// </summary> /// </summary>
[Flags]
public enum ObjectKind: byte public enum ObjectKind: byte
{ {
/// <summary> /// <summary>

View File

@ -1,45 +0,0 @@
using System;
namespace Capnp
{
class PrimitiveCoder
{
class Coder<T>
{
public static Func<T, T, T>? Fn { get; set; }
}
static PrimitiveCoder()
{
Coder<bool>.Fn = (x, y) => x != y;
Coder<sbyte>.Fn = (x, y) => (sbyte)(x ^ y);
Coder<byte>.Fn = (x, y) => (byte)(x ^ y);
Coder<short>.Fn = (x, y) => (short)(x ^ y);
Coder<ushort>.Fn = (x, y) => (ushort)(x ^ y);
Coder<int>.Fn = (x, y) => x ^ y;
Coder<uint>.Fn = (x, y) => x ^ y;
Coder<long>.Fn = (x, y) => x ^ y;
Coder<ulong>.Fn = (x, y) => x ^ y;
Coder<float>.Fn = (x, y) =>
{
int xi = x.ReplacementSingleToInt32Bits();
int yi = y.ReplacementSingleToInt32Bits();
int zi = xi ^ yi;
return BitConverter.ToSingle(BitConverter.GetBytes(zi), 0);
};
Coder<double>.Fn = (x, y) =>
{
long xi = BitConverter.DoubleToInt64Bits(x);
long yi = BitConverter.DoubleToInt64Bits(y);
long zi = xi ^ yi;
return BitConverter.Int64BitsToDouble(zi);
};
}
public static Func<T, T, T> Get<T>()
{
return Coder<T>.Fn ??
throw new NotSupportedException("Generic type argument is not a supported primitive type, no coder defined");
}
}
}

View File

@ -19,7 +19,7 @@
/// <exception cref="System.TypeLoadException">Problem with building the Skeleton type, or problem with loading some dependent class.</exception> /// <exception cref="System.TypeLoadException">Problem with building the Skeleton type, or problem with loading some dependent class.</exception>
public static BareProxy FromImpl(object impl) public static BareProxy FromImpl(object impl)
{ {
return new BareProxy(LocalCapability.Create(CapabilityReflection.CreateSkeleton(impl))); return new BareProxy(CapabilityReflection.CreateSkeletonInternal(impl).AsCapability());
} }
/// <summary> /// <summary>
@ -33,7 +33,7 @@
/// Constructs an instance and binds it to the given low-level capability. /// Constructs an instance and binds it to the given low-level capability.
/// </summary> /// </summary>
/// <param name="cap">low-level capability</param> /// <param name="cap">low-level capability</param>
public BareProxy(ConsumedCapability? cap): base(cap) public BareProxy(ConsumedCapability cap): base(cap)
{ {
} }

View File

@ -94,6 +94,8 @@ namespace Capnp.Rpc
new ConditionalWeakTable<Type, ProxyFactory>(); new ConditionalWeakTable<Type, ProxyFactory>();
static ConditionalWeakTable<Type, SkeletonFactory> _skeletonMap = static ConditionalWeakTable<Type, SkeletonFactory> _skeletonMap =
new ConditionalWeakTable<Type, SkeletonFactory>(); new ConditionalWeakTable<Type, SkeletonFactory>();
static ConditionalWeakTable<object, Skeleton> _implMap =
new ConditionalWeakTable<object, Skeleton>();
static CapabilityReflection() static CapabilityReflection()
{ {
@ -155,15 +157,23 @@ namespace Capnp.Rpc
/// <exception cref="System.Reflection.TargetInvocationException">Problem with instatiating the Skeleton (constructor threw exception).</exception> /// <exception cref="System.Reflection.TargetInvocationException">Problem with instatiating the Skeleton (constructor threw exception).</exception>
/// <exception cref="MemberAccessException">Caller does not have permission to invoke the Skeleton constructor.</exception> /// <exception cref="MemberAccessException">Caller does not have permission to invoke the Skeleton constructor.</exception>
/// <exception cref="TypeLoadException">Problem with building the Skeleton type, or problem with loading some dependent class.</exception> /// <exception cref="TypeLoadException">Problem with building the Skeleton type, or problem with loading some dependent class.</exception>
public static Skeleton CreateSkeleton(object obj) [Obsolete("Do not use this method directly. Instead, pass objects directly or use Proxy.Share<T>(). This method will be removed with next release.")]
public static Skeleton CreateSkeleton(object obj) => CreateSkeletonInternal(obj);
internal static Skeleton CreateSkeletonInternal(object obj)
{ {
if (obj == null) if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
var factory = GetSkeletonFactory(obj.GetType()); var result = _implMap.GetValue(obj, _ =>
var skeleton = factory.NewSkeleton(); {
skeleton.Bind(obj); var factory = GetSkeletonFactory(_.GetType());
return skeleton; var skeleton = factory.NewSkeleton();
skeleton.Bind(obj);
return skeleton;
});
return result;
} }
static ProxyFactory GetProxyFactory(Type type) static ProxyFactory GetProxyFactory(Type type)
@ -229,7 +239,7 @@ namespace Capnp.Rpc
} }
/// <summary> /// <summary>
/// Checkes whether a given type qualifies as cpapbility interface./> on failure. /// Checks whether a given type qualifies as capability interface.
/// </summary> /// </summary>
/// <param name="interfaceType">type to check</param> /// <param name="interfaceType">type to check</param>
/// <returns>true when <paramref name="interfaceType"/> is a capability interface</returns> /// <returns>true when <paramref name="interfaceType"/> is a capability interface</returns>
@ -251,9 +261,6 @@ namespace Capnp.Rpc
/// </summary> /// </summary>
/// <typeparam name="TInterface">Capability interface. Must be annotated with <see cref="ProxyAttribute"/>.</typeparam> /// <typeparam name="TInterface">Capability interface. Must be annotated with <see cref="ProxyAttribute"/>.</typeparam>
/// <param name="cap">low-level capability</param> /// <param name="cap">low-level capability</param>
/// <param name="memberName">debugging aid</param>
/// <param name="sourceFilePath">debugging aid</param>
/// <param name="sourceLineNumber">debugging aid</param>
/// <returns>The Proxy instance which implements <typeparamref name="TInterface"/>.</returns> /// <returns>The Proxy instance which implements <typeparamref name="TInterface"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="cap"/> is null.</exception> /// <exception cref="ArgumentNullException"><paramref name="cap"/> is null.</exception>
/// <exception cref="InvalidCapabilityInterfaceException"><typeparamref name="TInterface"/> did not qualify as capability interface.</exception> /// <exception cref="InvalidCapabilityInterfaceException"><typeparamref name="TInterface"/> did not qualify as capability interface.</exception>
@ -262,25 +269,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;
} }
} }

View File

@ -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,20 +15,14 @@
/// 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 Unfreeze();
internal abstract void AddRef(); internal abstract void AddRef();
internal abstract void Release( internal abstract void Release();
[System.Runtime.CompilerServices.CallerMemberName] string methodName = "", internal abstract Skeleton AsSkeleton();
[System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0);
#if DebugFinalizers #if DebugFinalizers
public string CreatorMemberName { get; set; } internal Proxy? OwningProxy { get; set; }
public string CreatorFilePath { get; set; } internal ConsumedCapability? ResolvingCap { get; set; }
public int CreatorLineNumber { get; set; }
#endif #endif
} }
} }

View File

@ -10,6 +10,11 @@
/// </summary> /// </summary>
void Forward(WireFrame frame); void Forward(WireFrame frame);
/// <summary>
/// Indicates that the endpoint should flush any buffered frames.
/// </summary>
void Flush();
/// <summary> /// <summary>
/// Close this endpoint. /// Close this endpoint.
/// </summary> /// </summary>

View File

@ -1,4 +1,5 @@
using System; using Capnp.Util;
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Capnp.Rpc namespace Capnp.Rpc
@ -15,13 +16,26 @@ namespace Capnp.Rpc
/// <summary> /// <summary>
/// Task which will complete when the RPC returns, delivering its result struct. /// Task which will complete when the RPC returns, delivering its result struct.
/// </summary> /// </summary>
Task<DeserializerState> WhenReturned { get; } StrictlyOrderedAwaitTask<DeserializerState> WhenReturned { get; }
/// <summary> /// <summary>
/// Creates a low-level capability for promise pipelining. /// Creates a low-level capability for promise pipelining.
/// </summary> /// </summary>
/// <param name="access">Path to the desired capability inside the result struct.</param> /// <param name="access">Path to the desired capability inside the result struct.</param>
/// <returns>Pipelined low-level capability</returns> /// <returns>Pipelined low-level capability</returns>
ConsumedCapability? Access(MemberAccessPath access); ConsumedCapability Access(MemberAccessPath access);
/// <summary>
///
/// </summary>
/// <param name="access">Creates a low-level capability for promise pipelining.</param>
/// <param name="proxyTask">Task returning the proxy whose ownership will be taken over</param>
/// <returns></returns>
ConsumedCapability Access(MemberAccessPath access, Task<IDisposable?> proxyTask);
/// <summary>
/// Whether the question was asked as tail call
/// </summary>
bool IsTailCall { get; }
} }
} }

View File

@ -1,4 +1,5 @@
using System.Threading.Tasks; using Capnp.Util;
using System.Threading.Tasks;
namespace Capnp.Rpc namespace Capnp.Rpc
{ {
@ -8,8 +9,15 @@ namespace Capnp.Rpc
public interface IResolvingCapability public interface IResolvingCapability
{ {
/// <summary> /// <summary>
/// Will eventually give the resolved capability. /// Completes when the capability gets resolved.
/// </summary> /// </summary>
Task<Proxy> WhenResolved { get; } StrictlyOrderedAwaitTask WhenResolved { get; }
/// <summary>
/// Returns the resolved capability
/// </summary>
/// <typeparam name="T">Capability interface or <see cref="BareProxy"/></typeparam>
/// <returns>the resolved capability, or null if it did not resolve yet</returns>
T? GetResolvedCapability<T>() where T: class;
} }
} }

View File

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

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -11,7 +12,7 @@ namespace Capnp.Rpc
public static class Impatient public static class Impatient
{ {
static readonly ConditionalWeakTable<Task, IPromisedAnswer> _taskTable = new ConditionalWeakTable<Task, IPromisedAnswer>(); static readonly ConditionalWeakTable<Task, IPromisedAnswer> _taskTable = new ConditionalWeakTable<Task, IPromisedAnswer>();
static readonly ThreadLocal<IRpcEndpoint?> _askingEndpoint = new ThreadLocal<IRpcEndpoint?>(); static readonly ThreadLocal<Stack<IRpcEndpoint>> _askingEndpoint = new ThreadLocal<Stack<IRpcEndpoint>>(() => new Stack<IRpcEndpoint>());
/// <summary> /// <summary>
/// Attaches a continuation to the given promise and registers the resulting task for pipelining. /// Attaches a continuation to the given promise and registers the resulting task for pipelining.
@ -26,45 +27,20 @@ namespace Capnp.Rpc
{ {
async Task<T> AwaitAnswer() async Task<T> AwaitAnswer()
{ {
return then(await promise.WhenReturned); var result = await promise.WhenReturned;
if (promise.IsTailCall)
throw new NoResultsException();
return then(result);
} }
var rtask = AwaitAnswer(); var rtask = AwaitAnswer();
try // Rare situation: .NET maintains a cache of some pre-computed tasks for standard results (such as (int)0, (object)null).
{ // AwaitAnswer() might indeed have chosen a fast-path optimization, such that rtask is a cached object instead of a new instance.
// Really weird: We'd expect AwaitAnswer() to initialize a new Task instance upon each invocation. // Once this happens the second time, and we return the same rtask for a different promise. GetAnswer()/TryGetAnswer() may return the "wrong"
// However, this does not seem to be always true (as indicated by CI test suite). An explanation might be // promise! Fortunately, this does not really matter, since the "wrong" promise is guaranteed to return exactly the same answer. :-)
// that the underlying implementation recycles Task instances (um, really? doesn't make sense. But the _taskTable.GetValue(rtask, _ => promise);
// observation doesn't make sense, either).
_taskTable.Add(rtask, promise);
}
catch (ArgumentException)
{
if (rtask.IsCompleted)
{
// Force .NET to create a new Task instance
if (rtask.IsCanceled)
{
rtask = Task.FromCanceled<T>(new CancellationToken(true));
}
else if (rtask.IsFaulted)
{
rtask = Task.FromException<T>(rtask.Exception!.InnerException!);
}
else
{
rtask = Task.FromResult<T>(rtask.Result);
}
_taskTable.Add(rtask, promise);
}
else
{
throw new InvalidOperationException("What the heck is wrong with Task?");
}
}
return rtask; return rtask;
} }
@ -76,6 +52,7 @@ namespace Capnp.Rpc
/// <returns>The underlying promise</returns> /// <returns>The underlying promise</returns>
/// <exception cref="ArgumentNullException"><paramref name="task"/> is null.</exception> /// <exception cref="ArgumentNullException"><paramref name="task"/> is null.</exception>
/// <exception cref="ArgumentException">The task was not registered using MakePipelineAware.</exception> /// <exception cref="ArgumentException">The task was not registered using MakePipelineAware.</exception>
[Obsolete("Please re-generate capnp code-behind. GetAnswer(task).Access(...) was replaced by Access(task, ...)")]
public static IPromisedAnswer GetAnswer(Task task) public static IPromisedAnswer GetAnswer(Task task)
{ {
if (!_taskTable.TryGetValue(task, out var answer)) if (!_taskTable.TryGetValue(task, out var answer))
@ -92,22 +69,18 @@ namespace Capnp.Rpc
return answer; return answer;
} }
static async Task<Proxy> AwaitProxy<T>(Task<T> task) where T: class /// <summary>
/// Returns a promise-pipelined capability for a remote method invocation Task.
/// </summary>
/// <param name="task">remote method invocation task</param>
/// <param name="access">path to the desired capability</param>
/// <param name="proxyTask">task returning a proxy to the desired capability</param>
/// <returns>Pipelined low-level capability</returns>
public static ConsumedCapability Access(Task task, MemberAccessPath access, Task<IDisposable?> proxyTask)
{ {
var item = await task; var answer = TryGetAnswer(task);
if (answer != null) return answer.Access(access, proxyTask);
switch (item) return new LazyCapability(proxyTask.AsProxyTask());
{
case Proxy proxy:
return proxy;
case null:
return CapabilityReflection.CreateProxy<T>(null);
}
var skel = Skeleton.GetOrCreateSkeleton(item!, false);
var localCap = LocalCapability.Create(skel);
return CapabilityReflection.CreateProxy<T>(localCap);
} }
/// <summary> /// <summary>
@ -116,22 +89,16 @@ namespace Capnp.Rpc
/// </summary> /// </summary>
/// <typeparam name="TInterface">Capability interface type</typeparam> /// <typeparam name="TInterface">Capability interface type</typeparam>
/// <param name="task">The task</param> /// <param name="task">The task</param>
/// <param name="memberName">debugging aid</param>
/// <param name="sourceFilePath">debugging aid</param>
/// <param name="sourceLineNumber">debugging aid</param>
/// <returns>A proxy for the given task.</returns> /// <returns>A proxy for the given task.</returns>
/// <exception cref="ArgumentNullException"><paramref name="task"/> is null.</exception> /// <exception cref="ArgumentNullException"><paramref name="task"/> is null.</exception>
/// <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 = "", where TInterface : class, IDisposable
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
where TInterface : class
{ {
var lazyCap = new LazyCapability(AwaitProxy(task)); var lazyCap = new LazyCapability(task.AsProxyTask());
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 +124,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)
@ -167,19 +134,65 @@ namespace Capnp.Rpc
throw new ArgumentException("The task was not returned from a remote method invocation. See documentation for details."); throw new ArgumentException("The task was not returned from a remote method invocation. See documentation for details.");
} }
var lazyCap = new LazyCapability(AwaitProxy(task)); var proxyTask = task.AsProxyTask();
return (CapabilityReflection.CreateProxy<TInterface>(lazyCap) as TInterface)!; if (proxyTask.ReplacementTaskIsCompletedSuccessfully())
{
return proxyTask.Result.Cast<TInterface>(true);
}
else
{
var lazyCap = new LazyCapability(proxyTask);
return (CapabilityReflection.CreateProxy<TInterface>(lazyCap) as TInterface)!;
}
} }
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)!;
} }
} }
/// <summary>
/// Unwraps given capability. Unwrapping walks the chain of promised capabilities and awaits their resolutions,
/// until we get the finally resolved capability. If it is the capability, the method returns a null reference.
/// If the capability is broken (resolved to exception, dependent answer faulted or cancelled, RPC endpoint closed),
/// it throws an exception.
/// </summary>
/// <typeparam name="TInterface">Capability interface</typeparam>
/// <param name="cap">capability to unwrap</param>
/// <returns>Task returning the eventually resolved capability</returns>
/// <exception cref="RpcException">Capability is broken</exception>
public static async Task<TInterface?> Unwrap<TInterface>(this TInterface cap) where TInterface: class, IDisposable
{
using var proxy = cap as Proxy;
if (proxy == null)
return cap;
var unwrapped = await proxy.ConsumedCap.Unwrap();
if (unwrapped == null || unwrapped == NullCapability.Instance)
return null;
return ((CapabilityReflection.CreateProxy<TInterface>(unwrapped)) as TInterface)!;
}
internal static IRpcEndpoint? AskingEndpoint internal static IRpcEndpoint? AskingEndpoint
{ {
get => _askingEndpoint.Value; get => _askingEndpoint.Value!.Count > 0 ? _askingEndpoint.Value.Peek() : null;
set { _askingEndpoint.Value = value; } }
internal static void PushAskingEndpoint(IRpcEndpoint endpoint)
{
_askingEndpoint.Value!.Push(endpoint);
}
internal static void PopAskingEndpoint()
{
_askingEndpoint.Value!.Pop();
} }
/// <summary> /// <summary>

View File

@ -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.
@ -26,16 +28,7 @@
return call; return call;
} }
internal override void Freeze(out IRpcEndpoint boundEndpoint) internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER capDesc)
{
boundEndpoint = _ep;
}
internal override void Unfreeze()
{
}
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER capDesc)
{ {
if (endpoint == _ep) if (endpoint == _ep)
{ {
@ -45,8 +38,9 @@
else else
{ {
capDesc.which = CapDescriptor.WHICH.SenderHosted; capDesc.which = CapDescriptor.WHICH.SenderHosted;
capDesc.SenderHosted = endpoint.AllocateExport(Vine.Create(this), out var _); capDesc.SenderHosted = endpoint.AllocateExport(AsSkeleton(), out var _);
} }
return null;
} }
} }
} }

View File

@ -1,4 +1,5 @@
using System; using Capnp.Util;
using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -18,36 +19,23 @@ namespace Capnp.Rpc.Interception
readonly CancellationTokenSource _cancelFromAlice = new CancellationTokenSource(); readonly CancellationTokenSource _cancelFromAlice = new CancellationTokenSource();
public PromisedAnswer(CallContext callContext) public PromisedAnswer(CallContext callContext)
{ {
_callContext = callContext; _callContext = callContext;
WhenReturned = _futureResult.Task.EnforceAwaitOrder();
} }
public Task<DeserializerState> WhenReturned => _futureResult.Task; public StrictlyOrderedAwaitTask<DeserializerState> WhenReturned { get; }
public CancellationToken CancelFromAlice => _cancelFromAlice.Token; public CancellationToken CancelFromAlice => _cancelFromAlice.Token;
async Task<Proxy> AccessWhenReturned(MemberAccessPath access) public ConsumedCapability Access(MemberAccessPath access)
{ {
await WhenReturned; return _callContext._censorCapability.Policy.Attach<ConsumedCapability>(new LocalAnswerCapability(WhenReturned, access));
return new Proxy(Access(access));
} }
public ConsumedCapability? Access(MemberAccessPath access) public ConsumedCapability Access(MemberAccessPath _, Task<IDisposable?> task)
{ {
if (_futureResult.Task.IsCompleted) var proxyTask = task.AsProxyTask();
{ return _callContext._censorCapability.Policy.Attach<ConsumedCapability>(new LocalAnswerCapability(proxyTask));
try
{
return access.Eval(WhenReturned.Result);
}
catch (AggregateException exception)
{
throw exception.InnerException!;
}
}
else
{
return new LazyCapability(AccessWhenReturned(access));
}
} }
public void Dispose() public void Dispose()
@ -84,6 +72,8 @@ namespace Capnp.Rpc.Interception
_cancelFromAlice.Dispose(); _cancelFromAlice.Dispose();
} }
} }
public bool IsTailCall => false;
} }
/// <summary> /// <summary>
@ -101,10 +91,16 @@ namespace Capnp.Rpc.Interception
/// </summary> /// </summary>
public InterceptionState State { get; private set; } public InterceptionState State { get; private set; }
SerializerState _inArgs;
/// <summary> /// <summary>
/// Input arguments /// Input arguments
/// </summary> /// </summary>
public SerializerState? InArgs { get; set; } public SerializerState InArgs
{
get => _inArgs;
set { _inArgs = value ?? throw new ArgumentNullException(nameof(value)); }
}
/// <summary> /// <summary>
/// Output arguments ("return value") /// Output arguments ("return value")
@ -150,45 +146,47 @@ namespace Capnp.Rpc.Interception
/// <item><description>A <see cref="Proxy"/>-derived object</description></item> /// <item><description>A <see cref="Proxy"/>-derived object</description></item>
/// <item><description>A <see cref="Skeleton"/>-derived object</description></item> /// <item><description>A <see cref="Skeleton"/>-derived object</description></item>
/// <item><description>A <see cref="ConsumedCapability"/>-derived object (low level capability)</description></item> /// <item><description>A <see cref="ConsumedCapability"/>-derived object (low level capability)</description></item>
/// <item><description>null</description></item>
/// </list> /// </list>
/// </summary> /// </summary>
public object? Bob /// <remarks>
/// Note that getting/setting this property does NOT transfer ownership.
/// </remarks>
public object Bob
{ {
get => _bob; get => _bob;
set set
{ {
if (value != _bob) if (value != _bob)
{ {
BobProxy?.Dispose();
BobProxy = null;
_bob = value;
switch (value) switch (value)
{ {
case null:
throw new ArgumentNullException(nameof(value));
case Proxy proxy: case Proxy proxy:
BobProxy = proxy; BobProxy = proxy.Cast<BareProxy>(false);
break;
case ConsumedCapability cap:
using (var temp = CapabilityReflection.CreateProxy<object>(cap))
{
Bob = temp;
}
break; break;
case Skeleton skeleton: case Skeleton skeleton:
BobProxy = CapabilityReflection.CreateProxy<object>( using (var nullProxy = new Proxy())
LocalCapability.Create(skeleton)); {
break; Bob = (object?)skeleton.AsCapability() ?? nullProxy;
}
case ConsumedCapability cap:
BobProxy = CapabilityReflection.CreateProxy<object>(cap);
break;
case null:
break; break;
default: default:
BobProxy = CapabilityReflection.CreateProxy<object>( Bob = CapabilityReflection.CreateSkeletonInternal(value);
LocalCapability.Create(
Skeleton.GetOrCreateSkeleton(value, false)));
break; break;
} }
_bob = value;
} }
} }
} }
@ -197,7 +195,7 @@ namespace Capnp.Rpc.Interception
readonly CensorCapability _censorCapability; readonly CensorCapability _censorCapability;
PromisedAnswer _promisedAnswer; PromisedAnswer _promisedAnswer;
object? _bob; object _bob;
internal IPromisedAnswer Answer => _promisedAnswer; internal IPromisedAnswer Answer => _promisedAnswer;
@ -205,13 +203,14 @@ namespace Capnp.Rpc.Interception
{ {
_censorCapability = censorCapability; _censorCapability = censorCapability;
_promisedAnswer = new PromisedAnswer(this); _promisedAnswer = new PromisedAnswer(this);
_inArgs = inArgs;
_bob = null!; // Will be initialized later here
CancelFromAlice = _promisedAnswer.CancelFromAlice; CancelFromAlice = _promisedAnswer.CancelFromAlice;
CancelToBob = CancelFromAlice; CancelToBob = CancelFromAlice;
Bob = censorCapability.InterceptedCapability; Bob = censorCapability.InterceptedCapability;
InterfaceId = interfaceId; InterfaceId = interfaceId;
MethodId = methodId; MethodId = methodId;
InArgs = inArgs;
State = InterceptionState.RequestedFromAlice; State = InterceptionState.RequestedFromAlice;
} }
@ -253,12 +252,8 @@ namespace Capnp.Rpc.Interception
/// Intercepts all capabilies inside the input arguments /// Intercepts all capabilies inside the input arguments
/// </summary> /// </summary>
/// <param name="policyOverride">Policy to use, or null to further use present policy</param> /// <param name="policyOverride">Policy to use, or null to further use present policy</param>
/// <exception cref="InvalidOperationException">InArgs not set</exception>
public void InterceptInCaps(IInterceptionPolicy? policyOverride = null) public void InterceptInCaps(IInterceptionPolicy? policyOverride = null)
{ {
if (InArgs == null)
throw new InvalidOperationException("InArgs not set");
InterceptCaps(InArgs, policyOverride ?? _censorCapability.Policy); InterceptCaps(InArgs, policyOverride ?? _censorCapability.Policy);
} }
@ -275,12 +270,8 @@ namespace Capnp.Rpc.Interception
/// Unintercepts all capabilies inside the input arguments /// Unintercepts all capabilies inside the input arguments
/// </summary> /// </summary>
/// <param name="policyOverride">Policy to remove, or null to remove present policy</param> /// <param name="policyOverride">Policy to remove, or null to remove present policy</param>
/// <exception cref="InvalidOperationException">InArgs not set</exception>
public void UninterceptInCaps(IInterceptionPolicy? policyOverride = null) public void UninterceptInCaps(IInterceptionPolicy? policyOverride = null)
{ {
if (InArgs == null)
throw new InvalidOperationException("InArgs not set");
UninterceptCaps(InArgs, policyOverride ?? _censorCapability.Policy); UninterceptCaps(InArgs, policyOverride ?? _censorCapability.Policy);
} }
@ -296,15 +287,8 @@ namespace Capnp.Rpc.Interception
/// <summary> /// <summary>
/// Forwards this intercepted call to the target capability ("Bob"). /// Forwards this intercepted call to the target capability ("Bob").
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">Bob/InArgs not set</exception>
public void ForwardToBob() public void ForwardToBob()
{ {
if (Bob == null)
throw new InvalidOperationException("Bob is null");
if (InArgs == null)
throw new InvalidOperationException("InArgs not set");
var answer = BobProxy!.Call(InterfaceId, MethodId, InArgs.Rewrap<DynamicSerializerState>(), default, CancelToBob); var answer = BobProxy!.Call(InterfaceId, MethodId, InArgs.Rewrap<DynamicSerializerState>(), default, CancelToBob);
State = InterceptionState.ForwardedToBob; State = InterceptionState.ForwardedToBob;

View File

@ -1,4 +1,6 @@
namespace Capnp.Rpc.Interception using System;
namespace Capnp.Rpc.Interception
{ {
class CensorCapability : RefCountingCapability class CensorCapability : RefCountingCapability
{ {
@ -7,12 +9,10 @@
InterceptedCapability = interceptedCapability; InterceptedCapability = interceptedCapability;
interceptedCapability.AddRef(); interceptedCapability.AddRef();
Policy = policy; Policy = policy;
MyVine = Vine.Create(this);
} }
public ConsumedCapability InterceptedCapability { get; } public ConsumedCapability InterceptedCapability { get; }
public IInterceptionPolicy Policy { get; } public IInterceptionPolicy Policy { get; }
internal Skeleton MyVine { get; }
protected override void ReleaseRemotely() protected override void ReleaseRemotely()
{ {
@ -26,19 +26,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(AsSkeleton(), out bool _);
} return null;
internal override void Freeze(out IRpcEndpoint? boundEndpoint)
{
boundEndpoint = null;
}
internal override void Unfreeze()
{
} }
} }
} }

View File

@ -9,9 +9,6 @@ namespace Capnp.Rpc.Interception
/// </summary> /// </summary>
public static class Interceptor public static class Interceptor
{ {
static readonly ConditionalWeakTable<ConsumedCapability, CensorCapability> _interceptMap =
new ConditionalWeakTable<ConsumedCapability, CensorCapability>();
/// <summary> /// <summary>
/// Attach this policy to given capability. /// Attach this policy to given capability.
/// </summary> /// </summary>
@ -45,16 +42,16 @@ namespace Capnp.Rpc.Interception
switch (cap) switch (cap)
{ {
case Proxy proxy: case Proxy proxy:
return (CapabilityReflection.CreateProxy<TCap>(Attach(policy, proxy.ConsumedCap!)) as TCap)!; return (CapabilityReflection.CreateProxy<TCap>(
Attach(policy, proxy.ConsumedCap!)) as TCap)!;
case ConsumedCapability ccap: case ConsumedCapability ccap:
return (new CensorCapability(ccap, policy) as TCap)!; return (new CensorCapability(ccap, policy) as TCap)!;
default: default:
return (Attach(policy, var temp = (CapabilityReflection.CreateProxy<TCap>(
(CapabilityReflection.CreateProxy<TCap>( CapabilityReflection.CreateSkeletonInternal(cap).AsCapability())) as TCap;
LocalCapability.Create( return Attach(policy, temp!)!;
Skeleton.GetOrCreateSkeleton(cap, false))) as TCap)!));
} }
} }

View File

@ -1,39 +1,76 @@
using System; using Capnp.Util;
using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Capnp.Rpc namespace Capnp.Rpc
{ {
class LazyCapability : RefCountingCapability, IResolvingCapability class LazyCapability : RefCountingCapability, IResolvingCapability
{ {
public static LazyCapability CreateBrokenCap(string message) public static LazyCapability CreateBrokenCap(string message)
{ {
var cap = new LazyCapability(Task.FromException<Proxy>(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<Proxy>(token)); return new LazyCapability(Task.FromCanceled<ConsumedCapability>(token));
cap.AddRef(); // Instance shall be persistent
return cap;
} }
public static LazyCapability Null { get; } = CreateBrokenCap("Null capability"); readonly StrictlyOrderedAwaitTask<Proxy>? _proxyTask;
readonly StrictlyOrderedAwaitTask<ConsumedCapability> _capTask;
public LazyCapability(Task<Proxy> capabilityTask) public LazyCapability(Task<ConsumedCapability> capabilityTask)
{ {
WhenResolved = capabilityTask; _capTask = capabilityTask.EnforceAwaitOrder();
} }
internal override void Freeze(out IRpcEndpoint? boundEndpoint) public LazyCapability(Task<Proxy> proxyTask)
{ {
if (WhenResolved.IsCompleted) _proxyTask = proxyTask.EnforceAwaitOrder();
async Task<ConsumedCapability> AwaitCap() => (await _proxyTask!).ConsumedCap;
_capTask = AwaitCap().EnforceAwaitOrder();
}
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
{
if (WhenResolved.IsCompleted && WhenResolved.WrappedTask.ReplacementTaskIsCompletedSuccessfully())
{
using var proxy = GetResolvedCapability<BareProxy>()!;
return proxy.Export(endpoint, writer);
}
else
{
return this.ExportAsSenderPromise(endpoint, writer);
}
}
protected override void ReleaseRemotely()
{
if (_proxyTask != null)
{
async void DisposeProxyWhenResolved()
{
try { using var _ = await _proxyTask!; }
catch { }
}
DisposeProxyWhenResolved();
}
}
public StrictlyOrderedAwaitTask WhenResolved => _capTask;
public T? GetResolvedCapability<T>() where T: class
{
if (_capTask.WrappedTask.IsCompleted)
{ {
try try
{ {
WhenResolved.Result.Freeze(out boundEndpoint); return (CapabilityReflection.CreateProxy<T>(_capTask.Result) as T)!;
} }
catch (AggregateException exception) catch (AggregateException exception)
{ {
@ -42,55 +79,31 @@ namespace Capnp.Rpc
} }
else else
{ {
boundEndpoint = null; return null;
} }
} }
internal override void Unfreeze()
{
}
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
{
if (WhenResolved.ReplacementTaskIsCompletedSuccessfully())
{
WhenResolved.Result.Export(endpoint, writer);
}
else
{
this.ExportAsSenderPromise(endpoint, writer);
}
}
async void DisposeProxyWhenResolved()
{
try
{
var cap = await WhenResolved;
if (cap != null) cap.Dispose();
}
catch
{
}
}
protected override void ReleaseRemotely()
{
DisposeProxyWhenResolved();
}
public Task<Proxy> WhenResolved { get; }
async Task<DeserializerState> CallImpl(ulong interfaceId, ushort methodId, DynamicSerializerState args, CancellationToken cancellationToken) async Task<DeserializerState> CallImpl(ulong interfaceId, ushort methodId, DynamicSerializerState args, CancellationToken cancellationToken)
{ {
var cap = await WhenResolved; ConsumedCapability cap;
try
{
cap = await _capTask;
}
catch
{
args.Dispose();
throw;
}
cancellationToken.ThrowIfCancellationRequested(); if (cancellationToken.IsCancellationRequested)
{
args.Dispose();
cancellationToken.ThrowIfCancellationRequested();
}
if (cap == null) using var proxy = new Proxy(cap);
throw new RpcException("Broken capability"); var call = proxy.Call(interfaceId, methodId, args, default);
var call = cap.Call(interfaceId, methodId, args, default);
var whenReturned = call.WhenReturned; var whenReturned = call.WhenReturned;
using (var registration = cancellationToken.Register(call.Dispose)) using (var registration = cancellationToken.Register(call.Dispose))

View File

@ -1,4 +1,5 @@
using System; using Capnp.Util;
using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -11,42 +12,36 @@ namespace Capnp.Rpc
public LocalAnswer(CancellationTokenSource cts, Task<DeserializerState> call) public LocalAnswer(CancellationTokenSource cts, Task<DeserializerState> call)
{ {
_cts = cts ?? throw new ArgumentNullException(nameof(cts)); _cts = cts ?? throw new ArgumentNullException(nameof(cts));
WhenReturned = call ?? throw new ArgumentNullException(nameof(call)); WhenReturned = call?.EnforceAwaitOrder() ?? throw new ArgumentNullException(nameof(call));
CleanupAfterReturn(); CleanupAfterReturn();
} }
async void CleanupAfterReturn() async void CleanupAfterReturn()
{ {
try try { await WhenReturned; }
{ catch { }
await WhenReturned; finally { _cts.Dispose(); }
}
catch
{
}
finally
{
_cts.Dispose();
}
} }
public Task<DeserializerState> WhenReturned { get; } public StrictlyOrderedAwaitTask<DeserializerState> WhenReturned { get; }
public bool IsTailCall => false;
public ConsumedCapability Access(MemberAccessPath access) public ConsumedCapability Access(MemberAccessPath access)
{ {
return new LocalAnswerCapability(WhenReturned, access); return new LocalAnswerCapability(WhenReturned, access);
} }
public ConsumedCapability Access(MemberAccessPath _, Task<IDisposable?> task)
{
return new LocalAnswerCapability(task.AsProxyTask());
}
public void Dispose() public void Dispose()
{ {
try try { _cts.Cancel(); }
{ catch (ObjectDisposedException) { }
_cts.Cancel();
}
catch (ObjectDisposedException)
{
}
} }
} }
} }

View File

@ -1,78 +1,77 @@
using System; using Capnp.Util;
using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Capnp.Rpc namespace Capnp.Rpc
{ {
class LocalAnswerCapability : RefCountingCapability, IResolvingCapability class LocalAnswerCapability : RefCountingCapability, IResolvingCapability
{ {
readonly Task<DeserializerState> _answer; static async Task<Proxy> TransferOwnershipToDummyProxy(StrictlyOrderedAwaitTask<DeserializerState> answer, MemberAccessPath access)
readonly MemberAccessPath _access;
public LocalAnswerCapability(Task<DeserializerState> answer, MemberAccessPath access)
{ {
_answer = answer; var result = await answer;
_access = access; var cap = access.Eval(result);
var proxy = new Proxy(cap);
cap?.Release();
return proxy;
} }
internal override void Freeze(out IRpcEndpoint? boundEndpoint) readonly StrictlyOrderedAwaitTask<Proxy> _whenResolvedProxy;
public LocalAnswerCapability(Task<Proxy> proxyTask)
{ {
boundEndpoint = null; _whenResolvedProxy = proxyTask.EnforceAwaitOrder();
} }
internal override void Unfreeze() public LocalAnswerCapability(StrictlyOrderedAwaitTask<DeserializerState> answer, MemberAccessPath access):
this(TransferOwnershipToDummyProxy(answer, access))
{ {
} }
async Task<Proxy> AwaitResolved() public StrictlyOrderedAwaitTask WhenResolved => _whenResolvedProxy;
{
var state = await _answer;
return new Proxy(_access.Eval(state));
}
public Task<Proxy> WhenResolved => AwaitResolved(); public T? GetResolvedCapability<T>() where T : class => _whenResolvedProxy.WrappedTask.GetResolvedCapability<T>();
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer) internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
{ {
if (_answer.IsCompleted) if (_whenResolvedProxy.IsCompleted)
{ {
DeserializerState result;
try try
{ {
result = _answer.Result; _whenResolvedProxy.Result.Export(endpoint, writer);
} }
catch (AggregateException exception) catch (AggregateException exception)
{ {
throw exception.InnerException!; throw exception.InnerException!;
} }
using (var proxy = new Proxy(_access.Eval(result))) return null;
{
proxy.Export(endpoint, writer);
}
} }
else else
{ {
this.ExportAsSenderPromise(endpoint, writer); return this.ExportAsSenderPromise(endpoint, writer);
} }
} }
async Task<DeserializerState> CallImpl(ulong interfaceId, ushort methodId, DynamicSerializerState args, CancellationToken cancellationToken) async Task<DeserializerState> CallImpl(ulong interfaceId, ushort methodId, DynamicSerializerState args, CancellationToken cancellationToken)
{ {
var cap = await AwaitResolved(); var proxy = await _whenResolvedProxy;
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
if (cap == null) if (proxy.IsNull)
{
args.Dispose();
throw new RpcException("Broken capability"); throw new RpcException("Broken capability");
}
var call = cap.Call(interfaceId, methodId, args, default); var call = proxy.Call(interfaceId, methodId, args, default);
var whenReturned = call.WhenReturned; var whenReturned = call.WhenReturned;
using (var registration = cancellationToken.Register(() => call.Dispose())) using var registration = cancellationToken.Register(() => call.Dispose());
{ return await whenReturned;
return await whenReturned;
}
} }
internal override IPromisedAnswer DoCall(ulong interfaceId, ushort methodId, DynamicSerializerState args) internal override IPromisedAnswer DoCall(ulong interfaceId, ushort methodId, DynamicSerializerState args)
@ -81,9 +80,10 @@ namespace Capnp.Rpc
return new LocalAnswer(cts, CallImpl(interfaceId, methodId, args, cts.Token)); return new LocalAnswer(cts, CallImpl(interfaceId, methodId, args, cts.Token));
} }
protected override void ReleaseRemotely() protected async override void ReleaseRemotely()
{ {
this.DisposeWhenResolved(); try { using var _ = await _whenResolvedProxy; }
catch { }
} }
} }
} }

View File

@ -7,17 +7,6 @@ namespace Capnp.Rpc
{ {
class LocalCapability : ConsumedCapability class LocalCapability : ConsumedCapability
{ {
static readonly ConditionalWeakTable<Skeleton, LocalCapability> _localCaps =
new ConditionalWeakTable<Skeleton, LocalCapability>();
public static ConsumedCapability Create(Skeleton skeleton)
{
if (skeleton is Vine vine)
return vine.Proxy.ConsumedCap!;
else
return _localCaps.GetValue(skeleton, _ => new LocalCapability(_));
}
static async Task<DeserializerState> AwaitAnswer(Task<AnswerOrCounterquestion> call) static async Task<DeserializerState> AwaitAnswer(Task<AnswerOrCounterquestion> call)
{ {
var aorcq = await call; var aorcq = await call;
@ -26,7 +15,9 @@ namespace Capnp.Rpc
public Skeleton ProvidedCap { get; } public Skeleton ProvidedCap { get; }
LocalCapability(Skeleton providedCap) internal override Skeleton AsSkeleton() => ProvidedCap;
public LocalCapability(Skeleton providedCap)
{ {
ProvidedCap = providedCap ?? throw new ArgumentNullException(nameof(providedCap)); ProvidedCap = providedCap ?? throw new ArgumentNullException(nameof(providedCap));
} }
@ -36,10 +27,7 @@ namespace Capnp.Rpc
ProvidedCap.Claim(); ProvidedCap.Claim();
} }
internal override void Release( internal override void Release()
[System.Runtime.CompilerServices.CallerMemberName] string methodName = "",
[System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0)
{ {
ProvidedCap.Relinquish(); ProvidedCap.Relinquish();
} }
@ -51,19 +39,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)
{
boundEndpoint = null;
}
internal override void Unfreeze()
{
} }
protected override void ReleaseRemotely() protected override void ReleaseRemotely()

View File

@ -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)
@ -169,7 +169,7 @@ namespace Capnp.Rpc
/// <param name="rpcState">The object (usually "params struct") on which to evaluate this path.</param> /// <param name="rpcState">The object (usually "params struct") on which to evaluate this path.</param>
/// <returns>Resulting low-level capability</returns> /// <returns>Resulting low-level capability</returns>
/// <exception cref="DeserializationException">Evaluation of this path did not give a capability</exception> /// <exception cref="DeserializationException">Evaluation of this path did not give a capability</exception>
public ConsumedCapability? Eval(DeserializerState rpcState) public ConsumedCapability Eval(DeserializerState rpcState)
{ {
var cur = rpcState; var cur = rpcState;
@ -181,7 +181,7 @@ namespace Capnp.Rpc
switch (cur.Kind) switch (cur.Kind)
{ {
case ObjectKind.Nil: case ObjectKind.Nil:
return null; return NullCapability.Instance;
case ObjectKind.Capability: case ObjectKind.Capability:
return rpcState.Caps![(int)cur.CapabilityIndex]; return rpcState.Caps![(int)cur.CapabilityIndex];

View File

@ -0,0 +1,16 @@
namespace Capnp.Rpc
{
/// <summary>
/// Thrown when a pending question did return, but was not configured to deliver the result back to the sender
/// (typcial for tail calls).
/// </summary>
public class NoResultsException: System.Exception
{
/// <summary>
/// Creates an instance
/// </summary>
public NoResultsException(): base("Pending question did return, but was not configured to deliver the result back to the sender")
{
}
}
}

View File

@ -0,0 +1,54 @@
using System;
namespace Capnp.Rpc
{
/// <summary>
/// Null capability
/// </summary>
public sealed class NullCapability : ConsumedCapability
{
/// <summary>
/// Singleton instance
/// </summary>
public static readonly NullCapability Instance = new NullCapability();
NullCapability()
{
}
/// <summary>
/// Does nothing
/// </summary>
protected override void ReleaseRemotely()
{
}
internal override void AddRef()
{
}
internal override Skeleton AsSkeleton() => NullSkeleton.Instance;
internal override IPromisedAnswer DoCall(ulong interfaceId, ushort methodId, DynamicSerializerState args)
{
args.Dispose();
throw new InvalidOperationException("Cannot call null capability");
}
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
{
writer.which = CapDescriptor.WHICH.None;
return null;
}
internal override void Release()
{
}
/// <summary>
/// String hint
/// </summary>
/// <returns>"Null capability"</returns>
public override string ToString() => "Null capability";
}
}

View File

@ -0,0 +1,41 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Capnp.Rpc
{
/// <summary>
/// Null skeleton
/// </summary>
public sealed class NullSkeleton : Skeleton
{
/// <summary>
/// Singleton instance
/// </summary>
public static readonly NullSkeleton Instance = new NullSkeleton();
NullSkeleton()
{
}
/// <summary>
/// Always throws an exception
/// </summary>
/// <exception cref="InvalidOperationException">always thrown</exception>
public override Task<AnswerOrCounterquestion> Invoke(ulong interfaceId, ushort methodId, DeserializerState args, CancellationToken cancellationToken = default)
{
args.Dispose();
throw new InvalidOperationException("Cannot call null capability");
}
internal override ConsumedCapability AsCapability() => NullCapability.Instance;
internal override void Claim()
{
}
internal override void Relinquish()
{
}
}
}

View File

@ -1,4 +1,6 @@
using System; using Capnp.Util;
using System;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -8,7 +10,7 @@ namespace Capnp.Rpc
{ {
readonly CancellationTokenSource? _cts; readonly CancellationTokenSource? _cts;
readonly TaskCompletionSource<AnswerOrCounterquestion> _cancelCompleter; readonly TaskCompletionSource<AnswerOrCounterquestion> _cancelCompleter;
readonly Task<AnswerOrCounterquestion> _answerTask; readonly StrictlyOrderedAwaitTask<AnswerOrCounterquestion> _answerTask;
public PendingAnswer(Task<AnswerOrCounterquestion> callTask, CancellationTokenSource? cts) public PendingAnswer(Task<AnswerOrCounterquestion> callTask, CancellationTokenSource? cts)
{ {
@ -22,92 +24,136 @@ namespace Capnp.Rpc
_cts = cts; _cts = cts;
_cancelCompleter = new TaskCompletionSource<AnswerOrCounterquestion>(); _cancelCompleter = new TaskCompletionSource<AnswerOrCounterquestion>();
_answerTask = CancelableAwaitWhenReady(); _answerTask = CancelableAwaitWhenReady().EnforceAwaitOrder();
TakeCapTableOwnership();
}
async void TakeCapTableOwnership()
{
try
{
var aorcq = await _answerTask;
if (aorcq.Answer != null)
{
if (aorcq.Answer.Caps != null)
{
foreach (var cap in aorcq.Answer.Caps)
{
cap.AddRef();
}
}
}
}
catch
{
}
}
async void ReleaseCapTableOwnership()
{
try
{
var aorcq = await _answerTask;
if (aorcq.Answer != null)
{
if (aorcq.Answer.Caps != null)
{
foreach (var cap in aorcq.Answer.Caps)
{
cap?.Release();
}
}
}
}
catch
{
}
} }
public CancellationToken CancellationToken => _cts?.Token ?? CancellationToken.None; public CancellationToken CancellationToken => _cts?.Token ?? CancellationToken.None;
public IReadOnlyList<CapDescriptor.WRITER>? CapTable { get; set; }
public void Cancel() public void Cancel()
{ {
_cts?.Cancel(); _cts?.Cancel();
_cancelCompleter.SetCanceled(); _cancelCompleter.SetCanceled();
} }
public void Chain(Action<Task<AnswerOrCounterquestion>> func) public void Chain(Action<StrictlyOrderedAwaitTask<AnswerOrCounterquestion>> func)
{ {
func(_answerTask); func(_answerTask);
} }
public void Chain(PromisedAnswer.READER rd, Action<Task<Proxy>> func) public void Chain(PromisedAnswer.READER rd, Action<Task<Proxy>> func)
{ {
Chain(t => async Task<Proxy> EvaluateProxy()
{ {
async Task<Proxy> EvaluateProxy() var aorcq = await _answerTask;
if (aorcq.Answer != null)
{ {
var aorcq = await t; DeserializerState cur = aorcq.Answer;
if (aorcq.Answer != null) foreach (var op in rd.Transform)
{ {
DeserializerState cur = aorcq.Answer; switch (op.which)
foreach (var op in rd.Transform)
{ {
switch (op.which) case PromisedAnswer.Op.WHICH.GetPointerField:
{
case PromisedAnswer.Op.WHICH.GetPointerField:
try
{
cur = cur.StructReadPointer(op.GetPointerField);
}
catch (System.Exception)
{
throw new ArgumentOutOfRangeException("Illegal pointer field in transformation operation");
}
break;
case PromisedAnswer.Op.WHICH.Noop:
break;
default:
throw new ArgumentOutOfRangeException("Unknown transformation operation");
}
}
Proxy proxy;
switch (cur.Kind)
{
case ObjectKind.Capability:
try try
{ {
var cap = aorcq.Answer.Caps![(int)cur.CapabilityIndex]; cur = cur.StructReadPointer(op.GetPointerField);
proxy = new Proxy(cap ?? LazyCapability.Null);
} }
catch (ArgumentOutOfRangeException) catch (System.Exception)
{ {
throw new ArgumentOutOfRangeException("Bad capability table in internal answer - internal error?"); throw new RpcException("Illegal pointer field in transformation operation");
} }
return proxy; break;
case PromisedAnswer.Op.WHICH.Noop:
break;
default: default:
throw new ArgumentOutOfRangeException("Transformation did not result in a capability"); throw new ArgumentOutOfRangeException("Unknown transformation operation");
} }
} }
else
switch (cur.Kind)
{ {
var path = MemberAccessPath.Deserialize(rd); case ObjectKind.Capability:
var cap = new RemoteAnswerCapability(aorcq.Counterquestion!, path); try
return new Proxy(cap); {
return new Proxy(aorcq.Answer.Caps![(int)cur.CapabilityIndex]);
}
catch (ArgumentOutOfRangeException)
{
throw new RpcException("Capability index out of range");
}
case ObjectKind.Nil:
return new Proxy(NullCapability.Instance);
default:
throw new ArgumentOutOfRangeException("Transformation did not result in a capability");
} }
} }
else
{
var path = MemberAccessPath.Deserialize(rd);
var cap = new RemoteAnswerCapability(aorcq.Counterquestion!, path);
return new Proxy(cap);
}
}
func(EvaluateProxy()); func(EvaluateProxy());
});
} }
public void Dispose() public void Dispose()
{ {
_cts?.Dispose(); _cts?.Dispose();
ReleaseCapTableOwnership();
} }
} }
} }

View File

@ -1,4 +1,6 @@
using System; using Capnp.Util;
using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -48,36 +50,25 @@ namespace Capnp.Rpc
/// <summary> /// <summary>
/// Question object was disposed. /// Question object was disposed.
/// </summary> /// </summary>
Disposed = 16, CanceledByDispose = 16
/// <summary>
/// Question object was finalized by GC.
/// This flag should only be observable when debugging the finalizer itself.
/// </summary>
Finalized = 32
} }
readonly TaskCompletionSource<DeserializerState> _tcs = new TaskCompletionSource<DeserializerState>(); readonly TaskCompletionSource<DeserializerState> _tcs = new TaskCompletionSource<DeserializerState>();
readonly StrictlyOrderedAwaitTask<DeserializerState> _whenReturned;
readonly uint _questionId; readonly uint _questionId;
ConsumedCapability? _target; ConsumedCapability? _target;
SerializerState? _inParams; SerializerState? _inParams;
int _inhibitFinishCounter; int _inhibitFinishCounter, _refCounter;
internal PendingQuestion(IRpcEndpoint ep, uint id, ConsumedCapability? target, SerializerState? inParams) internal PendingQuestion(IRpcEndpoint ep, uint id, ConsumedCapability target, SerializerState? inParams)
{ {
RpcEndpoint = ep ?? throw new ArgumentNullException(nameof(ep)); RpcEndpoint = ep ?? throw new ArgumentNullException(nameof(ep));
_questionId = id; _questionId = id;
_target = target; _target = target;
_inParams = inParams; _inParams = inParams;
StateFlags = inParams == null ? State.Sent : State.None; _whenReturned = _tcs.Task.EnforceAwaitOrder();
if (inParams != null) StateFlags = inParams == null ? State.Sent : State.None;
{
foreach (var cap in inParams.Caps!)
{
cap?.AddRef();
}
}
if (target != null) if (target != null)
{ {
@ -89,16 +80,20 @@ namespace Capnp.Rpc
internal object ReentrancyBlocker { get; } = new object(); internal object ReentrancyBlocker { get; } = new object();
internal uint QuestionId => _questionId; internal uint QuestionId => _questionId;
internal State StateFlags { get; private set; } internal State StateFlags { get; private set; }
internal IReadOnlyList<CapDescriptor.WRITER>? CapTable { get; set; }
/// <summary> /// <summary>
/// Eventually returns the server answer /// Eventually returns the server answer
/// </summary> /// </summary>
public Task<DeserializerState> WhenReturned => _tcs.Task; public StrictlyOrderedAwaitTask<DeserializerState> WhenReturned => _whenReturned;
internal bool IsTailCall /// <summary>
/// Whether this question represents a tail call
/// </summary>
public bool IsTailCall
{ {
get => StateFlags.HasFlag(State.TailCall); get => StateFlags.HasFlag(State.TailCall);
set internal set
{ {
if (value) if (value)
StateFlags |= State.TailCall; StateFlags |= State.TailCall;
@ -106,7 +101,6 @@ namespace Capnp.Rpc
StateFlags &= ~State.TailCall; StateFlags &= ~State.TailCall;
} }
} }
internal bool IsReturned => StateFlags.HasFlag(State.Returned);
internal void DisallowFinish() internal void DisallowFinish()
{ {
@ -119,7 +113,25 @@ namespace Capnp.Rpc
AutoFinish(); AutoFinish();
} }
internal void AddRef()
{
lock (ReentrancyBlocker)
{
++_refCounter;
}
}
internal void Release()
{
lock (ReentrancyBlocker)
{
--_refCounter;
AutoFinish();
}
}
const string ReturnDespiteTailCallMessage = "Peer sent actual results despite the question was sent as tail call. This was not expected and is a protocol error."; const string ReturnDespiteTailCallMessage = "Peer sent actual results despite the question was sent as tail call. This was not expected and is a protocol error.";
const string UnexpectedTailCallReturnMessage = "Peer sent the results of this questions somewhere else. This was not expected and is a protocol error.";
internal void OnReturn(DeserializerState results) internal void OnReturn(DeserializerState results)
{ {
@ -131,6 +143,7 @@ namespace Capnp.Rpc
if (StateFlags.HasFlag(State.TailCall)) if (StateFlags.HasFlag(State.TailCall))
{ {
_tcs.TrySetException(new RpcException(ReturnDespiteTailCallMessage)); _tcs.TrySetException(new RpcException(ReturnDespiteTailCallMessage));
throw new RpcProtocolErrorException(ReturnDespiteTailCallMessage);
} }
else else
{ {
@ -150,7 +163,8 @@ namespace Capnp.Rpc
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(UnexpectedTailCallReturnMessage));
throw new RpcProtocolErrorException(UnexpectedTailCallReturnMessage);
} }
else else
{ {
@ -165,7 +179,7 @@ namespace Capnp.Rpc
SetReturned(); SetReturned();
} }
_tcs.TrySetException(new RpcException(exception.Reason)); _tcs.TrySetException(new RpcException(exception.Reason ?? "unknown reason"));
} }
internal void OnException(System.Exception exception) internal void OnException(System.Exception exception)
@ -193,11 +207,6 @@ namespace Capnp.Rpc
RpcEndpoint.DeleteQuestion(this); RpcEndpoint.DeleteQuestion(this);
} }
internal void RequestFinish()
{
RpcEndpoint.Finish(_questionId);
}
void AutoFinish() void AutoFinish()
{ {
if (StateFlags.HasFlag(State.FinishRequested)) if (StateFlags.HasFlag(State.FinishRequested))
@ -205,12 +214,13 @@ namespace Capnp.Rpc
return; return;
} }
if ((_inhibitFinishCounter == 0 && StateFlags.HasFlag(State.Returned) && !StateFlags.HasFlag(State.TailCall)) if ((!IsTailCall && _inhibitFinishCounter == 0 && StateFlags.HasFlag(State.Returned)) ||
|| StateFlags.HasFlag(State.Disposed)) ( IsTailCall && _refCounter == 0 && StateFlags.HasFlag(State.Returned)) ||
StateFlags.HasFlag(State.CanceledByDispose))
{ {
StateFlags |= State.FinishRequested; StateFlags |= State.FinishRequested;
RequestFinish(); RpcEndpoint.Finish(_questionId);
} }
} }
@ -234,50 +244,27 @@ 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>
public ConsumedCapability? Access(MemberAccessPath access) public ConsumedCapability Access(MemberAccessPath access) => new RemoteAnswerCapability(this, access);
{
lock (ReentrancyBlocker)
{
if ( StateFlags.HasFlag(State.Returned) &&
!StateFlags.HasFlag(State.TailCall))
{
try
{
return access.Eval(WhenReturned.Result);
}
catch (AggregateException exception)
{
throw exception.InnerException!;
}
}
else
{
return new RemoteAnswerCapability(this, access);
}
}
}
static void ReleaseCaps(ConsumedCapability? target, SerializerState? inParams) /// <summary>
/// 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)
{ {
if (inParams != null) var proxyTask = task.AsProxyTask();
{ return new RemoteAnswerCapability(this, access, proxyTask);
foreach (var cap in inParams.Caps!)
{
cap?.Release();
}
}
if (target != null)
{
target.Release();
}
} }
static void ReleaseOutCaps(DeserializerState outParams) static void ReleaseOutCaps(DeserializerState outParams)
{ {
foreach (var cap in outParams.Caps!) foreach (var cap in outParams.Caps!)
{ {
cap?.Release(); cap.Release();
} }
} }
@ -299,70 +286,24 @@ namespace Capnp.Rpc
} }
var msg = (Message.WRITER)inParams!.MsgBuilder!.Root!; var msg = (Message.WRITER)inParams!.MsgBuilder!.Root!;
Debug.Assert(msg.Call.Target.which != MessageTarget.WHICH.undefined); Debug.Assert(msg.Call!.Target.which != MessageTarget.WHICH.undefined);
var call = msg.Call; var call = msg.Call;
call.QuestionId = QuestionId; call.QuestionId = QuestionId;
call.SendResultsTo.which = IsTailCall ? call.SendResultsTo.which = IsTailCall ?
Call.sendResultsTo.WHICH.Yourself : Call.sendResultsTo.WHICH.Yourself :
Call.sendResultsTo.WHICH.Caller; Call.sendResultsTo.WHICH.Caller;
try try
{ {
RpcEndpoint.SendQuestion(inParams, call.Params); RpcEndpoint.SendQuestion(inParams, call.Params);
CapTable = call.Params.CapTable;
} }
catch (System.Exception exception) catch (System.Exception exception)
{ {
OnException(exception); OnException(exception);
} }
ReleaseCaps(target!, inParams); target?.Release();
}
#region IDisposable Support
void Dispose(bool disposing)
{
SerializerState? inParams;
ConsumedCapability? target;
bool justDisposed = false;
lock (ReentrancyBlocker)
{
inParams = _inParams;
_inParams = null;
target = _target;
_target = null;
if (disposing)
{
if (!StateFlags.HasFlag(State.Disposed))
{
StateFlags |= State.Disposed;
justDisposed = true;
AutoFinish();
}
}
else
{
StateFlags |= State.Finalized;
}
}
ReleaseCaps(target, inParams);
if (justDisposed)
{
_tcs.TrySetCanceled();
}
}
/// <summary>
/// Finalizer
/// </summary>
~PendingQuestion()
{
Dispose(false);
} }
/// <summary> /// <summary>
@ -370,9 +311,23 @@ namespace Capnp.Rpc
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
Dispose(true); bool justDisposed = false;
GC.SuppressFinalize(this);
lock (ReentrancyBlocker)
{
if (!StateFlags.HasFlag(State.CanceledByDispose))
{
StateFlags |= State.CanceledByDispose;
justDisposed = true;
AutoFinish();
}
}
if (justDisposed)
{
_tcs.TrySetCanceled();
}
} }
#endregion
} }
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -8,7 +9,7 @@ namespace Capnp.Rpc
/// <summary> /// <summary>
/// Combines multiple skeletons to represent objects which implement multiple interfaces. /// Combines multiple skeletons to represent objects which implement multiple interfaces.
/// </summary> /// </summary>
public class PolySkeleton: Skeleton public class PolySkeleton: RefCountingSkeleton
{ {
readonly Dictionary<ulong, Skeleton> _ifmap = new Dictionary<ulong, Skeleton>(); readonly Dictionary<ulong, Skeleton> _ifmap = new Dictionary<ulong, Skeleton>();
@ -24,8 +25,9 @@ namespace Capnp.Rpc
if (skeleton == null) if (skeleton == null)
throw new ArgumentNullException(nameof(skeleton)); throw new ArgumentNullException(nameof(skeleton));
skeleton.Claim();
_ifmap.Add(interfaceId, skeleton); _ifmap.Add(interfaceId, skeleton);
if (_ifmap.Count == 1) // Claiming only the first one is sufficient
skeleton.Claim();
} }
internal void AddInterface(Skeleton skeleton) internal void AddInterface(Skeleton skeleton)
@ -54,17 +56,16 @@ namespace Capnp.Rpc
/// </summary> /// </summary>
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
foreach (var cap in _ifmap.Values) foreach (var cap in _ifmap.Values.Take(1))
// releasing first skeleton is sufficient. Avoid double-Dispose!
{ {
cap.Relinquish(); cap.Relinquish();
} }
base.Dispose(disposing);
} }
internal override void Bind(object impl) internal override void Bind(object impl)
{ {
foreach (Skeleton skel in _ifmap.Values) foreach (Skeleton skel in _ifmap.Values)
{ {
skel.Bind(impl); skel.Bind(impl);
} }

View File

@ -1,4 +1,5 @@
using System; using Capnp.Util;
using System;
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -8,78 +9,30 @@ namespace Capnp.Rpc
{ {
readonly uint _remoteId; readonly uint _remoteId;
readonly object _reentrancyBlocker = new object(); readonly object _reentrancyBlocker = new object();
readonly TaskCompletionSource<Proxy> _resolvedCap = new TaskCompletionSource<Proxy>(); readonly TaskCompletionSource<ConsumedCapability> _resolvedCap = new TaskCompletionSource<ConsumedCapability>();
readonly StrictlyOrderedAwaitTask<Proxy> _whenResolvedProxy;
bool _released; bool _released;
public PromisedCapability(IRpcEndpoint ep, uint remoteId): base(ep) public PromisedCapability(IRpcEndpoint ep, uint remoteId): base(ep)
{ {
_remoteId = remoteId; _remoteId = remoteId;
async Task<Proxy> AwaitProxy() => new Proxy(await _resolvedCap.Task);
_whenResolvedProxy = AwaitProxy().EnforceAwaitOrder();
} }
public override Task<Proxy> WhenResolved => _resolvedCap.Task; public override StrictlyOrderedAwaitTask WhenResolved => _whenResolvedProxy;
public override T? GetResolvedCapability<T>() where T: class => _whenResolvedProxy.WrappedTask.GetResolvedCapability<T>();
internal override void Freeze(out IRpcEndpoint? boundEndpoint) internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
{
lock (_reentrancyBlocker)
{
if (_resolvedCap.Task.IsCompleted && _pendingCallsOnPromise == 0)
{
try
{
_resolvedCap.Task.Result.Freeze(out boundEndpoint);
}
catch (AggregateException exception)
{
throw exception.InnerException!;
}
}
else
{
Debug.Assert(!_released);
++_pendingCallsOnPromise;
boundEndpoint = _ep;
}
}
}
internal override void Unfreeze()
{
bool release = false;
lock (_reentrancyBlocker)
{
if (_pendingCallsOnPromise == 0)
{
_resolvedCap.Task.Result.Unfreeze();
}
else
{
Debug.Assert(_pendingCallsOnPromise > 0);
Debug.Assert(!_released);
if (--_pendingCallsOnPromise == 0 && _resolvedCap.Task.IsCompleted)
{
release = true;
_released = true;
}
}
}
if (release)
{
_ep.ReleaseImport(_remoteId);
}
}
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
{ {
lock (_reentrancyBlocker) lock (_reentrancyBlocker)
{ {
if (_resolvedCap.Task.ReplacementTaskIsCompletedSuccessfully()) if (_resolvedCap.Task.ReplacementTaskIsCompletedSuccessfully())
{ {
_resolvedCap.Task.Result.Export(endpoint, writer); using var proxy = new Proxy(_resolvedCap.Task.Result);
proxy.Export(endpoint, writer);
} }
else else
{ {
@ -91,7 +44,7 @@ namespace Capnp.Rpc
Debug.Assert(!_released); Debug.Assert(!_released);
++_pendingCallsOnPromise; ++_pendingCallsOnPromise;
_ep.RequestPostAction(() => return () =>
{ {
bool release = false; bool release = false;
@ -108,7 +61,7 @@ namespace Capnp.Rpc
{ {
_ep.ReleaseImport(_remoteId); _ep.ReleaseImport(_remoteId);
} }
}); };
} }
else else
{ {
@ -116,9 +69,11 @@ namespace Capnp.Rpc
} }
} }
} }
return null;
} }
async void TrackCall(Task call) async void TrackCall(StrictlyOrderedAwaitTask call)
{ {
try try
{ {
@ -147,7 +102,7 @@ namespace Capnp.Rpc
} }
} }
protected override Proxy? ResolvedCap protected override ConsumedCapability? ResolvedCap
{ {
get get
{ {
@ -194,7 +149,11 @@ namespace Capnp.Rpc
lock (_reentrancyBlocker) lock (_reentrancyBlocker)
{ {
_resolvedCap.SetResult(new Proxy(resolvedCap)); #if DebugFinalizers
if (resolvedCap != null)
resolvedCap.ResolvingCap = this;
#endif
_resolvedCap.SetResult(resolvedCap!);
if (_pendingCallsOnPromise == 0) if (_pendingCallsOnPromise == 0)
{ {
@ -218,7 +177,7 @@ namespace Capnp.Rpc
#if false #if false
_resolvedCap.SetException(new RpcException(message)); _resolvedCap.SetException(new RpcException(message));
#else #else
_resolvedCap.SetResult(new Proxy(LazyCapability.CreateBrokenCap(message))); _resolvedCap.SetResult(LazyCapability.CreateBrokenCap(message));
#endif #endif
if (_pendingCallsOnPromise == 0) if (_pendingCallsOnPromise == 0)
@ -234,16 +193,16 @@ namespace Capnp.Rpc
} }
} }
protected override void ReleaseRemotely() protected async override void ReleaseRemotely()
{ {
if (!_released) if (!_released)
{ {
_released = true;
_ep.ReleaseImport(_remoteId); _ep.ReleaseImport(_remoteId);
} }
_ep.ReleaseImport(_remoteId); try { using var _ = await _whenResolvedProxy; }
catch { }
this.DisposeWhenResolved();
} }
protected override Call.WRITER SetupMessage(DynamicSerializerState args, ulong interfaceId, ushort methodId) protected override Call.WRITER SetupMessage(DynamicSerializerState args, ulong interfaceId, ushort methodId)

View File

@ -1,4 +1,7 @@
using System; using Capnp.Util;
using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -10,53 +13,70 @@ namespace Capnp.Rpc
/// </summary> /// </summary>
public class Proxy : IDisposable, IResolvingCapability public class Proxy : IDisposable, IResolvingCapability
{ {
#if DebugFinalizers /// <summary>
ILogger Logger { get; } = Logging.CreateLogger<Proxy>(); /// Creates a new proxy object for an existing implementation or proxy, sharing its ownership.
#endif /// </summary>
/// <typeparam name="T">Capability interface</typeparam>
/// <param name="obj">instance to share</param>
/// <returns></returns>
public static T Share<T>(T obj) where T : class
{
if (obj is Proxy proxy)
return proxy.Cast<T>(false);
else
return BareProxy.FromImpl(obj).Cast<T>(true);
}
bool _disposedValue = false; bool _disposedValue = false;
/// <summary> /// <summary>
/// Will eventually give the resolved capability, if this is a promised capability. /// Completes when the capability gets resolved.
/// </summary> /// </summary>
public Task<Proxy> WhenResolved public StrictlyOrderedAwaitTask WhenResolved
{ {
get get
{ {
if (ConsumedCap is IResolvingCapability resolving) return ConsumedCap is IResolvingCapability resolving ?
{ resolving.WhenResolved : Task.CompletedTask.EnforceAwaitOrder();
return resolving.WhenResolved;
}
else
{
return Task.FromResult(this);
}
} }
} }
/// <summary>
/// Returns the resolved capability
/// </summary>
/// <typeparam name="T">Capability interface or <see cref="BareProxy"/></typeparam>
/// <returns>the resolved capability, or null if it did not resolve yet</returns>
public T? GetResolvedCapability<T>() where T : class
{
if (ConsumedCap is IResolvingCapability resolving)
return resolving.GetResolvedCapability<T>();
else
return CapabilityReflection.CreateProxy<T>(ConsumedCap) as T;
}
ConsumedCapability _consumedCap = NullCapability.Instance;
/// <summary> /// <summary>
/// Underlying low-level capability /// Underlying low-level capability
/// </summary> /// </summary>
protected internal ConsumedCapability? ConsumedCap { get; private set; } public ConsumedCapability ConsumedCap => _disposedValue ?
throw new ObjectDisposedException(nameof(Proxy)) : _consumedCap;
/// <summary> /// <summary>
/// Whether is this a broken capability. /// Whether is this a broken capability.
/// </summary> /// </summary>
public bool IsNull => ConsumedCap == null; public bool IsNull => _consumedCap == NullCapability.Instance;
/// <summary>
/// Whether <see cref="Dispose()"/> was called on this Proxy.
/// </summary>
public bool IsDisposed => _disposedValue;
static async void DisposeCtrWhenReturned(CancellationTokenRegistration ctr, IPromisedAnswer answer) static async void DisposeCtrWhenReturned(CancellationTokenRegistration ctr, IPromisedAnswer answer)
{ {
try try { await answer.WhenReturned; }
{ catch { }
await answer.WhenReturned; finally { ctr.Dispose(); }
}
catch
{
}
finally
{
ctr.Dispose();
}
} }
/// <summary> /// <summary>
@ -76,10 +96,10 @@ namespace Capnp.Rpc
bool obsoleteAndIgnored, CancellationToken cancellationToken = default) bool obsoleteAndIgnored, CancellationToken cancellationToken = default)
{ {
if (_disposedValue) if (_disposedValue)
{
args.Dispose();
throw new ObjectDisposedException(nameof(Proxy)); throw new ObjectDisposedException(nameof(Proxy));
}
if (ConsumedCap == null)
throw new InvalidOperationException("Cannot call null capability");
var answer = ConsumedCap.DoCall(interfaceId, methodId, args); var answer = ConsumedCap.DoCall(interfaceId, methodId, args);
@ -96,38 +116,34 @@ 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);
} }
internal void Bind(ConsumedCapability? cap) internal void Bind(ConsumedCapability cap)
{ {
if (ConsumedCap != null) if (ConsumedCap != NullCapability.Instance)
throw new InvalidOperationException("Proxy was already bound"); throw new InvalidOperationException("Proxy was already bound");
if (cap == null) _consumedCap = cap ?? throw new ArgumentNullException(nameof(cap));
return;
ConsumedCap = cap;
cap.AddRef(); cap.AddRef();
#if DebugFinalizers
if (_consumedCap != null)
_consumedCap.OwningProxy = this;
#endif
} }
internal IProvidedCapability? GetProvider() internal async Task<Skeleton> GetProvider()
{ {
switch (ConsumedCap) var unwrapped = await ConsumedCap.Unwrap();
{ return unwrapped.AsSkeleton();
case LocalCapability lcap:
return lcap.ProvidedCap;
case null:
return null;
default:
return Vine.Create(ConsumedCap);
}
} }
/// <summary> /// <summary>
@ -139,20 +155,15 @@ namespace Capnp.Rpc
{ {
if (disposing) if (disposing)
{ {
ConsumedCap?.Release(); _consumedCap.Release();
} }
else else
{ {
// When called from the Finalizer, we must not throw. // When called from the Finalizer, we must not throw.
// But when reference counting goes wrong, ConsumedCapability.Release() will throw an InvalidOperationException. // But when reference counting goes wrong, ConsumedCapability.Release() will throw an InvalidOperationException.
// The only option here is to suppress that exception. // The only option here is to suppress that exception.
try try { _consumedCap?.Release(); }
{ catch { }
ConsumedCap?.Release();
}
catch
{
}
} }
_disposedValue = true; _disposedValue = true;
@ -165,7 +176,7 @@ namespace Capnp.Rpc
~Proxy() ~Proxy()
{ {
#if DebugFinalizers #if DebugFinalizers
Logger.LogWarning($"Caught orphaned Proxy, created from {CreatorMemberName} in {CreatorFilePath}, line {CreatorLineNumber}."); Debugger.Log(0, "DebugFinalizers", $"Caught orphaned Proxy, created from here: {CreatorStackTrace}.");
#endif #endif
Dispose(false); Dispose(false);
@ -187,54 +198,37 @@ namespace Capnp.Rpc
/// <param name="disposeThis">Whether to Dispose() this Proxy instance</param> /// <param name="disposeThis">Whether to Dispose() this Proxy instance</param>
/// <returns>Proxy for desired capability interface</returns> /// <returns>Proxy for desired capability interface</returns>
/// <exception cref="InvalidCapabilityInterfaceException"><typeparamref name="T"/> did not qualify as capability interface.</exception> /// <exception cref="InvalidCapabilityInterfaceException"><typeparamref name="T"/> did not qualify as capability interface.</exception>
/// <exception cref="InvalidOperationException">This capability is broken, or mismatch between generic type arguments (if capability interface is generic).</exception> /// <exception cref="InvalidOperationException">Mismatch between generic type arguments (if capability interface is generic).</exception>
/// <exception cref="ArgumentException">Mismatch between generic type arguments (if capability interface is generic).</exception> /// <exception cref="ArgumentException">Mismatch between generic type arguments (if capability interface is generic).</exception>
/// <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 T Cast<T>(bool disposeThis) where T: class public T Cast<T>(bool disposeThis) where T: class
{ {
if (IsNull)
throw new InvalidOperationException("Capability is broken");
using (disposeThis ? this : null) using (disposeThis ? this : null)
{ {
return (CapabilityReflection.CreateProxy<T>(ConsumedCap) as T)!; return (CapabilityReflection.CreateProxy<T>(ConsumedCap) as T)!;
} }
} }
internal void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer) internal Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
{ {
if (_disposedValue) if (_disposedValue)
throw new ObjectDisposedException(nameof(Proxy)); throw new ObjectDisposedException(nameof(Proxy));
if (ConsumedCap == null) if (ConsumedCap == null)
{
writer.which = CapDescriptor.WHICH.None; writer.which = CapDescriptor.WHICH.None;
return null;
}
else else
ConsumedCap.Export(endpoint, writer); {
} return ConsumedCap.Export(endpoint, writer);
}
internal void Freeze(out IRpcEndpoint? boundEndpoint)
{
if (_disposedValue)
throw new ObjectDisposedException(nameof(Proxy));
boundEndpoint = null;
ConsumedCap?.Freeze(out boundEndpoint);
}
internal void Unfreeze()
{
if (_disposedValue)
throw new ObjectDisposedException(nameof(Proxy));
ConsumedCap?.Unfreeze();
} }
#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
} }
} }

View File

@ -7,6 +7,7 @@ namespace Capnp.Rpc
abstract class RefCountingCapability: ConsumedCapability abstract class RefCountingCapability: ConsumedCapability
{ {
readonly object _reentrancyBlocker = new object(); readonly object _reentrancyBlocker = new object();
Vine? _vine;
// Note on reference counting: Works in analogy to COM. AddRef() adds a reference, // Note on reference counting: Works in analogy to COM. AddRef() adds a reference,
// Release() removes it. When the reference count reaches zero, the capability must be // Release() removes it. When the reference count reaches zero, the capability must be
@ -26,16 +27,24 @@ 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 DebugFinalizers
ILogger Logger { get; } = Logging.CreateLogger<RefCountingCapability>(); ILogger Logger { get; } = Logging.CreateLogger<RefCountingCapability>();
string CreatorStackTrace { get; set; }
string? _releasingMethodName;
string? _releasingFilePath;
int _releasingLineNumber;
#endif #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);
} }
@ -46,13 +55,8 @@ namespace Capnp.Rpc
{ {
if (disposing) if (disposing)
{ {
try try { ReleaseRemotely(); }
{ catch { }
ReleaseRemotely();
}
catch
{
}
} }
else else
{ {
@ -60,19 +64,14 @@ namespace Capnp.Rpc
{ {
Task.Run(() => Task.Run(() =>
{ {
try try { ReleaseRemotely(); }
{ catch { }
ReleaseRemotely();
}
catch
{
}
}); });
} }
} }
} }
internal sealed override void AddRef() internal override void AddRef()
{ {
lock (_reentrancyBlocker) lock (_reentrancyBlocker)
{ {
@ -90,10 +89,7 @@ namespace Capnp.Rpc
} }
} }
internal sealed override void Release( internal override void Release()
[System.Runtime.CompilerServices.CallerMemberName] string methodName = "",
[System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
[System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0)
{ {
lock (_reentrancyBlocker) lock (_reentrancyBlocker)
{ {
@ -103,12 +99,6 @@ namespace Capnp.Rpc
case 2: // actually ref. count 1 case 2: // actually ref. count 1
_refCount = 0; _refCount = 0;
#if DebugCapabilityLifecycle
_releasingMethodName = methodName;
_releasingFilePath = filePath;
_releasingLineNumber = lineNumber;
#endif
Dispose(true); Dispose(true);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
break; break;
@ -133,5 +123,15 @@ namespace Capnp.Rpc
} }
} }
} }
internal override Skeleton AsSkeleton()
{
lock (_reentrancyBlocker)
{
if (_vine == null)
_vine = new Vine(this);
return _vine;
}
}
} }
} }

View File

@ -0,0 +1,80 @@
using System;
using System.Threading;
namespace Capnp.Rpc
{
/// <summary>
/// Skeleton with reference counting and dispose pattern
/// </summary>
public abstract class RefCountingSkeleton: Skeleton
{
int _refCount;
LocalCapability? _localCap;
/// <summary>
/// Dispose pattern implementation
/// </summary>
protected virtual void Dispose(bool disposing)
{
}
/// <summary>
/// Finalizer
/// </summary>
~RefCountingSkeleton()
{
Dispose(false);
}
internal sealed override void Claim()
{
int count, newCount;
do
{
count = Volatile.Read(ref _refCount);
if (count < 0)
throw new ObjectDisposedException(nameof(RefCountingSkeleton));
newCount = count + 1;
} while (Interlocked.CompareExchange(ref _refCount, newCount, count) != count);
}
internal override void Relinquish()
{
int count, newCount;
do
{
count = Volatile.Read(ref _refCount);
if (count < 0)
throw new ObjectDisposedException(nameof(RefCountingSkeleton));
newCount = count > 0 ? count - 1 : int.MinValue;
} while (Interlocked.CompareExchange(ref _refCount, newCount, count) != count);
if (newCount == 0)
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
/// <summary>
/// Whether this instance is in disposed state.
/// </summary>
public bool IsDisposed => Volatile.Read(ref _refCount) < 0;
internal override ConsumedCapability AsCapability()
{
var cap = Volatile.Read(ref _localCap);
if (cap == null)
{
Interlocked.CompareExchange(ref _localCap, new LocalCapability(this), null);
}
return Volatile.Read(ref _localCap)!;
}
}
}

View File

@ -1,4 +1,5 @@
using System; using Capnp.Util;
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Capnp.Rpc namespace Capnp.Rpc
@ -16,17 +17,29 @@ namespace Capnp.Rpc
readonly PendingQuestion _question; readonly PendingQuestion _question;
readonly MemberAccessPath _access; readonly MemberAccessPath _access;
Proxy? _resolvedCap; readonly StrictlyOrderedAwaitTask<Proxy> _whenResolvedProxy;
public RemoteAnswerCapability(PendingQuestion question, MemberAccessPath access): base(question.RpcEndpoint) public RemoteAnswerCapability(PendingQuestion question, MemberAccessPath access, Task<Proxy> proxyTask) : base(question.RpcEndpoint)
{ {
_question = question ?? throw new ArgumentNullException(nameof(question)); _question = question ?? throw new ArgumentNullException(nameof(question));
_access = access ?? throw new ArgumentNullException(nameof(access)); _access = access ?? throw new ArgumentNullException(nameof(access));
_whenResolvedProxy = (proxyTask ?? throw new ArgumentNullException(nameof(proxyTask))).EnforceAwaitOrder();
_ = AwaitWhenResolved();
} }
async void ReAllowFinishWhenDone(Task task) 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();
return proxy;
}
public RemoteAnswerCapability(PendingQuestion question, MemberAccessPath access) : this(question, access, TransferOwnershipToDummyProxy(question, access))
{
}
async void ReAllowFinishWhenDone(StrictlyOrderedAwaitTask task)
{ {
try try
{ {
@ -47,57 +60,39 @@ namespace Capnp.Rpc
} }
} }
protected override void Dispose(bool disposing) protected override ConsumedCapability? ResolvedCap
{
base.Dispose(disposing);
lock (_question.ReentrancyBlocker)
{
_resolvedCap?.Dispose();
}
}
protected override Proxy? ResolvedCap
{ {
get get
{ {
lock (_question.ReentrancyBlocker) lock (_question.ReentrancyBlocker)
{ {
if (_resolvedCap == null && !_question.IsTailCall && _question.IsReturned) if (!_question.IsTailCall && _question.StateFlags.HasFlag(PendingQuestion.State.Returned))
{ {
DeserializerState result;
try try
{ {
result = _question.WhenReturned.Result; return _whenResolvedProxy.Result.ConsumedCap;
} }
catch (AggregateException exception) catch (AggregateException exception)
{ {
throw exception.InnerException!; throw exception.InnerException!;
} }
_resolvedCap = new Proxy(_access.Eval(result));
} }
return _resolvedCap; else
{
return null;
}
} }
} }
} }
async Task<Proxy> AwaitWhenResolved() public override StrictlyOrderedAwaitTask WhenResolved => _whenResolvedProxy;
{
await _question.WhenReturned;
if (_question.IsTailCall) public override T? GetResolvedCapability<T>() where T: class => _whenResolvedProxy.WrappedTask.GetResolvedCapability<T>();
throw new InvalidOperationException("Question is a tail call, so won't resolve back.");
return ResolvedCap!;
}
public override Task<Proxy> WhenResolved => AwaitWhenResolved();
protected override void GetMessageTarget(MessageTarget.WRITER wr) protected override void GetMessageTarget(MessageTarget.WRITER wr)
{ {
wr.which = MessageTarget.WHICH.PromisedAnswer; wr.which = MessageTarget.WHICH.PromisedAnswer;
wr.PromisedAnswer.QuestionId = _question.QuestionId; wr.PromisedAnswer!.QuestionId = _question.QuestionId;
_access.Serialize(wr.PromisedAnswer); _access.Serialize(wr.PromisedAnswer);
} }
@ -105,14 +100,9 @@ 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)
{
throw new RpcException("Answer did not resolve to expected capability");
}
return CallOnResolution(interfaceId, methodId, args); return CallOnResolution(interfaceId, methodId, args);
} }
else else
@ -120,16 +110,13 @@ namespace Capnp.Rpc
#if DebugEmbargos #if DebugEmbargos
Logger.LogDebug("Call by proxy"); Logger.LogDebug("Call by proxy");
#endif #endif
if (_question.StateFlags.HasFlag(PendingQuestion.State.Disposed)) if (_question.StateFlags.HasFlag(PendingQuestion.State.CanceledByDispose) ||
_question.StateFlags.HasFlag(PendingQuestion.State.FinishRequested))
{ {
args.Dispose();
throw new ObjectDisposedException(nameof(PendingQuestion)); throw new ObjectDisposedException(nameof(PendingQuestion));
} }
if (_question.StateFlags.HasFlag(PendingQuestion.State.FinishRequested))
{
throw new InvalidOperationException("Finish request was already sent");
}
_question.DisallowFinish(); _question.DisallowFinish();
++_pendingCallsOnPromise; ++_pendingCallsOnPromise;
var promisedAnswer = base.DoCall(interfaceId, methodId, args); var promisedAnswer = base.DoCall(interfaceId, methodId, args);
@ -164,61 +151,22 @@ namespace Capnp.Rpc
var call = base.SetupMessage(args, interfaceId, methodId); var call = base.SetupMessage(args, interfaceId, methodId);
call.Target.which = MessageTarget.WHICH.PromisedAnswer; call.Target.which = MessageTarget.WHICH.PromisedAnswer;
call.Target.PromisedAnswer.QuestionId = _question.QuestionId; call.Target.PromisedAnswer!.QuestionId = _question.QuestionId;
_access.Serialize(call.Target.PromisedAnswer); _access.Serialize(call.Target.PromisedAnswer);
return call; return call;
} }
internal override void Freeze(out IRpcEndpoint? boundEndpoint) internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
{ {
lock (_question.ReentrancyBlocker) lock (_question.ReentrancyBlocker)
{ {
if (_question.StateFlags.HasFlag(PendingQuestion.State.Returned) && if (_question.StateFlags.HasFlag(PendingQuestion.State.CanceledByDispose))
_pendingCallsOnPromise == 0)
{
if (ResolvedCap == null)
{
throw new RpcException("Answer did not resolve to expected capability");
}
ResolvedCap.Freeze(out boundEndpoint);
}
else
{
++_pendingCallsOnPromise;
_question.DisallowFinish();
boundEndpoint = _ep;
}
}
}
internal override void Unfreeze()
{
lock (_question.ReentrancyBlocker)
{
if (_pendingCallsOnPromise > 0)
{
--_pendingCallsOnPromise;
_question.AllowFinish();
}
else
{
ResolvedCap?.Unfreeze();
}
}
}
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
{
lock (_question.ReentrancyBlocker)
{
if (_question.StateFlags.HasFlag(PendingQuestion.State.Disposed))
throw new ObjectDisposedException(nameof(PendingQuestion)); throw new ObjectDisposedException(nameof(PendingQuestion));
if (_question.StateFlags.HasFlag(PendingQuestion.State.Returned)) if (_question.StateFlags.HasFlag(PendingQuestion.State.Returned) && !_question.IsTailCall)
{ {
ResolvedCap?.Export(endpoint, writer); ResolvedCap!.Export(endpoint, writer);
} }
else else
{ {
@ -228,32 +176,45 @@ namespace Capnp.Rpc
if (endpoint == _ep) if (endpoint == _ep)
{ {
writer.which = CapDescriptor.WHICH.ReceiverAnswer; writer.which = CapDescriptor.WHICH.ReceiverAnswer;
_access.Serialize(writer.ReceiverAnswer); _access.Serialize(writer.ReceiverAnswer!);
writer.ReceiverAnswer.QuestionId = _question.QuestionId; writer.ReceiverAnswer!.QuestionId = _question.QuestionId;
} }
else if (_question.IsTailCall) else if (_question.IsTailCall)
{ {
// FIXME: Resource management! We should prevent finishing this uint id = endpoint.AllocateExport(AsSkeleton(), out bool first);
// cap as long as it is exported. Unfortunately, we cannot determine
// when it gets removed from the export table.
var vine = Vine.Create(this);
uint id = endpoint.AllocateExport(vine, out bool first);
writer.which = CapDescriptor.WHICH.SenderHosted; writer.which = CapDescriptor.WHICH.SenderHosted;
writer.SenderHosted = id; writer.SenderHosted = id;
} }
else else
{ {
this.ExportAsSenderPromise(endpoint, writer); return this.ExportAsSenderPromise(endpoint, writer);
} }
} }
} }
return null;
} }
protected override void ReleaseRemotely() protected async override void ReleaseRemotely()
{ {
this.DisposeWhenResolved(); if (!_question.IsTailCall)
{
try { using var _ = await _whenResolvedProxy; }
catch { }
}
}
internal override void AddRef()
{
base.AddRef();
_question.AddRef();
}
internal override void Release()
{
_question.Release();
base.Release();
} }
} }
} }

View File

@ -13,6 +13,8 @@ namespace Capnp.Rpc
_ep = ep; _ep = ep;
} }
internal IRpcEndpoint Endpoint => _ep;
internal override IPromisedAnswer DoCall(ulong interfaceId, ushort methodId, DynamicSerializerState args) internal override IPromisedAnswer DoCall(ulong interfaceId, ushort methodId, DynamicSerializerState args)
{ {
var call = SetupMessage(args, interfaceId, methodId); var call = SetupMessage(args, interfaceId, methodId);
@ -29,7 +31,7 @@ namespace Capnp.Rpc
callMsg.which = Message.WHICH.Call; callMsg.which = Message.WHICH.Call;
var call = callMsg.Call; var call = callMsg.Call!;
call.AllowThirdPartyTailCall = false; call.AllowThirdPartyTailCall = false;
call.InterfaceId = interfaceId; call.InterfaceId = interfaceId;
call.MethodId = methodId; call.MethodId = methodId;

Some files were not shown because too many files have changed in this diff Show More