mirror of
https://github.com/FabInfra/capnproto-dotnetcore_Runtime.git
synced 2025-03-12 06:41:50 +01:00
commit
5b903584ad
3
.gitignore
vendored
3
.gitignore
vendored
@ -336,3 +336,6 @@ ASALocalRun/
|
||||
# Capnp code behind
|
||||
*.capnp.cs
|
||||
/globalPackages
|
||||
|
||||
# Coverage results folder
|
||||
coverage/
|
||||
|
@ -7,8 +7,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.12.0" />
|
||||
<PackageReference Include="Capnp.Net.Runtime" Version="1.3.34-g409e517587" />
|
||||
<PackageReference Include="CapnpC.CSharp.MsBuild.Generation" Version="1.2.138" />
|
||||
<PackageReference Include="Capnp.Net.Runtime" Version="1.3.97-g4f0abaac73" />
|
||||
<PackageReference Include="CapnpC.CSharp.MsBuild.Generation" Version="1.3.97-g4f0abaac73" />
|
||||
<PackageReference Include="Google.Protobuf" Version="3.11.3" />
|
||||
<PackageReference Include="Grpc.Net.Client" Version="2.27.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.27.0">
|
||||
|
@ -11,8 +11,11 @@ namespace Benchmark
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
BenchmarkRunner.Run<GrpcBenchmark>();
|
||||
BenchmarkRunner.Run<CapnpBenchmark>();
|
||||
if (args.Length == 0 || args[0] == "grpc")
|
||||
BenchmarkRunner.Run<GrpcBenchmark>();
|
||||
|
||||
if (args.Length == 0 || args[0] == "capnp")
|
||||
BenchmarkRunner.Run<CapnpBenchmark>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.29728.190
|
||||
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
|
||||
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
|
||||
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
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@ -15,10 +15,6 @@ Global
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
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.Build.0 = Debug|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}.Release|Any CPU.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -9,11 +9,12 @@
|
||||
<DebugType>full</DebugType>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<DefineConstants></DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Capnp.Net.Runtime" Version="1.3.34-g409e517587" />
|
||||
<PackageReference Include="CapnpC.CSharp.MsBuild.Generation" Version="1.3.29-g6d711b8579" />
|
||||
<PackageReference Include="Capnp.Net.Runtime" Version="1.3.90-g65e87e5aa9" />
|
||||
<PackageReference Include="CapnpC.CSharp.MsBuild.Generation" Version="1.3.90-g65e87e5aa9" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
63
Benchmarking/CapnpProfile/EnginePair.cs
Normal file
63
Benchmarking/CapnpProfile/EnginePair.cs
Normal 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;
|
||||
}
|
||||
}
|
@ -7,25 +7,62 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace CapnpProfile
|
||||
{
|
||||
|
||||
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];
|
||||
new Random().NextBytes(payload);
|
||||
|
||||
#if SOTASK_PERF
|
||||
int counter = 0;
|
||||
#endif
|
||||
|
||||
while (true)
|
||||
{
|
||||
var result = await echoer.Echo(payload);
|
||||
if (result.Count != payload.Length)
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Capnp.Net.Runtime" Version="1.3.34-g409e517587" />
|
||||
<PackageReference Include="CapnpC.CSharp.MsBuild.Generation" Version="1.2.138" />
|
||||
<PackageReference Include="Capnp.Net.Runtime" Version="1.3.97-g4f0abaac73" />
|
||||
<PackageReference Include="CapnpC.CSharp.MsBuild.Generation" Version="1.3.97-g4f0abaac73" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -5,7 +5,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Protobuf Include="Protos\Echo.proto" GrpcServices="Server" />
|
||||
<Protobuf Include="..\Benchmark\Protos\Echo.proto" GrpcServices="Server">
|
||||
<Link>Protos\Echo.proto</Link>
|
||||
</Protobuf>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -6,7 +6,7 @@ using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace EchoService
|
||||
namespace EchoServiceGrpc2
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
|
@ -1,13 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
service Echoer {
|
||||
rpc Echo (EchoRequest) returns (EchoReply);
|
||||
}
|
||||
|
||||
message EchoRequest {
|
||||
bytes payload = 1;
|
||||
}
|
||||
|
||||
message EchoReply {
|
||||
bytes payload = 1;
|
||||
}
|
@ -2,13 +2,14 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using EchoService;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace EchoService
|
||||
namespace EchoServiceGrpc2
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
|
@ -1,10 +1,10 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning",
|
||||
"System": "Warning",
|
||||
"Grpc": "Warning",
|
||||
"Microsoft": "Warning"
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Grpc": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,5 +2,6 @@
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
|
||||
<add key="local" value="..\GeneratedNuGetPackages\"/>
|
||||
</packageSources>
|
||||
</configuration>
|
@ -2,5 +2,6 @@
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
|
||||
<add key="local" value="..\GeneratedNuGetPackages\"/>
|
||||
</packageSources>
|
||||
</configuration>
|
@ -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>
|
@ -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>
|
30
Capnp.Net.Runtime.Tests/CapabilityReflectionTests.cs
Normal file
30
Capnp.Net.Runtime.Tests/CapabilityReflectionTests.cs
Normal 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)));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net471</TargetFramework>
|
||||
<TargetFrameworks>netcoreapp2.1;net471</TargetFrameworks>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
|
||||
@ -10,18 +10,25 @@
|
||||
<OutputType>Library</OutputType>
|
||||
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
|
||||
<RootNamespace>Capnp.Net.Runtime.Tests</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netcoreapp2.1|AnyCPU'">
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<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" />
|
||||
<PackageReference Include="System.IO.Pipelines" Version="4.7.0" />
|
||||
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.10.0" />
|
||||
</ItemGroup>
|
||||
|
@ -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
|
||||
{
|
||||
[TestClass]
|
||||
[TestCategory("Coverage")]
|
||||
public class DeserializationTests
|
||||
{
|
||||
[TestMethod]
|
||||
@ -144,6 +151,9 @@ namespace Capnp.Net.Runtime.Tests
|
||||
Assert.AreEqual(2, asListOfStructs.Count);
|
||||
Assert.AreEqual(0ul, asListOfStructs[0].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]
|
||||
@ -207,6 +217,22 @@ namespace Capnp.Net.Runtime.Tests
|
||||
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]
|
||||
public void ListOfStructsAsListOfBytes()
|
||||
{
|
||||
@ -353,5 +379,599 @@ namespace Capnp.Net.Runtime.Tests
|
||||
Assert.AreEqual(double.NegativeInfinity, asListOfDoubles[0]);
|
||||
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>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
198
Capnp.Net.Runtime.Tests/Dtbdct.cs
Normal file
198
Capnp.Net.Runtime.Tests/Dtbdct.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ using System.IO;
|
||||
namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
[TestClass]
|
||||
[TestCategory("Coverage")]
|
||||
public class DynamicSerializerStateTests
|
||||
{
|
||||
[TestMethod]
|
||||
|
1356
Capnp.Net.Runtime.Tests/EdgeCaseHandling.cs
Normal file
1356
Capnp.Net.Runtime.Tests/EdgeCaseHandling.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,7 @@ using System.Threading.Tasks;
|
||||
namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
[TestClass]
|
||||
[TestCategory("Coverage")]
|
||||
public class FramePumpTests
|
||||
{
|
||||
class MyStruct : SerializerState
|
||||
|
@ -1,15 +1,18 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Capnp.Rpc;
|
||||
using Capnp.Util;
|
||||
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]
|
||||
public class General
|
||||
public class General: TestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void AwaitOrderTest()
|
||||
@ -41,31 +44,174 @@ namespace Capnp.Net.Runtime.Tests
|
||||
Task.WhenAll(tasks).Wait();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AwaitOrderTest2()
|
||||
class PromisedAnswerMock : IPromisedAnswer
|
||||
{
|
||||
int returnCounter = 0;
|
||||
readonly TaskCompletionSource<DeserializerState> _tcs = new TaskCompletionSource<DeserializerState>();
|
||||
|
||||
async Task ExpectCount(Task task, int count)
|
||||
public PromisedAnswerMock()
|
||||
{
|
||||
await task;
|
||||
Assert.AreEqual(count, returnCounter++);
|
||||
WhenReturned = _tcs.Task.EnforceAwaitOrder();
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<int>();
|
||||
var cts = new CancellationTokenSource();
|
||||
public StrictlyOrderedAwaitTask<DeserializerState> WhenReturned { get; }
|
||||
|
||||
var tasks =
|
||||
from i in Enumerable.Range(0, 100)
|
||||
select ExpectCount(tcs.Task.ContinueWith(
|
||||
t => t,
|
||||
cts.Token,
|
||||
TaskContinuationOptions.ExecuteSynchronously,
|
||||
TaskScheduler.Current), i);
|
||||
public void Return()
|
||||
{
|
||||
_tcs.SetResult(default);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
208
Capnp.Net.Runtime.Tests/ImpatientTests.cs
Normal file
208
Capnp.Net.Runtime.Tests/ImpatientTests.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ using System.Threading.Tasks.Dataflow;
|
||||
namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
[TestClass]
|
||||
[TestCategory("Coverage")]
|
||||
public class Interception: TestBase
|
||||
{
|
||||
class MyPolicy : IInterceptionPolicy
|
||||
@ -66,9 +67,8 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
|
||||
server.Main = policy.Attach<ITestInterface>(new TestInterfaceImpl(counters));
|
||||
using (var main = client.GetMain<ITestInterface>())
|
||||
{
|
||||
@ -77,13 +77,13 @@ namespace Capnp.Net.Runtime.Tests
|
||||
Assert.IsTrue(fcc.Wait(MediumNonDbgTimeout));
|
||||
var cc = fcc.Result;
|
||||
|
||||
var pr = new Capnproto_test.Capnp.Test.TestInterface.Params_foo.READER(cc.InArgs);
|
||||
var pr = new Capnproto_test.Capnp.Test.TestInterface.Params_Foo.READER(cc.InArgs);
|
||||
Assert.AreEqual(123u, pr.I);
|
||||
|
||||
cc.ForwardToBob();
|
||||
|
||||
Assert.IsTrue(policy.Returns.ReceiveAsync().Wait(MediumNonDbgTimeout));
|
||||
var rr = new Capnproto_test.Capnp.Test.TestInterface.Result_foo.READER(cc.OutArgs);
|
||||
var rr = new Capnproto_test.Capnp.Test.TestInterface.Result_Foo.READER(cc.OutArgs);
|
||||
Assert.AreEqual("foo", rr.X);
|
||||
|
||||
cc.ReturnToAlice();
|
||||
@ -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]
|
||||
public void InterceptClientSideModifyCall()
|
||||
{
|
||||
@ -105,7 +145,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
//client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestInterfaceImpl(counters);
|
||||
@ -116,11 +156,11 @@ namespace Capnp.Net.Runtime.Tests
|
||||
|
||||
Assert.AreEqual(InterceptionState.RequestedFromAlice, cc.State);
|
||||
|
||||
var pr = new Capnproto_test.Capnp.Test.TestInterface.Params_foo.READER(cc.InArgs);
|
||||
var pr = new Capnproto_test.Capnp.Test.TestInterface.Params_Foo.READER(cc.InArgs);
|
||||
Assert.AreEqual(321u, pr.I);
|
||||
Assert.AreEqual(false, pr.J);
|
||||
|
||||
var pw = cc.InArgs.Rewrap<Capnproto_test.Capnp.Test.TestInterface.Params_foo.WRITER>();
|
||||
var pw = cc.InArgs.Rewrap<Capnproto_test.Capnp.Test.TestInterface.Params_Foo.WRITER>();
|
||||
pw.I = 123u;
|
||||
pw.J = true;
|
||||
|
||||
@ -129,15 +169,15 @@ namespace Capnp.Net.Runtime.Tests
|
||||
var rx = policy.Returns.ReceiveAsync();
|
||||
|
||||
// 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));
|
||||
var rr = new Capnproto_test.Capnp.Test.TestInterface.Result_foo.READER(cc.OutArgs);
|
||||
var rr = new Capnproto_test.Capnp.Test.TestInterface.Result_Foo.READER(cc.OutArgs);
|
||||
Assert.AreEqual("foo", rr.X);
|
||||
|
||||
Assert.IsFalse(request1.IsCompleted);
|
||||
|
||||
var rw = ((DynamicSerializerState)cc.OutArgs).Rewrap<Capnproto_test.Capnp.Test.TestInterface.Result_foo.WRITER>();
|
||||
var rw = ((DynamicSerializerState)cc.OutArgs).Rewrap<Capnproto_test.Capnp.Test.TestInterface.Result_Foo.WRITER>();
|
||||
rw.X = "bar";
|
||||
cc.OutArgs = rw;
|
||||
|
||||
@ -163,7 +203,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
//client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestInterfaceImpl(counters);
|
||||
@ -173,7 +213,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
Assert.IsTrue(policy.Calls.TryReceive(out var cc));
|
||||
Assert.IsFalse(request1.IsCompleted);
|
||||
|
||||
var rw = SerializerState.CreateForRpc<Capnproto_test.Capnp.Test.TestInterface.Result_foo.WRITER>();
|
||||
var rw = SerializerState.CreateForRpc<Capnproto_test.Capnp.Test.TestInterface.Result_Foo.WRITER>();
|
||||
rw.X = "bar";
|
||||
cc.OutArgs = rw;
|
||||
|
||||
@ -196,7 +236,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
//client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestInterfaceImpl(counters);
|
||||
@ -227,7 +267,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
//client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestInterfaceImpl(counters);
|
||||
@ -261,17 +301,17 @@ namespace Capnp.Net.Runtime.Tests
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestInterfaceImpl(counters);
|
||||
using (var main = policy.Attach(client.GetMain<ITestInterface>()))
|
||||
server.Main = new TestMoreStuffImpl(counters);
|
||||
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.IsFalse(request1.IsCompleted);
|
||||
Assert.IsTrue(cc.CancelFromAlice.IsCancellationRequested);
|
||||
|
||||
cc.ForwardToBob();
|
||||
Assert.IsTrue(policy.Returns.ReceiveAsync().Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(cc.ReturnCanceled);
|
||||
Assert.IsTrue(policy.Returns.ReceiveAsync().Wait(MediumNonDbgTimeout), "must return");
|
||||
Assert.IsTrue(cc.ReturnCanceled, "must be canceled");
|
||||
cc.ReturnCanceled = false;
|
||||
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]
|
||||
public void InterceptClientSideRedirectCall()
|
||||
{
|
||||
@ -293,7 +368,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
//client.WhenConnected.Wait();
|
||||
|
||||
var counters = new 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]
|
||||
public void InterfaceAndMethodId()
|
||||
{
|
||||
@ -330,7 +443,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
//client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestInterfaceImpl(counters);
|
||||
@ -355,7 +468,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
//client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestTailCallerImpl(counters);
|
||||
@ -387,7 +500,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
//client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestMoreStuffImpl(counters);
|
||||
@ -427,7 +540,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
//client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = policy.Attach<ITestMoreStuff>(new TestMoreStuffImpl(counters));
|
||||
@ -484,7 +597,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
//client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestMoreStuffImpl(counters);
|
||||
@ -552,7 +665,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
//client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestMoreStuffImpl(counters);
|
||||
@ -592,7 +705,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
//client.WhenConnected.Wait();
|
||||
|
||||
server.Main = implAc;
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
253
Capnp.Net.Runtime.Tests/LocalRpc.cs
Normal file
253
Capnp.Net.Runtime.Tests/LocalRpc.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
[TestClass]
|
||||
[TestCategory("Coverage")]
|
||||
public class MessageBuilderTests
|
||||
{
|
||||
class Struct2D0P : SerializerState
|
||||
|
@ -420,18 +420,53 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
|
||||
|
||||
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)
|
||||
{
|
||||
Interlocked.Increment(ref _counters.CallCount);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
Assert.AreEqual(123u, i);
|
||||
Assert.IsTrue(j);
|
||||
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
|
||||
|
||||
#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_)
|
||||
{
|
||||
Interlocked.Increment(ref _counters.CallCount);
|
||||
Assert.AreEqual(234u, n);
|
||||
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)) });
|
||||
using (inCap)
|
||||
{
|
||||
Interlocked.Increment(ref _counters.CallCount);
|
||||
Assert.AreEqual(234u, n);
|
||||
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_)
|
||||
{
|
||||
Interlocked.Increment(ref _counters.CallCount);
|
||||
Assert.AreEqual(234u, n);
|
||||
var s = await inCap.Foo(123, true, cancellationToken_);
|
||||
Assert.AreEqual("foo", s);
|
||||
return ("bar", new TestPipeline.Box() { Cap = new TestExtendsImpl(_counters) });
|
||||
using (inCap)
|
||||
{
|
||||
Interlocked.Increment(ref _counters.CallCount);
|
||||
Assert.AreEqual(234u, n);
|
||||
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_)
|
||||
@ -506,24 +547,61 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
|
||||
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
|
||||
|
||||
#region TestCallOrder
|
||||
class TestCallOrderImpl : ITestCallOrder
|
||||
{
|
||||
readonly object _lock = new object();
|
||||
uint _counter;
|
||||
|
||||
ILogger Logger { get; } = Logging.CreateLogger<TestCallOrderImpl>();
|
||||
|
||||
public uint Count { get; set; }
|
||||
|
||||
public uint? CountToDispose { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
Assert.IsTrue(!CountToDispose.HasValue || Count == CountToDispose, "Must not dispose at this point");
|
||||
{
|
||||
Assert.IsTrue(!CountToDispose.HasValue || _counter == CountToDispose, $"Must not dispose at this point: {_counter} {Thread.CurrentThread.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -531,26 +609,24 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return Task.FromResult(Count++);
|
||||
Assert.AreEqual(expected, _counter);
|
||||
return Task.FromResult(_counter++);
|
||||
}
|
||||
}
|
||||
|
||||
public uint Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _counter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion TestCallOrder
|
||||
|
||||
#region TestTailCaller
|
||||
class TestTailCaller : ITestTailCaller
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public Task<TestTailCallee.TailResult> Foo(int i, ITestTailCallee callee, CancellationToken cancellationToken_)
|
||||
{
|
||||
return callee.Foo(i, "from TestTailCaller", cancellationToken_);
|
||||
}
|
||||
}
|
||||
#endregion TestTailCaller
|
||||
|
||||
#region TestTailCaller
|
||||
class TestTailCallerImpl : ITestTailCaller
|
||||
{
|
||||
@ -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
|
||||
|
||||
#region TestTailCallee
|
||||
@ -589,10 +766,15 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
IsDisposed = true;
|
||||
}
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
public Task<TestTailCallee.TailResult> Foo(int i, string t, CancellationToken cancellationToken_)
|
||||
{
|
||||
Assert.IsFalse(IsDisposed);
|
||||
|
||||
Interlocked.Increment(ref _counters.CallCount);
|
||||
|
||||
var result = new TestTailCallee.TailResult()
|
||||
@ -638,9 +820,12 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
|
||||
public async Task<string> CallFooWhenResolved(ITestInterface cap, CancellationToken cancellationToken_)
|
||||
{
|
||||
Interlocked.Increment(ref _counters.CallCount);
|
||||
await ((Proxy)cap).WhenResolved;
|
||||
string s = await cap.Foo(123, true, cancellationToken_);
|
||||
Assert.AreEqual("foo", s);
|
||||
using (cap)
|
||||
{
|
||||
await ((Proxy)cap).WhenResolved;
|
||||
string s = await cap.Foo(123, true, cancellationToken_);
|
||||
Assert.AreEqual("foo", s);
|
||||
}
|
||||
return "bar";
|
||||
}
|
||||
|
||||
@ -656,6 +841,7 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
|
||||
public void Dispose()
|
||||
{
|
||||
ClientToHold?.Dispose();
|
||||
ClientToHold = null;
|
||||
}
|
||||
|
||||
public Task<ITestCallOrder> Echo(ITestCallOrder cap, CancellationToken cancellationToken_)
|
||||
@ -687,7 +873,7 @@ namespace Capnp.Net.Runtime.Tests.GenImpls
|
||||
public Task<ITestInterface> GetHeld(CancellationToken cancellationToken_)
|
||||
{
|
||||
Interlocked.Increment(ref _counters.CallCount);
|
||||
return Task.FromResult(ClientToHold);
|
||||
return Task.FromResult(Proxy.Share(ClientToHold));
|
||||
}
|
||||
|
||||
public Task<ITestMoreStuff> GetNull(CancellationToken cancellationToken_)
|
||||
@ -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
|
||||
|
||||
#region TestHandle
|
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@ using Capnp.Rpc;
|
||||
|
||||
namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
class ProvidedCapabilityMock : Skeleton
|
||||
class ProvidedCapabilityMock : RefCountingSkeleton
|
||||
{
|
||||
readonly TaskCompletionSource<(ulong, ushort, DeserializerState, CancellationToken)>
|
||||
_call = new TaskCompletionSource<(ulong, ushort, DeserializerState, CancellationToken)>();
|
||||
|
@ -6,7 +6,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
class ProvidedCapabilityMultiCallMock : Skeleton
|
||||
class ProvidedCapabilityMultiCallMock : RefCountingSkeleton
|
||||
{
|
||||
readonly BufferBlock<TestCallContext> _ccs = new BufferBlock<TestCallContext>();
|
||||
|
||||
|
@ -1,9 +1,12 @@
|
||||
using Capnp.Rpc;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
[TestClass]
|
||||
[TestCategory("Coverage")]
|
||||
public class RpcSchemaTests
|
||||
{
|
||||
[TestMethod]
|
||||
@ -365,5 +368,406 @@ namespace Capnp.Net.Runtime.Tests
|
||||
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);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
[TestClass]
|
||||
[TestCategory("Coverage")]
|
||||
public class SegmentAllocatorTests
|
||||
{
|
||||
[TestMethod]
|
||||
|
1212
Capnp.Net.Runtime.Tests/SerializationTests.cs
Normal file
1212
Capnp.Net.Runtime.Tests/SerializationTests.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -7,43 +7,21 @@ using Capnp.Rpc;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
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
|
||||
{
|
||||
|
||||
[TestClass]
|
||||
public class TcpRpc
|
||||
[TestCategory("Coverage")]
|
||||
public class TcpRpc: TestBase
|
||||
{
|
||||
public static int TcpPort = 49153;
|
||||
bool ExpectingLogOutput { get; set; } = true;
|
||||
|
||||
(TcpRpcServer, TcpRpcClient) SetupClientServerPair()
|
||||
{
|
||||
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)]
|
||||
[TestMethod]
|
||||
public void CreateAndDispose()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
@ -54,7 +32,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void ConnectAndDispose()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
@ -64,8 +42,8 @@ namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
try
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
|
||||
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
|
||||
Assert.AreEqual(1, server.ConnectionCount);
|
||||
}
|
||||
catch (System.Exception e)
|
||||
@ -76,16 +54,17 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void ConnectAndBootstrap()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
@ -93,18 +72,18 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
|
||||
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
|
||||
Assert.AreEqual(1, server.ConnectionCount);
|
||||
|
||||
server.Main = new ProvidedCapabilityMock();
|
||||
var main = client.GetMain<BareProxy>();
|
||||
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()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
@ -112,17 +91,17 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
|
||||
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
|
||||
Assert.AreEqual(1, server.ConnectionCount);
|
||||
|
||||
var main = client.GetMain<BareProxy>();
|
||||
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()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
@ -130,20 +109,20 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
|
||||
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
|
||||
Assert.AreEqual(1, server.ConnectionCount);
|
||||
|
||||
var mock = new ProvidedCapabilityMock();
|
||||
server.Main = mock;
|
||||
var main = client.GetMain<BareProxy>();
|
||||
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout));
|
||||
Assert.IsTrue(main.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
var args = DynamicSerializerState.CreateForRpc();
|
||||
args.SetStruct(1, 0);
|
||||
args.WriteData(0, 123456);
|
||||
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;
|
||||
Assert.AreEqual<ulong>(0x1234567812345678, interfaceId);
|
||||
Assert.AreEqual<ushort>(0x3333, methodId);
|
||||
@ -155,7 +134,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
result.WriteData(0, 654321);
|
||||
mock.Return.SetResult(result);
|
||||
|
||||
Assert.IsTrue(answer.WhenReturned.Wait(MediumTimeout));
|
||||
Assert.IsTrue(answer.WhenReturned.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
var outresult = answer.WhenReturned.Result;
|
||||
Assert.AreEqual(ObjectKind.Struct, outresult.Kind);
|
||||
Assert.AreEqual(654321, outresult.ReadDataInt(0));
|
||||
@ -163,7 +142,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void CallCancelOnServer()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
@ -171,20 +150,20 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
|
||||
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
|
||||
Assert.AreEqual(1, server.ConnectionCount);
|
||||
|
||||
var mock = new ProvidedCapabilityMock();
|
||||
server.Main = mock;
|
||||
var main = client.GetMain<BareProxy>();
|
||||
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout));
|
||||
Assert.IsTrue(main.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
var args = DynamicSerializerState.CreateForRpc();
|
||||
args.SetStruct(1, 0);
|
||||
args.WriteData(0, 123456);
|
||||
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;
|
||||
Assert.AreEqual<ulong>(0x1234567812345678, interfaceId);
|
||||
Assert.AreEqual<ushort>(0x3333, methodId);
|
||||
@ -193,12 +172,12 @@ namespace Capnp.Net.Runtime.Tests
|
||||
|
||||
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()
|
||||
{
|
||||
ExpectingLogOutput = false;
|
||||
@ -210,22 +189,22 @@ namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
try
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
|
||||
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
|
||||
Assert.AreEqual(1, server.ConnectionCount);
|
||||
|
||||
var mock = new ProvidedCapabilityMock();
|
||||
server.Main = mock;
|
||||
var main = client.GetMain<BareProxy>();
|
||||
var resolving = main as IResolvingCapability;
|
||||
Assert.IsTrue(resolving.WhenResolved.Wait(MediumTimeout));
|
||||
Assert.IsTrue(resolving.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
var args = DynamicSerializerState.CreateForRpc();
|
||||
args.SetStruct(1, 0);
|
||||
args.WriteData(0, 123456);
|
||||
CancellationToken ctx;
|
||||
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;
|
||||
Assert.AreEqual<ulong>(0x1234567812345678, interfaceId);
|
||||
Assert.AreEqual<ushort>(0x3333, methodId);
|
||||
@ -234,7 +213,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
ctx = ct;
|
||||
}
|
||||
|
||||
Assert.IsTrue(SpinWait.SpinUntil(() => ctx.IsCancellationRequested, MediumTimeout));
|
||||
Assert.IsTrue(SpinWait.SpinUntil(() => ctx.IsCancellationRequested, MediumNonDbgTimeout));
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -243,7 +222,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void CallReturnAfterClientSideCancel()
|
||||
{
|
||||
ExpectingLogOutput = false;
|
||||
@ -254,14 +233,14 @@ namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
try
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
|
||||
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
|
||||
Assert.AreEqual(1, server.ConnectionCount);
|
||||
|
||||
var mock = new ProvidedCapabilityMock();
|
||||
server.Main = mock;
|
||||
var main = client.GetMain<BareProxy>();
|
||||
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout));
|
||||
Assert.IsTrue(main.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
var args = DynamicSerializerState.CreateForRpc();
|
||||
args.SetStruct(1, 0);
|
||||
args.WriteData(0, 123456);
|
||||
@ -269,7 +248,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
IPromisedAnswer answer;
|
||||
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;
|
||||
Assert.AreEqual<ulong>(0x1234567812345678, interfaceId);
|
||||
Assert.AreEqual<ushort>(0x3333, methodId);
|
||||
@ -278,7 +257,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
ctx = ct;
|
||||
}
|
||||
|
||||
Assert.IsTrue(SpinWait.SpinUntil(() => ctx.IsCancellationRequested, MediumTimeout));
|
||||
Assert.IsTrue(SpinWait.SpinUntil(() => ctx.IsCancellationRequested, MediumNonDbgTimeout));
|
||||
|
||||
var mbr = MessageBuilder.Create();
|
||||
mbr.InitCapTable();
|
||||
@ -289,7 +268,8 @@ namespace Capnp.Net.Runtime.Tests
|
||||
|
||||
// Even after the client cancelled the call, the server must still send
|
||||
// a response.
|
||||
Assert.IsTrue(answer.WhenReturned.ContinueWith(t => { }).Wait(MediumTimeout));
|
||||
async Task AwaitWhenReturned() => await answer.WhenReturned;
|
||||
Assert.IsTrue(AwaitWhenReturned().ContinueWith(t => { }).Wait(MediumNonDbgTimeout));
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -305,7 +285,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void CallServerSideException()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
@ -313,20 +293,20 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
|
||||
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
|
||||
Assert.AreEqual(1, server.ConnectionCount);
|
||||
|
||||
var mock = new ProvidedCapabilityMock();
|
||||
server.Main = mock;
|
||||
var main = client.GetMain<BareProxy>();
|
||||
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout));
|
||||
Assert.IsTrue(main.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
var args = DynamicSerializerState.CreateForRpc();
|
||||
args.SetStruct(1, 0);
|
||||
args.WriteData(0, 123456);
|
||||
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;
|
||||
Assert.AreEqual<ulong>(0x1234567812345678, interfaceId);
|
||||
Assert.AreEqual<ushort>(0x3333, methodId);
|
||||
@ -335,14 +315,14 @@ namespace Capnp.Net.Runtime.Tests
|
||||
|
||||
mock.Return.SetException(new MyTestException());
|
||||
|
||||
var exTask = Assert.ThrowsExceptionAsync<RpcException>(() => answer.WhenReturned);
|
||||
Assert.IsTrue(exTask.Wait(MediumTimeout));
|
||||
var exTask = Assert.ThrowsExceptionAsync<RpcException>(async () => await answer.WhenReturned);
|
||||
Assert.IsTrue(exTask.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(exTask.Result.Message.Contains(new MyTestException().Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void PipelineBeforeReturn()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
@ -350,20 +330,20 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
|
||||
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
|
||||
Assert.AreEqual(1, server.ConnectionCount);
|
||||
|
||||
var mock = new ProvidedCapabilityMock();
|
||||
server.Main = mock;
|
||||
var main = client.GetMain<BareProxy>();
|
||||
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout));
|
||||
Assert.IsTrue(main.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
var args = DynamicSerializerState.CreateForRpc();
|
||||
args.SetStruct(1, 0);
|
||||
args.WriteData(0, 123456);
|
||||
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) })));
|
||||
@ -385,15 +365,15 @@ namespace Capnp.Net.Runtime.Tests
|
||||
var result = DynamicSerializerState.CreateForRpc();
|
||||
result.SetStruct(1, 2);
|
||||
result.WriteData(0, 654321);
|
||||
uint id = result.ProvideCapability(mock2);
|
||||
uint id = result.ProvideCapability(mock2).Value;
|
||||
result.LinkToCapability(1, id);
|
||||
|
||||
mock.Return.SetResult(result);
|
||||
|
||||
Assert.IsTrue(answer.WhenReturned.Wait(MediumTimeout));
|
||||
Assert.IsTrue(answer.WhenReturned.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
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;
|
||||
Assert.AreEqual<ulong>(0x8765432187654321, interfaceId2);
|
||||
@ -406,7 +386,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
result2.WriteData(0, 222222);
|
||||
mock2.Return.SetResult(result2);
|
||||
|
||||
Assert.IsTrue(answer2.WhenReturned.Wait(MediumTimeout));
|
||||
Assert.IsTrue(answer2.WhenReturned.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
var outresult2 = answer2.WhenReturned.Result;
|
||||
Assert.AreEqual(ObjectKind.Struct, outresult2.Kind);
|
||||
Assert.AreEqual(222222, outresult2.ReadDataInt(0));
|
||||
@ -415,7 +395,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void PipelineAfterReturn()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
@ -423,20 +403,20 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
|
||||
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
|
||||
Assert.AreEqual(1, server.ConnectionCount);
|
||||
|
||||
var mock = new ProvidedCapabilityMock();
|
||||
server.Main = mock;
|
||||
var main = client.GetMain<BareProxy>();
|
||||
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout));
|
||||
Assert.IsTrue(main.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
var args = DynamicSerializerState.CreateForRpc();
|
||||
args.SetStruct(1, 0);
|
||||
args.WriteData(0, 123456);
|
||||
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;
|
||||
Assert.AreEqual<ulong>(0x1234567812345678, interfaceId);
|
||||
@ -449,7 +429,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
var result = DynamicSerializerState.CreateForRpc();
|
||||
result.SetStruct(1, 2);
|
||||
result.WriteData(0, 654321);
|
||||
uint id = result.ProvideCapability(mock2);
|
||||
uint id = result.ProvideCapability(mock2).Value;
|
||||
result.LinkToCapability(1, id);
|
||||
|
||||
mock.Return.SetResult(result);
|
||||
@ -466,8 +446,8 @@ namespace Capnp.Net.Runtime.Tests
|
||||
|
||||
using (var answer2 = pipelined.Call(0x8765432187654321, 0x4444, args2))
|
||||
{
|
||||
Assert.IsTrue(answer.WhenReturned.Wait(MediumTimeout));
|
||||
Assert.IsTrue(mock2.WhenCalled.Wait(MediumTimeout));
|
||||
Assert.IsTrue(answer.WhenReturned.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(mock2.WhenCalled.Wait(MediumNonDbgTimeout));
|
||||
|
||||
(var interfaceId2, var methodId2, var inargs2, var ct2) = mock2.WhenCalled.Result;
|
||||
Assert.AreEqual<ulong>(0x8765432187654321, interfaceId2);
|
||||
@ -480,7 +460,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
result2.WriteData(0, 222222);
|
||||
mock2.Return.SetResult(result2);
|
||||
|
||||
Assert.IsTrue(answer2.WhenReturned.Wait(MediumTimeout));
|
||||
Assert.IsTrue(answer2.WhenReturned.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
var outresult2 = answer2.WhenReturned.Result;
|
||||
Assert.AreEqual(ObjectKind.Struct, outresult2.Kind);
|
||||
Assert.AreEqual(222222, outresult2.ReadDataInt(0));
|
||||
@ -491,7 +471,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void PipelineMultiple()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
@ -499,20 +479,20 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
|
||||
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
|
||||
Assert.AreEqual(1, server.ConnectionCount);
|
||||
|
||||
var mock = new ProvidedCapabilityMock();
|
||||
server.Main = mock;
|
||||
var main = client.GetMain<BareProxy>();
|
||||
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout));
|
||||
Assert.IsTrue(main.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
var args = DynamicSerializerState.CreateForRpc();
|
||||
args.SetStruct(1, 0);
|
||||
args.WriteData(0, 123456);
|
||||
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) })));
|
||||
|
||||
@ -539,12 +519,12 @@ namespace Capnp.Net.Runtime.Tests
|
||||
var result = DynamicSerializerState.CreateForRpc();
|
||||
result.SetStruct(1, 2);
|
||||
result.WriteData(0, 654321);
|
||||
uint id = result.ProvideCapability(mock2);
|
||||
uint id = result.ProvideCapability(mock2).Value;
|
||||
result.LinkToCapability(1, id);
|
||||
|
||||
mock.Return.SetResult(result);
|
||||
|
||||
Assert.IsTrue(answer.WhenReturned.Wait(MediumTimeout));
|
||||
Assert.IsTrue(answer.WhenReturned.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsFalse(ct.IsCancellationRequested);
|
||||
|
||||
var args4 = DynamicSerializerState.CreateForRpc();
|
||||
@ -563,10 +543,10 @@ namespace Capnp.Net.Runtime.Tests
|
||||
var call4 = mock2.WhenCalled;
|
||||
var call5 = mock2.WhenCalled;
|
||||
|
||||
Assert.IsTrue(call2.Wait(MediumTimeout));
|
||||
Assert.IsTrue(call3.Wait(MediumTimeout));
|
||||
Assert.IsTrue(call4.Wait(MediumTimeout));
|
||||
Assert.IsTrue(call5.Wait(MediumTimeout));
|
||||
Assert.IsTrue(call2.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(call3.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(call4.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(call5.Wait(MediumNonDbgTimeout));
|
||||
|
||||
Assert.AreEqual<ulong>(0x1111111111111111, call2.Result.InterfaceId);
|
||||
Assert.AreEqual<ulong>(0x2222222222222222, call3.Result.InterfaceId);
|
||||
@ -593,10 +573,10 @@ namespace Capnp.Net.Runtime.Tests
|
||||
ret5.WriteData(0, -4);
|
||||
call5.Result.Result.SetResult(ret5);
|
||||
|
||||
Assert.IsTrue(answer2.WhenReturned.Wait(MediumTimeout));
|
||||
Assert.IsTrue(answer3.WhenReturned.Wait(MediumTimeout));
|
||||
Assert.IsTrue(answer4.WhenReturned.Wait(MediumTimeout));
|
||||
Assert.IsTrue(answer5.WhenReturned.Wait(MediumTimeout));
|
||||
Assert.IsTrue(answer2.WhenReturned.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(answer3.WhenReturned.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(answer4.WhenReturned.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(answer5.WhenReturned.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
|
||||
Assert.AreEqual(-1, answer2.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()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
@ -616,21 +596,21 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
|
||||
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
|
||||
Assert.AreEqual(1, server.ConnectionCount);
|
||||
|
||||
var mock = new ProvidedCapabilityMock();
|
||||
server.Main = mock;
|
||||
var main = client.GetMain<BareProxy>();
|
||||
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout));
|
||||
Assert.IsTrue(main.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
var args = DynamicSerializerState.CreateForRpc();
|
||||
args.SetStruct(1, 0);
|
||||
args.WriteData(0, 123456);
|
||||
BareProxy pipelined;
|
||||
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>(
|
||||
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()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
@ -663,21 +643,21 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumTimeout);
|
||||
Assert.IsTrue(client.WhenConnected.Wait(MediumNonDbgTimeout));
|
||||
SpinWait.SpinUntil(() => server.ConnectionCount > 0, MediumNonDbgTimeout);
|
||||
Assert.AreEqual(1, server.ConnectionCount);
|
||||
|
||||
var mock = new ProvidedCapabilityMock();
|
||||
server.Main = mock;
|
||||
var main = client.GetMain<BareProxy>();
|
||||
Assert.IsTrue(main.WhenResolved.Wait(MediumTimeout));
|
||||
Assert.IsTrue(main.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
var args = DynamicSerializerState.CreateForRpc();
|
||||
args.SetStruct(1, 0);
|
||||
args.WriteData(0, 123456);
|
||||
IPromisedAnswer answer2;
|
||||
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) })));
|
||||
|
||||
@ -695,7 +675,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
var tcs = new TaskCompletionSource<int>();
|
||||
using (ct.Register(() => tcs.SetResult(0)))
|
||||
{
|
||||
Assert.IsTrue(tcs.Task.Wait(MediumTimeout));
|
||||
Assert.IsTrue(tcs.Task.Wait(MediumNonDbgTimeout));
|
||||
}
|
||||
|
||||
var mock2 = new ProvidedCapabilityMock();
|
||||
@ -703,15 +683,178 @@ namespace Capnp.Net.Runtime.Tests
|
||||
var result = DynamicSerializerState.CreateForRpc();
|
||||
result.SetStruct(1, 2);
|
||||
result.WriteData(0, 654321);
|
||||
uint id = result.ProvideCapability(mock2);
|
||||
uint id = result.ProvideCapability(mock2).Value;
|
||||
result.LinkToCapability(1, id);
|
||||
|
||||
mock.Return.SetResult(result);
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Capnp.Net.Runtime.Tests.GenImpls;
|
||||
using Capnp.Net.Runtime.Tests.Util;
|
||||
using Capnp.Rpc;
|
||||
using Capnproto_test.Capnp.Test;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
@ -10,12 +11,14 @@ using System.Threading.Tasks;
|
||||
namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
[TestClass]
|
||||
[TestCategory("Coverage")]
|
||||
public class TcpRpcAdvancedStuff : TestBase
|
||||
{
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
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 tcs = new TaskCompletionSource<int>();
|
||||
@ -23,9 +26,9 @@ namespace Capnp.Net.Runtime.Tests
|
||||
|
||||
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>())
|
||||
{
|
||||
@ -50,19 +53,20 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void TwoClients()
|
||||
{
|
||||
using (var server = SetupServer())
|
||||
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
|
||||
using (var server = SetupServer(addr, port))
|
||||
{
|
||||
var counters = new Counters();
|
||||
server.Main = new TestMoreStuffImpl(counters);
|
||||
|
||||
using (var client1 = SetupClient())
|
||||
using (var client2 = SetupClient())
|
||||
using (var client1 = SetupClient(addr, port))
|
||||
using (var client2 = SetupClient(addr, port))
|
||||
{
|
||||
Assert.IsTrue(client1.WhenConnected.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(client2.WhenConnected.Wait(MediumNonDbgTimeout));
|
||||
//Assert.IsTrue(client1.WhenConnected.Wait(MediumNonDbgTimeout));
|
||||
//Assert.IsTrue(client2.WhenConnected.Wait(MediumNonDbgTimeout));
|
||||
|
||||
using (var main = client1.GetMain<ITestMoreStuff>())
|
||||
{
|
||||
@ -89,17 +93,18 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void ClosingServerWhileRequestingBootstrap()
|
||||
{
|
||||
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 tcs = new TaskCompletionSource<int>();
|
||||
server.Main = new TestInterfaceImpl(counters, tcs);
|
||||
|
||||
using (var client = SetupClient())
|
||||
using (var client = SetupClient(addr, port))
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
@ -111,7 +116,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
|
||||
try
|
||||
{
|
||||
Assert.IsTrue(((IResolvingCapability)main).WhenResolved.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(((IResolvingCapability)main).WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
}
|
||||
catch (AggregateException)
|
||||
{
|
||||
@ -124,14 +129,15 @@ namespace Capnp.Net.Runtime.Tests
|
||||
[TestMethod]
|
||||
public void InheritFromGenericInterface()
|
||||
{
|
||||
using (var server = SetupServer())
|
||||
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
|
||||
using (var server = SetupServer(addr, port))
|
||||
{
|
||||
var counters = new Counters();
|
||||
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>())
|
||||
{
|
||||
@ -147,13 +153,14 @@ namespace Capnp.Net.Runtime.Tests
|
||||
[TestMethod]
|
||||
public void Issue25()
|
||||
{
|
||||
using (var server = SetupServer())
|
||||
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
|
||||
using (var server = SetupServer(addr, port))
|
||||
{
|
||||
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>())
|
||||
{
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,16 @@
|
||||
using Capnp.FrameTracing;
|
||||
using Capnp.Net.Runtime.Tests.GenImpls;
|
||||
using Capnp.Net.Runtime.Tests.Util;
|
||||
using Capnp.Rpc;
|
||||
using Capnproto_test.Capnp.Test;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -36,7 +39,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
|
||||
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 config;
|
||||
@ -47,7 +50,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
#endif
|
||||
string path = Path.Combine(myPath, $@"..\..\..\..\{config}\CapnpCompatTest.exe");
|
||||
path = Path.GetFullPath(path);
|
||||
string arguments = $"{whichTest} 127.0.0.1:{TcpPort}";
|
||||
string arguments = $"{whichTest} {addr}:{port}";
|
||||
var startInfo = new ProcessStartInfo(path, arguments)
|
||||
{
|
||||
UseShellExecute = false,
|
||||
@ -63,21 +66,25 @@ namespace Capnp.Net.Runtime.Tests
|
||||
|
||||
try
|
||||
{
|
||||
_currentProcess.StandardError.ReadToEndAsync().ContinueWith(t => Console.Error.WriteLine(t.Result));
|
||||
var firstLine = _currentProcess.StandardOutput.ReadLineAsync();
|
||||
Assert.IsTrue(firstLine.Wait(MediumNonDbgTimeout), "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");
|
||||
try
|
||||
{
|
||||
_currentProcess.StandardError.ReadToEndAsync().ContinueWith(t => Console.Error.WriteLine(t.Result));
|
||||
var firstLine = _currentProcess.StandardOutput.ReadLineAsync();
|
||||
Assert.IsTrue(firstLine.Wait(MediumNonDbgTimeout), "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);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (AssertFailedException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
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++)
|
||||
{
|
||||
if (TryLaunchCompatTestProcess(whichTest, test))
|
||||
if (TryLaunchCompatTestProcess(addr, port, whichTest, test))
|
||||
return;
|
||||
|
||||
if (whichTest.StartsWith("server:"))
|
||||
PrepareNextTest();
|
||||
}
|
||||
|
||||
Assert.Fail("Problem after launching test process");
|
||||
@ -117,20 +122,15 @@ namespace Capnp.Net.Runtime.Tests
|
||||
Assert.AreEqual(expected, line.Result);
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void PrepareNextTest()
|
||||
{
|
||||
IncrementTcpPort();
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
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>())
|
||||
{
|
||||
@ -154,15 +154,16 @@ namespace Capnp.Net.Runtime.Tests
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void BasicServer()
|
||||
{
|
||||
using (var server = SetupServer())
|
||||
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
|
||||
using (var server = SetupServer(addr, port))
|
||||
{
|
||||
var counters = new 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 end");
|
||||
@ -171,16 +172,17 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
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));
|
||||
|
||||
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>())
|
||||
{
|
||||
@ -208,32 +210,35 @@ namespace Capnp.Net.Runtime.Tests
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void PipelineServer()
|
||||
{
|
||||
using (var server = SetupServer())
|
||||
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
|
||||
using (var server = SetupServer(addr, port))
|
||||
{
|
||||
var counters = new Counters();
|
||||
server.Main = new TestPipelineImpl(counters);
|
||||
|
||||
LaunchCompatTestProcess("client:Pipelining", stdout =>
|
||||
LaunchCompatTestProcess("client:Pipelining", addr, port, stdout =>
|
||||
{
|
||||
AssertOutput(stdout, "Pipelining test start");
|
||||
AssertOutput(stdout, "foo 123 1");
|
||||
AssertOutput(stdout, "~");
|
||||
AssertOutput(stdout, "Pipelining test end");
|
||||
Assert.AreEqual(3, counters.CallCount);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
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>())
|
||||
{
|
||||
@ -259,15 +264,16 @@ namespace Capnp.Net.Runtime.Tests
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void ReleaseServer()
|
||||
{
|
||||
using (var server = SetupServer())
|
||||
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
|
||||
using (var server = SetupServer(addr, port))
|
||||
{
|
||||
var counters = new Counters();
|
||||
server.Main = new TestMoreStuffImpl(counters);
|
||||
|
||||
LaunchCompatTestProcess("client:Release", stdout =>
|
||||
LaunchCompatTestProcess("client:Release", addr, port, stdout =>
|
||||
{
|
||||
AssertOutput(stdout, "Release test start");
|
||||
AssertOutput(stdout, "sync");
|
||||
@ -295,15 +301,16 @@ namespace Capnp.Net.Runtime.Tests
|
||||
// later on verify that the handle count is 0.
|
||||
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>())
|
||||
{
|
||||
((Proxy)main).WhenResolved.Wait(MediumNonDbgTimeout);
|
||||
((Proxy)main).WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout);
|
||||
|
||||
async Task VerifyOutput()
|
||||
{
|
||||
@ -342,17 +349,19 @@ namespace Capnp.Net.Runtime.Tests
|
||||
for (int i = 0; i < iterationCount; i++)
|
||||
{
|
||||
var task = main.GetHandle(default);
|
||||
taskList.Add(task.ContinueWith(t =>
|
||||
async Task TerminateAnswer()
|
||||
{
|
||||
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)
|
||||
@ -369,15 +378,16 @@ namespace Capnp.Net.Runtime.Tests
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void ReleaseOnCancelServer()
|
||||
{
|
||||
using (var server = SetupServer())
|
||||
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
|
||||
using (var server = SetupServer(addr, port))
|
||||
{
|
||||
var counters = new 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 end");
|
||||
@ -387,18 +397,20 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestCategory("Coverage")]
|
||||
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())
|
||||
{
|
||||
var tracer = new RpcFrameTracer(Console.Out);
|
||||
|
||||
client.AttachTracer(tracer);
|
||||
client.Connect("localhost", TcpPort);
|
||||
client.Connect(addr.ToString(), port);
|
||||
|
||||
client.WhenConnected.Wait();
|
||||
//client.WhenConnected.Wait();
|
||||
|
||||
using (var main = client.GetMain<ITestTailCaller>())
|
||||
{
|
||||
@ -406,20 +418,22 @@ namespace Capnp.Net.Runtime.Tests
|
||||
var callee = new TestTailCalleeImpl(calleeCallCount);
|
||||
|
||||
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.AreEqual(456u, promise.Result.I);
|
||||
Assert.AreEqual("from TestTailCaller", promise.Result.T);
|
||||
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);
|
||||
|
||||
AssertOutput(stdout, "foo");
|
||||
Assert.IsTrue(dependentCall0.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(dependentCall1.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(dependentCall2.Wait(MediumNonDbgTimeout));
|
||||
var dependentCall1 = c.GetCallSequence(1, 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));
|
||||
}
|
||||
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
|
||||
public void TestTailCallServer()
|
||||
{
|
||||
|
||||
using (var server = SetupServer())
|
||||
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
|
||||
using (var server = SetupServer(addr, port))
|
||||
{
|
||||
var tracer = new RpcFrameTracer(Console.Out);
|
||||
|
||||
@ -444,7 +458,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
var counters = new Counters();
|
||||
server.Main = new TestTailCallerImpl(counters);
|
||||
|
||||
LaunchCompatTestProcess("client:TailCall", stdout =>
|
||||
LaunchCompatTestProcess("client:TailCall", addr, port, stdout =>
|
||||
{
|
||||
AssertOutput(stdout, "TailCall test start");
|
||||
AssertOutput(stdout, "foo");
|
||||
@ -455,16 +469,17 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
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));
|
||||
|
||||
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>())
|
||||
{
|
||||
@ -485,15 +500,16 @@ namespace Capnp.Net.Runtime.Tests
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void CancelationClient()
|
||||
{
|
||||
using (var server = SetupServer())
|
||||
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
|
||||
using (var server = SetupServer(addr, port))
|
||||
{
|
||||
var counters = new Counters();
|
||||
server.Main = new TestMoreStuffImpl(counters);
|
||||
|
||||
LaunchCompatTestProcess("client:Cancelation", stdout =>
|
||||
LaunchCompatTestProcess("client:Cancelation", addr, port, stdout =>
|
||||
{
|
||||
AssertOutput(stdout, "Cancelation test start");
|
||||
AssertOutput(stdout, "~");
|
||||
@ -502,40 +518,43 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
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>())
|
||||
{
|
||||
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);
|
||||
AssertOutput(stdout, "callFoo");
|
||||
var request2 = main.CallFooWhenResolved(eager, default);
|
||||
AssertOutput(stdout, "callFooWhenResolved");
|
||||
var gcs = main.GetCallSequence(0, default);
|
||||
AssertOutput(stdout, "getCallSequence");
|
||||
Assert.IsTrue(gcs.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual(2u, gcs.Result);
|
||||
|
||||
var gcs = main.GetCallSequence(0, default);
|
||||
AssertOutput(stdout, "getCallSequence");
|
||||
Assert.IsTrue(gcs.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual(2u, gcs.Result);
|
||||
var chainedCallCount = new Counters();
|
||||
var tiimpl = new TestInterfaceImpl(chainedCallCount);
|
||||
tcs.SetResult(tiimpl);
|
||||
|
||||
var chainedCallCount = new Counters();
|
||||
var tiimpl = new TestInterfaceImpl(chainedCallCount);
|
||||
tcs.SetResult(tiimpl);
|
||||
Assert.IsTrue(request.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(request2.Wait(MediumNonDbgTimeout));
|
||||
|
||||
Assert.IsTrue(request.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(request2.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual("bar", request.Result);
|
||||
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");
|
||||
@ -544,38 +563,41 @@ namespace Capnp.Net.Runtime.Tests
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void PromiseResolveClient()
|
||||
{
|
||||
using (var server = SetupServer())
|
||||
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
|
||||
using (var server = SetupServer(addr, port))
|
||||
{
|
||||
var counters = new Counters();
|
||||
server.Main = new TestMoreStuffImpl(counters);
|
||||
|
||||
LaunchCompatTestProcess("client:PromiseResolve", stdout =>
|
||||
LaunchCompatTestProcess("client:PromiseResolve", addr, port, stdout =>
|
||||
{
|
||||
AssertOutput(stdout, "PromiseResolve test start");
|
||||
AssertOutput(stdout, "foo 123 1");
|
||||
AssertOutput(stdout, "foo 123 1");
|
||||
AssertOutput(stdout, "~");
|
||||
AssertOutput(stdout, "PromiseResolve test end");
|
||||
Assert.AreEqual(3, counters.CallCount);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void RetainAndReleaseServer()
|
||||
{
|
||||
var destructionPromise = new TaskCompletionSource<int>();
|
||||
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));
|
||||
|
||||
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>())
|
||||
{
|
||||
@ -641,15 +663,16 @@ namespace Capnp.Net.Runtime.Tests
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void RetainAndReleaseClient()
|
||||
{
|
||||
using (var server = SetupServer())
|
||||
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
|
||||
using (var server = SetupServer(addr, port))
|
||||
{
|
||||
var counters = new Counters();
|
||||
server.Main = new TestMoreStuffImpl(counters);
|
||||
|
||||
LaunchCompatTestProcess("client:RetainAndRelease", stdout =>
|
||||
LaunchCompatTestProcess("client:RetainAndRelease", addr, port, stdout =>
|
||||
{
|
||||
AssertOutput(stdout, "RetainAndRelease test start");
|
||||
AssertOutput(stdout, "foo 123 1");
|
||||
@ -661,14 +684,15 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
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 destructionTask = destructionPromise.Task;
|
||||
@ -702,15 +726,16 @@ namespace Capnp.Net.Runtime.Tests
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void CancelClient()
|
||||
{
|
||||
using (var server = SetupServer())
|
||||
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
|
||||
using (var server = SetupServer(addr, port))
|
||||
{
|
||||
var counters = new Counters();
|
||||
server.Main = new TestMoreStuffImpl(counters);
|
||||
|
||||
LaunchCompatTestProcess("client:Cancel", stdout =>
|
||||
LaunchCompatTestProcess("client:Cancel", addr, port, stdout =>
|
||||
{
|
||||
AssertOutput(stdout, "Cancel test start");
|
||||
AssertOutput(stdout, "~");
|
||||
@ -719,14 +744,15 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
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>())
|
||||
{
|
||||
@ -763,15 +789,16 @@ namespace Capnp.Net.Runtime.Tests
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void SendTwiceClient()
|
||||
{
|
||||
using (var server = SetupServer())
|
||||
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
|
||||
using (var server = SetupServer(addr, port))
|
||||
{
|
||||
var counters = new Counters();
|
||||
server.Main = new TestMoreStuffImpl(counters);
|
||||
|
||||
LaunchCompatTestProcess("client:SendTwice", stdout =>
|
||||
LaunchCompatTestProcess("client:SendTwice", addr, port, stdout =>
|
||||
{
|
||||
AssertOutput(stdout, "SendTwice test start");
|
||||
AssertOutput(stdout, "foo 123 1");
|
||||
@ -783,41 +810,21 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void EmbargoServer()
|
||||
{
|
||||
LaunchCompatTestProcess("server:MoreStuff", stdout =>
|
||||
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
|
||||
LaunchCompatTestProcess("server:MoreStuff", addr, port, stdout =>
|
||||
{
|
||||
int retry = 0;
|
||||
|
||||
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 wrapped = client.GetMain<ITestMoreStuff>())
|
||||
{
|
||||
var resolving = main as IResolvingCapability;
|
||||
|
||||
bool success;
|
||||
|
||||
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 unwrap = wrapped.Unwrap();
|
||||
Assert.IsTrue(unwrap.Wait(MediumNonDbgTimeout));
|
||||
var main = unwrap.Result;
|
||||
|
||||
var cap = new TestCallOrderImpl();
|
||||
cap.CountToDispose = 6;
|
||||
@ -868,17 +875,18 @@ namespace Capnp.Net.Runtime.Tests
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void EmbargoServer2()
|
||||
{
|
||||
LaunchCompatTestProcess("server:MoreStuff", stdout =>
|
||||
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
|
||||
LaunchCompatTestProcess("server:MoreStuff", addr, port, stdout =>
|
||||
{
|
||||
int retry = 0;
|
||||
|
||||
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>())
|
||||
{
|
||||
@ -888,7 +896,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
|
||||
try
|
||||
{
|
||||
success = resolving.WhenResolved.Wait(MediumNonDbgTimeout);
|
||||
success = resolving.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -951,15 +959,16 @@ namespace Capnp.Net.Runtime.Tests
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void EmbargoClient()
|
||||
{
|
||||
using (var server = SetupServer())
|
||||
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
|
||||
using (var server = SetupServer(addr, port))
|
||||
{
|
||||
var counters = new 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 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>())
|
||||
{
|
||||
var resolving = main as IResolvingCapability;
|
||||
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(resolving.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
|
||||
var cap = new TaskCompletionSource<ITestCallOrder>();
|
||||
|
||||
@ -1021,33 +1030,36 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
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)]
|
||||
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()
|
||||
{
|
||||
using (var server = SetupServer())
|
||||
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
|
||||
using (var server = SetupServer(addr, port))
|
||||
{
|
||||
var counters = new 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 end");
|
||||
@ -1055,17 +1067,18 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void EmbargoNullServer()
|
||||
{
|
||||
LaunchCompatTestProcess("server:MoreStuff", stdout =>
|
||||
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
|
||||
LaunchCompatTestProcess("server:MoreStuff", addr, port, stdout =>
|
||||
{
|
||||
int retry = 0;
|
||||
|
||||
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>())
|
||||
{
|
||||
@ -1075,7 +1088,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
|
||||
try
|
||||
{
|
||||
success = resolving.WhenResolved.Wait(MediumNonDbgTimeout);
|
||||
success = resolving.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -1115,15 +1128,16 @@ namespace Capnp.Net.Runtime.Tests
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void EmbargoNullClient()
|
||||
{
|
||||
using (var server = SetupServer())
|
||||
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
|
||||
using (var server = SetupServer(addr, port))
|
||||
{
|
||||
var counters = new 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 end");
|
||||
@ -1131,19 +1145,20 @@ namespace Capnp.Net.Runtime.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
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>())
|
||||
{
|
||||
var resolving = main as IResolvingCapability;
|
||||
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(resolving.WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout));
|
||||
|
||||
var tcs = new TaskCompletionSource<ITestInterface>();
|
||||
|
||||
@ -1168,15 +1183,16 @@ namespace Capnp.Net.Runtime.Tests
|
||||
});
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
[TestMethod]
|
||||
public void CallBrokenPromiseClient()
|
||||
{
|
||||
using (var server = SetupServer())
|
||||
(var addr, int port) = TcpManager.Instance.GetLocalAddressAndPort();
|
||||
using (var server = SetupServer(addr, port))
|
||||
{
|
||||
var counters = new 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 end");
|
||||
|
@ -12,105 +12,27 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
|
||||
[TestClass]
|
||||
[TestCategory("Coverage")]
|
||||
public class TcpRpcPorted: TestBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void Basic()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestInterfaceImpl(counters);
|
||||
using (var main = client.GetMain<ITestInterface>())
|
||||
{
|
||||
var request1 = main.Foo(123, true, default);
|
||||
var request3 = Assert.ThrowsExceptionAsync<RpcException>(() => main.Bar(default));
|
||||
var s = new TestAllTypes();
|
||||
Common.InitTestMessage(s);
|
||||
var request2 = main.Baz(s, default);
|
||||
|
||||
Assert.IsTrue(request1.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(request2.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(request3.Wait(MediumNonDbgTimeout));
|
||||
|
||||
Assert.AreEqual("foo", request1.Result);
|
||||
Assert.AreEqual(2, counters.CallCount);
|
||||
}
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.Basic);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Pipeline()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestPipelineImpl(counters);
|
||||
using (var main = client.GetMain<ITestPipeline>())
|
||||
{
|
||||
var chainedCallCount = new Counters();
|
||||
var request = main.GetCap(234, new TestInterfaceImpl(chainedCallCount), default);
|
||||
using (var outBox = request.OutBox_Cap())
|
||||
{
|
||||
var pipelineRequest = outBox.Foo(321, false, default);
|
||||
var pipelineRequest2 = ((Proxy)outBox).Cast<ITestExtends>(false).Grault(default);
|
||||
|
||||
Assert.IsTrue(pipelineRequest.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(pipelineRequest2.Wait(MediumNonDbgTimeout));
|
||||
|
||||
Assert.AreEqual("bar", pipelineRequest.Result);
|
||||
Common.CheckTestMessage(pipelineRequest2.Result);
|
||||
|
||||
Assert.AreEqual(3, counters.CallCount);
|
||||
Assert.AreEqual(1, chainedCallCount.CallCount);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
NewLocalhostTcpTestbed(TcpRpcTestOptions.ClientTracer).RunTest(Testsuite.Pipeline);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Release()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestMoreStuffImpl(counters);
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
{
|
||||
var task1 = main.GetHandle(default);
|
||||
var task2 = main.GetHandle(default);
|
||||
Assert.IsTrue(task1.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(task2.Wait(MediumNonDbgTimeout));
|
||||
|
||||
Assert.AreEqual(2, counters.HandleCount);
|
||||
|
||||
task1.Result.Dispose();
|
||||
|
||||
Assert.IsTrue(SpinWait.SpinUntil(() => counters.HandleCount == 1, MediumNonDbgTimeout));
|
||||
|
||||
task2.Result.Dispose();
|
||||
|
||||
Assert.IsTrue(SpinWait.SpinUntil(() => counters.HandleCount == 0, MediumNonDbgTimeout));
|
||||
}
|
||||
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.Release);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@ -121,13 +43,13 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
//client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestMoreStuffImpl(counters);
|
||||
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
|
||||
// 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]
|
||||
public void TestTailCall()
|
||||
public void TailCall()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestTailCallerImpl(counters);
|
||||
using (var main = client.GetMain<ITestTailCaller>())
|
||||
{
|
||||
var calleeCallCount = new Counters();
|
||||
var callee = new TestTailCalleeImpl(calleeCallCount);
|
||||
|
||||
var promise = main.Foo(456, callee, default);
|
||||
var dependentCall0 = promise.C().GetCallSequence(0, default);
|
||||
|
||||
Assert.IsTrue(promise.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual(456u, promise.Result.I);
|
||||
Assert.AreEqual("from TestTailCaller", promise.Result.T);
|
||||
|
||||
var dependentCall1 = promise.C().GetCallSequence(0, default);
|
||||
var dependentCall2 = promise.C().GetCallSequence(0, default);
|
||||
|
||||
Assert.IsTrue(dependentCall0.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(dependentCall1.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(dependentCall2.Wait(MediumNonDbgTimeout));
|
||||
|
||||
Assert.AreEqual(1, counters.CallCount);
|
||||
Assert.AreEqual(1, calleeCallCount.CallCount);
|
||||
}
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.TailCall);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Cancelation()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestMoreStuffImpl(counters);
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
{
|
||||
var destroyed = new TaskCompletionSource<int>();
|
||||
var impl = new TestInterfaceImpl(counters, destroyed);
|
||||
var cts = new CancellationTokenSource();
|
||||
var cancelTask = main.ExpectCancel(impl, cts.Token);
|
||||
|
||||
Assert.IsFalse(SpinWait.SpinUntil(() => destroyed.Task.IsCompleted || cancelTask.IsCompleted, ShortTimeout));
|
||||
|
||||
cts.Cancel();
|
||||
|
||||
Assert.IsTrue(destroyed.Task.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsFalse(cancelTask.IsCompleted && !cancelTask.IsCanceled);
|
||||
}
|
||||
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.Cancelation);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PromiseResolve()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
server.Main = impl;
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
{
|
||||
var tcs = new TaskCompletionSource<ITestInterface>();
|
||||
var eager = tcs.Task.Eager(true);
|
||||
|
||||
var request = main.CallFoo(eager, default);
|
||||
var request2 = main.CallFooWhenResolved(eager, default);
|
||||
|
||||
var gcs = main.GetCallSequence(0, default);
|
||||
Assert.IsTrue(gcs.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual(2u, gcs.Result);
|
||||
Assert.AreEqual(3, counters.CallCount);
|
||||
|
||||
var chainedCallCount = new Counters();
|
||||
var tiimpl = new TestInterfaceImpl(chainedCallCount);
|
||||
tcs.SetResult(tiimpl);
|
||||
|
||||
Assert.IsTrue(request.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(request2.Wait(MediumNonDbgTimeout));
|
||||
|
||||
Assert.AreEqual("bar", request.Result);
|
||||
Assert.AreEqual("bar", request2.Result);
|
||||
Assert.AreEqual(3, counters.CallCount);
|
||||
Assert.AreEqual(2, chainedCallCount.CallCount);
|
||||
}
|
||||
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.PromiseResolve);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RetainAndRelease()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var destructionPromise = new TaskCompletionSource<int>();
|
||||
var destructionTask = destructionPromise.Task;
|
||||
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
server.Main = impl;
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
{
|
||||
var holdTask = main.Hold(new TestInterfaceImpl(new Counters(), destructionPromise), default);
|
||||
Assert.IsTrue(holdTask.Wait(MediumNonDbgTimeout));
|
||||
|
||||
var cstask = main.GetCallSequence(0, default);
|
||||
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual(1u, cstask.Result);
|
||||
|
||||
Assert.IsFalse(destructionTask.IsCompleted);
|
||||
|
||||
var htask = main.CallHeld(default);
|
||||
Assert.IsTrue(htask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual("bar", htask.Result);
|
||||
|
||||
var gtask = main.GetHeld(default);
|
||||
Assert.IsTrue(gtask.Wait(MediumNonDbgTimeout));
|
||||
// We can get the cap back from it.
|
||||
using (var cap = gtask.Result)
|
||||
{
|
||||
// Wait for balanced state
|
||||
WaitClientServerIdle(server, client);
|
||||
|
||||
// And call it, without any network communications.
|
||||
long oldSendCount = client.SendCount;
|
||||
var ftask = cap.Foo(123, true, default);
|
||||
Assert.IsTrue(ftask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual("foo", ftask.Result);
|
||||
Assert.AreEqual(oldSendCount, client.SendCount);
|
||||
|
||||
// We can send another copy of the same cap to another method, and it works.
|
||||
var ctask = main.CallFoo(cap, default);
|
||||
Assert.IsTrue(ctask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual("bar", ctask.Result);
|
||||
|
||||
// Give some time to settle.
|
||||
cstask = main.GetCallSequence(0, default);
|
||||
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual(5u, cstask.Result);
|
||||
cstask = main.GetCallSequence(0, default);
|
||||
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual(6u, cstask.Result);
|
||||
cstask = main.GetCallSequence(0, default);
|
||||
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual(7u, cstask.Result);
|
||||
|
||||
// Can't be destroyed, we haven't released it.
|
||||
Assert.IsFalse(destructionTask.IsCompleted);
|
||||
}
|
||||
|
||||
// In deviation from original test, we have null the held capability on the main interface.
|
||||
// This is because the main interface is the bootstrap capability and, as such, won't be disposed
|
||||
// after disconnect.
|
||||
var holdNullTask = main.Hold(null, default);
|
||||
Assert.IsTrue(holdNullTask.Wait(MediumNonDbgTimeout));
|
||||
}
|
||||
|
||||
Assert.IsTrue(destructionTask.Wait(MediumNonDbgTimeout));
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.RetainAndRelease);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Cancel()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var destructionPromise = new TaskCompletionSource<int>();
|
||||
var destructionTask = destructionPromise.Task;
|
||||
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
server.Main = impl;
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
using (var cts = new CancellationTokenSource())
|
||||
{
|
||||
var ntask = main.NeverReturn(new TestInterfaceImpl(counters, destructionPromise), cts.Token);
|
||||
|
||||
// Allow some time to settle.
|
||||
var cstask = main.GetCallSequence(0, default);
|
||||
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual(1u, cstask.Result);
|
||||
cstask = main.GetCallSequence(0, default);
|
||||
Assert.IsTrue(cstask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual(2u, cstask.Result);
|
||||
|
||||
// The cap shouldn't have been destroyed yet because the call never returned.
|
||||
Assert.IsFalse(destructionTask.IsCompleted);
|
||||
|
||||
// There will be no automatic cancellation just because "ntask" goes of of scope or
|
||||
// because the Proxy is disposed. Even ntask.Dispose() would not cancel the request.
|
||||
// In .NET this needs to be done explicitly.
|
||||
cts.Cancel();
|
||||
}
|
||||
|
||||
// Now the cap should be released.
|
||||
Assert.IsTrue(destructionTask.Wait(MediumNonDbgTimeout));
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.Cancel);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SendTwice()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var destructionPromise = new TaskCompletionSource<int>();
|
||||
var destructionTask = destructionPromise.Task;
|
||||
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
server.Main = impl;
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
{
|
||||
var cap = new TestInterfaceImpl(new Counters(), destructionPromise);
|
||||
|
||||
Task<string> ftask1, ftask2;
|
||||
|
||||
using (Skeleton.Claim(cap))
|
||||
{
|
||||
var ftask = main.CallFoo(cap, default);
|
||||
Assert.IsTrue(ftask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual("bar", ftask.Result);
|
||||
|
||||
var ctask = main.GetCallSequence(0, default);
|
||||
Assert.IsTrue(ctask.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual(1u, ctask.Result);
|
||||
|
||||
ftask1 = main.CallFoo(cap, default);
|
||||
ftask2 = main.CallFoo(cap, default);
|
||||
}
|
||||
|
||||
Assert.IsTrue(ftask1.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual("bar", ftask1.Result);
|
||||
Assert.IsTrue(ftask2.Wait(MediumNonDbgTimeout));
|
||||
Assert.AreEqual("bar", ftask2.Result);
|
||||
|
||||
// Now the cap should be released.
|
||||
Assert.IsTrue(destructionTask.Wait(MediumNonDbgTimeout));
|
||||
}
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.SendTwice);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Embargo()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
server.Main = impl;
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
{
|
||||
var resolving = main as IResolvingCapability;
|
||||
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout));
|
||||
|
||||
var cap = new TestCallOrderImpl();
|
||||
cap.CountToDispose = 6;
|
||||
|
||||
var earlyCall = main.GetCallSequence(0, default);
|
||||
|
||||
var echo = main.Echo(cap, default);
|
||||
|
||||
using (var pipeline = echo.Eager())
|
||||
{
|
||||
var call0 = pipeline.GetCallSequence(0, default);
|
||||
var call1 = pipeline.GetCallSequence(1, default);
|
||||
|
||||
Assert.IsTrue(earlyCall.Wait(MediumNonDbgTimeout));
|
||||
|
||||
impl.EnableEcho();
|
||||
|
||||
var call2 = pipeline.GetCallSequence(2, default);
|
||||
|
||||
Assert.IsTrue(echo.Wait(MediumNonDbgTimeout));
|
||||
using (var resolved = echo.Result)
|
||||
{
|
||||
var call3 = pipeline.GetCallSequence(3, default);
|
||||
var call4 = pipeline.GetCallSequence(4, default);
|
||||
var call5 = pipeline.GetCallSequence(5, default);
|
||||
|
||||
try
|
||||
{
|
||||
bool flag = call0.Wait(MediumNonDbgTimeout);
|
||||
Assert.IsTrue(flag);
|
||||
}
|
||||
catch (AggregateException exception) when (exception.InnerException is RpcException rpcException && rpcException.Message == "Cannot access a disposed object.")
|
||||
{
|
||||
Console.WriteLine($"Oops, object disposed. Counter = {cap.Count}, tx count = {client.SendCount}, rx count = {client.RecvCount}");
|
||||
throw;
|
||||
}
|
||||
|
||||
Assert.IsTrue(call1.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(call2.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(call3.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(call4.Wait(MediumNonDbgTimeout));
|
||||
Assert.IsTrue(call5.Wait(MediumNonDbgTimeout));
|
||||
|
||||
Assert.AreEqual(0u, call0.Result);
|
||||
Assert.AreEqual(1u, call1.Result);
|
||||
Assert.AreEqual(2u, call2.Result);
|
||||
Assert.AreEqual(3u, call3.Result);
|
||||
Assert.AreEqual(4u, call4.Result);
|
||||
Assert.AreEqual(5u, call5.Result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.EmbargoOnPromisedAnswer);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EmbargoError()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
server.Main = impl;
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
{
|
||||
var resolving = main as IResolvingCapability;
|
||||
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout));
|
||||
|
||||
var cap = new TaskCompletionSource<ITestCallOrder>();
|
||||
|
||||
var earlyCall = main.GetCallSequence(0, default);
|
||||
|
||||
var echo = main.Echo(cap.Task.Eager(true), default);
|
||||
|
||||
var pipeline = echo.Eager();
|
||||
|
||||
var call0 = pipeline.GetCallSequence(0, default);
|
||||
var call1 = pipeline.GetCallSequence(1, default);
|
||||
|
||||
Assert.IsTrue(earlyCall.Wait(MediumNonDbgTimeout));
|
||||
|
||||
impl.EnableEcho();
|
||||
|
||||
var call2 = pipeline.GetCallSequence(2, default);
|
||||
|
||||
Assert.IsTrue(echo.Wait(MediumNonDbgTimeout));
|
||||
var resolved = echo.Result;
|
||||
|
||||
var call3 = pipeline.GetCallSequence(3, default);
|
||||
var call4 = pipeline.GetCallSequence(4, default);
|
||||
var call5 = pipeline.GetCallSequence(5, default);
|
||||
|
||||
cap.SetException(new InvalidOperationException("I'm annoying"));
|
||||
|
||||
ExpectPromiseThrows(call0);
|
||||
ExpectPromiseThrows(call1);
|
||||
ExpectPromiseThrows(call2);
|
||||
ExpectPromiseThrows(call3);
|
||||
ExpectPromiseThrows(call4);
|
||||
ExpectPromiseThrows(call5);
|
||||
|
||||
// Verify that we're still connected (there were no protocol errors).
|
||||
Assert.IsTrue(main.GetCallSequence(1, default).Wait(MediumNonDbgTimeout));
|
||||
}
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.EmbargoError);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EmbargoNull()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
server.Main = impl;
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
{
|
||||
var resolving = main as IResolvingCapability;
|
||||
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout));
|
||||
|
||||
var promise = main.GetNull(default);
|
||||
|
||||
var cap = promise.Eager();
|
||||
|
||||
var call0 = cap.GetCallSequence(0, default);
|
||||
|
||||
Assert.IsTrue(promise.Wait(MediumNonDbgTimeout));
|
||||
|
||||
var call1 = cap.GetCallSequence(1, default);
|
||||
|
||||
ExpectPromiseThrows(call0);
|
||||
ExpectPromiseThrows(call1);
|
||||
|
||||
// Verify that we're still connected (there were no protocol errors).
|
||||
Assert.IsTrue(main.GetCallSequence(1, default).Wait(MediumNonDbgTimeout));
|
||||
}
|
||||
}
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.EmbargoNull);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CallBrokenPromise()
|
||||
{
|
||||
NewLocalhostTcpTestbed().RunTest(Testsuite.CallBrokenPromise);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void BootstrapReuse()
|
||||
{
|
||||
(var server, var client) = SetupClientServerPair();
|
||||
|
||||
var counters = new Counters();
|
||||
var impl = new TestInterfaceImpl(counters);
|
||||
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
//client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
server.Main = impl;
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
var resolving = main as IResolvingCapability;
|
||||
Assert.IsTrue(resolving.WhenResolved.Wait(MediumNonDbgTimeout));
|
||||
|
||||
var tcs = new TaskCompletionSource<ITestInterface>();
|
||||
|
||||
var req = main.Hold(tcs.Task.Eager(true), default);
|
||||
Assert.IsTrue(req.Wait(MediumNonDbgTimeout));
|
||||
|
||||
var req2 = main.CallHeld(default);
|
||||
|
||||
Assert.IsFalse(req2.Wait(ShortTimeout));
|
||||
|
||||
tcs.SetException(new InvalidOperationException("I'm a promise-breaker!"));
|
||||
|
||||
ExpectPromiseThrows(req2);
|
||||
|
||||
// Verify that we're still connected (there were no protocol errors).
|
||||
Assert.IsTrue(main.GetCallSequence(1, default).Wait(MediumNonDbgTimeout));
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
{
|
||||
((Proxy)main).WhenResolved.WrappedTask.Wait(MediumNonDbgTimeout);
|
||||
}
|
||||
Assert.IsFalse(impl.IsDisposed);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Capnp.Net.Runtime.Tests.GenImpls;
|
||||
using Capnp.Net.Runtime.Tests.Util;
|
||||
using Capnp.Rpc;
|
||||
using Capnproto_test.Capnp.Test;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -6,6 +7,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
@ -19,7 +21,6 @@ namespace Capnp.Net.Runtime.Tests
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Logger.LogTrace("Repetition {0}", i);
|
||||
IncrementTcpPort();
|
||||
action();
|
||||
}
|
||||
}
|
||||
@ -34,7 +35,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (server)
|
||||
using (client)
|
||||
{
|
||||
client.WhenConnected.Wait();
|
||||
//client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
var impl = new TestMoreStuffImpl(counters);
|
||||
@ -42,7 +43,7 @@ namespace Capnp.Net.Runtime.Tests
|
||||
using (var main = client.GetMain<ITestMoreStuff>())
|
||||
{
|
||||
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()
|
||||
{
|
||||
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();
|
||||
Repeat(100, t2.EmbargoServer);
|
||||
Repeat(20, t2.EmbargoServer);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@ -94,14 +102,15 @@ namespace Capnp.Net.Runtime.Tests
|
||||
[TestMethod]
|
||||
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())
|
||||
{
|
||||
server.InjectMidlayer(s => new ScatteringStream(s, 7));
|
||||
client.InjectMidlayer(s => new ScatteringStream(s, 10));
|
||||
client.Connect("localhost", TcpPort);
|
||||
client.WhenConnected.Wait();
|
||||
client.Connect(addr.ToString(), port);
|
||||
//client.WhenConnected.Wait();
|
||||
|
||||
var counters = new Counters();
|
||||
server.Main = new TestInterfaceImpl(counters);
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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();
|
||||
// }
|
||||
}
|
||||
|
870
Capnp.Net.Runtime.Tests/Testsuite.cs
Normal file
870
Capnp.Net.Runtime.Tests/Testsuite.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
98
Capnp.Net.Runtime.Tests/Util/DecisionTree.cs
Normal file
98
Capnp.Net.Runtime.Tests/Util/DecisionTree.cs
Normal file
@ -0,0 +1,98 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
public class DecisionTree
|
||||
{
|
||||
readonly ILogger Logger = Logging.CreateLogger<DecisionTree>();
|
||||
|
||||
readonly List<bool> _decisions = new List<bool>();
|
||||
readonly Stack<int> _freezeStack = new Stack<int>();
|
||||
int _pos, _freezeCounter;
|
||||
|
||||
public DecisionTree()
|
||||
{
|
||||
_freezeStack.Push(0);
|
||||
}
|
||||
|
||||
public DecisionTree(params bool[] initialDecisions): this()
|
||||
{
|
||||
_decisions.AddRange(initialDecisions);
|
||||
}
|
||||
|
||||
public bool MakeDecision()
|
||||
{
|
||||
if (_pos >= _decisions.Count)
|
||||
{
|
||||
_decisions.Add(false);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return _decisions[_pos++];
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
Logger.LogError($"WTF?! {_pos - 1}, {_decisions.Count}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void Freeze()
|
||||
{
|
||||
_freezeStack.Push(_pos);
|
||||
}
|
||||
|
||||
public bool NextRound()
|
||||
{
|
||||
_pos = 0;
|
||||
|
||||
for (int i = 0; i < _freezeCounter; i++)
|
||||
_freezeStack.Pop();
|
||||
|
||||
while (_freezeStack.Count > 1)
|
||||
{
|
||||
int end = _freezeStack.Pop();
|
||||
int begin = _freezeStack.Peek();
|
||||
|
||||
for (int i = end - 1; i >= begin; i--)
|
||||
{
|
||||
if (_decisions[i] == false)
|
||||
{
|
||||
_decisions[i] = true;
|
||||
_freezeStack.Clear();
|
||||
_freezeStack.Push(0);
|
||||
return true;
|
||||
}
|
||||
//else
|
||||
//{
|
||||
// _decisions.RemoveAt(i);
|
||||
//}
|
||||
}
|
||||
|
||||
++_freezeCounter;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "[" + string.Join("|", _decisions) + "]";
|
||||
}
|
||||
|
||||
public void Iterate(Action testMethod)
|
||||
{
|
||||
Logger.LogInformation("Starting decision-tree based combinatorial test");
|
||||
int iter = 0;
|
||||
do
|
||||
{
|
||||
Logger.LogInformation($"Iteration {iter}: pattern {ToString()}");
|
||||
testMethod();
|
||||
++iter;
|
||||
} while (NextRound());
|
||||
}
|
||||
}
|
||||
}
|
50
Capnp.Net.Runtime.Tests/Util/FluctStream.cs
Normal file
50
Capnp.Net.Runtime.Tests/Util/FluctStream.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Capnp.Net.Runtime.Tests
|
32
Capnp.Net.Runtime.Tests/Util/TcpManager.cs
Normal file
32
Capnp.Net.Runtime.Tests/Util/TcpManager.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
485
Capnp.Net.Runtime.Tests/Util/TestBase.cs
Normal file
485
Capnp.Net.Runtime.Tests/Util/TestBase.cs
Normal 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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ using System;
|
||||
namespace Capnp.Net.Runtime.Tests
|
||||
{
|
||||
[TestClass]
|
||||
[TestCategory("Coverage")]
|
||||
public class WirePointerTests
|
||||
{
|
||||
[TestMethod]
|
||||
|
3
Capnp.Net.Runtime/Assembly.cs
Normal file
3
Capnp.Net.Runtime/Assembly.cs
Normal file
@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Capnp.Net.Runtime.Tests")]
|
@ -22,14 +22,18 @@
|
||||
<PackageTags>capnp "Cap'n Proto" RPC serialization cerealization</PackageTags>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<DefineConstants>TRACE;DEBUG</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<DefineConstants>DebugCapabilityLifecycle</DefineConstants>
|
||||
<DefineConstants></DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.0|AnyCPU'">
|
||||
@ -39,6 +43,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -36,7 +36,8 @@ namespace Capnp
|
||||
|
||||
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)
|
||||
@ -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 =
|
||||
new ConditionalWeakTable<Type, Func<DeserializerState, object?>>();
|
||||
|
||||
static CapnpSerializable()
|
||||
{
|
||||
_typeMap.Add(typeof(object), CreateFromAny);
|
||||
_typeMap.Add(typeof(string), d => d.RequireList().CastText());
|
||||
_typeMap.Add(typeof(IReadOnlyList<bool>), d => d.RequireList().CastBool());
|
||||
_typeMap.Add(typeof(IReadOnlyList<sbyte>), d => d.RequireList().CastSByte());
|
||||
@ -147,29 +159,30 @@ namespace Capnp
|
||||
/// <list type="bullet">
|
||||
/// <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><see cref="String"/></description></item>
|
||||
/// <item><description><code>IReadOnlyList{Boolean}</code></description></item>
|
||||
/// <item><description><code>IReadOnlyList{SByte}"</code></description></item>
|
||||
/// <item><description><code>IReadOnlyList{Byte}"</code></description></item>
|
||||
/// <item><description><code>IReadOnlyList{Int16}"</code></description></item>
|
||||
/// <item><description><code>IReadOnlyList{UInt16}"</code></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>
|
||||
/// <item><description><see cref="string"/></description></item>
|
||||
/// <item><description>IReadOnlyList<bool>, IReadOnlyList<sbyte>, IReadOnlyList<byte></description></item>
|
||||
/// <item><description>IReadOnlyList<short>, IReadOnlyList<ushort>, IReadOnlyList<int></description></item>
|
||||
/// <item><description>IReadOnlyList<uint>, IReadOnlyList<long>, IReadOnlyList<ulong></description></item>
|
||||
/// <item><description>IReadOnlyList<float>, IReadOnlyList<double></description></item>
|
||||
/// <item><description>IReadOnlyList<T> whereby T is one of the things listed here.</description></item>
|
||||
/// </list>
|
||||
/// </typeparam>
|
||||
/// <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
|
||||
/// <paramref name="state"/> represents the nil object (obtained from a null pointer). For all other types, when the state is nil,
|
||||
/// the method still constructs a valid but "empty" object instance (such as domain object without any properties set, empty string, empty list etc.)</returns>
|
||||
/// <returns>The domain object instance. Nullability note: The returned reference may be null if
|
||||
/// <paramref name="state"/> represents the nil object.</returns>
|
||||
/// <exception cref="ArgumentException">Cannot construct object of type <typeparamref name="T"/></exception>
|
||||
/// <remarks>Note that capability ownership is moved to the domain object</remarks>
|
||||
public static T? Create<T>(DeserializerState state)
|
||||
where T: class
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ namespace Capnp
|
||||
/// Although it is public, you should not use it directly. Instead, use the reader, writer, and domain class adapters which are produced
|
||||
/// by the code generator.
|
||||
/// </summary>
|
||||
public struct DeserializerState: IStructDeserializer
|
||||
public struct DeserializerState: IStructDeserializer, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// A wire message is essentially a collection of memory blocks.
|
||||
@ -45,12 +45,13 @@ namespace Capnp
|
||||
/// The kind of object this state currently represents.
|
||||
/// </summary>
|
||||
public ObjectKind Kind { get; set; }
|
||||
bool _disposed;
|
||||
/// <summary>
|
||||
/// The capabilities imported from the capability table. Only valid in RPC context.
|
||||
/// </summary>
|
||||
public IList<Rpc.ConsumedCapability?>? Caps { get; set; }
|
||||
public IList<Rpc.ConsumedCapability>? Caps { get; set; }
|
||||
/// <summary>
|
||||
/// Current segment (essentially Segments[CurrentSegmentIndex]
|
||||
/// Current segment (essentially Segments[CurrentSegmentIndex])
|
||||
/// </summary>
|
||||
public ReadOnlySpan<ulong> CurrentSegment => Segments != null ? Segments[(int)CurrentSegmentIndex].Span : default;
|
||||
|
||||
@ -65,6 +66,7 @@ namespace Capnp
|
||||
StructPtrCount = 1;
|
||||
Kind = ObjectKind.Struct;
|
||||
Caps = null;
|
||||
_disposed = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -158,7 +160,6 @@ namespace Capnp
|
||||
/// Memory span which represents this struct's data section (given this state actually represents a struct)
|
||||
/// </summary>
|
||||
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> GetRawBytes() => CurrentSegment.Slice(Offset, (ListElementCount + 7) / 8);
|
||||
@ -172,26 +173,15 @@ namespace Capnp
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (Kind)
|
||||
return Kind switch
|
||||
{
|
||||
case ObjectKind.ListOfBits:
|
||||
return GetRawBits();
|
||||
|
||||
case ObjectKind.ListOfBytes:
|
||||
return GetRawBytes();
|
||||
|
||||
case ObjectKind.ListOfShorts:
|
||||
return GetRawShorts();
|
||||
|
||||
case ObjectKind.ListOfInts:
|
||||
return GetRawInts();
|
||||
|
||||
case ObjectKind.ListOfLongs:
|
||||
return GetRawLongs();
|
||||
|
||||
default:
|
||||
return default;
|
||||
}
|
||||
ObjectKind.ListOfBits => GetRawBits(),
|
||||
ObjectKind.ListOfBytes => GetRawBytes(),
|
||||
ObjectKind.ListOfShorts => GetRawShorts(),
|
||||
ObjectKind.ListOfInts => GetRawInts(),
|
||||
ObjectKind.ListOfLongs => GetRawLongs(),
|
||||
_ => default,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,6 +203,10 @@ namespace Capnp
|
||||
GetRawBytes();
|
||||
break;
|
||||
|
||||
case ObjectKind.ListOfShorts:
|
||||
GetRawShorts();
|
||||
break;
|
||||
|
||||
case ObjectKind.ListOfInts:
|
||||
GetRawInts();
|
||||
break;
|
||||
@ -313,6 +307,8 @@ namespace Capnp
|
||||
|
||||
case ListKind.ListOfStructs:
|
||||
{
|
||||
if (Offset >= CurrentSegment.Length)
|
||||
throw new DeserializationException("List of composites pointer exceeds segment bounds");
|
||||
WirePointer tag = CurrentSegment[Offset];
|
||||
if (tag.Kind != PointerKind.Struct)
|
||||
throw new DeserializationException("Unexpected: List of composites with non-struct type tag");
|
||||
@ -333,9 +329,16 @@ namespace Capnp
|
||||
|
||||
case PointerKind.Far:
|
||||
|
||||
if (pointer.TargetSegmentIndex >= Segments.Count)
|
||||
throw new DeserializationException("Error decoding pointer: Invalid target segment index");
|
||||
|
||||
CurrentSegmentIndex = pointer.TargetSegmentIndex;
|
||||
|
||||
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;
|
||||
|
||||
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");
|
||||
|
||||
CurrentSegmentIndex = pointer1.TargetSegmentIndex;
|
||||
Offset = 0;
|
||||
Offset = pointer1.LandingPadOffset;
|
||||
pointer = pointer2;
|
||||
offset = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentSegmentIndex = pointer.TargetSegmentIndex;
|
||||
Offset = 0;
|
||||
offset = pointer.LandingPadOffset;
|
||||
|
||||
if (pointer.LandingPadOffset >= CurrentSegment.Length)
|
||||
throw new DeserializationException("Error decoding pointer: exceeds segment bounds");
|
||||
|
||||
pointer = CurrentSegment[pointer.LandingPadOffset];
|
||||
}
|
||||
continue;
|
||||
@ -383,14 +389,14 @@ namespace Capnp
|
||||
/// </summary>
|
||||
/// <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>
|
||||
/// <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="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)
|
||||
{
|
||||
throw new IndexOutOfRangeException(nameof(offset));
|
||||
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
}
|
||||
|
||||
if (Caps == null)
|
||||
@ -404,7 +410,7 @@ namespace Capnp
|
||||
{
|
||||
// Despite this behavior is not officially specified,
|
||||
// the official C++ implementation seems to send null pointers for null caps.
|
||||
return null;
|
||||
return Rpc.NullCapability.Instance;
|
||||
}
|
||||
|
||||
if (pointer.Kind != PointerKind.Other)
|
||||
@ -496,13 +502,13 @@ namespace Capnp
|
||||
return state;
|
||||
}
|
||||
|
||||
internal Rpc.ConsumedCapability? StructReadRawCap(int index)
|
||||
internal Rpc.ConsumedCapability StructReadRawCap(int index)
|
||||
{
|
||||
if (Kind != ObjectKind.Struct && Kind != ObjectKind.Nil)
|
||||
throw new InvalidOperationException("Allowed on structs only");
|
||||
|
||||
if (index >= StructPtrCount)
|
||||
return null;
|
||||
return Rpc.NullCapability.Instance;
|
||||
|
||||
return DecodeCapPointer(index + StructDataCount);
|
||||
}
|
||||
@ -646,20 +652,14 @@ namespace Capnp
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Capability interface</typeparam>
|
||||
/// <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>
|
||||
/// <exception cref="IndexOutOfRangeException">negative index</exception>
|
||||
/// <exception cref="DeserializationException">state does not represent a struct, invalid pointer,
|
||||
/// non-capability pointer, traversal limit exceeded</exception>
|
||||
public T? ReadCap<T>(int index,
|
||||
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
|
||||
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
|
||||
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0) where T: class
|
||||
public T? ReadCap<T>(int index) where T: class
|
||||
{
|
||||
var cap = StructReadRawCap(index);
|
||||
return Rpc.CapabilityReflection.CreateProxy<T>(cap, memberName, sourceFilePath, sourceLineNumber) as T;
|
||||
return Rpc.CapabilityReflection.CreateProxy<T>(cap) as T;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -693,9 +693,26 @@ namespace Capnp
|
||||
throw new DeserializationException("Expected a capability");
|
||||
|
||||
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)!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the capability table
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (Caps != null && !_disposed)
|
||||
{
|
||||
foreach (var cap in Caps)
|
||||
{
|
||||
cap.Release();
|
||||
}
|
||||
|
||||
Caps = null;
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -42,7 +42,9 @@ namespace Capnp
|
||||
{
|
||||
var mb = MessageBuilder.Create();
|
||||
if (state.Caps != null)
|
||||
{
|
||||
mb.InitCapTable();
|
||||
}
|
||||
var sstate = mb.CreateObject<DynamicSerializerState>();
|
||||
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>Another state is already linked to the specified position (sorry, no overwrite allowed)</description></item></list>
|
||||
/// </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>
|
||||
/// 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>
|
||||
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>
|
||||
/// Determines the underlying object to be a list of (primitive) values.
|
||||
/// </summary>
|
||||
@ -121,31 +130,27 @@ namespace Capnp
|
||||
/// <param name="obj">Object representation. Must be one of the following:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>An instance implementing <see cref="ICapnpSerializable"/></description></item>
|
||||
/// <item><description>null</description></item>
|
||||
/// <item><description>A <see cref="String"/></description></item>
|
||||
/// <item><description>A <code><![CDATA[IReadOnlyList<byte>]]></code></description></item>
|
||||
/// <item><description>A <code><![CDATA[IReadOnlyList<sbyte>]]></code></description></item>
|
||||
/// <item><description>A <code><![CDATA[IReadOnlyList<ushort>]]></code></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>null</description>, <see cref="String"/></item>
|
||||
/// <item><description><c>IReadOnlyList<byte></c>, <c>IReadOnlyList<sbyte></c>, <c>IReadOnlyList<ushort></c>, <c>IReadOnlyList<short></c></description></item>
|
||||
/// <item><description><c>IReadOnlyList<int></c>, <c>IReadOnlyList<uint></c>, <c>IReadOnlyList<long></c>, <c>IReadOnlyList<ulong></c></description></item>
|
||||
/// <item><description><c>IReadOnlyList<float></c>, <c>IReadOnlyList<double></c>, <c>IReadOnlyList<bool></c>, <c>IReadOnlyList<string></c></description></item>
|
||||
/// <item><description>Another <see cref="DeserializerState"/> or <see cref="SerializerState"/></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>Skeleton object (<see cref="Rpc.Skeleton"/>)</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<object></c>, whereby each list item is one of the things listed here.</description></item>
|
||||
/// </list>
|
||||
/// </param>
|
||||
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)
|
||||
{
|
||||
case ICapnpSerializable serializable:
|
||||
@ -157,55 +162,55 @@ namespace Capnp
|
||||
break;
|
||||
|
||||
case IReadOnlyList<byte> bytes:
|
||||
Rewrap<ListOfPrimitivesSerializer<byte>>().Init(bytes);
|
||||
RewrapAndInheritBack<ListOfPrimitivesSerializer<byte>>(_ => _.Init(bytes));
|
||||
break;
|
||||
|
||||
case IReadOnlyList<sbyte> sbytes:
|
||||
Rewrap<ListOfPrimitivesSerializer<sbyte>>().Init(sbytes);
|
||||
RewrapAndInheritBack<ListOfPrimitivesSerializer<sbyte>>(_ => _.Init(sbytes));
|
||||
break;
|
||||
|
||||
case IReadOnlyList<ushort> ushorts:
|
||||
Rewrap<ListOfPrimitivesSerializer<ushort>>().Init(ushorts);
|
||||
RewrapAndInheritBack<ListOfPrimitivesSerializer<ushort>>(_ => _.Init(ushorts));
|
||||
break;
|
||||
|
||||
case IReadOnlyList<short> shorts:
|
||||
Rewrap<ListOfPrimitivesSerializer<short>>().Init(shorts);
|
||||
RewrapAndInheritBack<ListOfPrimitivesSerializer<short>>(_ => _.Init(shorts));
|
||||
break;
|
||||
|
||||
case IReadOnlyList<uint> uints:
|
||||
Rewrap<ListOfPrimitivesSerializer<uint>>().Init(uints);
|
||||
RewrapAndInheritBack<ListOfPrimitivesSerializer<uint>>(_ => _.Init(uints));
|
||||
break;
|
||||
|
||||
case IReadOnlyList<int> ints:
|
||||
Rewrap<ListOfPrimitivesSerializer<int>>().Init(ints);
|
||||
RewrapAndInheritBack<ListOfPrimitivesSerializer<int>>(_ => _.Init(ints));
|
||||
break;
|
||||
|
||||
case IReadOnlyList<ulong> ulongs:
|
||||
Rewrap<ListOfPrimitivesSerializer<ulong>>().Init(ulongs);
|
||||
RewrapAndInheritBack<ListOfPrimitivesSerializer<ulong>>(_ => _.Init(ulongs));
|
||||
break;
|
||||
|
||||
case IReadOnlyList<long> longs:
|
||||
Rewrap<ListOfPrimitivesSerializer<long>>().Init(longs);
|
||||
RewrapAndInheritBack<ListOfPrimitivesSerializer<long>>(_ => _.Init(longs));
|
||||
break;
|
||||
|
||||
case IReadOnlyList<float> floats:
|
||||
Rewrap<ListOfPrimitivesSerializer<float>>().Init(floats);
|
||||
RewrapAndInheritBack<ListOfPrimitivesSerializer<float>>(_ => _.Init(floats));
|
||||
break;
|
||||
|
||||
case IReadOnlyList<double> doubles:
|
||||
Rewrap<ListOfPrimitivesSerializer<double>>().Init(doubles);
|
||||
RewrapAndInheritBack<ListOfPrimitivesSerializer<double>>(_ => _.Init(doubles));
|
||||
break;
|
||||
|
||||
case IReadOnlyList<bool> bools:
|
||||
Rewrap<ListOfBitsSerializer>().Init(bools);
|
||||
RewrapAndInheritBack<ListOfBitsSerializer>(_ => _.Init(bools));
|
||||
break;
|
||||
|
||||
case IReadOnlyList<string> strings:
|
||||
Rewrap<ListOfTextSerializer>().Init(strings);
|
||||
RewrapAndInheritBack<ListOfTextSerializer>(_ => _.Init(strings));
|
||||
break;
|
||||
|
||||
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;
|
||||
|
||||
case DeserializerState ds:
|
||||
|
@ -8,14 +8,14 @@ namespace Capnp
|
||||
/// <summary>
|
||||
/// Implements an empty <see cref="IReadOnlyList{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="T">list element type</typeparam>
|
||||
public class EmptyList<T> : IReadOnlyList<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Always throws an <see cref="ArgumentOutOfRangeException"/>.
|
||||
/// Always throws an <see cref="IndexOutOfRangeException"/>.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
/// Always 0.
|
||||
|
@ -68,7 +68,7 @@ namespace Capnp
|
||||
/// <summary>
|
||||
/// Returns an empty string.
|
||||
/// </summary>
|
||||
public override string CastText() => string.Empty;
|
||||
public override string? CastText() => null;
|
||||
|
||||
/// <summary>
|
||||
/// Returns an empty <code><![CDATA[IReadOnlyList<uint>]]></code>.
|
||||
|
@ -2,7 +2,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
@ -116,8 +118,24 @@ namespace Capnp
|
||||
#endif
|
||||
_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)
|
||||
{
|
||||
|
@ -18,15 +18,26 @@ namespace Capnp.FrameTracing
|
||||
|
||||
readonly Stopwatch _timer = new Stopwatch();
|
||||
readonly TextWriter _traceWriter;
|
||||
readonly bool _disposeWriter;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance
|
||||
/// </summary>
|
||||
/// <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.WriteLine(Header);
|
||||
_disposeWriter = dispose;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -35,7 +46,8 @@ namespace Capnp.FrameTracing
|
||||
public void Dispose()
|
||||
{
|
||||
_traceWriter.WriteLine("<end of trace>");
|
||||
_traceWriter.Dispose();
|
||||
if (_disposeWriter)
|
||||
_traceWriter.Dispose();
|
||||
}
|
||||
|
||||
void RenderMessageTarget(MessageTarget.READER target, FrameDirection dir)
|
||||
|
@ -11,6 +11,9 @@ namespace Capnp
|
||||
static class GenericCasts<T>
|
||||
{
|
||||
public static Func<ListDeserializer, T>? CastFunc;
|
||||
|
||||
public static Func<ListDeserializer, T> GetCastFunc() => CastFunc ??
|
||||
throw new NotSupportedException("Requested cast is not supported");
|
||||
}
|
||||
|
||||
static ListDeserializer()
|
||||
@ -26,7 +29,7 @@ namespace Capnp
|
||||
GenericCasts<IReadOnlyList<ulong>>.CastFunc = _ => _.CastULong();
|
||||
GenericCasts<IReadOnlyList<float>>.CastFunc = _ => _.CastFloat();
|
||||
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>
|
||||
@ -45,12 +48,7 @@ namespace Capnp
|
||||
|
||||
T Cast<T>()
|
||||
{
|
||||
var func = GenericCasts<T>.CastFunc;
|
||||
|
||||
if (func == null)
|
||||
throw new NotSupportedException("Requested cast is not supported");
|
||||
|
||||
return func(this);
|
||||
return GenericCasts<T>.GetCastFunc()(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -89,9 +87,9 @@ namespace Capnp
|
||||
/// <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="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)
|
||||
@ -158,6 +156,7 @@ namespace Capnp
|
||||
/// <exception cref="NotSupportedException">If this list cannot be represented in the desired manner.</exception>
|
||||
public IReadOnlyList<IReadOnlyList<T>> Cast2D<T>()
|
||||
{
|
||||
GenericCasts<IReadOnlyList<T>>.GetCastFunc(); // Probe to avoid lazy NotSupportedException
|
||||
return CastList().LazyListSelect(ld => ld.Cast<IReadOnlyList<T>>());
|
||||
}
|
||||
|
||||
@ -269,7 +268,7 @@ namespace Capnp
|
||||
/// </summary>
|
||||
/// <returns>The desired representation</returns>
|
||||
/// <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>
|
||||
/// Represents this list as Text. For representing it as List(Text), use <seealso cref="CastText2"/>.
|
||||
@ -285,7 +284,7 @@ namespace Capnp
|
||||
/// </remarks>
|
||||
/// <returns>The decoded text</returns>
|
||||
/// <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");
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Capnp
|
||||
{
|
||||
@ -10,7 +11,6 @@ namespace Capnp
|
||||
/// </summary>
|
||||
public class ListOfBitsSerializer: SerializerState, IReadOnlyList<bool>
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the element at given index.
|
||||
/// </summary>
|
||||
@ -22,6 +22,8 @@ namespace Capnp
|
||||
{
|
||||
get
|
||||
{
|
||||
ListSerializerHelper.EnsureAllocated(this);
|
||||
|
||||
if (index < 0 || index >= Count)
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
@ -32,6 +34,8 @@ namespace Capnp
|
||||
}
|
||||
set
|
||||
{
|
||||
ListSerializerHelper.EnsureAllocated(this);
|
||||
|
||||
if (index < 0 || index >= Count)
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
@ -88,11 +92,17 @@ namespace Capnp
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<bool> Enumerate()
|
||||
{
|
||||
for (int i = 0; i < Count; i++)
|
||||
yield return this[i];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="IEnumerable{Boolean}"/>
|
||||
/// </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();
|
||||
}
|
||||
}
|
@ -35,16 +35,29 @@ namespace Capnp
|
||||
[AllowNull]
|
||||
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
|
||||
{
|
||||
if (!IsAllocated)
|
||||
throw new InvalidOperationException("Call Init() first");
|
||||
ListSerializerHelper.EnsureAllocated(this);
|
||||
|
||||
if (index < 0 || index >= RawData.Length)
|
||||
throw new IndexOutOfRangeException("index out of range");
|
||||
|
||||
RawData[index] = ProvideCapability(value);
|
||||
var p = default(WirePointer);
|
||||
p.SetCapability(ProvideCapability(value));
|
||||
RawData[index] = p;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,9 +83,9 @@ namespace Capnp
|
||||
/// </summary>
|
||||
/// <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>
|
||||
public override IReadOnlyList<ListOfCapsDeserializer<T>> CastCapList<T>()
|
||||
public override IReadOnlyList<T> CastCapList<T>()
|
||||
{
|
||||
return this.LazyListSelect(d => d.RequireCapList<T>());
|
||||
return State.RequireCapList<T>();
|
||||
}
|
||||
}
|
||||
}
|
@ -31,7 +31,7 @@ namespace Capnp
|
||||
var state = _lpd.State;
|
||||
|
||||
if (index < 0 || index >= _lpd.Count)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
state.Offset += index;
|
||||
state.Kind = ObjectKind.Struct;
|
||||
|
@ -29,9 +29,9 @@ namespace Capnp
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the underlying memory span of the represented items.
|
||||
/// The list's data
|
||||
/// </summary>
|
||||
public Span<T> Span => MemoryMarshal.Cast<ulong, T>(RawData);
|
||||
public Span<T> Data => MemoryMarshal.Cast<ulong, T>(RawData).Slice(0, Count);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value at given index.
|
||||
@ -40,8 +40,16 @@ namespace Capnp
|
||||
/// <returns>Element value</returns>
|
||||
public T this[int index]
|
||||
{
|
||||
get => Span[index];
|
||||
set => Span[index] = value;
|
||||
get
|
||||
{
|
||||
ListSerializerHelper.EnsureAllocated(this);
|
||||
return Data[index];
|
||||
}
|
||||
set
|
||||
{
|
||||
ListSerializerHelper.EnsureAllocated(this);
|
||||
Data[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -84,19 +92,19 @@ namespace Capnp
|
||||
switch (items)
|
||||
{
|
||||
case T[] array:
|
||||
array.CopyTo(Span);
|
||||
array.CopyTo(Data);
|
||||
break;
|
||||
|
||||
case ArraySegment<T> segment:
|
||||
segment.AsSpan().CopyTo(Span);
|
||||
segment.AsSpan().CopyTo(Data);
|
||||
break;
|
||||
|
||||
case ListOfPrimitivesDeserializer<T> deser:
|
||||
deser.Span.CopyTo(Span);
|
||||
deser.Span.CopyTo(Data);
|
||||
break;
|
||||
|
||||
case ListOfPrimitivesSerializer<T> ser:
|
||||
ser.Span.CopyTo(Span);
|
||||
ser.Data.CopyTo(Data);
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -108,11 +116,18 @@ namespace Capnp
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<T> Enumerate()
|
||||
{
|
||||
for (int i = 0; i < Count; i++)
|
||||
yield return Data[i];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements <see cref="IEnumerable{T}"/>.
|
||||
/// </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();
|
||||
}
|
||||
}
|
@ -22,8 +22,7 @@ namespace Capnp
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsAllocated)
|
||||
throw new InvalidOperationException("Not initialized");
|
||||
ListSerializerHelper.EnsureAllocated(this);
|
||||
|
||||
if (index < 0 || index >= Count)
|
||||
throw new IndexOutOfRangeException();
|
||||
@ -32,8 +31,7 @@ namespace Capnp
|
||||
}
|
||||
set
|
||||
{
|
||||
if (!IsAllocated)
|
||||
throw new InvalidOperationException("Not initialized");
|
||||
ListSerializerHelper.EnsureAllocated(this);
|
||||
|
||||
if (index < 0 || index >= Count)
|
||||
throw new IndexOutOfRangeException();
|
||||
@ -53,7 +51,7 @@ namespace Capnp
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
yield return TryGetPointer<SerializerState>(i)?.ListReadAsText();
|
||||
yield return this[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
13
Capnp.Net.Runtime/ListSerializerHelper.cs
Normal file
13
Capnp.Net.Runtime/ListSerializerHelper.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ namespace Capnp
|
||||
{
|
||||
readonly ISegmentAllocator _allocator;
|
||||
readonly DynamicSerializerState _rootPtrBuilder;
|
||||
List<Rpc.ConsumedCapability?>? _capTable;
|
||||
List<Rpc.ConsumedCapability>? _capTable;
|
||||
|
||||
MessageBuilder(ISegmentAllocator allocator)
|
||||
{
|
||||
@ -64,7 +64,7 @@ namespace Capnp
|
||||
/// <summary>
|
||||
/// Creates an object and sets it as root object.
|
||||
/// </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>
|
||||
public TS BuildRoot<TS>() where TS: SerializerState, new()
|
||||
{
|
||||
@ -72,6 +72,9 @@ namespace Capnp
|
||||
throw new InvalidOperationException("Root already set");
|
||||
|
||||
var root = CreateObject<TS>();
|
||||
if (root.Kind != ObjectKind.Struct)
|
||||
throw new InvalidOperationException("Root object must be a struct");
|
||||
|
||||
Root = root;
|
||||
return root;
|
||||
}
|
||||
@ -89,13 +92,13 @@ namespace Capnp
|
||||
if (_capTable != null)
|
||||
throw new InvalidOperationException("Capability table was already initialized");
|
||||
|
||||
_capTable = new List<Rpc.ConsumedCapability?>();
|
||||
_capTable = new List<Rpc.ConsumedCapability>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns this message builder's segment allocator.
|
||||
/// </summary>
|
||||
public ISegmentAllocator Allocator => _allocator;
|
||||
internal List<Rpc.ConsumedCapability?>? Caps => _capTable;
|
||||
internal List<Rpc.ConsumedCapability>? Caps => _capTable;
|
||||
}
|
||||
}
|
@ -4,9 +4,7 @@ namespace Capnp
|
||||
{
|
||||
/// <summary>
|
||||
/// The different kinds of Cap'n Proto objects.
|
||||
/// Despite this is a [Flags] enum, it does not make sense to mutually combine literals.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ObjectKind: byte
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@
|
||||
/// <exception cref="System.TypeLoadException">Problem with building the Skeleton type, or problem with loading some dependent class.</exception>
|
||||
public static BareProxy FromImpl(object impl)
|
||||
{
|
||||
return new BareProxy(LocalCapability.Create(CapabilityReflection.CreateSkeleton(impl)));
|
||||
return new BareProxy(CapabilityReflection.CreateSkeletonInternal(impl).AsCapability());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -33,7 +33,7 @@
|
||||
/// Constructs an instance and binds it to the given low-level capability.
|
||||
/// </summary>
|
||||
/// <param name="cap">low-level capability</param>
|
||||
public BareProxy(ConsumedCapability? cap): base(cap)
|
||||
public BareProxy(ConsumedCapability cap): base(cap)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -94,6 +94,8 @@ namespace Capnp.Rpc
|
||||
new ConditionalWeakTable<Type, ProxyFactory>();
|
||||
static ConditionalWeakTable<Type, SkeletonFactory> _skeletonMap =
|
||||
new ConditionalWeakTable<Type, SkeletonFactory>();
|
||||
static ConditionalWeakTable<object, Skeleton> _implMap =
|
||||
new ConditionalWeakTable<object, Skeleton>();
|
||||
|
||||
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="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>
|
||||
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)
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
var factory = GetSkeletonFactory(obj.GetType());
|
||||
var skeleton = factory.NewSkeleton();
|
||||
skeleton.Bind(obj);
|
||||
return skeleton;
|
||||
var result = _implMap.GetValue(obj, _ =>
|
||||
{
|
||||
var factory = GetSkeletonFactory(_.GetType());
|
||||
var skeleton = factory.NewSkeleton();
|
||||
skeleton.Bind(obj);
|
||||
return skeleton;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static ProxyFactory GetProxyFactory(Type type)
|
||||
@ -229,7 +239,7 @@ namespace Capnp.Rpc
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checkes whether a given type qualifies as cpapbility interface./> on failure.
|
||||
/// Checks whether a given type qualifies as capability interface.
|
||||
/// </summary>
|
||||
/// <param name="interfaceType">type to check</param>
|
||||
/// <returns>true when <paramref name="interfaceType"/> is a capability interface</returns>
|
||||
@ -251,9 +261,6 @@ namespace Capnp.Rpc
|
||||
/// </summary>
|
||||
/// <typeparam name="TInterface">Capability interface. Must be annotated with <see cref="ProxyAttribute"/>.</typeparam>
|
||||
/// <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>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="cap"/> is null.</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="MemberAccessException">Caller does not have permission to invoke the Proxy constructor.</exception>
|
||||
/// <exception cref="TypeLoadException">Problem with building the Proxy type, or problem with loading some dependent class.</exception>
|
||||
public static Proxy CreateProxy<TInterface>(ConsumedCapability? cap,
|
||||
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
|
||||
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
|
||||
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
|
||||
public static Proxy CreateProxy<TInterface>(ConsumedCapability cap)
|
||||
{
|
||||
var factory = GetProxyFactory(typeof(TInterface));
|
||||
var proxy = factory.NewProxy();
|
||||
proxy.Bind(cap);
|
||||
#if DebugFinalizers
|
||||
proxy.CreatorMemberName = memberName;
|
||||
proxy.CreatorFilePath = sourceFilePath;
|
||||
proxy.CreatorLineNumber = sourceLineNumber;
|
||||
if (cap != null)
|
||||
{
|
||||
cap.CreatorFilePath = proxy.CreatorFilePath;
|
||||
cap.CreatorLineNumber = proxy.CreatorLineNumber;
|
||||
cap.CreatorMemberName = proxy.CreatorMemberName;
|
||||
}
|
||||
#endif
|
||||
return proxy;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
namespace Capnp.Rpc
|
||||
using System;
|
||||
|
||||
namespace Capnp.Rpc
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for a low-level capability at consumer side. It is created by the <see cref="RpcEngine"/>. An application does not directly interact with it
|
||||
@ -13,20 +15,14 @@
|
||||
/// which usually also means to remove it from the remote peer's export table.
|
||||
/// </summary>
|
||||
protected abstract void ReleaseRemotely();
|
||||
internal abstract void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer);
|
||||
internal abstract void Freeze(out IRpcEndpoint? boundEndpoint);
|
||||
internal abstract void Unfreeze();
|
||||
|
||||
internal abstract Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer);
|
||||
internal abstract void AddRef();
|
||||
internal abstract void Release(
|
||||
[System.Runtime.CompilerServices.CallerMemberName] string methodName = "",
|
||||
[System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
|
||||
[System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0);
|
||||
internal abstract void Release();
|
||||
internal abstract Skeleton AsSkeleton();
|
||||
|
||||
#if DebugFinalizers
|
||||
public string CreatorMemberName { get; set; }
|
||||
public string CreatorFilePath { get; set; }
|
||||
public int CreatorLineNumber { get; set; }
|
||||
internal Proxy? OwningProxy { get; set; }
|
||||
internal ConsumedCapability? ResolvingCap { get; set; }
|
||||
#endif
|
||||
}
|
||||
}
|
@ -10,6 +10,11 @@
|
||||
/// </summary>
|
||||
void Forward(WireFrame frame);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the endpoint should flush any buffered frames.
|
||||
/// </summary>
|
||||
void Flush();
|
||||
|
||||
/// <summary>
|
||||
/// Close this endpoint.
|
||||
/// </summary>
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Capnp.Util;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Capnp.Rpc
|
||||
@ -15,13 +16,26 @@ namespace Capnp.Rpc
|
||||
/// <summary>
|
||||
/// Task which will complete when the RPC returns, delivering its result struct.
|
||||
/// </summary>
|
||||
Task<DeserializerState> WhenReturned { get; }
|
||||
StrictlyOrderedAwaitTask<DeserializerState> WhenReturned { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a low-level capability for promise pipelining.
|
||||
/// </summary>
|
||||
/// <param name="access">Path to the desired capability inside the result struct.</param>
|
||||
/// <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; }
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using Capnp.Util;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Capnp.Rpc
|
||||
{
|
||||
@ -8,8 +9,15 @@ namespace Capnp.Rpc
|
||||
public interface IResolvingCapability
|
||||
{
|
||||
/// <summary>
|
||||
/// Will eventually give the resolved capability.
|
||||
/// Completes when the capability gets resolved.
|
||||
/// </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;
|
||||
}
|
||||
}
|
@ -8,7 +8,6 @@ namespace Capnp.Rpc
|
||||
PendingQuestion BeginQuestion(ConsumedCapability target, SerializerState inParams);
|
||||
void SendQuestion(SerializerState inParams, Payload.WRITER payload);
|
||||
uint AllocateExport(Skeleton providedCapability, out bool first);
|
||||
void RequestPostAction(Action postAction);
|
||||
void Finish(uint questionId);
|
||||
void ReleaseImport(uint importId);
|
||||
void Resolve(uint preliminaryId, Skeleton preliminaryCap, Func<ConsumedCapability> resolvedCapGetter);
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -11,7 +12,7 @@ namespace Capnp.Rpc
|
||||
public static class Impatient
|
||||
{
|
||||
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>
|
||||
/// 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()
|
||||
{
|
||||
return then(await promise.WhenReturned);
|
||||
var result = await promise.WhenReturned;
|
||||
if (promise.IsTailCall)
|
||||
throw new NoResultsException();
|
||||
|
||||
return then(result);
|
||||
}
|
||||
|
||||
var rtask = AwaitAnswer();
|
||||
|
||||
try
|
||||
{
|
||||
// Really weird: We'd expect AwaitAnswer() to initialize a new Task instance upon each invocation.
|
||||
// However, this does not seem to be always true (as indicated by CI test suite). An explanation might be
|
||||
// that the underlying implementation recycles Task instances (um, really? doesn't make sense. But the
|
||||
// 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?");
|
||||
}
|
||||
}
|
||||
// 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.
|
||||
// Once this happens the second time, and we return the same rtask for a different promise. GetAnswer()/TryGetAnswer() may return the "wrong"
|
||||
// promise! Fortunately, this does not really matter, since the "wrong" promise is guaranteed to return exactly the same answer. :-)
|
||||
_taskTable.GetValue(rtask, _ => promise);
|
||||
|
||||
return rtask;
|
||||
}
|
||||
@ -76,6 +52,7 @@ namespace Capnp.Rpc
|
||||
/// <returns>The underlying promise</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="task"/> is null.</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)
|
||||
{
|
||||
if (!_taskTable.TryGetValue(task, out var answer))
|
||||
@ -92,22 +69,18 @@ namespace Capnp.Rpc
|
||||
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;
|
||||
|
||||
switch (item)
|
||||
{
|
||||
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);
|
||||
var answer = TryGetAnswer(task);
|
||||
if (answer != null) return answer.Access(access, proxyTask);
|
||||
return new LazyCapability(proxyTask.AsProxyTask());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -116,22 +89,16 @@ namespace Capnp.Rpc
|
||||
/// </summary>
|
||||
/// <typeparam name="TInterface">Capability interface type</typeparam>
|
||||
/// <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>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="task"/> is null.</exception>
|
||||
/// <exception cref="InvalidCapabilityInterfaceException"><typeparamref name="TInterface"/> did not
|
||||
/// quality as capability interface.</exception>
|
||||
[Obsolete("Call Eager<TInterface>(task, true) instead")]
|
||||
public static TInterface PseudoEager<TInterface>(this Task<TInterface> task,
|
||||
[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
|
||||
[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
|
||||
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
|
||||
where TInterface : class
|
||||
public static TInterface PseudoEager<TInterface>(this Task<TInterface> task)
|
||||
where TInterface : class, IDisposable
|
||||
{
|
||||
var lazyCap = new LazyCapability(AwaitProxy(task));
|
||||
return (CapabilityReflection.CreateProxy<TInterface>(lazyCap, memberName, sourceFilePath, sourceLineNumber) as TInterface)!;
|
||||
var lazyCap = new LazyCapability(task.AsProxyTask());
|
||||
return (CapabilityReflection.CreateProxy<TInterface>(lazyCap) as TInterface)!;
|
||||
}
|
||||
|
||||
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="TypeLoadException">Problem with building the Proxy type, or problem with loading some dependent class.</exception>
|
||||
public static TInterface Eager<TInterface>(this Task<TInterface> task, bool allowNoPipeliningFallback = false)
|
||||
where TInterface : class
|
||||
where TInterface : class, IDisposable
|
||||
{
|
||||
var answer = TryGetAnswer(task);
|
||||
if (answer == null)
|
||||
@ -167,19 +134,65 @@ namespace Capnp.Rpc
|
||||
throw new ArgumentException("The task was not returned from a remote method invocation. See documentation for details.");
|
||||
}
|
||||
|
||||
var lazyCap = new LazyCapability(AwaitProxy(task));
|
||||
return (CapabilityReflection.CreateProxy<TInterface>(lazyCap) as TInterface)!;
|
||||
var proxyTask = task.AsProxyTask();
|
||||
if (proxyTask.ReplacementTaskIsCompletedSuccessfully())
|
||||
{
|
||||
return proxyTask.Result.Cast<TInterface>(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var lazyCap = new LazyCapability(proxyTask);
|
||||
return (CapabilityReflection.CreateProxy<TInterface>(lazyCap) as TInterface)!;
|
||||
}
|
||||
}
|
||||
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
|
||||
{
|
||||
get => _askingEndpoint.Value;
|
||||
set { _askingEndpoint.Value = value; }
|
||||
get => _askingEndpoint.Value!.Count > 0 ? _askingEndpoint.Value.Peek() : null;
|
||||
}
|
||||
|
||||
internal static void PushAskingEndpoint(IRpcEndpoint endpoint)
|
||||
{
|
||||
_askingEndpoint.Value!.Push(endpoint);
|
||||
}
|
||||
|
||||
internal static void PopAskingEndpoint()
|
||||
{
|
||||
_askingEndpoint.Value!.Pop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,4 +1,6 @@
|
||||
namespace Capnp.Rpc
|
||||
using System;
|
||||
|
||||
namespace Capnp.Rpc
|
||||
{
|
||||
/// <summary>
|
||||
/// Low-level capability which as imported from a remote peer.
|
||||
@ -26,16 +28,7 @@
|
||||
return call;
|
||||
}
|
||||
|
||||
internal override void Freeze(out IRpcEndpoint boundEndpoint)
|
||||
{
|
||||
boundEndpoint = _ep;
|
||||
}
|
||||
|
||||
internal override void Unfreeze()
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER capDesc)
|
||||
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER capDesc)
|
||||
{
|
||||
if (endpoint == _ep)
|
||||
{
|
||||
@ -45,8 +38,9 @@
|
||||
else
|
||||
{
|
||||
capDesc.which = CapDescriptor.WHICH.SenderHosted;
|
||||
capDesc.SenderHosted = endpoint.AllocateExport(Vine.Create(this), out var _);
|
||||
capDesc.SenderHosted = endpoint.AllocateExport(AsSkeleton(), out var _);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Capnp.Util;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -18,36 +19,23 @@ namespace Capnp.Rpc.Interception
|
||||
readonly CancellationTokenSource _cancelFromAlice = new CancellationTokenSource();
|
||||
|
||||
public PromisedAnswer(CallContext callContext)
|
||||
{
|
||||
{
|
||||
_callContext = callContext;
|
||||
WhenReturned = _futureResult.Task.EnforceAwaitOrder();
|
||||
}
|
||||
|
||||
public Task<DeserializerState> WhenReturned => _futureResult.Task;
|
||||
public StrictlyOrderedAwaitTask<DeserializerState> WhenReturned { get; }
|
||||
public CancellationToken CancelFromAlice => _cancelFromAlice.Token;
|
||||
|
||||
async Task<Proxy> AccessWhenReturned(MemberAccessPath access)
|
||||
public ConsumedCapability Access(MemberAccessPath access)
|
||||
{
|
||||
await WhenReturned;
|
||||
return new Proxy(Access(access));
|
||||
return _callContext._censorCapability.Policy.Attach<ConsumedCapability>(new LocalAnswerCapability(WhenReturned, access));
|
||||
}
|
||||
|
||||
public ConsumedCapability? Access(MemberAccessPath access)
|
||||
public ConsumedCapability Access(MemberAccessPath _, Task<IDisposable?> task)
|
||||
{
|
||||
if (_futureResult.Task.IsCompleted)
|
||||
{
|
||||
try
|
||||
{
|
||||
return access.Eval(WhenReturned.Result);
|
||||
}
|
||||
catch (AggregateException exception)
|
||||
{
|
||||
throw exception.InnerException!;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new LazyCapability(AccessWhenReturned(access));
|
||||
}
|
||||
var proxyTask = task.AsProxyTask();
|
||||
return _callContext._censorCapability.Policy.Attach<ConsumedCapability>(new LocalAnswerCapability(proxyTask));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@ -84,6 +72,8 @@ namespace Capnp.Rpc.Interception
|
||||
_cancelFromAlice.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsTailCall => false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -101,10 +91,16 @@ namespace Capnp.Rpc.Interception
|
||||
/// </summary>
|
||||
public InterceptionState State { get; private set; }
|
||||
|
||||
SerializerState _inArgs;
|
||||
|
||||
/// <summary>
|
||||
/// Input arguments
|
||||
/// </summary>
|
||||
public SerializerState? InArgs { get; set; }
|
||||
public SerializerState InArgs
|
||||
{
|
||||
get => _inArgs;
|
||||
set { _inArgs = value ?? throw new ArgumentNullException(nameof(value)); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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="Skeleton"/>-derived object</description></item>
|
||||
/// <item><description>A <see cref="ConsumedCapability"/>-derived object (low level capability)</description></item>
|
||||
/// <item><description>null</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public object? Bob
|
||||
/// <remarks>
|
||||
/// Note that getting/setting this property does NOT transfer ownership.
|
||||
/// </remarks>
|
||||
public object Bob
|
||||
{
|
||||
get => _bob;
|
||||
set
|
||||
{
|
||||
if (value != _bob)
|
||||
{
|
||||
BobProxy?.Dispose();
|
||||
BobProxy = null;
|
||||
|
||||
_bob = value;
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case null:
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
|
||||
case Proxy proxy:
|
||||
BobProxy = proxy;
|
||||
BobProxy = proxy.Cast<BareProxy>(false);
|
||||
break;
|
||||
|
||||
case ConsumedCapability cap:
|
||||
using (var temp = CapabilityReflection.CreateProxy<object>(cap))
|
||||
{
|
||||
Bob = temp;
|
||||
}
|
||||
break;
|
||||
|
||||
case Skeleton skeleton:
|
||||
BobProxy = CapabilityReflection.CreateProxy<object>(
|
||||
LocalCapability.Create(skeleton));
|
||||
break;
|
||||
|
||||
case ConsumedCapability cap:
|
||||
BobProxy = CapabilityReflection.CreateProxy<object>(cap);
|
||||
break;
|
||||
|
||||
case null:
|
||||
using (var nullProxy = new Proxy())
|
||||
{
|
||||
Bob = (object?)skeleton.AsCapability() ?? nullProxy;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
BobProxy = CapabilityReflection.CreateProxy<object>(
|
||||
LocalCapability.Create(
|
||||
Skeleton.GetOrCreateSkeleton(value, false)));
|
||||
Bob = CapabilityReflection.CreateSkeletonInternal(value);
|
||||
break;
|
||||
}
|
||||
|
||||
_bob = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -197,7 +195,7 @@ namespace Capnp.Rpc.Interception
|
||||
|
||||
readonly CensorCapability _censorCapability;
|
||||
PromisedAnswer _promisedAnswer;
|
||||
object? _bob;
|
||||
object _bob;
|
||||
|
||||
internal IPromisedAnswer Answer => _promisedAnswer;
|
||||
|
||||
@ -205,13 +203,14 @@ namespace Capnp.Rpc.Interception
|
||||
{
|
||||
_censorCapability = censorCapability;
|
||||
_promisedAnswer = new PromisedAnswer(this);
|
||||
_inArgs = inArgs;
|
||||
_bob = null!; // Will be initialized later here
|
||||
|
||||
CancelFromAlice = _promisedAnswer.CancelFromAlice;
|
||||
CancelToBob = CancelFromAlice;
|
||||
Bob = censorCapability.InterceptedCapability;
|
||||
InterfaceId = interfaceId;
|
||||
MethodId = methodId;
|
||||
InArgs = inArgs;
|
||||
State = InterceptionState.RequestedFromAlice;
|
||||
}
|
||||
|
||||
@ -253,12 +252,8 @@ namespace Capnp.Rpc.Interception
|
||||
/// Intercepts all capabilies inside the input arguments
|
||||
/// </summary>
|
||||
/// <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)
|
||||
{
|
||||
if (InArgs == null)
|
||||
throw new InvalidOperationException("InArgs not set");
|
||||
|
||||
InterceptCaps(InArgs, policyOverride ?? _censorCapability.Policy);
|
||||
}
|
||||
|
||||
@ -275,12 +270,8 @@ namespace Capnp.Rpc.Interception
|
||||
/// Unintercepts all capabilies inside the input arguments
|
||||
/// </summary>
|
||||
/// <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)
|
||||
{
|
||||
if (InArgs == null)
|
||||
throw new InvalidOperationException("InArgs not set");
|
||||
|
||||
UninterceptCaps(InArgs, policyOverride ?? _censorCapability.Policy);
|
||||
}
|
||||
|
||||
@ -296,15 +287,8 @@ namespace Capnp.Rpc.Interception
|
||||
/// <summary>
|
||||
/// Forwards this intercepted call to the target capability ("Bob").
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Bob/InArgs not set</exception>
|
||||
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);
|
||||
|
||||
State = InterceptionState.ForwardedToBob;
|
||||
|
@ -1,4 +1,6 @@
|
||||
namespace Capnp.Rpc.Interception
|
||||
using System;
|
||||
|
||||
namespace Capnp.Rpc.Interception
|
||||
{
|
||||
class CensorCapability : RefCountingCapability
|
||||
{
|
||||
@ -7,12 +9,10 @@
|
||||
InterceptedCapability = interceptedCapability;
|
||||
interceptedCapability.AddRef();
|
||||
Policy = policy;
|
||||
MyVine = Vine.Create(this);
|
||||
}
|
||||
|
||||
public ConsumedCapability InterceptedCapability { get; }
|
||||
public IInterceptionPolicy Policy { get; }
|
||||
internal Skeleton MyVine { get; }
|
||||
|
||||
protected override void ReleaseRemotely()
|
||||
{
|
||||
@ -26,19 +26,11 @@
|
||||
return cc.Answer;
|
||||
}
|
||||
|
||||
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
|
||||
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
|
||||
{
|
||||
writer.which = CapDescriptor.WHICH.SenderHosted;
|
||||
writer.SenderHosted = endpoint.AllocateExport(MyVine, out bool _);
|
||||
}
|
||||
|
||||
internal override void Freeze(out IRpcEndpoint? boundEndpoint)
|
||||
{
|
||||
boundEndpoint = null;
|
||||
}
|
||||
|
||||
internal override void Unfreeze()
|
||||
{
|
||||
writer.SenderHosted = endpoint.AllocateExport(AsSkeleton(), out bool _);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -9,9 +9,6 @@ namespace Capnp.Rpc.Interception
|
||||
/// </summary>
|
||||
public static class Interceptor
|
||||
{
|
||||
static readonly ConditionalWeakTable<ConsumedCapability, CensorCapability> _interceptMap =
|
||||
new ConditionalWeakTable<ConsumedCapability, CensorCapability>();
|
||||
|
||||
/// <summary>
|
||||
/// Attach this policy to given capability.
|
||||
/// </summary>
|
||||
@ -45,16 +42,16 @@ namespace Capnp.Rpc.Interception
|
||||
switch (cap)
|
||||
{
|
||||
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:
|
||||
return (new CensorCapability(ccap, policy) as TCap)!;
|
||||
|
||||
default:
|
||||
return (Attach(policy,
|
||||
(CapabilityReflection.CreateProxy<TCap>(
|
||||
LocalCapability.Create(
|
||||
Skeleton.GetOrCreateSkeleton(cap, false))) as TCap)!));
|
||||
var temp = (CapabilityReflection.CreateProxy<TCap>(
|
||||
CapabilityReflection.CreateSkeletonInternal(cap).AsCapability())) as TCap;
|
||||
return Attach(policy, temp!)!;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,39 +1,76 @@
|
||||
using System;
|
||||
using Capnp.Util;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Capnp.Rpc
|
||||
{
|
||||
|
||||
class LazyCapability : RefCountingCapability, IResolvingCapability
|
||||
{
|
||||
public static LazyCapability CreateBrokenCap(string message)
|
||||
{
|
||||
var cap = new LazyCapability(Task.FromException<Proxy>(new RpcException(message)));
|
||||
cap.AddRef(); // Instance shall be persistent
|
||||
return cap;
|
||||
return new LazyCapability(Task.FromException<ConsumedCapability>(new RpcException(message)));
|
||||
}
|
||||
|
||||
public static LazyCapability CreateCanceledCap(CancellationToken token)
|
||||
{
|
||||
var cap = new LazyCapability(Task.FromCanceled<Proxy>(token));
|
||||
cap.AddRef(); // Instance shall be persistent
|
||||
return cap;
|
||||
return new LazyCapability(Task.FromCanceled<ConsumedCapability>(token));
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
WhenResolved.Result.Freeze(out boundEndpoint);
|
||||
return (CapabilityReflection.CreateProxy<T>(_capTask.Result) as T)!;
|
||||
}
|
||||
catch (AggregateException exception)
|
||||
{
|
||||
@ -42,55 +79,31 @@ namespace Capnp.Rpc
|
||||
}
|
||||
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)
|
||||
{
|
||||
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)
|
||||
throw new RpcException("Broken capability");
|
||||
|
||||
var call = cap.Call(interfaceId, methodId, args, default);
|
||||
using var proxy = new Proxy(cap);
|
||||
var call = proxy.Call(interfaceId, methodId, args, default);
|
||||
var whenReturned = call.WhenReturned;
|
||||
|
||||
using (var registration = cancellationToken.Register(call.Dispose))
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Capnp.Util;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -11,42 +12,36 @@ namespace Capnp.Rpc
|
||||
public LocalAnswer(CancellationTokenSource cts, Task<DeserializerState> call)
|
||||
{
|
||||
_cts = cts ?? throw new ArgumentNullException(nameof(cts));
|
||||
WhenReturned = call ?? throw new ArgumentNullException(nameof(call));
|
||||
WhenReturned = call?.EnforceAwaitOrder() ?? throw new ArgumentNullException(nameof(call));
|
||||
|
||||
CleanupAfterReturn();
|
||||
}
|
||||
|
||||
async void CleanupAfterReturn()
|
||||
{
|
||||
try
|
||||
{
|
||||
await WhenReturned;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
_cts.Dispose();
|
||||
}
|
||||
try { await WhenReturned; }
|
||||
catch { }
|
||||
finally { _cts.Dispose(); }
|
||||
}
|
||||
|
||||
public Task<DeserializerState> WhenReturned { get; }
|
||||
public StrictlyOrderedAwaitTask<DeserializerState> WhenReturned { get; }
|
||||
|
||||
public bool IsTailCall => false;
|
||||
|
||||
public ConsumedCapability Access(MemberAccessPath access)
|
||||
{
|
||||
return new LocalAnswerCapability(WhenReturned, access);
|
||||
}
|
||||
|
||||
public ConsumedCapability Access(MemberAccessPath _, Task<IDisposable?> task)
|
||||
{
|
||||
return new LocalAnswerCapability(task.AsProxyTask());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
_cts.Cancel();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
try { _cts.Cancel(); }
|
||||
catch (ObjectDisposedException) { }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,78 +1,77 @@
|
||||
using System;
|
||||
using Capnp.Util;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Capnp.Rpc
|
||||
{
|
||||
|
||||
class LocalAnswerCapability : RefCountingCapability, IResolvingCapability
|
||||
{
|
||||
readonly Task<DeserializerState> _answer;
|
||||
readonly MemberAccessPath _access;
|
||||
|
||||
public LocalAnswerCapability(Task<DeserializerState> answer, MemberAccessPath access)
|
||||
static async Task<Proxy> TransferOwnershipToDummyProxy(StrictlyOrderedAwaitTask<DeserializerState> answer, MemberAccessPath access)
|
||||
{
|
||||
_answer = answer;
|
||||
_access = access;
|
||||
var result = await answer;
|
||||
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()
|
||||
{
|
||||
var state = await _answer;
|
||||
return new Proxy(_access.Eval(state));
|
||||
}
|
||||
public StrictlyOrderedAwaitTask WhenResolved => _whenResolvedProxy;
|
||||
|
||||
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
|
||||
{
|
||||
result = _answer.Result;
|
||||
_whenResolvedProxy.Result.Export(endpoint, writer);
|
||||
}
|
||||
catch (AggregateException exception)
|
||||
{
|
||||
throw exception.InnerException!;
|
||||
}
|
||||
|
||||
using (var proxy = new Proxy(_access.Eval(result)))
|
||||
{
|
||||
proxy.Export(endpoint, writer);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ExportAsSenderPromise(endpoint, writer);
|
||||
return this.ExportAsSenderPromise(endpoint, writer);
|
||||
}
|
||||
}
|
||||
|
||||
async Task<DeserializerState> CallImpl(ulong interfaceId, ushort methodId, DynamicSerializerState args, CancellationToken cancellationToken)
|
||||
{
|
||||
var cap = await AwaitResolved();
|
||||
var proxy = await _whenResolvedProxy;
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (cap == null)
|
||||
if (proxy.IsNull)
|
||||
{
|
||||
args.Dispose();
|
||||
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;
|
||||
|
||||
using (var registration = cancellationToken.Register(() => call.Dispose()))
|
||||
{
|
||||
return await whenReturned;
|
||||
}
|
||||
using var registration = cancellationToken.Register(() => call.Dispose());
|
||||
return await whenReturned;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
protected override void ReleaseRemotely()
|
||||
protected async override void ReleaseRemotely()
|
||||
{
|
||||
this.DisposeWhenResolved();
|
||||
try { using var _ = await _whenResolvedProxy; }
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
@ -7,17 +7,6 @@ namespace Capnp.Rpc
|
||||
{
|
||||
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)
|
||||
{
|
||||
var aorcq = await call;
|
||||
@ -26,7 +15,9 @@ namespace Capnp.Rpc
|
||||
|
||||
public Skeleton ProvidedCap { get; }
|
||||
|
||||
LocalCapability(Skeleton providedCap)
|
||||
internal override Skeleton AsSkeleton() => ProvidedCap;
|
||||
|
||||
public LocalCapability(Skeleton providedCap)
|
||||
{
|
||||
ProvidedCap = providedCap ?? throw new ArgumentNullException(nameof(providedCap));
|
||||
}
|
||||
@ -36,10 +27,7 @@ namespace Capnp.Rpc
|
||||
ProvidedCap.Claim();
|
||||
}
|
||||
|
||||
internal override void Release(
|
||||
[System.Runtime.CompilerServices.CallerMemberName] string methodName = "",
|
||||
[System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
|
||||
[System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0)
|
||||
internal override void Release()
|
||||
{
|
||||
ProvidedCap.Relinquish();
|
||||
}
|
||||
@ -51,19 +39,11 @@ namespace Capnp.Rpc
|
||||
return new LocalAnswer(cts, AwaitAnswer(call));
|
||||
}
|
||||
|
||||
internal override void Export(IRpcEndpoint endpoint, CapDescriptor.WRITER capDesc)
|
||||
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER capDesc)
|
||||
{
|
||||
capDesc.which = CapDescriptor.WHICH.SenderHosted;
|
||||
capDesc.SenderHosted = endpoint.AllocateExport(ProvidedCap, out bool _);
|
||||
}
|
||||
|
||||
internal override void Freeze(out IRpcEndpoint? boundEndpoint)
|
||||
{
|
||||
boundEndpoint = null;
|
||||
}
|
||||
|
||||
internal override void Unfreeze()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override void ReleaseRemotely()
|
||||
|
@ -132,7 +132,7 @@ namespace Capnp.Rpc
|
||||
{
|
||||
if (state.Kind == ObjectKind.Nil)
|
||||
{
|
||||
return default(DeserializerState);
|
||||
return default;
|
||||
}
|
||||
|
||||
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>
|
||||
/// <returns>Resulting low-level capability</returns>
|
||||
/// <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;
|
||||
|
||||
@ -181,7 +181,7 @@ namespace Capnp.Rpc
|
||||
switch (cur.Kind)
|
||||
{
|
||||
case ObjectKind.Nil:
|
||||
return null;
|
||||
return NullCapability.Instance;
|
||||
|
||||
case ObjectKind.Capability:
|
||||
return rpcState.Caps![(int)cur.CapabilityIndex];
|
||||
|
16
Capnp.Net.Runtime/Rpc/NoResultsException.cs
Normal file
16
Capnp.Net.Runtime/Rpc/NoResultsException.cs
Normal 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")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
54
Capnp.Net.Runtime/Rpc/NullCapability.cs
Normal file
54
Capnp.Net.Runtime/Rpc/NullCapability.cs
Normal 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";
|
||||
}
|
||||
}
|
41
Capnp.Net.Runtime/Rpc/NullSkeleton.cs
Normal file
41
Capnp.Net.Runtime/Rpc/NullSkeleton.cs
Normal 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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using Capnp.Util;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -8,7 +10,7 @@ namespace Capnp.Rpc
|
||||
{
|
||||
readonly CancellationTokenSource? _cts;
|
||||
readonly TaskCompletionSource<AnswerOrCounterquestion> _cancelCompleter;
|
||||
readonly Task<AnswerOrCounterquestion> _answerTask;
|
||||
readonly StrictlyOrderedAwaitTask<AnswerOrCounterquestion> _answerTask;
|
||||
|
||||
public PendingAnswer(Task<AnswerOrCounterquestion> callTask, CancellationTokenSource? cts)
|
||||
{
|
||||
@ -22,92 +24,136 @@ namespace Capnp.Rpc
|
||||
|
||||
_cts = cts;
|
||||
_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 IReadOnlyList<CapDescriptor.WRITER>? CapTable { get; set; }
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_cancelCompleter.SetCanceled();
|
||||
}
|
||||
|
||||
public void Chain(Action<Task<AnswerOrCounterquestion>> func)
|
||||
public void Chain(Action<StrictlyOrderedAwaitTask<AnswerOrCounterquestion>> func)
|
||||
{
|
||||
func(_answerTask);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
foreach (var op in rd.Transform)
|
||||
switch (op.which)
|
||||
{
|
||||
switch (op.which)
|
||||
{
|
||||
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:
|
||||
case PromisedAnswer.Op.WHICH.GetPointerField:
|
||||
try
|
||||
{
|
||||
var cap = aorcq.Answer.Caps![(int)cur.CapabilityIndex];
|
||||
proxy = new Proxy(cap ?? LazyCapability.Null);
|
||||
cur = cur.StructReadPointer(op.GetPointerField);
|
||||
}
|
||||
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:
|
||||
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);
|
||||
var cap = new RemoteAnswerCapability(aorcq.Counterquestion!, path);
|
||||
return new Proxy(cap);
|
||||
case ObjectKind.Capability:
|
||||
try
|
||||
{
|
||||
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()
|
||||
{
|
||||
_cts?.Dispose();
|
||||
ReleaseCapTableOwnership();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using Capnp.Util;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -48,36 +50,25 @@ namespace Capnp.Rpc
|
||||
/// <summary>
|
||||
/// Question object was disposed.
|
||||
/// </summary>
|
||||
Disposed = 16,
|
||||
|
||||
/// <summary>
|
||||
/// Question object was finalized by GC.
|
||||
/// This flag should only be observable when debugging the finalizer itself.
|
||||
/// </summary>
|
||||
Finalized = 32
|
||||
CanceledByDispose = 16
|
||||
}
|
||||
|
||||
readonly TaskCompletionSource<DeserializerState> _tcs = new TaskCompletionSource<DeserializerState>();
|
||||
readonly StrictlyOrderedAwaitTask<DeserializerState> _whenReturned;
|
||||
readonly uint _questionId;
|
||||
ConsumedCapability? _target;
|
||||
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));
|
||||
_questionId = id;
|
||||
_target = target;
|
||||
_inParams = inParams;
|
||||
StateFlags = inParams == null ? State.Sent : State.None;
|
||||
_whenReturned = _tcs.Task.EnforceAwaitOrder();
|
||||
|
||||
if (inParams != null)
|
||||
{
|
||||
foreach (var cap in inParams.Caps!)
|
||||
{
|
||||
cap?.AddRef();
|
||||
}
|
||||
}
|
||||
StateFlags = inParams == null ? State.Sent : State.None;
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
@ -89,16 +80,20 @@ namespace Capnp.Rpc
|
||||
internal object ReentrancyBlocker { get; } = new object();
|
||||
internal uint QuestionId => _questionId;
|
||||
internal State StateFlags { get; private set; }
|
||||
internal IReadOnlyList<CapDescriptor.WRITER>? CapTable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Eventually returns the server answer
|
||||
/// </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);
|
||||
set
|
||||
internal set
|
||||
{
|
||||
if (value)
|
||||
StateFlags |= State.TailCall;
|
||||
@ -106,7 +101,6 @@ namespace Capnp.Rpc
|
||||
StateFlags &= ~State.TailCall;
|
||||
}
|
||||
}
|
||||
internal bool IsReturned => StateFlags.HasFlag(State.Returned);
|
||||
|
||||
internal void DisallowFinish()
|
||||
{
|
||||
@ -119,7 +113,25 @@ namespace Capnp.Rpc
|
||||
AutoFinish();
|
||||
}
|
||||
|
||||
internal void AddRef()
|
||||
{
|
||||
lock (ReentrancyBlocker)
|
||||
{
|
||||
++_refCounter;
|
||||
}
|
||||
}
|
||||
|
||||
internal void Release()
|
||||
{
|
||||
lock (ReentrancyBlocker)
|
||||
{
|
||||
--_refCounter;
|
||||
AutoFinish();
|
||||
}
|
||||
}
|
||||
|
||||
const string ReturnDespiteTailCallMessage = "Peer sent actual results despite the question was sent as tail call. This was not expected and is a protocol error.";
|
||||
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)
|
||||
{
|
||||
@ -131,6 +143,7 @@ namespace Capnp.Rpc
|
||||
if (StateFlags.HasFlag(State.TailCall))
|
||||
{
|
||||
_tcs.TrySetException(new RpcException(ReturnDespiteTailCallMessage));
|
||||
throw new RpcProtocolErrorException(ReturnDespiteTailCallMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -150,7 +163,8 @@ namespace Capnp.Rpc
|
||||
|
||||
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
|
||||
{
|
||||
@ -165,7 +179,7 @@ namespace Capnp.Rpc
|
||||
SetReturned();
|
||||
}
|
||||
|
||||
_tcs.TrySetException(new RpcException(exception.Reason));
|
||||
_tcs.TrySetException(new RpcException(exception.Reason ?? "unknown reason"));
|
||||
}
|
||||
|
||||
internal void OnException(System.Exception exception)
|
||||
@ -193,11 +207,6 @@ namespace Capnp.Rpc
|
||||
RpcEndpoint.DeleteQuestion(this);
|
||||
}
|
||||
|
||||
internal void RequestFinish()
|
||||
{
|
||||
RpcEndpoint.Finish(_questionId);
|
||||
}
|
||||
|
||||
void AutoFinish()
|
||||
{
|
||||
if (StateFlags.HasFlag(State.FinishRequested))
|
||||
@ -205,12 +214,13 @@ namespace Capnp.Rpc
|
||||
return;
|
||||
}
|
||||
|
||||
if ((_inhibitFinishCounter == 0 && StateFlags.HasFlag(State.Returned) && !StateFlags.HasFlag(State.TailCall))
|
||||
|| StateFlags.HasFlag(State.Disposed))
|
||||
if ((!IsTailCall && _inhibitFinishCounter == 0 && StateFlags.HasFlag(State.Returned)) ||
|
||||
( IsTailCall && _refCounter == 0 && StateFlags.HasFlag(State.Returned)) ||
|
||||
StateFlags.HasFlag(State.CanceledByDispose))
|
||||
{
|
||||
StateFlags |= State.FinishRequested;
|
||||
|
||||
RequestFinish();
|
||||
RpcEndpoint.Finish(_questionId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -234,50 +244,27 @@ namespace Capnp.Rpc
|
||||
/// <param name="access">Access path</param>
|
||||
/// <returns>Low-level capability</returns>
|
||||
/// <exception cref="DeserializationException">The referenced member does not exist or does not resolve to a capability pointer.</exception>
|
||||
public ConsumedCapability? Access(MemberAccessPath 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
public ConsumedCapability Access(MemberAccessPath access) => 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)
|
||||
{
|
||||
foreach (var cap in inParams.Caps!)
|
||||
{
|
||||
cap?.Release();
|
||||
}
|
||||
}
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
target.Release();
|
||||
}
|
||||
var proxyTask = task.AsProxyTask();
|
||||
return new RemoteAnswerCapability(this, access, proxyTask);
|
||||
}
|
||||
|
||||
static void ReleaseOutCaps(DeserializerState outParams)
|
||||
{
|
||||
foreach (var cap in outParams.Caps!)
|
||||
{
|
||||
cap?.Release();
|
||||
cap.Release();
|
||||
}
|
||||
}
|
||||
|
||||
@ -299,70 +286,24 @@ namespace Capnp.Rpc
|
||||
}
|
||||
|
||||
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;
|
||||
call.QuestionId = QuestionId;
|
||||
call.SendResultsTo.which = IsTailCall ?
|
||||
Call.sendResultsTo.WHICH.Yourself :
|
||||
call.SendResultsTo.which = IsTailCall ?
|
||||
Call.sendResultsTo.WHICH.Yourself :
|
||||
Call.sendResultsTo.WHICH.Caller;
|
||||
|
||||
try
|
||||
{
|
||||
RpcEndpoint.SendQuestion(inParams, call.Params);
|
||||
CapTable = call.Params.CapTable;
|
||||
}
|
||||
catch (System.Exception exception)
|
||||
{
|
||||
OnException(exception);
|
||||
}
|
||||
|
||||
ReleaseCaps(target!, inParams);
|
||||
}
|
||||
|
||||
#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);
|
||||
target?.Release();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -370,9 +311,23 @@ namespace Capnp.Rpc
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
bool justDisposed = false;
|
||||
|
||||
lock (ReentrancyBlocker)
|
||||
{
|
||||
if (!StateFlags.HasFlag(State.CanceledByDispose))
|
||||
{
|
||||
StateFlags |= State.CanceledByDispose;
|
||||
justDisposed = true;
|
||||
|
||||
AutoFinish();
|
||||
}
|
||||
}
|
||||
|
||||
if (justDisposed)
|
||||
{
|
||||
_tcs.TrySetCanceled();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -8,7 +9,7 @@ namespace Capnp.Rpc
|
||||
/// <summary>
|
||||
/// Combines multiple skeletons to represent objects which implement multiple interfaces.
|
||||
/// </summary>
|
||||
public class PolySkeleton: Skeleton
|
||||
public class PolySkeleton: RefCountingSkeleton
|
||||
{
|
||||
readonly Dictionary<ulong, Skeleton> _ifmap = new Dictionary<ulong, Skeleton>();
|
||||
|
||||
@ -24,8 +25,9 @@ namespace Capnp.Rpc
|
||||
if (skeleton == null)
|
||||
throw new ArgumentNullException(nameof(skeleton));
|
||||
|
||||
skeleton.Claim();
|
||||
_ifmap.Add(interfaceId, skeleton);
|
||||
if (_ifmap.Count == 1) // Claiming only the first one is sufficient
|
||||
skeleton.Claim();
|
||||
}
|
||||
|
||||
internal void AddInterface(Skeleton skeleton)
|
||||
@ -54,17 +56,16 @@ namespace Capnp.Rpc
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
internal override void Bind(object impl)
|
||||
{
|
||||
foreach (Skeleton skel in _ifmap.Values)
|
||||
foreach (Skeleton skel in _ifmap.Values)
|
||||
{
|
||||
skel.Bind(impl);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Capnp.Util;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -8,78 +9,30 @@ namespace Capnp.Rpc
|
||||
{
|
||||
readonly uint _remoteId;
|
||||
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;
|
||||
|
||||
public PromisedCapability(IRpcEndpoint ep, uint remoteId): base(ep)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
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)
|
||||
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
|
||||
{
|
||||
lock (_reentrancyBlocker)
|
||||
{
|
||||
|
||||
if (_resolvedCap.Task.ReplacementTaskIsCompletedSuccessfully())
|
||||
{
|
||||
_resolvedCap.Task.Result.Export(endpoint, writer);
|
||||
using var proxy = new Proxy(_resolvedCap.Task.Result);
|
||||
proxy.Export(endpoint, writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -91,7 +44,7 @@ namespace Capnp.Rpc
|
||||
Debug.Assert(!_released);
|
||||
++_pendingCallsOnPromise;
|
||||
|
||||
_ep.RequestPostAction(() =>
|
||||
return () =>
|
||||
{
|
||||
bool release = false;
|
||||
|
||||
@ -108,7 +61,7 @@ namespace Capnp.Rpc
|
||||
{
|
||||
_ep.ReleaseImport(_remoteId);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -116,9 +69,11 @@ namespace Capnp.Rpc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async void TrackCall(Task call)
|
||||
async void TrackCall(StrictlyOrderedAwaitTask call)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -147,7 +102,7 @@ namespace Capnp.Rpc
|
||||
}
|
||||
}
|
||||
|
||||
protected override Proxy? ResolvedCap
|
||||
protected override ConsumedCapability? ResolvedCap
|
||||
{
|
||||
get
|
||||
{
|
||||
@ -194,7 +149,11 @@ namespace Capnp.Rpc
|
||||
|
||||
lock (_reentrancyBlocker)
|
||||
{
|
||||
_resolvedCap.SetResult(new Proxy(resolvedCap));
|
||||
#if DebugFinalizers
|
||||
if (resolvedCap != null)
|
||||
resolvedCap.ResolvingCap = this;
|
||||
#endif
|
||||
_resolvedCap.SetResult(resolvedCap!);
|
||||
|
||||
if (_pendingCallsOnPromise == 0)
|
||||
{
|
||||
@ -218,7 +177,7 @@ namespace Capnp.Rpc
|
||||
#if false
|
||||
_resolvedCap.SetException(new RpcException(message));
|
||||
#else
|
||||
_resolvedCap.SetResult(new Proxy(LazyCapability.CreateBrokenCap(message)));
|
||||
_resolvedCap.SetResult(LazyCapability.CreateBrokenCap(message));
|
||||
#endif
|
||||
|
||||
if (_pendingCallsOnPromise == 0)
|
||||
@ -234,16 +193,16 @@ namespace Capnp.Rpc
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ReleaseRemotely()
|
||||
protected async override void ReleaseRemotely()
|
||||
{
|
||||
if (!_released)
|
||||
{
|
||||
_released = true;
|
||||
_ep.ReleaseImport(_remoteId);
|
||||
}
|
||||
|
||||
_ep.ReleaseImport(_remoteId);
|
||||
|
||||
this.DisposeWhenResolved();
|
||||
try { using var _ = await _whenResolvedProxy; }
|
||||
catch { }
|
||||
}
|
||||
|
||||
protected override Call.WRITER SetupMessage(DynamicSerializerState args, ulong interfaceId, ushort methodId)
|
||||
|
@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using Capnp.Util;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -10,53 +13,70 @@ namespace Capnp.Rpc
|
||||
/// </summary>
|
||||
public class Proxy : IDisposable, IResolvingCapability
|
||||
{
|
||||
#if DebugFinalizers
|
||||
ILogger Logger { get; } = Logging.CreateLogger<Proxy>();
|
||||
#endif
|
||||
/// <summary>
|
||||
/// Creates a new proxy object for an existing implementation or proxy, sharing its ownership.
|
||||
/// </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;
|
||||
|
||||
/// <summary>
|
||||
/// Will eventually give the resolved capability, if this is a promised capability.
|
||||
/// Completes when the capability gets resolved.
|
||||
/// </summary>
|
||||
public Task<Proxy> WhenResolved
|
||||
public StrictlyOrderedAwaitTask WhenResolved
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ConsumedCap is IResolvingCapability resolving)
|
||||
{
|
||||
return resolving.WhenResolved;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Task.FromResult(this);
|
||||
}
|
||||
return ConsumedCap is IResolvingCapability resolving ?
|
||||
resolving.WhenResolved : Task.CompletedTask.EnforceAwaitOrder();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Underlying low-level capability
|
||||
/// </summary>
|
||||
protected internal ConsumedCapability? ConsumedCap { get; private set; }
|
||||
public ConsumedCapability ConsumedCap => _disposedValue ?
|
||||
throw new ObjectDisposedException(nameof(Proxy)) : _consumedCap;
|
||||
|
||||
/// <summary>
|
||||
/// Whether is this a broken capability.
|
||||
/// </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)
|
||||
{
|
||||
try
|
||||
{
|
||||
await answer.WhenReturned;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
ctr.Dispose();
|
||||
}
|
||||
try { await answer.WhenReturned; }
|
||||
catch { }
|
||||
finally { ctr.Dispose(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -76,10 +96,10 @@ namespace Capnp.Rpc
|
||||
bool obsoleteAndIgnored, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_disposedValue)
|
||||
{
|
||||
args.Dispose();
|
||||
throw new ObjectDisposedException(nameof(Proxy));
|
||||
|
||||
if (ConsumedCap == null)
|
||||
throw new InvalidOperationException("Cannot call null capability");
|
||||
}
|
||||
|
||||
var answer = ConsumedCap.DoCall(interfaceId, methodId, args);
|
||||
|
||||
@ -96,38 +116,34 @@ namespace Capnp.Rpc
|
||||
/// </summary>
|
||||
public Proxy()
|
||||
{
|
||||
#if DebugFinalizers
|
||||
CreatorStackTrace = Environment.StackTrace;
|
||||
#endif
|
||||
}
|
||||
|
||||
internal Proxy(ConsumedCapability? cap)
|
||||
internal Proxy(ConsumedCapability cap): this()
|
||||
{
|
||||
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");
|
||||
|
||||
if (cap == null)
|
||||
return;
|
||||
|
||||
ConsumedCap = cap;
|
||||
_consumedCap = cap ?? throw new ArgumentNullException(nameof(cap));
|
||||
cap.AddRef();
|
||||
|
||||
#if DebugFinalizers
|
||||
if (_consumedCap != null)
|
||||
_consumedCap.OwningProxy = this;
|
||||
#endif
|
||||
}
|
||||
|
||||
internal IProvidedCapability? GetProvider()
|
||||
internal async Task<Skeleton> GetProvider()
|
||||
{
|
||||
switch (ConsumedCap)
|
||||
{
|
||||
case LocalCapability lcap:
|
||||
return lcap.ProvidedCap;
|
||||
|
||||
case null:
|
||||
return null;
|
||||
|
||||
default:
|
||||
return Vine.Create(ConsumedCap);
|
||||
}
|
||||
var unwrapped = await ConsumedCap.Unwrap();
|
||||
return unwrapped.AsSkeleton();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -139,20 +155,15 @@ namespace Capnp.Rpc
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
ConsumedCap?.Release();
|
||||
_consumedCap.Release();
|
||||
}
|
||||
else
|
||||
{
|
||||
// When called from the Finalizer, we must not throw.
|
||||
// But when reference counting goes wrong, ConsumedCapability.Release() will throw an InvalidOperationException.
|
||||
// The only option here is to suppress that exception.
|
||||
try
|
||||
{
|
||||
ConsumedCap?.Release();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
try { _consumedCap?.Release(); }
|
||||
catch { }
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
@ -165,7 +176,7 @@ namespace Capnp.Rpc
|
||||
~Proxy()
|
||||
{
|
||||
#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
|
||||
|
||||
Dispose(false);
|
||||
@ -187,54 +198,37 @@ namespace Capnp.Rpc
|
||||
/// <param name="disposeThis">Whether to Dispose() this Proxy instance</param>
|
||||
/// <returns>Proxy for desired capability interface</returns>
|
||||
/// <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="System.Reflection.TargetInvocationException">Problem with instatiating the Proxy (constructor threw exception).</exception>
|
||||
/// <exception cref="MemberAccessException">Caller does not have permission to invoke the Proxy constructor.</exception>
|
||||
/// <exception cref="TypeLoadException">Problem with building the Proxy type, or problem with loading some dependent class.</exception>
|
||||
public T Cast<T>(bool disposeThis) where T: class
|
||||
{
|
||||
if (IsNull)
|
||||
throw new InvalidOperationException("Capability is broken");
|
||||
|
||||
using (disposeThis ? this : null)
|
||||
{
|
||||
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)
|
||||
throw new ObjectDisposedException(nameof(Proxy));
|
||||
|
||||
if (ConsumedCap == null)
|
||||
{
|
||||
writer.which = CapDescriptor.WHICH.None;
|
||||
return null;
|
||||
}
|
||||
else
|
||||
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();
|
||||
{
|
||||
return ConsumedCap.Export(endpoint, writer);
|
||||
}
|
||||
}
|
||||
|
||||
#if DebugFinalizers
|
||||
public string CreatorMemberName { get; set; }
|
||||
public string CreatorFilePath { get; set; }
|
||||
public int CreatorLineNumber { get; set; }
|
||||
string CreatorStackTrace { get; set; }
|
||||
#endif
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ namespace Capnp.Rpc
|
||||
abstract class RefCountingCapability: ConsumedCapability
|
||||
{
|
||||
readonly object _reentrancyBlocker = new object();
|
||||
Vine? _vine;
|
||||
|
||||
// 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
|
||||
@ -26,16 +27,24 @@ namespace Capnp.Rpc
|
||||
// Value 0 has the special meaning of being in state C.
|
||||
long _refCount = 1;
|
||||
|
||||
#if DebugCapabilityLifecycle
|
||||
#if DebugFinalizers
|
||||
ILogger Logger { get; } = Logging.CreateLogger<RefCountingCapability>();
|
||||
|
||||
string? _releasingMethodName;
|
||||
string? _releasingFilePath;
|
||||
int _releasingLineNumber;
|
||||
string CreatorStackTrace { get; set; }
|
||||
#endif
|
||||
|
||||
public RefCountingCapability()
|
||||
{
|
||||
#if DebugFinalizers
|
||||
CreatorStackTrace = Environment.StackTrace;
|
||||
#endif
|
||||
}
|
||||
|
||||
~RefCountingCapability()
|
||||
{
|
||||
#if DebugFinalizers
|
||||
Logger?.LogWarning($"Caught orphaned capability, created from here: {CreatorStackTrace}.");
|
||||
#endif
|
||||
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
@ -46,13 +55,8 @@ namespace Capnp.Rpc
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
try
|
||||
{
|
||||
ReleaseRemotely();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
try { ReleaseRemotely(); }
|
||||
catch { }
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -60,19 +64,14 @@ namespace Capnp.Rpc
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
ReleaseRemotely();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
try { ReleaseRemotely(); }
|
||||
catch { }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed override void AddRef()
|
||||
internal override void AddRef()
|
||||
{
|
||||
lock (_reentrancyBlocker)
|
||||
{
|
||||
@ -90,10 +89,7 @@ namespace Capnp.Rpc
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed override void Release(
|
||||
[System.Runtime.CompilerServices.CallerMemberName] string methodName = "",
|
||||
[System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
|
||||
[System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0)
|
||||
internal override void Release()
|
||||
{
|
||||
lock (_reentrancyBlocker)
|
||||
{
|
||||
@ -103,12 +99,6 @@ namespace Capnp.Rpc
|
||||
case 2: // actually ref. count 1
|
||||
_refCount = 0;
|
||||
|
||||
#if DebugCapabilityLifecycle
|
||||
_releasingMethodName = methodName;
|
||||
_releasingFilePath = filePath;
|
||||
_releasingLineNumber = lineNumber;
|
||||
#endif
|
||||
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
break;
|
||||
@ -133,5 +123,15 @@ namespace Capnp.Rpc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override Skeleton AsSkeleton()
|
||||
{
|
||||
lock (_reentrancyBlocker)
|
||||
{
|
||||
if (_vine == null)
|
||||
_vine = new Vine(this);
|
||||
return _vine;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
80
Capnp.Net.Runtime/Rpc/RefCountingSkeleton.cs
Normal file
80
Capnp.Net.Runtime/Rpc/RefCountingSkeleton.cs
Normal 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)!;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Capnp.Util;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Capnp.Rpc
|
||||
@ -16,17 +17,29 @@ namespace Capnp.Rpc
|
||||
|
||||
readonly PendingQuestion _question;
|
||||
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));
|
||||
_access = access ?? throw new ArgumentNullException(nameof(access));
|
||||
|
||||
_ = AwaitWhenResolved();
|
||||
_whenResolvedProxy = (proxyTask ?? throw new ArgumentNullException(nameof(proxyTask))).EnforceAwaitOrder();
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
@ -47,57 +60,39 @@ namespace Capnp.Rpc
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
lock (_question.ReentrancyBlocker)
|
||||
{
|
||||
_resolvedCap?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected override Proxy? ResolvedCap
|
||||
protected override ConsumedCapability? ResolvedCap
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_question.ReentrancyBlocker)
|
||||
{
|
||||
if (_resolvedCap == null && !_question.IsTailCall && _question.IsReturned)
|
||||
if (!_question.IsTailCall && _question.StateFlags.HasFlag(PendingQuestion.State.Returned))
|
||||
{
|
||||
DeserializerState result;
|
||||
try
|
||||
{
|
||||
result = _question.WhenReturned.Result;
|
||||
return _whenResolvedProxy.Result.ConsumedCap;
|
||||
}
|
||||
catch (AggregateException exception)
|
||||
{
|
||||
throw exception.InnerException!;
|
||||
}
|
||||
|
||||
_resolvedCap = new Proxy(_access.Eval(result));
|
||||
}
|
||||
return _resolvedCap;
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async Task<Proxy> AwaitWhenResolved()
|
||||
{
|
||||
await _question.WhenReturned;
|
||||
public override StrictlyOrderedAwaitTask WhenResolved => _whenResolvedProxy;
|
||||
|
||||
if (_question.IsTailCall)
|
||||
throw new InvalidOperationException("Question is a tail call, so won't resolve back.");
|
||||
|
||||
return ResolvedCap!;
|
||||
}
|
||||
|
||||
public override Task<Proxy> WhenResolved => AwaitWhenResolved();
|
||||
public override T? GetResolvedCapability<T>() where T: class => _whenResolvedProxy.WrappedTask.GetResolvedCapability<T>();
|
||||
|
||||
protected override void GetMessageTarget(MessageTarget.WRITER wr)
|
||||
{
|
||||
wr.which = MessageTarget.WHICH.PromisedAnswer;
|
||||
wr.PromisedAnswer.QuestionId = _question.QuestionId;
|
||||
wr.PromisedAnswer!.QuestionId = _question.QuestionId;
|
||||
_access.Serialize(wr.PromisedAnswer);
|
||||
}
|
||||
|
||||
@ -105,14 +100,9 @@ namespace Capnp.Rpc
|
||||
{
|
||||
lock (_question.ReentrancyBlocker)
|
||||
{
|
||||
if (_question.StateFlags.HasFlag(PendingQuestion.State.Returned) &&
|
||||
!_question.StateFlags.HasFlag(PendingQuestion.State.TailCall))
|
||||
if (!_question.StateFlags.HasFlag(PendingQuestion.State.TailCall) &&
|
||||
_question.StateFlags.HasFlag(PendingQuestion.State.Returned))
|
||||
{
|
||||
if (ResolvedCap == null)
|
||||
{
|
||||
throw new RpcException("Answer did not resolve to expected capability");
|
||||
}
|
||||
|
||||
return CallOnResolution(interfaceId, methodId, args);
|
||||
}
|
||||
else
|
||||
@ -120,16 +110,13 @@ namespace Capnp.Rpc
|
||||
#if DebugEmbargos
|
||||
Logger.LogDebug("Call by proxy");
|
||||
#endif
|
||||
if (_question.StateFlags.HasFlag(PendingQuestion.State.Disposed))
|
||||
if (_question.StateFlags.HasFlag(PendingQuestion.State.CanceledByDispose) ||
|
||||
_question.StateFlags.HasFlag(PendingQuestion.State.FinishRequested))
|
||||
{
|
||||
args.Dispose();
|
||||
throw new ObjectDisposedException(nameof(PendingQuestion));
|
||||
}
|
||||
|
||||
if (_question.StateFlags.HasFlag(PendingQuestion.State.FinishRequested))
|
||||
{
|
||||
throw new InvalidOperationException("Finish request was already sent");
|
||||
}
|
||||
|
||||
_question.DisallowFinish();
|
||||
++_pendingCallsOnPromise;
|
||||
var promisedAnswer = base.DoCall(interfaceId, methodId, args);
|
||||
@ -164,61 +151,22 @@ namespace Capnp.Rpc
|
||||
var call = base.SetupMessage(args, interfaceId, methodId);
|
||||
|
||||
call.Target.which = MessageTarget.WHICH.PromisedAnswer;
|
||||
call.Target.PromisedAnswer.QuestionId = _question.QuestionId;
|
||||
call.Target.PromisedAnswer!.QuestionId = _question.QuestionId;
|
||||
_access.Serialize(call.Target.PromisedAnswer);
|
||||
|
||||
return call;
|
||||
}
|
||||
|
||||
internal override void Freeze(out IRpcEndpoint? boundEndpoint)
|
||||
internal override Action? Export(IRpcEndpoint endpoint, CapDescriptor.WRITER writer)
|
||||
{
|
||||
lock (_question.ReentrancyBlocker)
|
||||
{
|
||||
if (_question.StateFlags.HasFlag(PendingQuestion.State.Returned) &&
|
||||
_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))
|
||||
if (_question.StateFlags.HasFlag(PendingQuestion.State.CanceledByDispose))
|
||||
throw new ObjectDisposedException(nameof(PendingQuestion));
|
||||
|
||||
if (_question.StateFlags.HasFlag(PendingQuestion.State.Returned))
|
||||
if (_question.StateFlags.HasFlag(PendingQuestion.State.Returned) && !_question.IsTailCall)
|
||||
{
|
||||
ResolvedCap?.Export(endpoint, writer);
|
||||
ResolvedCap!.Export(endpoint, writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -228,32 +176,45 @@ namespace Capnp.Rpc
|
||||
if (endpoint == _ep)
|
||||
{
|
||||
writer.which = CapDescriptor.WHICH.ReceiverAnswer;
|
||||
_access.Serialize(writer.ReceiverAnswer);
|
||||
writer.ReceiverAnswer.QuestionId = _question.QuestionId;
|
||||
_access.Serialize(writer.ReceiverAnswer!);
|
||||
writer.ReceiverAnswer!.QuestionId = _question.QuestionId;
|
||||
}
|
||||
else if (_question.IsTailCall)
|
||||
{
|
||||
// FIXME: Resource management! We should prevent finishing this
|
||||
// cap as long as it is exported. Unfortunately, we cannot determine
|
||||
// when it gets removed from the export table.
|
||||
|
||||
var vine = Vine.Create(this);
|
||||
uint id = endpoint.AllocateExport(vine, out bool first);
|
||||
uint id = endpoint.AllocateExport(AsSkeleton(), out bool first);
|
||||
|
||||
writer.which = CapDescriptor.WHICH.SenderHosted;
|
||||
writer.SenderHosted = id;
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -13,6 +13,8 @@ namespace Capnp.Rpc
|
||||
_ep = ep;
|
||||
}
|
||||
|
||||
internal IRpcEndpoint Endpoint => _ep;
|
||||
|
||||
internal override IPromisedAnswer DoCall(ulong interfaceId, ushort methodId, DynamicSerializerState args)
|
||||
{
|
||||
var call = SetupMessage(args, interfaceId, methodId);
|
||||
@ -29,7 +31,7 @@ namespace Capnp.Rpc
|
||||
|
||||
callMsg.which = Message.WHICH.Call;
|
||||
|
||||
var call = callMsg.Call;
|
||||
var call = callMsg.Call!;
|
||||
call.AllowThirdPartyTailCall = false;
|
||||
call.InterfaceId = interfaceId;
|
||||
call.MethodId = methodId;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user