mirror of https://github.com/grpc/grpc.git
parent
ea6f6d99f4
commit
a7608b081e
50 changed files with 4600 additions and 0 deletions
@ -0,0 +1,2 @@ |
|||||||
|
test-results |
||||||
|
bin |
@ -0,0 +1,74 @@ |
|||||||
|
using System; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Reactive.Linq; |
||||||
|
|
||||||
|
namespace math |
||||||
|
{ |
||||||
|
// /// <summary> |
||||||
|
// /// Dummy local implementation of math service. |
||||||
|
// /// </summary> |
||||||
|
// public class DummyMathServiceClient : IMathServiceClient |
||||||
|
// { |
||||||
|
// public DivReply Div(DivArgs args, CancellationToken token = default(CancellationToken)) |
||||||
|
// { |
||||||
|
// // TODO: cancellation... |
||||||
|
// return DivInternal(args); |
||||||
|
// } |
||||||
|
// |
||||||
|
// public Task<DivReply> DivAsync(DivArgs args, CancellationToken token = default(CancellationToken)) |
||||||
|
// { |
||||||
|
// return Task.Factory.StartNew(() => DivInternal(args), token); |
||||||
|
// } |
||||||
|
// |
||||||
|
// public IObservable<Num> Fib(FibArgs args, CancellationToken token = default(CancellationToken)) |
||||||
|
// { |
||||||
|
// if (args.Limit > 0) |
||||||
|
// { |
||||||
|
// // TODO: cancellation |
||||||
|
// return FibInternal(args.Limit).ToObservable(); |
||||||
|
// } |
||||||
|
// |
||||||
|
// throw new NotImplementedException("Not implemented yet"); |
||||||
|
// } |
||||||
|
// |
||||||
|
// public Task<Num> Sum(IObservable<Num> inputs, CancellationToken token = default(CancellationToken)) |
||||||
|
// { |
||||||
|
// // TODO: implement |
||||||
|
// inputs = null; |
||||||
|
// return Task.Factory.StartNew(() => Num.CreateBuilder().Build(), token); |
||||||
|
// } |
||||||
|
// |
||||||
|
// public IObservable<DivReply> DivMany(IObservable<DivArgs> inputs, CancellationToken token = default(CancellationToken)) |
||||||
|
// { |
||||||
|
// // TODO: implement |
||||||
|
// inputs = null; |
||||||
|
// return new List<DivReply> { }.ToObservable (); |
||||||
|
// } |
||||||
|
// |
||||||
|
// |
||||||
|
// DivReply DivInternal(DivArgs args) |
||||||
|
// { |
||||||
|
// long quotient = args.Dividend / args.Divisor; |
||||||
|
// long remainder = args.Dividend % args.Divisor; |
||||||
|
// return new DivReply.Builder{ Quotient = quotient, Remainder = remainder }.Build(); |
||||||
|
// } |
||||||
|
// |
||||||
|
// IEnumerable<Num> FibInternal(long n) |
||||||
|
// { |
||||||
|
// long a = 0; |
||||||
|
// yield return new Num.Builder{Num_=a}.Build(); |
||||||
|
// |
||||||
|
// long b = 1; |
||||||
|
// for (long i = 0; i < n - 1; i++) |
||||||
|
// { |
||||||
|
// long temp = a; |
||||||
|
// a = b; |
||||||
|
// b = temp + b; |
||||||
|
// yield return new Num.Builder{Num_=a}.Build(); |
||||||
|
// } |
||||||
|
// } |
||||||
|
// } |
||||||
|
} |
||||||
|
|
@ -0,0 +1,97 @@ |
|||||||
|
using System; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Reactive.Linq; |
||||||
|
|
||||||
|
namespace math |
||||||
|
{ |
||||||
|
public class Examples |
||||||
|
{ |
||||||
|
public static void DivExample(IMathServiceClient stub) |
||||||
|
{ |
||||||
|
DivReply result = stub.Div(new DivArgs.Builder { Dividend = 10, Divisor = 3 }.Build()); |
||||||
|
Console.WriteLine("Div Result: " + result); |
||||||
|
} |
||||||
|
|
||||||
|
public static void DivAsyncExample(IMathServiceClient stub) |
||||||
|
{ |
||||||
|
Task<DivReply> call = stub.DivAsync(new DivArgs.Builder { Dividend = 4, Divisor = 5 }.Build()); |
||||||
|
DivReply result = call.Result; |
||||||
|
Console.WriteLine(result); |
||||||
|
} |
||||||
|
|
||||||
|
public static void DivAsyncWithCancellationExample(IMathServiceClient stub) |
||||||
|
{ |
||||||
|
Task<DivReply> call = stub.DivAsync(new DivArgs.Builder { Dividend = 4, Divisor = 5 }.Build()); |
||||||
|
DivReply result = call.Result; |
||||||
|
Console.WriteLine(result); |
||||||
|
} |
||||||
|
|
||||||
|
public static void FibExample(IMathServiceClient stub) |
||||||
|
{ |
||||||
|
var recorder = new RecordingObserver<Num>(); |
||||||
|
stub.Fib(new FibArgs.Builder { Limit = 5 }.Build(), recorder); |
||||||
|
|
||||||
|
List<Num> numbers = recorder.ToList().Result; |
||||||
|
Console.WriteLine("Fib Result: " + string.Join("|", recorder.ToList().Result)); |
||||||
|
} |
||||||
|
|
||||||
|
public static void SumExample(IMathServiceClient stub) |
||||||
|
{ |
||||||
|
List<Num> numbers = new List<Num>{new Num.Builder { Num_ = 1 }.Build(), |
||||||
|
new Num.Builder { Num_ = 2 }.Build(), |
||||||
|
new Num.Builder { Num_ = 3 }.Build()}; |
||||||
|
|
||||||
|
var res = stub.Sum(); |
||||||
|
foreach (var num in numbers) { |
||||||
|
res.Inputs.OnNext(num); |
||||||
|
} |
||||||
|
res.Inputs.OnCompleted(); |
||||||
|
|
||||||
|
Console.WriteLine("Sum Result: " + res.Task.Result); |
||||||
|
} |
||||||
|
|
||||||
|
public static void DivManyExample(IMathServiceClient stub) |
||||||
|
{ |
||||||
|
List<DivArgs> divArgsList = new List<DivArgs>{ |
||||||
|
new DivArgs.Builder { Dividend = 10, Divisor = 3 }.Build(), |
||||||
|
new DivArgs.Builder { Dividend = 100, Divisor = 21 }.Build(), |
||||||
|
new DivArgs.Builder { Dividend = 7, Divisor = 2 }.Build() |
||||||
|
}; |
||||||
|
|
||||||
|
var recorder = new RecordingObserver<DivReply>(); |
||||||
|
|
||||||
|
var inputs = stub.DivMany(recorder); |
||||||
|
foreach (var input in divArgsList) |
||||||
|
{ |
||||||
|
inputs.OnNext(input); |
||||||
|
} |
||||||
|
inputs.OnCompleted(); |
||||||
|
|
||||||
|
Console.WriteLine("DivMany Result: " + string.Join("|", recorder.ToList().Result)); |
||||||
|
} |
||||||
|
|
||||||
|
public static void DependendRequestsExample(IMathServiceClient stub) |
||||||
|
{ |
||||||
|
var numberList = new List<Num> |
||||||
|
{ new Num.Builder{ Num_ = 1 }.Build(), |
||||||
|
new Num.Builder{ Num_ = 2 }.Build(), new Num.Builder{ Num_ = 3 }.Build() |
||||||
|
}; |
||||||
|
|
||||||
|
numberList.ToObservable(); |
||||||
|
|
||||||
|
//IObserver<Num> numbers; |
||||||
|
//Task<Num> call = stub.Sum(out numbers); |
||||||
|
//foreach (var num in numberList) |
||||||
|
//{ |
||||||
|
// numbers.OnNext(num); |
||||||
|
//} |
||||||
|
//numbers.OnCompleted(); |
||||||
|
|
||||||
|
//Num sum = call.Result; |
||||||
|
|
||||||
|
//DivReply result = stub.Div(new DivArgs.Builder { Dividend = sum.Num_, Divisor = numberList.Count }.Build()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,64 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||||
|
<PropertyGroup> |
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> |
||||||
|
<ProductVersion>10.0.0</ProductVersion> |
||||||
|
<SchemaVersion>2.0</SchemaVersion> |
||||||
|
<ProjectGuid>{7DC1433E-3225-42C7-B7EA-546D56E27A4B}</ProjectGuid> |
||||||
|
<OutputType>Library</OutputType> |
||||||
|
<RootNamespace>GrpcApi</RootNamespace> |
||||||
|
<AssemblyName>GrpcApi</AssemblyName> |
||||||
|
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> |
||||||
|
</PropertyGroup> |
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> |
||||||
|
<DebugSymbols>true</DebugSymbols> |
||||||
|
<DebugType>full</DebugType> |
||||||
|
<Optimize>false</Optimize> |
||||||
|
<OutputPath>bin\Debug</OutputPath> |
||||||
|
<DefineConstants>DEBUG;</DefineConstants> |
||||||
|
<ErrorReport>prompt</ErrorReport> |
||||||
|
<WarningLevel>4</WarningLevel> |
||||||
|
<ConsolePause>false</ConsolePause> |
||||||
|
</PropertyGroup> |
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> |
||||||
|
<DebugType>full</DebugType> |
||||||
|
<Optimize>true</Optimize> |
||||||
|
<OutputPath>bin\Release</OutputPath> |
||||||
|
<ErrorReport>prompt</ErrorReport> |
||||||
|
<WarningLevel>4</WarningLevel> |
||||||
|
<ConsolePause>false</ConsolePause> |
||||||
|
</PropertyGroup> |
||||||
|
<ItemGroup> |
||||||
|
<Reference Include="System" /> |
||||||
|
<Reference Include="System.Reactive.Linq, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"> |
||||||
|
<Private>False</Private> |
||||||
|
</Reference> |
||||||
|
<Reference Include="System.Data.Linq" /> |
||||||
|
<Reference Include="System.Reactive.Interfaces, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"> |
||||||
|
<Private>False</Private> |
||||||
|
</Reference> |
||||||
|
<Reference Include="System.Reactive.Core, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"> |
||||||
|
<Private>False</Private> |
||||||
|
</Reference> |
||||||
|
<Reference Include="Google.ProtocolBuffers"> |
||||||
|
<HintPath>..\lib\Google.ProtocolBuffers.dll</HintPath> |
||||||
|
</Reference> |
||||||
|
</ItemGroup> |
||||||
|
<ItemGroup> |
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" /> |
||||||
|
<Compile Include="Examples.cs" /> |
||||||
|
<Compile Include="IMathServiceClient.cs" /> |
||||||
|
<Compile Include="Math.cs" /> |
||||||
|
<Compile Include="DummyMathServiceClient.cs" /> |
||||||
|
<Compile Include="MathServiceClientStub.cs" /> |
||||||
|
<Compile Include="RecordingObserver.cs" /> |
||||||
|
</ItemGroup> |
||||||
|
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> |
||||||
|
<ItemGroup> |
||||||
|
<ProjectReference Include="..\GrpcCore\GrpcCore.csproj"> |
||||||
|
<Project>{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}</Project> |
||||||
|
<Name>GrpcCore</Name> |
||||||
|
</ProjectReference> |
||||||
|
</ItemGroup> |
||||||
|
</Project> |
@ -0,0 +1,26 @@ |
|||||||
|
using System; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Reactive.Linq; |
||||||
|
using Google.GRPC.Core; |
||||||
|
|
||||||
|
namespace math |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// Hand-written stub for MathService defined in math.proto. |
||||||
|
/// This code will be generated by gRPC codegen in the future. |
||||||
|
/// </summary> |
||||||
|
public interface IMathServiceClient |
||||||
|
{ |
||||||
|
DivReply Div(DivArgs args, CancellationToken token = default(CancellationToken)); |
||||||
|
|
||||||
|
Task<DivReply> DivAsync(DivArgs args, CancellationToken token = default(CancellationToken)); |
||||||
|
|
||||||
|
Task Fib(FibArgs args, IObserver<Num> outputs, CancellationToken token = default(CancellationToken)); |
||||||
|
|
||||||
|
ClientStreamingAsyncResult<Num, Num> Sum(CancellationToken token = default(CancellationToken)); |
||||||
|
|
||||||
|
IObserver<DivArgs> DivMany(IObserver<DivReply> outputs, CancellationToken token = default(CancellationToken)); |
||||||
|
} |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,75 @@ |
|||||||
|
using System; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Reactive.Linq; |
||||||
|
using Google.GRPC.Core; |
||||||
|
|
||||||
|
namespace math |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// Implementation of math service stub (this is handwritten version of code |
||||||
|
/// that will normally be generated). |
||||||
|
/// </summary> |
||||||
|
public class MathServiceClientStub : IMathServiceClient |
||||||
|
{ |
||||||
|
readonly Channel channel; |
||||||
|
readonly TimeSpan methodTimeout; |
||||||
|
|
||||||
|
public MathServiceClientStub(Channel channel, TimeSpan methodTimeout) |
||||||
|
{ |
||||||
|
this.channel = channel; |
||||||
|
this.methodTimeout = methodTimeout; |
||||||
|
} |
||||||
|
|
||||||
|
public DivReply Div(DivArgs args, CancellationToken token = default(CancellationToken)) |
||||||
|
{ |
||||||
|
var call = new Google.GRPC.Core.Call<DivArgs, DivReply>("/math.Math/Div", Serialize_DivArgs, Deserialize_DivReply, methodTimeout, channel); |
||||||
|
return Calls.BlockingUnaryCall(call, args, token); |
||||||
|
} |
||||||
|
|
||||||
|
public Task<DivReply> DivAsync(DivArgs args, CancellationToken token = default(CancellationToken)) |
||||||
|
{ |
||||||
|
var call = new Google.GRPC.Core.Call<DivArgs, DivReply>("/math.Math/Div", Serialize_DivArgs, Deserialize_DivReply, methodTimeout, channel); |
||||||
|
return Calls.AsyncUnaryCall(call, args, token); |
||||||
|
} |
||||||
|
|
||||||
|
public Task Fib(FibArgs args, IObserver<Num> outputs, CancellationToken token = default(CancellationToken)) |
||||||
|
{ |
||||||
|
var call = new Google.GRPC.Core.Call<FibArgs, Num>("/math.Math/Fib", Serialize_FibArgs, Deserialize_Num, methodTimeout, channel); |
||||||
|
return Calls.AsyncServerStreamingCall(call, args, outputs, token); |
||||||
|
} |
||||||
|
|
||||||
|
public ClientStreamingAsyncResult<Num, Num> Sum(CancellationToken token = default(CancellationToken)) |
||||||
|
{ |
||||||
|
var call = new Google.GRPC.Core.Call<Num, Num>("/math.Math/Sum", Serialize_Num, Deserialize_Num, methodTimeout, channel); |
||||||
|
return Calls.AsyncClientStreamingCall(call, token); |
||||||
|
} |
||||||
|
|
||||||
|
public IObserver<DivArgs> DivMany(IObserver<DivReply> outputs, CancellationToken token = default(CancellationToken)) |
||||||
|
{ |
||||||
|
var call = new Google.GRPC.Core.Call<DivArgs, DivReply>("/math.Math/DivMany", Serialize_DivArgs, Deserialize_DivReply, methodTimeout, channel); |
||||||
|
return Calls.DuplexStreamingCall(call, outputs, token); |
||||||
|
} |
||||||
|
|
||||||
|
private static byte[] Serialize_DivArgs(DivArgs arg) { |
||||||
|
return arg.ToByteArray(); |
||||||
|
} |
||||||
|
|
||||||
|
private static byte[] Serialize_FibArgs(FibArgs arg) { |
||||||
|
return arg.ToByteArray(); |
||||||
|
} |
||||||
|
|
||||||
|
private static byte[] Serialize_Num(Num arg) { |
||||||
|
return arg.ToByteArray(); |
||||||
|
} |
||||||
|
|
||||||
|
private static DivReply Deserialize_DivReply(byte[] payload) { |
||||||
|
return DivReply.CreateBuilder().MergeFrom(payload).Build(); |
||||||
|
} |
||||||
|
|
||||||
|
private static Num Deserialize_Num(byte[] payload) { |
||||||
|
return Num.CreateBuilder().MergeFrom(payload).Build(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
//using System; |
||||||
|
|
||||||
|
//namespace Google.GRPC.Examples.Math |
||||||
|
//{ |
||||||
|
// // Messages in this file are placeholders for actual protobuf message classes |
||||||
|
// // that will be generated from math.proto file. |
||||||
|
// |
||||||
|
// public class DivArgs |
||||||
|
// { |
||||||
|
// public long Dividend{ get; set; } |
||||||
|
// public long Divisor { get; set; } |
||||||
|
// } |
||||||
|
// |
||||||
|
// public class DivReply |
||||||
|
// { |
||||||
|
// public long Quotient { get; set; } |
||||||
|
// public long Remainder { get; set; } |
||||||
|
// } |
||||||
|
// |
||||||
|
// public class FibArgs |
||||||
|
// { |
||||||
|
// public long Limit { get; set; } |
||||||
|
// } |
||||||
|
// |
||||||
|
// public class Number |
||||||
|
// { |
||||||
|
// public long Num { get; set; } |
||||||
|
// } |
||||||
|
// |
||||||
|
// public class FibReply |
||||||
|
// { |
||||||
|
// public long Count { get; set; } |
||||||
|
// } |
||||||
|
//} |
||||||
|
|
@ -0,0 +1,22 @@ |
|||||||
|
using System.Reflection; |
||||||
|
using System.Runtime.CompilerServices; |
||||||
|
|
||||||
|
// Information about this assembly is defined by the following attributes. |
||||||
|
// Change them to the values specific to your project. |
||||||
|
[assembly: AssemblyTitle ("GrpcApi")] |
||||||
|
[assembly: AssemblyDescription ("")] |
||||||
|
[assembly: AssemblyConfiguration ("")] |
||||||
|
[assembly: AssemblyCompany ("")] |
||||||
|
[assembly: AssemblyProduct ("")] |
||||||
|
[assembly: AssemblyCopyright ("jtattermusch")] |
||||||
|
[assembly: AssemblyTrademark ("")] |
||||||
|
[assembly: AssemblyCulture ("")] |
||||||
|
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". |
||||||
|
// The form "{Major}.{Minor}.*" will automatically update the build and revision, |
||||||
|
// and "{Major}.{Minor}.{Build}.*" will update just the revision. |
||||||
|
[assembly: AssemblyVersion ("1.0.*")] |
||||||
|
// The following attributes are used to specify the signing key for the assembly, |
||||||
|
// if desired. See the Mono documentation for more information about signing. |
||||||
|
//[assembly: AssemblyDelaySign(false)] |
||||||
|
//[assembly: AssemblyKeyFile("")] |
||||||
|
|
@ -0,0 +1,32 @@ |
|||||||
|
using System; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using System.Collections.Generic; |
||||||
|
|
||||||
|
namespace math |
||||||
|
{ |
||||||
|
public class RecordingObserver<T> : IObserver<T> |
||||||
|
{ |
||||||
|
TaskCompletionSource<List<T>> tcs = new TaskCompletionSource<List<T>>(); |
||||||
|
List<T> data = new List<T>(); |
||||||
|
|
||||||
|
public void OnCompleted() |
||||||
|
{ |
||||||
|
tcs.SetResult(data); |
||||||
|
} |
||||||
|
|
||||||
|
public void OnError(Exception error) |
||||||
|
{ |
||||||
|
tcs.SetException(error); |
||||||
|
} |
||||||
|
|
||||||
|
public void OnNext(T value) |
||||||
|
{ |
||||||
|
data.Add(value); |
||||||
|
} |
||||||
|
|
||||||
|
public Task<List<T>> ToList() { |
||||||
|
return tcs.Task; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,50 @@ |
|||||||
|
syntax = "proto2"; |
||||||
|
|
||||||
|
package math; |
||||||
|
|
||||||
|
message DivArgs { |
||||||
|
optional int64 dividend = 1; |
||||||
|
optional int64 divisor = 2; |
||||||
|
} |
||||||
|
|
||||||
|
message DivReply { |
||||||
|
optional int64 quotient = 1; |
||||||
|
optional int64 remainder = 2; |
||||||
|
} |
||||||
|
|
||||||
|
message FibArgs { |
||||||
|
optional int64 limit = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message Num { |
||||||
|
optional int64 num = 1; |
||||||
|
} |
||||||
|
|
||||||
|
message FibReply { |
||||||
|
optional int64 count = 1; |
||||||
|
} |
||||||
|
|
||||||
|
service Math { |
||||||
|
// Div divides args.dividend by args.divisor and returns the quotient and |
||||||
|
// remainder. |
||||||
|
rpc Div (DivArgs) returns (DivReply) { |
||||||
|
} |
||||||
|
|
||||||
|
// DivMany accepts an arbitrary number of division args from the client stream |
||||||
|
// and sends back the results in the reply stream. The stream continues until |
||||||
|
// the client closes its end; the server does the same after sending all the |
||||||
|
// replies. The stream ends immediately if either end aborts. |
||||||
|
rpc DivMany (stream DivArgs) returns (stream DivReply) { |
||||||
|
} |
||||||
|
|
||||||
|
// Fib generates numbers in the Fibonacci sequence. If args.limit > 0, Fib |
||||||
|
// generates up to limit numbers; otherwise it continues until the call is |
||||||
|
// canceled. Unlike Fib above, Fib has no final FibReply. |
||||||
|
rpc Fib (FibArgs) returns (stream Num) { |
||||||
|
} |
||||||
|
|
||||||
|
// Sum sums a stream of numbers, returning the final result once the stream |
||||||
|
// is closed. |
||||||
|
rpc Sum (stream Num) returns (Num) { |
||||||
|
} |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
bin |
@ -0,0 +1,69 @@ |
|||||||
|
using System; |
||||||
|
using Google.GRPC.Core.Internal; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core |
||||||
|
{ |
||||||
|
public class Call<TRequest, TResponse> |
||||||
|
{ |
||||||
|
readonly string methodName; |
||||||
|
readonly Func<TRequest, byte[]> requestSerializer; |
||||||
|
readonly Func<byte[], TResponse> responseDeserializer; |
||||||
|
readonly TimeSpan timeout; |
||||||
|
readonly Channel channel; |
||||||
|
|
||||||
|
// TODO: channel param should be removed in the future. |
||||||
|
public Call(string methodName, |
||||||
|
Func<TRequest, byte[]> requestSerializer, |
||||||
|
Func<byte[], TResponse> responseDeserializer, |
||||||
|
TimeSpan timeout, |
||||||
|
Channel channel) { |
||||||
|
this.methodName = methodName; |
||||||
|
this.requestSerializer = requestSerializer; |
||||||
|
this.responseDeserializer = responseDeserializer; |
||||||
|
this.timeout = timeout; |
||||||
|
this.channel = channel; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public Channel Channel |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
return this.channel; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public TimeSpan Timeout |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
return this.timeout; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public string MethodName |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
return this.methodName; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public Func<TRequest, byte[]> RequestSerializer |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
return this.requestSerializer; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public Func<byte[], TResponse> ResponseDeserializer |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
return this.responseDeserializer; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,85 @@ |
|||||||
|
using System; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using Google.GRPC.Core.Internal; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core |
||||||
|
{ |
||||||
|
// NOTE: this class is work-in-progress |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Helper methods for generated stubs to make RPC calls. |
||||||
|
/// </summary> |
||||||
|
public static class Calls |
||||||
|
{ |
||||||
|
public static TResponse BlockingUnaryCall<TRequest, TResponse>(Call<TRequest, TResponse> call, TRequest req, CancellationToken token) |
||||||
|
{ |
||||||
|
//TODO: implement this in real synchronous style once new GRPC C core API is available. |
||||||
|
return AsyncUnaryCall(call, req, token).Result; |
||||||
|
} |
||||||
|
|
||||||
|
public static async Task<TResponse> AsyncUnaryCall<TRequest, TResponse>(Call<TRequest, TResponse> call, TRequest req, CancellationToken token) |
||||||
|
{ |
||||||
|
var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestSerializer, call.ResponseDeserializer); |
||||||
|
asyncCall.Initialize(call.Channel, call.MethodName); |
||||||
|
asyncCall.Start(false, GetCompletionQueue()); |
||||||
|
|
||||||
|
await asyncCall.WriteAsync(req); |
||||||
|
await asyncCall.WritesCompletedAsync(); |
||||||
|
|
||||||
|
TResponse response = await asyncCall.ReadAsync(); |
||||||
|
|
||||||
|
Status status = await asyncCall.Finished; |
||||||
|
|
||||||
|
if (status.StatusCode != StatusCode.GRPC_STATUS_OK) |
||||||
|
{ |
||||||
|
throw new RpcException(status); |
||||||
|
} |
||||||
|
return response; |
||||||
|
} |
||||||
|
|
||||||
|
public static async Task AsyncServerStreamingCall<TRequest, TResponse>(Call<TRequest, TResponse> call, TRequest req, IObserver<TResponse> outputs, CancellationToken token) |
||||||
|
{ |
||||||
|
var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestSerializer, call.ResponseDeserializer); |
||||||
|
asyncCall.Initialize(call.Channel, call.MethodName); |
||||||
|
asyncCall.Start(false, GetCompletionQueue()); |
||||||
|
|
||||||
|
asyncCall.StartReadingToStream(outputs); |
||||||
|
|
||||||
|
await asyncCall.WriteAsync(req); |
||||||
|
await asyncCall.WritesCompletedAsync(); |
||||||
|
} |
||||||
|
|
||||||
|
public static ClientStreamingAsyncResult<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Call<TRequest, TResponse> call, CancellationToken token) |
||||||
|
{ |
||||||
|
var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestSerializer, call.ResponseDeserializer); |
||||||
|
asyncCall.Initialize(call.Channel, call.MethodName); |
||||||
|
asyncCall.Start(false, GetCompletionQueue()); |
||||||
|
|
||||||
|
var task = asyncCall.ReadAsync(); |
||||||
|
var inputs = new StreamingInputObserver<TRequest, TResponse>(asyncCall); |
||||||
|
return new ClientStreamingAsyncResult<TRequest, TResponse>(task, inputs); |
||||||
|
} |
||||||
|
|
||||||
|
public static TResponse BlockingClientStreamingCall<TRequest, TResponse>(Call<TRequest, TResponse> call, IObservable<TRequest> inputs, CancellationToken token) |
||||||
|
{ |
||||||
|
throw new NotImplementedException(); |
||||||
|
} |
||||||
|
|
||||||
|
public static IObserver<TRequest> DuplexStreamingCall<TRequest, TResponse>(Call<TRequest, TResponse> call, IObserver<TResponse> outputs, CancellationToken token) |
||||||
|
{ |
||||||
|
var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestSerializer, call.ResponseDeserializer); |
||||||
|
asyncCall.Initialize(call.Channel, call.MethodName); |
||||||
|
asyncCall.Start(false, GetCompletionQueue()); |
||||||
|
|
||||||
|
asyncCall.StartReadingToStream(outputs); |
||||||
|
var inputs = new StreamingInputObserver<TRequest, TResponse>(asyncCall); |
||||||
|
return inputs; |
||||||
|
} |
||||||
|
|
||||||
|
private static CompletionQueueSafeHandle GetCompletionQueue() { |
||||||
|
return GrpcEnvironment.ThreadPool.CompletionQueue; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,59 @@ |
|||||||
|
using System; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using Google.GRPC.Core.Internal; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core |
||||||
|
{ |
||||||
|
public class Channel : IDisposable |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// Make sure GPRC environment is initialized before any channels get used. |
||||||
|
/// </summary> |
||||||
|
static Channel() { |
||||||
|
GrpcEnvironment.EnsureInitialized(); |
||||||
|
} |
||||||
|
|
||||||
|
readonly ChannelSafeHandle handle; |
||||||
|
readonly String target; |
||||||
|
|
||||||
|
// TODO: add way how to create grpc_secure_channel.... |
||||||
|
// TODO: add support for channel args... |
||||||
|
public Channel(string target) |
||||||
|
{ |
||||||
|
this.handle = ChannelSafeHandle.Create(target, IntPtr.Zero); |
||||||
|
this.target = target; |
||||||
|
} |
||||||
|
|
||||||
|
internal ChannelSafeHandle Handle |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
return this.handle; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public string Target |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
return this.target; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void Dispose() |
||||||
|
{ |
||||||
|
Dispose(true); |
||||||
|
GC.SuppressFinalize(this); |
||||||
|
} |
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing) |
||||||
|
{ |
||||||
|
if (handle != null && !handle.IsInvalid) |
||||||
|
{ |
||||||
|
handle.Dispose(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
using System; |
||||||
|
using System.Threading.Tasks; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// Return type for client streaming async method. |
||||||
|
/// </summary> |
||||||
|
public struct ClientStreamingAsyncResult<TRequest, TResponse> |
||||||
|
{ |
||||||
|
readonly Task<TResponse> task; |
||||||
|
readonly IObserver<TRequest> inputs; |
||||||
|
|
||||||
|
public ClientStreamingAsyncResult(Task<TResponse> task, IObserver<TRequest> inputs) |
||||||
|
{ |
||||||
|
this.task = task; |
||||||
|
this.inputs = inputs; |
||||||
|
} |
||||||
|
|
||||||
|
public Task<TResponse> Task |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
return this.task; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public IObserver<TRequest> Inputs |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
return this.inputs; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,62 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||||
|
<PropertyGroup> |
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> |
||||||
|
<ProductVersion>10.0.0</ProductVersion> |
||||||
|
<SchemaVersion>2.0</SchemaVersion> |
||||||
|
<ProjectGuid>{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}</ProjectGuid> |
||||||
|
<OutputType>Library</OutputType> |
||||||
|
<RootNamespace>GrpcCore</RootNamespace> |
||||||
|
<AssemblyName>GrpcCore</AssemblyName> |
||||||
|
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> |
||||||
|
</PropertyGroup> |
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> |
||||||
|
<DebugSymbols>true</DebugSymbols> |
||||||
|
<DebugType>full</DebugType> |
||||||
|
<Optimize>false</Optimize> |
||||||
|
<OutputPath>bin\Debug</OutputPath> |
||||||
|
<DefineConstants>DEBUG;</DefineConstants> |
||||||
|
<ErrorReport>prompt</ErrorReport> |
||||||
|
<WarningLevel>4</WarningLevel> |
||||||
|
<ConsolePause>false</ConsolePause> |
||||||
|
</PropertyGroup> |
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> |
||||||
|
<DebugType>full</DebugType> |
||||||
|
<Optimize>true</Optimize> |
||||||
|
<OutputPath>bin\Release</OutputPath> |
||||||
|
<ErrorReport>prompt</ErrorReport> |
||||||
|
<WarningLevel>4</WarningLevel> |
||||||
|
<ConsolePause>false</ConsolePause> |
||||||
|
</PropertyGroup> |
||||||
|
<ItemGroup> |
||||||
|
<Reference Include="System" /> |
||||||
|
</ItemGroup> |
||||||
|
<ItemGroup> |
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" /> |
||||||
|
<Compile Include="RpcException.cs" /> |
||||||
|
<Compile Include="Calls.cs" /> |
||||||
|
<Compile Include="Call.cs" /> |
||||||
|
<Compile Include="ClientStreamingAsyncResult.cs" /> |
||||||
|
<Compile Include="GrpcEnvironment.cs" /> |
||||||
|
<Compile Include="Status.cs" /> |
||||||
|
<Compile Include="StatusCode.cs" /> |
||||||
|
<Compile Include="Server.cs" /> |
||||||
|
<Compile Include="Channel.cs" /> |
||||||
|
<Compile Include="Internal\CallSafeHandle.cs" /> |
||||||
|
<Compile Include="Internal\ChannelSafeHandle.cs" /> |
||||||
|
<Compile Include="Internal\CompletionQueueSafeHandle.cs" /> |
||||||
|
<Compile Include="Internal\Enums.cs" /> |
||||||
|
<Compile Include="Internal\Event.cs" /> |
||||||
|
<Compile Include="Internal\SafeHandleZeroIsInvalid.cs" /> |
||||||
|
<Compile Include="Internal\Timespec.cs" /> |
||||||
|
<Compile Include="Internal\GrpcThreadPool.cs" /> |
||||||
|
<Compile Include="Internal\AsyncCall.cs" /> |
||||||
|
<Compile Include="Internal\ServerSafeHandle.cs" /> |
||||||
|
<Compile Include="Internal\StreamingInputObserver.cs" /> |
||||||
|
</ItemGroup> |
||||||
|
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> |
||||||
|
<ItemGroup> |
||||||
|
<Folder Include="Internal\" /> |
||||||
|
</ItemGroup> |
||||||
|
</Project> |
@ -0,0 +1,91 @@ |
|||||||
|
using System; |
||||||
|
using Google.GRPC.Core.Internal; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// Encapsulates initialization and shutdown of GRPC C core library. |
||||||
|
/// You should not need to initialize it manually, as static constructors |
||||||
|
/// should load the library when needed. |
||||||
|
/// </summary> |
||||||
|
public static class GrpcEnvironment |
||||||
|
{ |
||||||
|
const int THREAD_POOL_SIZE = 1; |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern void grpc_init(); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern void grpc_shutdown(); |
||||||
|
|
||||||
|
static object staticLock = new object(); |
||||||
|
static bool initCalled = false; |
||||||
|
static bool shutdownCalled = false; |
||||||
|
|
||||||
|
static GrpcThreadPool threadPool = new GrpcThreadPool(THREAD_POOL_SIZE); |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Makes sure GRPC environment is initialized. |
||||||
|
/// </summary> |
||||||
|
public static void EnsureInitialized() { |
||||||
|
lock(staticLock) |
||||||
|
{ |
||||||
|
if (!initCalled) |
||||||
|
{ |
||||||
|
initCalled = true; |
||||||
|
GrpcInit(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Shuts down the GRPC environment if it was initialized before. |
||||||
|
/// Repeated invocations have no effect. |
||||||
|
/// </summary> |
||||||
|
public static void Shutdown() |
||||||
|
{ |
||||||
|
lock(staticLock) |
||||||
|
{ |
||||||
|
if (initCalled && !shutdownCalled) |
||||||
|
{ |
||||||
|
shutdownCalled = true; |
||||||
|
GrpcShutdown(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Initializes GRPC C Core library. |
||||||
|
/// </summary> |
||||||
|
private static void GrpcInit() |
||||||
|
{ |
||||||
|
grpc_init(); |
||||||
|
threadPool.Start(); |
||||||
|
// TODO: use proper logging here |
||||||
|
Console.WriteLine("GRPC initialized."); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Shutdown GRPC C Core library. |
||||||
|
/// </summary> |
||||||
|
private static void GrpcShutdown() |
||||||
|
{ |
||||||
|
threadPool.Stop(); |
||||||
|
grpc_shutdown(); |
||||||
|
|
||||||
|
// TODO: use proper logging here |
||||||
|
Console.WriteLine("GRPC shutdown."); |
||||||
|
} |
||||||
|
|
||||||
|
internal static GrpcThreadPool ThreadPool |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
return threadPool; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,485 @@ |
|||||||
|
using System; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using System.Runtime.CompilerServices; |
||||||
|
using Google.GRPC.Core.Internal; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core.Internal |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// Listener for call events that can be delivered from a completion queue. |
||||||
|
/// </summary> |
||||||
|
internal interface ICallEventListener { |
||||||
|
|
||||||
|
void OnClientMetadata(); |
||||||
|
|
||||||
|
void OnRead(byte[] payload); |
||||||
|
|
||||||
|
void OnWriteAccepted(GRPCOpError error); |
||||||
|
|
||||||
|
void OnFinishAccepted(GRPCOpError error); |
||||||
|
|
||||||
|
// ignore the status on server |
||||||
|
void OnFinished(Status status); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Handle native call lifecycle and provides convenience methods. |
||||||
|
/// </summary> |
||||||
|
internal class AsyncCall<TWrite, TRead>: ICallEventListener, IDisposable |
||||||
|
{ |
||||||
|
readonly Func<TWrite, byte[]> serializer; |
||||||
|
readonly Func<byte[], TRead> deserializer; |
||||||
|
|
||||||
|
// TODO: make sure the delegate doesn't get garbage collected while |
||||||
|
// native callbacks are in the completion queue. |
||||||
|
readonly EventCallbackDelegate callbackHandler; |
||||||
|
|
||||||
|
object myLock = new object(); |
||||||
|
bool disposed; |
||||||
|
CallSafeHandle call; |
||||||
|
|
||||||
|
bool started; |
||||||
|
bool errorOccured; |
||||||
|
|
||||||
|
bool cancelRequested; |
||||||
|
bool halfcloseRequested; |
||||||
|
bool halfclosed; |
||||||
|
bool doneWithReading; |
||||||
|
Nullable<Status> finishedStatus; |
||||||
|
|
||||||
|
TaskCompletionSource<object> writeTcs; |
||||||
|
TaskCompletionSource<TRead> readTcs; |
||||||
|
TaskCompletionSource<object> halfcloseTcs = new TaskCompletionSource<object>(); |
||||||
|
TaskCompletionSource<Status> finishedTcs = new TaskCompletionSource<Status>(); |
||||||
|
|
||||||
|
IObserver<TRead> readObserver; |
||||||
|
|
||||||
|
public AsyncCall(Func<TWrite, byte[]> serializer, Func<byte[], TRead> deserializer) |
||||||
|
{ |
||||||
|
this.serializer = serializer; |
||||||
|
this.deserializer = deserializer; |
||||||
|
this.callbackHandler = HandleEvent; |
||||||
|
} |
||||||
|
|
||||||
|
public Task WriteAsync(TWrite msg) |
||||||
|
{ |
||||||
|
return StartWrite(msg, false).Task; |
||||||
|
} |
||||||
|
|
||||||
|
public Task WritesCompletedAsync() |
||||||
|
{ |
||||||
|
WritesDone(); |
||||||
|
return halfcloseTcs.Task; |
||||||
|
} |
||||||
|
|
||||||
|
public Task WriteStatusAsync(Status status) |
||||||
|
{ |
||||||
|
WriteStatus(status); |
||||||
|
return halfcloseTcs.Task; |
||||||
|
} |
||||||
|
|
||||||
|
public Task<TRead> ReadAsync() |
||||||
|
{ |
||||||
|
return StartRead().Task; |
||||||
|
} |
||||||
|
|
||||||
|
public Task<Status> Finished |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
return finishedTcs.Task; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Initiates reading to given observer. |
||||||
|
/// </summary> |
||||||
|
public void StartReadingToStream(IObserver<TRead> readObserver) { |
||||||
|
lock (myLock) |
||||||
|
{ |
||||||
|
CheckStarted(); |
||||||
|
if (this.readObserver != null) |
||||||
|
{ |
||||||
|
throw new InvalidOperationException("Already registered an observer."); |
||||||
|
} |
||||||
|
this.readObserver = readObserver; |
||||||
|
StartRead(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void Initialize(Channel channel, String methodName) { |
||||||
|
lock (myLock) |
||||||
|
{ |
||||||
|
this.call = CallSafeHandle.Create(channel.Handle, methodName, channel.Target, Timespec.InfFuture); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void InitializeServer(CallSafeHandle call) |
||||||
|
{ |
||||||
|
lock(myLock) |
||||||
|
{ |
||||||
|
this.call = call; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Client only |
||||||
|
public void Start(bool buffered, CompletionQueueSafeHandle cq) |
||||||
|
{ |
||||||
|
lock (myLock) |
||||||
|
{ |
||||||
|
if (started) |
||||||
|
{ |
||||||
|
throw new InvalidOperationException("Already started."); |
||||||
|
} |
||||||
|
|
||||||
|
call.Invoke(cq, buffered, callbackHandler, callbackHandler); |
||||||
|
started = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Server only |
||||||
|
public void Accept(CompletionQueueSafeHandle cq) |
||||||
|
{ |
||||||
|
lock (myLock) |
||||||
|
{ |
||||||
|
if (started) |
||||||
|
{ |
||||||
|
throw new InvalidOperationException("Already started."); |
||||||
|
} |
||||||
|
|
||||||
|
call.ServerAccept(cq, callbackHandler); |
||||||
|
call.ServerEndInitialMetadata(0); |
||||||
|
started = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public TaskCompletionSource<object> StartWrite(TWrite msg, bool buffered) |
||||||
|
{ |
||||||
|
lock (myLock) |
||||||
|
{ |
||||||
|
CheckStarted(); |
||||||
|
CheckNotFinished(); |
||||||
|
CheckNoError(); |
||||||
|
CheckCancelNotRequested(); |
||||||
|
|
||||||
|
if (halfcloseRequested || halfclosed) |
||||||
|
{ |
||||||
|
throw new InvalidOperationException("Already halfclosed."); |
||||||
|
} |
||||||
|
|
||||||
|
if (writeTcs != null) |
||||||
|
{ |
||||||
|
throw new InvalidOperationException("Only one write can be pending at a time"); |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: wrap serialization... |
||||||
|
byte[] payload = serializer(msg); |
||||||
|
|
||||||
|
call.StartWrite(payload, buffered, callbackHandler); |
||||||
|
writeTcs = new TaskCompletionSource<object>(); |
||||||
|
return writeTcs; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// client only |
||||||
|
public void WritesDone() |
||||||
|
{ |
||||||
|
lock (myLock) |
||||||
|
{ |
||||||
|
CheckStarted(); |
||||||
|
CheckNotFinished(); |
||||||
|
CheckNoError(); |
||||||
|
CheckCancelNotRequested(); |
||||||
|
|
||||||
|
if (halfcloseRequested || halfclosed) |
||||||
|
{ |
||||||
|
throw new InvalidOperationException("Already halfclosed."); |
||||||
|
} |
||||||
|
|
||||||
|
call.WritesDone(callbackHandler); |
||||||
|
halfcloseRequested = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// server only |
||||||
|
public void WriteStatus(Status status) |
||||||
|
{ |
||||||
|
lock (myLock) |
||||||
|
{ |
||||||
|
CheckStarted(); |
||||||
|
CheckNotFinished(); |
||||||
|
CheckNoError(); |
||||||
|
CheckCancelNotRequested(); |
||||||
|
|
||||||
|
if (halfcloseRequested || halfclosed) |
||||||
|
{ |
||||||
|
throw new InvalidOperationException("Already halfclosed."); |
||||||
|
} |
||||||
|
|
||||||
|
call.StartWriteStatus(status, callbackHandler); |
||||||
|
halfcloseRequested = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public TaskCompletionSource<TRead> StartRead() |
||||||
|
{ |
||||||
|
lock (myLock) |
||||||
|
{ |
||||||
|
CheckStarted(); |
||||||
|
CheckNotFinished(); |
||||||
|
CheckNoError(); |
||||||
|
|
||||||
|
// TODO: add check for not cancelled? |
||||||
|
|
||||||
|
if (doneWithReading) |
||||||
|
{ |
||||||
|
throw new InvalidOperationException("Already read the last message."); |
||||||
|
} |
||||||
|
|
||||||
|
if (readTcs != null) |
||||||
|
{ |
||||||
|
throw new InvalidOperationException("Only one read can be pending at a time"); |
||||||
|
} |
||||||
|
|
||||||
|
call.StartRead(callbackHandler); |
||||||
|
|
||||||
|
readTcs = new TaskCompletionSource<TRead>(); |
||||||
|
return readTcs; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void Cancel() |
||||||
|
{ |
||||||
|
lock (myLock) |
||||||
|
{ |
||||||
|
CheckStarted(); |
||||||
|
CheckNotFinished(); |
||||||
|
|
||||||
|
cancelRequested = true; |
||||||
|
} |
||||||
|
// grpc_call_cancel is threadsafe |
||||||
|
call.Cancel(); |
||||||
|
} |
||||||
|
|
||||||
|
public void CancelWithStatus(Status status) |
||||||
|
{ |
||||||
|
lock (myLock) |
||||||
|
{ |
||||||
|
CheckStarted(); |
||||||
|
CheckNotFinished(); |
||||||
|
|
||||||
|
cancelRequested = true; |
||||||
|
} |
||||||
|
// grpc_call_cancel_with_status is threadsafe |
||||||
|
call.CancelWithStatus(status); |
||||||
|
} |
||||||
|
|
||||||
|
public void OnClientMetadata() |
||||||
|
{ |
||||||
|
// TODO: implement.... |
||||||
|
} |
||||||
|
|
||||||
|
public void OnRead(byte[] payload) |
||||||
|
{ |
||||||
|
TaskCompletionSource<TRead> oldTcs = null; |
||||||
|
IObserver<TRead> observer = null; |
||||||
|
lock (myLock) |
||||||
|
{ |
||||||
|
oldTcs = readTcs; |
||||||
|
readTcs = null; |
||||||
|
if (payload == null) |
||||||
|
{ |
||||||
|
doneWithReading = true; |
||||||
|
} |
||||||
|
observer = readObserver; |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: wrap deserialization... |
||||||
|
TRead msg = payload != null ? deserializer(payload) : default(TRead); |
||||||
|
|
||||||
|
oldTcs.SetResult(msg); |
||||||
|
|
||||||
|
// TODO: make sure we deliver reads in the right order. |
||||||
|
|
||||||
|
if (observer != null) |
||||||
|
{ |
||||||
|
if (payload != null) |
||||||
|
{ |
||||||
|
// TODO: wrap to handle exceptions |
||||||
|
observer.OnNext(msg); |
||||||
|
|
||||||
|
// start a new read |
||||||
|
StartRead(); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
// TODO: wrap to handle exceptions; |
||||||
|
observer.OnCompleted(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void OnWriteAccepted(GRPCOpError error) |
||||||
|
{ |
||||||
|
TaskCompletionSource<object> oldTcs = null; |
||||||
|
lock (myLock) |
||||||
|
{ |
||||||
|
UpdateErrorOccured(error); |
||||||
|
oldTcs = writeTcs; |
||||||
|
writeTcs = null; |
||||||
|
} |
||||||
|
|
||||||
|
if (errorOccured) |
||||||
|
{ |
||||||
|
// TODO: use the right type of exception... |
||||||
|
oldTcs.SetException(new Exception("Write failed")); |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
// TODO: where does the continuation run? |
||||||
|
oldTcs.SetResult(null); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void OnFinishAccepted(GRPCOpError error) |
||||||
|
{ |
||||||
|
lock (myLock) |
||||||
|
{ |
||||||
|
UpdateErrorOccured(error); |
||||||
|
halfclosed = true; |
||||||
|
} |
||||||
|
|
||||||
|
if (errorOccured) |
||||||
|
{ |
||||||
|
halfcloseTcs.SetException(new Exception("Halfclose failed")); |
||||||
|
|
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
halfcloseTcs.SetResult(null); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public void OnFinished(Status status) |
||||||
|
{ |
||||||
|
lock (myLock) |
||||||
|
{ |
||||||
|
finishedStatus = status; |
||||||
|
|
||||||
|
DisposeResourcesIfNeeded(); |
||||||
|
} |
||||||
|
finishedTcs.SetResult(status); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public void Dispose() |
||||||
|
{ |
||||||
|
Dispose(true); |
||||||
|
GC.SuppressFinalize(this); |
||||||
|
} |
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing) |
||||||
|
{ |
||||||
|
if (!disposed) |
||||||
|
{ |
||||||
|
if (disposing) |
||||||
|
{ |
||||||
|
if (call != null) |
||||||
|
{ |
||||||
|
call.Dispose(); |
||||||
|
} |
||||||
|
} |
||||||
|
disposed = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void UpdateErrorOccured(GRPCOpError error) |
||||||
|
{ |
||||||
|
if (error == GRPCOpError.GRPC_OP_ERROR) |
||||||
|
{ |
||||||
|
errorOccured = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void CheckStarted() |
||||||
|
{ |
||||||
|
if (!started) |
||||||
|
{ |
||||||
|
throw new InvalidOperationException("Call not started"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void CheckNoError() |
||||||
|
{ |
||||||
|
if (errorOccured) |
||||||
|
{ |
||||||
|
throw new InvalidOperationException("Error occured when processing call."); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void CheckNotFinished() |
||||||
|
{ |
||||||
|
if (finishedStatus.HasValue) |
||||||
|
{ |
||||||
|
throw new InvalidOperationException("Already finished."); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void CheckCancelNotRequested() |
||||||
|
{ |
||||||
|
if (cancelRequested) |
||||||
|
{ |
||||||
|
throw new InvalidOperationException("Cancel has been requested."); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void DisposeResourcesIfNeeded() |
||||||
|
{ |
||||||
|
if (call != null && started && finishedStatus.HasValue) |
||||||
|
{ |
||||||
|
// TODO: should we also wait for all the pending events to finish? |
||||||
|
|
||||||
|
call.Dispose(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void HandleEvent(IntPtr eventPtr) { |
||||||
|
try { |
||||||
|
var ev = new EventSafeHandleNotOwned(eventPtr); |
||||||
|
switch (ev.GetCompletionType()) |
||||||
|
{ |
||||||
|
case GRPCCompletionType.GRPC_CLIENT_METADATA_READ: |
||||||
|
OnClientMetadata(); |
||||||
|
break; |
||||||
|
|
||||||
|
case GRPCCompletionType.GRPC_READ: |
||||||
|
byte[] payload = ev.GetReadData(); |
||||||
|
OnRead(payload); |
||||||
|
break; |
||||||
|
|
||||||
|
case GRPCCompletionType.GRPC_WRITE_ACCEPTED: |
||||||
|
OnWriteAccepted(ev.GetWriteAccepted()); |
||||||
|
break; |
||||||
|
|
||||||
|
case GRPCCompletionType.GRPC_FINISH_ACCEPTED: |
||||||
|
OnFinishAccepted(ev.GetFinishAccepted()); |
||||||
|
break; |
||||||
|
|
||||||
|
case GRPCCompletionType.GRPC_FINISHED: |
||||||
|
OnFinished(ev.GetFinished()); |
||||||
|
break; |
||||||
|
|
||||||
|
default: |
||||||
|
throw new ArgumentException("Unexpected completion type"); |
||||||
|
} |
||||||
|
} catch(Exception e) { |
||||||
|
Console.WriteLine("Caught exception in a native handler: " + e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,182 @@ |
|||||||
|
using System; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
using System.Diagnostics; |
||||||
|
using Google.GRPC.Core; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core.Internal |
||||||
|
{ |
||||||
|
// TODO: we need to make sure that the delegates are not collected before invoked. |
||||||
|
internal delegate void EventCallbackDelegate(IntPtr eventPtr); |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// grpc_call from <grpc/grpc.h> |
||||||
|
/// </summary> |
||||||
|
internal class CallSafeHandle : SafeHandleZeroIsInvalid |
||||||
|
{ |
||||||
|
const UInt32 GRPC_WRITE_BUFFER_HINT = 1; |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern CallSafeHandle grpc_channel_create_call_old(ChannelSafeHandle channel, string method, string host, Timespec deadline); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern GRPCCallError grpc_call_add_metadata(CallSafeHandle call, IntPtr metadata, UInt32 flags); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern GRPCCallError grpc_call_invoke_old(CallSafeHandle call, CompletionQueueSafeHandle cq, IntPtr metadataReadTag, IntPtr finishedTag, UInt32 flags); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so", EntryPoint = "grpc_call_invoke_old")] |
||||||
|
static extern GRPCCallError grpc_call_invoke_old_CALLBACK(CallSafeHandle call, CompletionQueueSafeHandle cq, |
||||||
|
[MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate metadataReadCallback, |
||||||
|
[MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate finishedCallback, |
||||||
|
UInt32 flags); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern GRPCCallError grpc_call_server_accept_old(CallSafeHandle call, CompletionQueueSafeHandle completionQueue, IntPtr finishedTag); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so", EntryPoint = "grpc_call_server_accept_old")] |
||||||
|
static extern GRPCCallError grpc_call_server_accept_old_CALLBACK(CallSafeHandle call, CompletionQueueSafeHandle completionQueue, [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate finishedCallback); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern GRPCCallError grpc_call_server_end_initial_metadata_old(CallSafeHandle call, UInt32 flags); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern GRPCCallError grpc_call_cancel(CallSafeHandle call); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern GRPCCallError grpc_call_cancel_with_status(CallSafeHandle call, StatusCode status, string description); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern GRPCCallError grpc_call_start_write_status_old(CallSafeHandle call, StatusCode statusCode, string statusMessage, IntPtr tag); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so", EntryPoint = "grpc_call_start_write_status_old")] |
||||||
|
static extern GRPCCallError grpc_call_start_write_status_old_CALLBACK(CallSafeHandle call, StatusCode statusCode, string statusMessage, [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate callback); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern GRPCCallError grpc_call_writes_done_old(CallSafeHandle call, IntPtr tag); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so", EntryPoint = "grpc_call_writes_done_old")] |
||||||
|
static extern GRPCCallError grpc_call_writes_done_old_CALLBACK(CallSafeHandle call, [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate callback); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern GRPCCallError grpc_call_start_read_old(CallSafeHandle call, IntPtr tag); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so", EntryPoint = "grpc_call_start_read_old")] |
||||||
|
static extern GRPCCallError grpc_call_start_read_old_CALLBACK(CallSafeHandle call, [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate callback); |
||||||
|
|
||||||
|
[DllImport("libgrpc_csharp_ext.so")] |
||||||
|
static extern void grpc_call_start_write_from_copied_buffer(CallSafeHandle call, |
||||||
|
byte[] buffer, UIntPtr length, |
||||||
|
IntPtr tag, UInt32 flags); |
||||||
|
|
||||||
|
[DllImport("libgrpc_csharp_ext.so", EntryPoint = "grpc_call_start_write_from_copied_buffer")] |
||||||
|
static extern void grpc_call_start_write_from_copied_buffer_CALLBACK(CallSafeHandle call, |
||||||
|
byte[] buffer, UIntPtr length, |
||||||
|
[MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate callback, |
||||||
|
UInt32 flags); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern void grpc_call_destroy(IntPtr call); |
||||||
|
|
||||||
|
private CallSafeHandle() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Creates a client call. |
||||||
|
/// </summary> |
||||||
|
public static CallSafeHandle Create(ChannelSafeHandle channel, string method, string host, Timespec deadline) |
||||||
|
{ |
||||||
|
return grpc_channel_create_call_old(channel, method, host, deadline); |
||||||
|
} |
||||||
|
|
||||||
|
public void Invoke(CompletionQueueSafeHandle cq, IntPtr metadataReadTag, IntPtr finishedTag, bool buffered) |
||||||
|
{ |
||||||
|
AssertCallOk(grpc_call_invoke_old(this, cq, metadataReadTag, finishedTag, GetFlags(buffered))); |
||||||
|
} |
||||||
|
|
||||||
|
public void Invoke(CompletionQueueSafeHandle cq, bool buffered, EventCallbackDelegate metadataReadCallback, EventCallbackDelegate finishedCallback) |
||||||
|
{ |
||||||
|
AssertCallOk(grpc_call_invoke_old_CALLBACK(this, cq, metadataReadCallback, finishedCallback, GetFlags(buffered))); |
||||||
|
} |
||||||
|
|
||||||
|
public void ServerAccept(CompletionQueueSafeHandle cq, IntPtr finishedTag) |
||||||
|
{ |
||||||
|
AssertCallOk(grpc_call_server_accept_old(this, cq, finishedTag)); |
||||||
|
} |
||||||
|
|
||||||
|
public void ServerAccept(CompletionQueueSafeHandle cq, EventCallbackDelegate callback) |
||||||
|
{ |
||||||
|
AssertCallOk(grpc_call_server_accept_old_CALLBACK(this, cq, callback)); |
||||||
|
} |
||||||
|
|
||||||
|
public void ServerEndInitialMetadata(UInt32 flags) |
||||||
|
{ |
||||||
|
AssertCallOk(grpc_call_server_end_initial_metadata_old(this, flags)); |
||||||
|
} |
||||||
|
|
||||||
|
public void StartWrite(byte[] payload, IntPtr tag, bool buffered) |
||||||
|
{ |
||||||
|
grpc_call_start_write_from_copied_buffer(this, payload, new UIntPtr((ulong) payload.Length), tag, GetFlags(buffered)); |
||||||
|
} |
||||||
|
|
||||||
|
public void StartWrite(byte[] payload, bool buffered, EventCallbackDelegate callback) |
||||||
|
{ |
||||||
|
grpc_call_start_write_from_copied_buffer_CALLBACK(this, payload, new UIntPtr((ulong) payload.Length), callback, GetFlags(buffered)); |
||||||
|
} |
||||||
|
|
||||||
|
public void StartWriteStatus(Status status, IntPtr tag) |
||||||
|
{ |
||||||
|
AssertCallOk(grpc_call_start_write_status_old(this, status.StatusCode, status.Detail, tag)); |
||||||
|
} |
||||||
|
|
||||||
|
public void StartWriteStatus(Status status, EventCallbackDelegate callback) |
||||||
|
{ |
||||||
|
AssertCallOk(grpc_call_start_write_status_old_CALLBACK(this, status.StatusCode, status.Detail, callback)); |
||||||
|
} |
||||||
|
|
||||||
|
public void WritesDone(IntPtr tag) |
||||||
|
{ |
||||||
|
AssertCallOk(grpc_call_writes_done_old(this, tag)); |
||||||
|
} |
||||||
|
|
||||||
|
public void WritesDone(EventCallbackDelegate callback) |
||||||
|
{ |
||||||
|
AssertCallOk(grpc_call_writes_done_old_CALLBACK(this, callback)); |
||||||
|
} |
||||||
|
|
||||||
|
public void StartRead(IntPtr tag) |
||||||
|
{ |
||||||
|
AssertCallOk(grpc_call_start_read_old(this, tag)); |
||||||
|
} |
||||||
|
|
||||||
|
public void StartRead(EventCallbackDelegate callback) |
||||||
|
{ |
||||||
|
AssertCallOk(grpc_call_start_read_old_CALLBACK(this, callback)); |
||||||
|
} |
||||||
|
|
||||||
|
public void Cancel() |
||||||
|
{ |
||||||
|
AssertCallOk(grpc_call_cancel(this)); |
||||||
|
} |
||||||
|
|
||||||
|
public void CancelWithStatus(Status status) |
||||||
|
{ |
||||||
|
AssertCallOk(grpc_call_cancel_with_status(this, status.StatusCode, status.Detail)); |
||||||
|
} |
||||||
|
|
||||||
|
protected override bool ReleaseHandle() |
||||||
|
{ |
||||||
|
grpc_call_destroy(handle); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
private static void AssertCallOk(GRPCCallError callError) |
||||||
|
{ |
||||||
|
Trace.Assert(callError == GRPCCallError.GRPC_CALL_OK, "Status not GRPC_CALL_OK"); |
||||||
|
} |
||||||
|
|
||||||
|
private static UInt32 GetFlags(bool buffered) { |
||||||
|
return buffered ? 0 : GRPC_WRITE_BUFFER_HINT; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
using System; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core.Internal |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// grpc_channel from <grpc/grpc.h> |
||||||
|
/// </summary> |
||||||
|
internal class ChannelSafeHandle : SafeHandleZeroIsInvalid |
||||||
|
{ |
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern ChannelSafeHandle grpc_channel_create(string target, IntPtr channelArgs); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern void grpc_channel_destroy(IntPtr channel); |
||||||
|
|
||||||
|
private ChannelSafeHandle() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public static ChannelSafeHandle Create(string target, IntPtr channelArgs) |
||||||
|
{ |
||||||
|
return grpc_channel_create(target, channelArgs); |
||||||
|
} |
||||||
|
|
||||||
|
protected override bool ReleaseHandle() |
||||||
|
{ |
||||||
|
grpc_channel_destroy(handle); |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,66 @@ |
|||||||
|
using System; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
using System.Threading.Tasks; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core.Internal |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// grpc_completion_queue from <grpc/grpc.h> |
||||||
|
/// </summary> |
||||||
|
internal class CompletionQueueSafeHandle : SafeHandleZeroIsInvalid |
||||||
|
{ |
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern CompletionQueueSafeHandle grpc_completion_queue_create(); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern EventSafeHandle grpc_completion_queue_pluck(CompletionQueueSafeHandle cq, IntPtr tag, Timespec deadline); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern EventSafeHandle grpc_completion_queue_next(CompletionQueueSafeHandle cq, Timespec deadline); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern void grpc_completion_queue_shutdown(CompletionQueueSafeHandle cq); |
||||||
|
|
||||||
|
[DllImport("libgrpc_csharp_ext.so")] |
||||||
|
static extern GRPCCompletionType grpc_completion_queue_next_with_callback(CompletionQueueSafeHandle cq); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern void grpc_completion_queue_destroy(IntPtr cq); |
||||||
|
|
||||||
|
private CompletionQueueSafeHandle() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public static CompletionQueueSafeHandle Create() |
||||||
|
{ |
||||||
|
return grpc_completion_queue_create(); |
||||||
|
} |
||||||
|
|
||||||
|
public EventSafeHandle Next(Timespec deadline) |
||||||
|
{ |
||||||
|
return grpc_completion_queue_next(this, deadline); |
||||||
|
} |
||||||
|
|
||||||
|
public GRPCCompletionType NextWithCallback() |
||||||
|
{ |
||||||
|
return grpc_completion_queue_next_with_callback(this); |
||||||
|
} |
||||||
|
|
||||||
|
public EventSafeHandle Pluck(IntPtr tag, Timespec deadline) |
||||||
|
{ |
||||||
|
return grpc_completion_queue_pluck(this, tag, deadline); |
||||||
|
} |
||||||
|
|
||||||
|
public void Shutdown() |
||||||
|
{ |
||||||
|
grpc_completion_queue_shutdown(this); |
||||||
|
} |
||||||
|
|
||||||
|
protected override bool ReleaseHandle() |
||||||
|
{ |
||||||
|
grpc_completion_queue_destroy(handle); |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,75 @@ |
|||||||
|
using System; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core.Internal |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// from grpc/grpc.h |
||||||
|
/// </summary> |
||||||
|
internal enum GRPCCallError |
||||||
|
{ |
||||||
|
/* everything went ok */ |
||||||
|
GRPC_CALL_OK = 0, |
||||||
|
/* something failed, we don't know what */ |
||||||
|
GRPC_CALL_ERROR, |
||||||
|
/* this method is not available on the server */ |
||||||
|
GRPC_CALL_ERROR_NOT_ON_SERVER, |
||||||
|
/* this method is not available on the client */ |
||||||
|
GRPC_CALL_ERROR_NOT_ON_CLIENT, |
||||||
|
/* this method must be called before server_accept */ |
||||||
|
GRPC_CALL_ERROR_ALREADY_ACCEPTED, |
||||||
|
/* this method must be called before invoke */ |
||||||
|
GRPC_CALL_ERROR_ALREADY_INVOKED, |
||||||
|
/* this method must be called after invoke */ |
||||||
|
GRPC_CALL_ERROR_NOT_INVOKED, |
||||||
|
/* this call is already finished |
||||||
|
(writes_done or write_status has already been called) */ |
||||||
|
GRPC_CALL_ERROR_ALREADY_FINISHED, |
||||||
|
/* there is already an outstanding read/write operation on the call */ |
||||||
|
GRPC_CALL_ERROR_TOO_MANY_OPERATIONS, |
||||||
|
/* the flags value was illegal for this call */ |
||||||
|
GRPC_CALL_ERROR_INVALID_FLAGS |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// grpc_completion_type from grpc/grpc.h |
||||||
|
/// </summary> |
||||||
|
internal enum GRPCCompletionType |
||||||
|
{ |
||||||
|
GRPC_QUEUE_SHUTDOWN, |
||||||
|
/* Shutting down */ |
||||||
|
GRPC_READ, |
||||||
|
/* A read has completed */ |
||||||
|
GRPC_INVOKE_ACCEPTED, |
||||||
|
/* An invoke call has been accepted by flow |
||||||
|
control */ |
||||||
|
GRPC_WRITE_ACCEPTED, |
||||||
|
/* A write has been accepted by |
||||||
|
flow control */ |
||||||
|
GRPC_FINISH_ACCEPTED, |
||||||
|
/* writes_done or write_status has been accepted */ |
||||||
|
GRPC_CLIENT_METADATA_READ, |
||||||
|
/* The metadata array sent by server received at |
||||||
|
client */ |
||||||
|
GRPC_FINISHED, |
||||||
|
/* An RPC has finished. The event contains status. |
||||||
|
On the server this will be OK or Cancelled. */ |
||||||
|
GRPC_SERVER_RPC_NEW, |
||||||
|
/* A new RPC has arrived at the server */ |
||||||
|
GRPC_COMPLETION_DO_NOT_USE |
||||||
|
/* must be last, forces users to include |
||||||
|
a default: case */ |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// grpc_op_error from grpc/grpc.h |
||||||
|
/// </summary> |
||||||
|
internal enum GRPCOpError |
||||||
|
{ |
||||||
|
/* everything went ok */ |
||||||
|
GRPC_OP_OK = 0, |
||||||
|
/* something failed, we don't know what */ |
||||||
|
GRPC_OP_ERROR |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,191 @@ |
|||||||
|
using System; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
using Google.GRPC.Core; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core.Internal |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// grpc_event from grpc/grpc.h |
||||||
|
/// </summary> |
||||||
|
internal class EventSafeHandle : SafeHandleZeroIsInvalid |
||||||
|
{ |
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern void grpc_event_finish(IntPtr ev); |
||||||
|
|
||||||
|
[DllImport("libgrpc_csharp_ext.so")] |
||||||
|
static extern GRPCCompletionType grpc_event_type(EventSafeHandle ev); |
||||||
|
|
||||||
|
[DllImport("libgrpc_csharp_ext.so")] |
||||||
|
static extern CallSafeHandle grpc_event_call(EventSafeHandle ev); |
||||||
|
|
||||||
|
[DllImport("libgrpc_csharp_ext.so")] |
||||||
|
static extern GRPCOpError grpc_event_write_accepted(EventSafeHandle ev); |
||||||
|
|
||||||
|
[DllImport("libgrpc_csharp_ext.so")] |
||||||
|
static extern GRPCOpError grpc_event_finish_accepted(EventSafeHandle ev); |
||||||
|
|
||||||
|
[DllImport("libgrpc_csharp_ext.so")] |
||||||
|
static extern StatusCode grpc_event_finished_status(EventSafeHandle ev); |
||||||
|
|
||||||
|
[DllImport("libgrpc_csharp_ext.so")] |
||||||
|
static extern IntPtr grpc_event_finished_details(EventSafeHandle ev); // returns const char* |
||||||
|
|
||||||
|
[DllImport("libgrpc_csharp_ext.so")] |
||||||
|
static extern IntPtr grpc_event_read_length(EventSafeHandle ev); |
||||||
|
|
||||||
|
[DllImport("libgrpc_csharp_ext.so")] |
||||||
|
static extern void grpc_event_read_copy_to_buffer(EventSafeHandle ev, byte[] buffer, UIntPtr bufferLen); |
||||||
|
|
||||||
|
[DllImport("libgrpc_csharp_ext.so")] |
||||||
|
static extern IntPtr grpc_event_server_rpc_new_method(EventSafeHandle ev); // returns const char* |
||||||
|
|
||||||
|
public GRPCCompletionType GetCompletionType() |
||||||
|
{ |
||||||
|
return grpc_event_type(this); |
||||||
|
} |
||||||
|
|
||||||
|
public GRPCOpError GetWriteAccepted() |
||||||
|
{ |
||||||
|
return grpc_event_write_accepted(this); |
||||||
|
} |
||||||
|
|
||||||
|
public GRPCOpError GetFinishAccepted() |
||||||
|
{ |
||||||
|
return grpc_event_finish_accepted(this); |
||||||
|
} |
||||||
|
|
||||||
|
public Status GetFinished() |
||||||
|
{ |
||||||
|
// TODO: can the native method return string directly? |
||||||
|
string details = Marshal.PtrToStringAnsi(grpc_event_finished_details(this)); |
||||||
|
return new Status(grpc_event_finished_status(this), details); |
||||||
|
} |
||||||
|
|
||||||
|
public byte[] GetReadData() |
||||||
|
{ |
||||||
|
IntPtr len = grpc_event_read_length(this); |
||||||
|
if (len == new IntPtr(-1)) |
||||||
|
{ |
||||||
|
return null; |
||||||
|
} |
||||||
|
byte[] data = new byte[(int) len]; |
||||||
|
grpc_event_read_copy_to_buffer(this, data, new UIntPtr((ulong)data.Length)); |
||||||
|
return data; |
||||||
|
} |
||||||
|
|
||||||
|
public CallSafeHandle GetCall() { |
||||||
|
return grpc_event_call(this); |
||||||
|
} |
||||||
|
|
||||||
|
public string GetServerRpcNewMethod() { |
||||||
|
// TODO: can the native method return string directly? |
||||||
|
return Marshal.PtrToStringAnsi(grpc_event_server_rpc_new_method(this)); |
||||||
|
} |
||||||
|
|
||||||
|
//TODO: client_metadata_read event type |
||||||
|
|
||||||
|
protected override bool ReleaseHandle() |
||||||
|
{ |
||||||
|
grpc_event_finish(handle); |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: this is basically c&p of EventSafeHandle. Unify! |
||||||
|
/// <summary> |
||||||
|
/// Not owned version of |
||||||
|
/// grpc_event from grpc/grpc.h |
||||||
|
/// </summary> |
||||||
|
internal class EventSafeHandleNotOwned : SafeHandleZeroIsInvalid |
||||||
|
{ |
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern void grpc_event_finish(IntPtr ev); |
||||||
|
|
||||||
|
[DllImport("libgrpc_csharp_ext.so")] |
||||||
|
static extern GRPCCompletionType grpc_event_type(EventSafeHandleNotOwned ev); |
||||||
|
|
||||||
|
[DllImport("libgrpc_csharp_ext.so")] |
||||||
|
static extern CallSafeHandle grpc_event_call(EventSafeHandleNotOwned ev); |
||||||
|
|
||||||
|
[DllImport("libgrpc_csharp_ext.so")] |
||||||
|
static extern GRPCOpError grpc_event_write_accepted(EventSafeHandleNotOwned ev); |
||||||
|
|
||||||
|
[DllImport("libgrpc_csharp_ext.so")] |
||||||
|
static extern GRPCOpError grpc_event_finish_accepted(EventSafeHandleNotOwned ev); |
||||||
|
|
||||||
|
[DllImport("libgrpc_csharp_ext.so")] |
||||||
|
static extern StatusCode grpc_event_finished_status(EventSafeHandleNotOwned ev); |
||||||
|
|
||||||
|
[DllImport("libgrpc_csharp_ext.so")] |
||||||
|
static extern IntPtr grpc_event_finished_details(EventSafeHandleNotOwned ev); // returns const char* |
||||||
|
|
||||||
|
[DllImport("libgrpc_csharp_ext.so")] |
||||||
|
static extern IntPtr grpc_event_read_length(EventSafeHandleNotOwned ev); |
||||||
|
|
||||||
|
[DllImport("libgrpc_csharp_ext.so")] |
||||||
|
static extern void grpc_event_read_copy_to_buffer(EventSafeHandleNotOwned ev, byte[] buffer, UIntPtr bufferLen); |
||||||
|
|
||||||
|
[DllImport("libgrpc_csharp_ext.so")] |
||||||
|
static extern IntPtr grpc_event_server_rpc_new_method(EventSafeHandleNotOwned ev); // returns const char* |
||||||
|
|
||||||
|
public EventSafeHandleNotOwned() : base(false) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public EventSafeHandleNotOwned(IntPtr handle) : base(false) |
||||||
|
{ |
||||||
|
SetHandle(handle); |
||||||
|
} |
||||||
|
|
||||||
|
public GRPCCompletionType GetCompletionType() |
||||||
|
{ |
||||||
|
return grpc_event_type(this); |
||||||
|
} |
||||||
|
|
||||||
|
public GRPCOpError GetWriteAccepted() |
||||||
|
{ |
||||||
|
return grpc_event_write_accepted(this); |
||||||
|
} |
||||||
|
|
||||||
|
public GRPCOpError GetFinishAccepted() |
||||||
|
{ |
||||||
|
return grpc_event_finish_accepted(this); |
||||||
|
} |
||||||
|
|
||||||
|
public Status GetFinished() |
||||||
|
{ |
||||||
|
// TODO: can the native method return string directly? |
||||||
|
string details = Marshal.PtrToStringAnsi(grpc_event_finished_details(this)); |
||||||
|
return new Status(grpc_event_finished_status(this), details); |
||||||
|
} |
||||||
|
|
||||||
|
public byte[] GetReadData() |
||||||
|
{ |
||||||
|
IntPtr len = grpc_event_read_length(this); |
||||||
|
if (len == new IntPtr(-1)) |
||||||
|
{ |
||||||
|
return null; |
||||||
|
} |
||||||
|
byte[] data = new byte[(int) len]; |
||||||
|
grpc_event_read_copy_to_buffer(this, data, new UIntPtr((ulong)data.Length)); |
||||||
|
return data; |
||||||
|
} |
||||||
|
|
||||||
|
public CallSafeHandle GetCall() { |
||||||
|
return grpc_event_call(this); |
||||||
|
} |
||||||
|
|
||||||
|
public string GetServerRpcNewMethod() { |
||||||
|
// TODO: can the native method return string directly? |
||||||
|
return Marshal.PtrToStringAnsi(grpc_event_server_rpc_new_method(this)); |
||||||
|
} |
||||||
|
|
||||||
|
//TODO: client_metadata_read event type |
||||||
|
|
||||||
|
protected override bool ReleaseHandle() |
||||||
|
{ |
||||||
|
grpc_event_finish(handle); |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,129 @@ |
|||||||
|
using System; |
||||||
|
using Google.GRPC.Core.Internal; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using System.Collections.Generic; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core.Internal |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// Pool of threads polling on the same completion queue. |
||||||
|
/// </summary> |
||||||
|
internal class GrpcThreadPool |
||||||
|
{ |
||||||
|
readonly object myLock = new object(); |
||||||
|
readonly List<Thread> threads = new List<Thread>(); |
||||||
|
readonly int poolSize; |
||||||
|
readonly Action<EventSafeHandle> eventHandler; |
||||||
|
|
||||||
|
CompletionQueueSafeHandle cq; |
||||||
|
|
||||||
|
public GrpcThreadPool(int poolSize) { |
||||||
|
this.poolSize = poolSize; |
||||||
|
} |
||||||
|
|
||||||
|
internal GrpcThreadPool(int poolSize, Action<EventSafeHandle> eventHandler) { |
||||||
|
this.poolSize = poolSize; |
||||||
|
this.eventHandler = eventHandler; |
||||||
|
} |
||||||
|
|
||||||
|
public void Start() { |
||||||
|
|
||||||
|
lock (myLock) |
||||||
|
{ |
||||||
|
if (cq != null) |
||||||
|
{ |
||||||
|
throw new InvalidOperationException("Already started."); |
||||||
|
} |
||||||
|
|
||||||
|
cq = CompletionQueueSafeHandle.Create(); |
||||||
|
|
||||||
|
for (int i = 0; i < poolSize; i++) |
||||||
|
{ |
||||||
|
threads.Add(CreateAndStartThread(i)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void Stop() { |
||||||
|
|
||||||
|
lock (myLock) |
||||||
|
{ |
||||||
|
cq.Shutdown(); |
||||||
|
|
||||||
|
Console.WriteLine("Waiting for GPRC threads to finish."); |
||||||
|
foreach (var thread in threads) |
||||||
|
{ |
||||||
|
thread.Join(); |
||||||
|
} |
||||||
|
|
||||||
|
cq.Dispose(); |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal CompletionQueueSafeHandle CompletionQueue |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
return cq; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Thread CreateAndStartThread(int i) { |
||||||
|
Action body; |
||||||
|
if (eventHandler != null) |
||||||
|
{ |
||||||
|
body = ThreadBodyWithHandler; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
body = ThreadBodyNoHandler; |
||||||
|
} |
||||||
|
var thread = new Thread(new ThreadStart(body)); |
||||||
|
thread.IsBackground = false; |
||||||
|
thread.Start(); |
||||||
|
if (eventHandler != null) |
||||||
|
{ |
||||||
|
thread.Name = "grpc_server_newrpc " + i; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
thread.Name = "grpc " + i; |
||||||
|
} |
||||||
|
return thread; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Body of the polling thread. |
||||||
|
/// </summary> |
||||||
|
private void ThreadBodyNoHandler() |
||||||
|
{ |
||||||
|
GRPCCompletionType completionType; |
||||||
|
do |
||||||
|
{ |
||||||
|
completionType = cq.NextWithCallback(); |
||||||
|
} while(completionType != GRPCCompletionType.GRPC_QUEUE_SHUTDOWN); |
||||||
|
Console.WriteLine("Completion queue has shutdown successfully, thread " + Thread.CurrentThread.Name + " exiting."); |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Body of the polling thread. |
||||||
|
/// </summary> |
||||||
|
private void ThreadBodyWithHandler() |
||||||
|
{ |
||||||
|
GRPCCompletionType completionType; |
||||||
|
do |
||||||
|
{ |
||||||
|
using (EventSafeHandle ev = cq.Next(Timespec.InfFuture)) { |
||||||
|
completionType = ev.GetCompletionType(); |
||||||
|
eventHandler(ev); |
||||||
|
} |
||||||
|
} while(completionType != GRPCCompletionType.GRPC_QUEUE_SHUTDOWN); |
||||||
|
Console.WriteLine("Completion queue has shutdown successfully, thread " + Thread.CurrentThread.Name + " exiting."); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
@ -0,0 +1,28 @@ |
|||||||
|
using System; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core.Internal |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// Safe handle to wrap native objects. |
||||||
|
/// </summary> |
||||||
|
internal abstract class SafeHandleZeroIsInvalid : SafeHandle |
||||||
|
{ |
||||||
|
public SafeHandleZeroIsInvalid() : base(IntPtr.Zero, true) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public SafeHandleZeroIsInvalid(bool ownsHandle) : base(IntPtr.Zero, ownsHandle) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public override bool IsInvalid |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
return handle == IntPtr.Zero; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,76 @@ |
|||||||
|
using System; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.Collections.Concurrent; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core.Internal |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// grpc_server from grpc/grpc.h |
||||||
|
/// </summary> |
||||||
|
internal sealed class ServerSafeHandle : SafeHandleZeroIsInvalid |
||||||
|
{ |
||||||
|
[DllImport("libgrpc.so", EntryPoint = "grpc_server_request_call_old")] |
||||||
|
static extern GRPCCallError grpc_server_request_call_old_CALLBACK(ServerSafeHandle server, [MarshalAs(UnmanagedType.FunctionPtr)] EventCallbackDelegate callback); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern ServerSafeHandle grpc_server_create(CompletionQueueSafeHandle cq, IntPtr args); |
||||||
|
|
||||||
|
// TODO: check int representation size |
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern int grpc_server_add_http2_port(ServerSafeHandle server, string addr); |
||||||
|
|
||||||
|
// TODO: check int representation size |
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern int grpc_server_add_secure_http2_port(ServerSafeHandle server, string addr); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern void grpc_server_start(ServerSafeHandle server); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern void grpc_server_shutdown(ServerSafeHandle server); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern void grpc_server_shutdown_and_notify(ServerSafeHandle server, IntPtr tag); |
||||||
|
|
||||||
|
[DllImport("libgrpc.so")] |
||||||
|
static extern void grpc_server_destroy(IntPtr server); |
||||||
|
|
||||||
|
private ServerSafeHandle() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public static ServerSafeHandle NewServer(CompletionQueueSafeHandle cq, IntPtr args) |
||||||
|
{ |
||||||
|
// TODO: also grpc_secure_server_create... |
||||||
|
return grpc_server_create(cq, args); |
||||||
|
} |
||||||
|
|
||||||
|
public int AddPort(string addr) |
||||||
|
{ |
||||||
|
// TODO: also grpc_server_add_secure_http2_port... |
||||||
|
return grpc_server_add_http2_port(this, addr); |
||||||
|
} |
||||||
|
|
||||||
|
public void Start() |
||||||
|
{ |
||||||
|
grpc_server_start(this); |
||||||
|
} |
||||||
|
|
||||||
|
public void Shutdown() |
||||||
|
{ |
||||||
|
grpc_server_shutdown(this); |
||||||
|
} |
||||||
|
|
||||||
|
public GRPCCallError RequestCall(EventCallbackDelegate callback) |
||||||
|
{ |
||||||
|
return grpc_server_request_call_old_CALLBACK(this, callback); |
||||||
|
} |
||||||
|
|
||||||
|
protected override bool ReleaseHandle() |
||||||
|
{ |
||||||
|
grpc_server_destroy(handle); |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,33 @@ |
|||||||
|
using System; |
||||||
|
using Google.GRPC.Core.Internal; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core |
||||||
|
{ |
||||||
|
internal class StreamingInputObserver<TWrite, TRead> : IObserver<TWrite> |
||||||
|
{ |
||||||
|
readonly AsyncCall<TWrite, TRead> call; |
||||||
|
|
||||||
|
public StreamingInputObserver(AsyncCall<TWrite, TRead> call) |
||||||
|
{ |
||||||
|
this.call = call; |
||||||
|
} |
||||||
|
|
||||||
|
public void OnCompleted() |
||||||
|
{ |
||||||
|
// TODO: how bad is the Wait here? |
||||||
|
call.WritesCompletedAsync().Wait(); |
||||||
|
} |
||||||
|
|
||||||
|
public void OnError(Exception error) |
||||||
|
{ |
||||||
|
throw new InvalidOperationException("This should never be called."); |
||||||
|
} |
||||||
|
|
||||||
|
public void OnNext(TWrite value) |
||||||
|
{ |
||||||
|
// TODO: how bad is the Wait here? |
||||||
|
call.WriteAsync(value).Wait(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,67 @@ |
|||||||
|
using System; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
using System.Threading; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core.Internal |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// gpr_timespec from grpc/support/time.h |
||||||
|
/// </summary> |
||||||
|
[StructLayout(LayoutKind.Sequential)] |
||||||
|
internal struct Timespec |
||||||
|
{ |
||||||
|
const int nanosPerSecond = 1000 * 1000 * 1000; |
||||||
|
const int nanosPerTick = 100; |
||||||
|
|
||||||
|
[DllImport("libgpr.so")] |
||||||
|
static extern Timespec gpr_now(); |
||||||
|
|
||||||
|
// TODO: this only works on 64bit linux, can we autoselect the right size of ints? |
||||||
|
// perhaps using IntPtr would work. |
||||||
|
public System.Int64 tv_sec; |
||||||
|
public System.Int64 tv_nsec; |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Timespec a long time in the future. |
||||||
|
/// </summary> |
||||||
|
public static Timespec InfFuture |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
// TODO: set correct value based on the length of the struct |
||||||
|
return new Timespec { tv_sec = Int32.MaxValue, tv_nsec = 0 }; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static Timespec Now |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
return gpr_now(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// Creates a GPR deadline from current instant and given timeout. |
||||||
|
/// </summary> |
||||||
|
/// <returns>The from timeout.</returns> |
||||||
|
public static Timespec DeadlineFromTimeout(TimeSpan timeout) { |
||||||
|
if (timeout == Timeout.InfiniteTimeSpan) |
||||||
|
{ |
||||||
|
return Timespec.InfFuture; |
||||||
|
} |
||||||
|
return Timespec.Now.Add(timeout); |
||||||
|
} |
||||||
|
|
||||||
|
public Timespec Add(TimeSpan timeSpan) { |
||||||
|
long nanos = tv_nsec + (timeSpan.Ticks % TimeSpan.TicksPerSecond) * nanosPerTick; |
||||||
|
long overflow_sec = (nanos > nanosPerSecond) ? 1 : 0; |
||||||
|
|
||||||
|
Timespec result; |
||||||
|
result.tv_nsec = nanos % nanosPerSecond; |
||||||
|
result.tv_sec = tv_sec + (timeSpan.Ticks / TimeSpan.TicksPerSecond) + overflow_sec; |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,24 @@ |
|||||||
|
using System.Reflection; |
||||||
|
using System.Runtime.CompilerServices; |
||||||
|
|
||||||
|
// Information about this assembly is defined by the following attributes. |
||||||
|
// Change them to the values specific to your project. |
||||||
|
[assembly: AssemblyTitle ("GrpcCore")] |
||||||
|
[assembly: AssemblyDescription ("")] |
||||||
|
[assembly: AssemblyConfiguration ("")] |
||||||
|
[assembly: AssemblyCompany ("")] |
||||||
|
[assembly: AssemblyProduct ("")] |
||||||
|
[assembly: AssemblyCopyright ("jtattermusch")] |
||||||
|
[assembly: AssemblyTrademark ("")] |
||||||
|
[assembly: AssemblyCulture ("")] |
||||||
|
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". |
||||||
|
// The form "{Major}.{Minor}.*" will automatically update the build and revision, |
||||||
|
// and "{Major}.{Minor}.{Build}.*" will update just the revision. |
||||||
|
[assembly: AssemblyVersion ("1.0.*")] |
||||||
|
// The following attributes are used to specify the signing key for the assembly, |
||||||
|
// if desired. See the Mono documentation for more information about signing. |
||||||
|
//[assembly: AssemblyDelaySign(false)] |
||||||
|
//[assembly: AssemblyKeyFile("")] |
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("GrpcCoreTests")] |
||||||
|
|
@ -0,0 +1,27 @@ |
|||||||
|
using System; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core |
||||||
|
{ |
||||||
|
public class RpcException : Exception |
||||||
|
{ |
||||||
|
private readonly Status status; |
||||||
|
|
||||||
|
public RpcException(Status status) |
||||||
|
{ |
||||||
|
this.status = status; |
||||||
|
} |
||||||
|
|
||||||
|
public RpcException(Status status, string message) : base(message) |
||||||
|
{ |
||||||
|
this.status = status; |
||||||
|
} |
||||||
|
|
||||||
|
public Status Status { |
||||||
|
get |
||||||
|
{ |
||||||
|
return status; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,141 @@ |
|||||||
|
using System; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.Collections.Concurrent; |
||||||
|
using Google.GRPC.Core.Internal; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// Server is implemented only to be able to do |
||||||
|
/// in-process testing. |
||||||
|
/// </summary> |
||||||
|
public class Server |
||||||
|
{ |
||||||
|
// TODO: make sure the delegate doesn't get garbage collected while |
||||||
|
// native callbacks are in the completion queue. |
||||||
|
readonly EventCallbackDelegate newRpcHandler; |
||||||
|
|
||||||
|
readonly BlockingCollection<NewRpcInfo> newRpcQueue = new BlockingCollection<NewRpcInfo>(); |
||||||
|
readonly ServerSafeHandle handle; |
||||||
|
|
||||||
|
static Server() { |
||||||
|
GrpcEnvironment.EnsureInitialized(); |
||||||
|
} |
||||||
|
|
||||||
|
public Server() |
||||||
|
{ |
||||||
|
// TODO: what is the tag for server shutdown? |
||||||
|
this.handle = ServerSafeHandle.NewServer(GetCompletionQueue(), IntPtr.Zero); |
||||||
|
this.newRpcHandler = HandleNewRpc; |
||||||
|
} |
||||||
|
|
||||||
|
public int AddPort(string addr) { |
||||||
|
return handle.AddPort(addr); |
||||||
|
} |
||||||
|
|
||||||
|
public void Start() |
||||||
|
{ |
||||||
|
handle.Start(); |
||||||
|
} |
||||||
|
|
||||||
|
public void RunRpc() |
||||||
|
{ |
||||||
|
AllowOneRpc(); |
||||||
|
|
||||||
|
try { |
||||||
|
var rpcInfo = newRpcQueue.Take(); |
||||||
|
|
||||||
|
Console.WriteLine("Server received RPC " + rpcInfo.Method); |
||||||
|
|
||||||
|
AsyncCall<byte[], byte[]> asyncCall = new AsyncCall<byte[], byte[]>( |
||||||
|
(payload) => payload, (payload) => payload); |
||||||
|
|
||||||
|
asyncCall.InitializeServer(rpcInfo.Call); |
||||||
|
|
||||||
|
asyncCall.Accept(GetCompletionQueue()); |
||||||
|
|
||||||
|
while(true) { |
||||||
|
byte[] payload = asyncCall.ReadAsync().Result; |
||||||
|
if (payload == null) |
||||||
|
{ |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
asyncCall.WriteAsync(new byte[] { }).Wait(); |
||||||
|
|
||||||
|
// TODO: what should be the details? |
||||||
|
asyncCall.WriteStatusAsync(new Status(StatusCode.GRPC_STATUS_OK, "")).Wait(); |
||||||
|
|
||||||
|
asyncCall.Finished.Wait(); |
||||||
|
} catch(Exception e) { |
||||||
|
Console.WriteLine("Exception while handling RPC: " + e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: implement disposal properly... |
||||||
|
public void Shutdown() { |
||||||
|
handle.Shutdown(); |
||||||
|
|
||||||
|
|
||||||
|
//handle.Dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
private void AllowOneRpc() |
||||||
|
{ |
||||||
|
AssertCallOk(handle.RequestCall(newRpcHandler)); |
||||||
|
} |
||||||
|
|
||||||
|
private void HandleNewRpc(IntPtr eventPtr) |
||||||
|
{ |
||||||
|
try |
||||||
|
{ |
||||||
|
var ev = new EventSafeHandleNotOwned(eventPtr); |
||||||
|
newRpcQueue.Add(new NewRpcInfo(ev.GetCall(), ev.GetServerRpcNewMethod())); |
||||||
|
} |
||||||
|
catch (Exception e) |
||||||
|
{ |
||||||
|
Console.WriteLine("Caught exception in a native handler: " + e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static void AssertCallOk(GRPCCallError callError) |
||||||
|
{ |
||||||
|
Trace.Assert(callError == GRPCCallError.GRPC_CALL_OK, "Status not GRPC_CALL_OK"); |
||||||
|
} |
||||||
|
|
||||||
|
private static CompletionQueueSafeHandle GetCompletionQueue() |
||||||
|
{ |
||||||
|
return GrpcEnvironment.ThreadPool.CompletionQueue; |
||||||
|
} |
||||||
|
|
||||||
|
private struct NewRpcInfo |
||||||
|
{ |
||||||
|
private CallSafeHandle call; |
||||||
|
private string method; |
||||||
|
|
||||||
|
public NewRpcInfo(CallSafeHandle call, string method) |
||||||
|
{ |
||||||
|
this.call = call; |
||||||
|
this.method = method; |
||||||
|
} |
||||||
|
|
||||||
|
public CallSafeHandle Call |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
return this.call; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public string Method |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
return this.method; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
using System; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// Represents RPC result. |
||||||
|
/// </summary> |
||||||
|
public struct Status |
||||||
|
{ |
||||||
|
readonly StatusCode statusCode; |
||||||
|
readonly string detail; |
||||||
|
|
||||||
|
public Status(StatusCode statusCode, string detail) |
||||||
|
{ |
||||||
|
this.statusCode = statusCode; |
||||||
|
this.detail = detail; |
||||||
|
} |
||||||
|
|
||||||
|
public StatusCode StatusCode |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
return statusCode; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public string Detail |
||||||
|
{ |
||||||
|
get |
||||||
|
{ |
||||||
|
return detail; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,150 @@ |
|||||||
|
using System; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core |
||||||
|
{ |
||||||
|
// TODO: element names should changed to comply with C# naming conventions. |
||||||
|
/// <summary> |
||||||
|
/// grpc_status_code from grpc/status.h |
||||||
|
/// </summary> |
||||||
|
public enum StatusCode |
||||||
|
{ |
||||||
|
/* Not an error; returned on success |
||||||
|
|
||||||
|
HTTP Mapping: 200 OK */ |
||||||
|
GRPC_STATUS_OK = 0, |
||||||
|
/* The operation was cancelled (typically by the caller). |
||||||
|
|
||||||
|
HTTP Mapping: 499 Client Closed Request */ |
||||||
|
GRPC_STATUS_CANCELLED = 1, |
||||||
|
/* Unknown error. An example of where this error may be returned is |
||||||
|
if a Status value received from another address space belongs to |
||||||
|
an error-space that is not known in this address space. Also |
||||||
|
errors raised by APIs that do not return enough error information |
||||||
|
may be converted to this error. |
||||||
|
|
||||||
|
HTTP Mapping: 500 Internal Server Error */ |
||||||
|
GRPC_STATUS_UNKNOWN = 2, |
||||||
|
/* Client specified an invalid argument. Note that this differs |
||||||
|
from FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments |
||||||
|
that are problematic regardless of the state of the system |
||||||
|
(e.g., a malformed file name). |
||||||
|
|
||||||
|
HTTP Mapping: 400 Bad Request */ |
||||||
|
GRPC_STATUS_INVALID_ARGUMENT = 3, |
||||||
|
/* Deadline expired before operation could complete. For operations |
||||||
|
that change the state of the system, this error may be returned |
||||||
|
even if the operation has completed successfully. For example, a |
||||||
|
successful response from a server could have been delayed long |
||||||
|
enough for the deadline to expire. |
||||||
|
|
||||||
|
HTTP Mapping: 504 Gateway Timeout */ |
||||||
|
GRPC_STATUS_DEADLINE_EXCEEDED = 4, |
||||||
|
/* Some requested entity (e.g., file or directory) was not found. |
||||||
|
|
||||||
|
HTTP Mapping: 404 Not Found */ |
||||||
|
GRPC_STATUS_NOT_FOUND = 5, |
||||||
|
/* Some entity that we attempted to create (e.g., file or directory) |
||||||
|
already exists. |
||||||
|
|
||||||
|
HTTP Mapping: 409 Conflict */ |
||||||
|
GRPC_STATUS_ALREADY_EXISTS = 6, |
||||||
|
/* The caller does not have permission to execute the specified |
||||||
|
operation. PERMISSION_DENIED must not be used for rejections |
||||||
|
caused by exhausting some resource (use RESOURCE_EXHAUSTED |
||||||
|
instead for those errors). PERMISSION_DENIED must not be |
||||||
|
used if the caller can not be identified (use UNAUTHENTICATED |
||||||
|
instead for those errors). |
||||||
|
|
||||||
|
HTTP Mapping: 403 Forbidden */ |
||||||
|
GRPC_STATUS_PERMISSION_DENIED = 7, |
||||||
|
/* The request does not have valid authentication credentials for the |
||||||
|
operation. |
||||||
|
|
||||||
|
HTTP Mapping: 401 Unauthorized */ |
||||||
|
GRPC_STATUS_UNAUTHENTICATED = 16, |
||||||
|
/* Some resource has been exhausted, perhaps a per-user quota, or |
||||||
|
perhaps the entire file system is out of space. |
||||||
|
|
||||||
|
HTTP Mapping: 429 Too Many Requests */ |
||||||
|
GRPC_STATUS_RESOURCE_EXHAUSTED = 8, |
||||||
|
/* Operation was rejected because the system is not in a state |
||||||
|
required for the operation's execution. For example, directory |
||||||
|
to be deleted may be non-empty, an rmdir operation is applied to |
||||||
|
a non-directory, etc. |
||||||
|
|
||||||
|
A litmus test that may help a service implementor in deciding |
||||||
|
between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE: |
||||||
|
(a) Use UNAVAILABLE if the client can retry just the failing call. |
||||||
|
(b) Use ABORTED if the client should retry at a higher-level |
||||||
|
(e.g., restarting a read-modify-write sequence). |
||||||
|
(c) Use FAILED_PRECONDITION if the client should not retry until |
||||||
|
the system state has been explicitly fixed. E.g., if an "rmdir" |
||||||
|
fails because the directory is non-empty, FAILED_PRECONDITION |
||||||
|
should be returned since the client should not retry unless |
||||||
|
they have first fixed up the directory by deleting files from it. |
||||||
|
(d) Use FAILED_PRECONDITION if the client performs conditional |
||||||
|
REST Get/Update/Delete on a resource and the resource on the |
||||||
|
server does not match the condition. E.g., conflicting |
||||||
|
read-modify-write on the same resource. |
||||||
|
|
||||||
|
HTTP Mapping: 400 Bad Request |
||||||
|
|
||||||
|
NOTE: HTTP spec says 412 Precondition Failed should only be used if |
||||||
|
the request contains Etag related headers. So if the server does see |
||||||
|
Etag related headers in the request, it may choose to return 412 |
||||||
|
instead of 400 for this error code. */ |
||||||
|
GRPC_STATUS_FAILED_PRECONDITION = 9, |
||||||
|
/* The operation was aborted, typically due to a concurrency issue |
||||||
|
like sequencer check failures, transaction aborts, etc. |
||||||
|
|
||||||
|
See litmus test above for deciding between FAILED_PRECONDITION, |
||||||
|
ABORTED, and UNAVAILABLE. |
||||||
|
|
||||||
|
HTTP Mapping: 409 Conflict */ |
||||||
|
GRPC_STATUS_ABORTED = 10, |
||||||
|
/* Operation was attempted past the valid range. E.g., seeking or |
||||||
|
reading past end of file. |
||||||
|
|
||||||
|
Unlike INVALID_ARGUMENT, this error indicates a problem that may |
||||||
|
be fixed if the system state changes. For example, a 32-bit file |
||||||
|
system will generate INVALID_ARGUMENT if asked to read at an |
||||||
|
offset that is not in the range [0,2^32-1], but it will generate |
||||||
|
OUT_OF_RANGE if asked to read from an offset past the current |
||||||
|
file size. |
||||||
|
|
||||||
|
There is a fair bit of overlap between FAILED_PRECONDITION and |
||||||
|
OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific |
||||||
|
error) when it applies so that callers who are iterating through |
||||||
|
a space can easily look for an OUT_OF_RANGE error to detect when |
||||||
|
they are done. |
||||||
|
|
||||||
|
HTTP Mapping: 400 Bad Request */ |
||||||
|
GRPC_STATUS_OUT_OF_RANGE = 11, |
||||||
|
/* Operation is not implemented or not supported/enabled in this service. |
||||||
|
|
||||||
|
HTTP Mapping: 501 Not Implemented */ |
||||||
|
GRPC_STATUS_UNIMPLEMENTED = 12, |
||||||
|
/* Internal errors. Means some invariants expected by underlying |
||||||
|
system has been broken. If you see one of these errors, |
||||||
|
something is very broken. |
||||||
|
|
||||||
|
HTTP Mapping: 500 Internal Server Error */ |
||||||
|
GRPC_STATUS_INTERNAL = 13, |
||||||
|
/* The service is currently unavailable. This is a most likely a |
||||||
|
transient condition and may be corrected by retrying with |
||||||
|
a backoff. |
||||||
|
|
||||||
|
See litmus test above for deciding between FAILED_PRECONDITION, |
||||||
|
ABORTED, and UNAVAILABLE. |
||||||
|
|
||||||
|
HTTP Mapping: 503 Service Unavailable */ |
||||||
|
GRPC_STATUS_UNAVAILABLE = 14, |
||||||
|
/* Unrecoverable data loss or corruption. |
||||||
|
|
||||||
|
HTTP Mapping: 500 Internal Server Error */ |
||||||
|
GRPC_STATUS_DATA_LOSS = 15, |
||||||
|
/* Force users to include a default branch: */ |
||||||
|
GRPC_STATUS__DO_NOT_USE = -1 |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,2 @@ |
|||||||
|
test-results |
||||||
|
bin |
@ -0,0 +1,48 @@ |
|||||||
|
using System; |
||||||
|
using NUnit.Framework; |
||||||
|
using Google.GRPC.Core.Internal; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core.Tests |
||||||
|
{ |
||||||
|
public class ClientServerTest |
||||||
|
{ |
||||||
|
string request = "REQUEST"; |
||||||
|
string serverAddr = "localhost:" + Utils.PickUnusedPort(); |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void EmptyCall() |
||||||
|
{ |
||||||
|
Server server = new Server(); |
||||||
|
server.AddPort(serverAddr); |
||||||
|
server.Start(); |
||||||
|
|
||||||
|
Task.Factory.StartNew( |
||||||
|
() => { |
||||||
|
server.RunRpc(); |
||||||
|
} |
||||||
|
); |
||||||
|
|
||||||
|
using (Channel channel = new Channel(serverAddr)) |
||||||
|
{ |
||||||
|
CreateCall(channel); |
||||||
|
string response = Calls.BlockingUnaryCall(CreateCall(channel), request, default(CancellationToken)); |
||||||
|
Console.WriteLine("Received response: " + response); |
||||||
|
} |
||||||
|
|
||||||
|
server.Shutdown(); |
||||||
|
|
||||||
|
GrpcEnvironment.Shutdown(); |
||||||
|
} |
||||||
|
|
||||||
|
private Call<string, string> CreateCall(Channel channel) |
||||||
|
{ |
||||||
|
return new Call<string, string>("/tests.Test/EmptyCall", |
||||||
|
(s) => System.Text.Encoding.ASCII.GetBytes(s), |
||||||
|
(b) => System.Text.Encoding.ASCII.GetString(b), |
||||||
|
Timeout.InfiniteTimeSpan, channel); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,53 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||||
|
<PropertyGroup> |
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> |
||||||
|
<ProductVersion>10.0.0</ProductVersion> |
||||||
|
<SchemaVersion>2.0</SchemaVersion> |
||||||
|
<ProjectGuid>{86EC5CB4-4EA2-40A2-8057-86542A0353BB}</ProjectGuid> |
||||||
|
<OutputType>Library</OutputType> |
||||||
|
<RootNamespace>GrpcCoreTests</RootNamespace> |
||||||
|
<AssemblyName>GrpcCoreTests</AssemblyName> |
||||||
|
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> |
||||||
|
</PropertyGroup> |
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> |
||||||
|
<DebugSymbols>true</DebugSymbols> |
||||||
|
<DebugType>full</DebugType> |
||||||
|
<Optimize>false</Optimize> |
||||||
|
<OutputPath>bin\Debug</OutputPath> |
||||||
|
<DefineConstants>DEBUG;</DefineConstants> |
||||||
|
<ErrorReport>prompt</ErrorReport> |
||||||
|
<WarningLevel>4</WarningLevel> |
||||||
|
<ConsolePause>false</ConsolePause> |
||||||
|
</PropertyGroup> |
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> |
||||||
|
<DebugType>full</DebugType> |
||||||
|
<Optimize>true</Optimize> |
||||||
|
<OutputPath>bin\Release</OutputPath> |
||||||
|
<ErrorReport>prompt</ErrorReport> |
||||||
|
<WarningLevel>4</WarningLevel> |
||||||
|
<ConsolePause>false</ConsolePause> |
||||||
|
</PropertyGroup> |
||||||
|
<ItemGroup> |
||||||
|
<Reference Include="System" /> |
||||||
|
<Reference Include="nunit.framework, Version=2.6.0.0, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77"> |
||||||
|
<Private>False</Private> |
||||||
|
</Reference> |
||||||
|
</ItemGroup> |
||||||
|
<ItemGroup> |
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" /> |
||||||
|
<Compile Include="ClientServerTest.cs" /> |
||||||
|
<Compile Include="ServerTest.cs" /> |
||||||
|
<Compile Include="Utils.cs" /> |
||||||
|
<Compile Include="GrpcEnvironmentTest.cs" /> |
||||||
|
<Compile Include="TimespecTest.cs" /> |
||||||
|
</ItemGroup> |
||||||
|
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> |
||||||
|
<ItemGroup> |
||||||
|
<ProjectReference Include="..\GrpcCore\GrpcCore.csproj"> |
||||||
|
<Project>{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}</Project> |
||||||
|
<Name>GrpcCore</Name> |
||||||
|
</ProjectReference> |
||||||
|
</ItemGroup> |
||||||
|
</Project> |
@ -0,0 +1,18 @@ |
|||||||
|
using System; |
||||||
|
using NUnit.Framework; |
||||||
|
using Google.GRPC.Core; |
||||||
|
using System.Threading; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core.Tests |
||||||
|
{ |
||||||
|
public class GrpcEnvironmentTest |
||||||
|
{ |
||||||
|
[Test] |
||||||
|
public void InitializeAndShutdownGrpcEnvironment() { |
||||||
|
GrpcEnvironment.EnsureInitialized(); |
||||||
|
Thread.Sleep(500); |
||||||
|
Assert.IsNotNull(GrpcEnvironment.ThreadPool.CompletionQueue); |
||||||
|
GrpcEnvironment.Shutdown(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
using System.Reflection; |
||||||
|
using System.Runtime.CompilerServices; |
||||||
|
|
||||||
|
// Information about this assembly is defined by the following attributes. |
||||||
|
// Change them to the values specific to your project. |
||||||
|
[assembly: AssemblyTitle("GrpcCoreTests")] |
||||||
|
[assembly: AssemblyDescription("")] |
||||||
|
[assembly: AssemblyConfiguration("")] |
||||||
|
[assembly: AssemblyCompany("")] |
||||||
|
[assembly: AssemblyProduct("")] |
||||||
|
[assembly: AssemblyCopyright("jtattermusch")] |
||||||
|
[assembly: AssemblyTrademark("")] |
||||||
|
[assembly: AssemblyCulture("")] |
||||||
|
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". |
||||||
|
// The form "{Major}.{Minor}.*" will automatically update the build and revision, |
||||||
|
// and "{Major}.{Minor}.{Build}.*" will update just the revision. |
||||||
|
[assembly: AssemblyVersion("1.0.*")] |
||||||
|
// The following attributes are used to specify the signing key for the assembly, |
||||||
|
// if desired. See the Mono documentation for more information about signing. |
||||||
|
//[assembly: AssemblyDelaySign(false)] |
||||||
|
//[assembly: AssemblyKeyFile("")] |
||||||
|
|
@ -0,0 +1,21 @@ |
|||||||
|
using System; |
||||||
|
using NUnit.Framework; |
||||||
|
using Google.GRPC.Core.Internal; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core.Tests |
||||||
|
{ |
||||||
|
public class ServerTest |
||||||
|
{ |
||||||
|
[Test] |
||||||
|
public void StartAndShutdownServer() { |
||||||
|
|
||||||
|
Server server = new Server(); |
||||||
|
server.AddPort("localhost:" + Utils.PickUnusedPort()); |
||||||
|
server.Start(); |
||||||
|
server.Shutdown(); |
||||||
|
|
||||||
|
GrpcEnvironment.Shutdown(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,41 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8" standalone="no"?> |
||||||
|
<!--This file represents the results of running a test suite--> |
||||||
|
<test-results name="/usr/local/google/home/jtattermusch/github/grpc/src/csharp/GrpcCoreTests/bin/Debug/GrpcCoreTests.dll" total="3" errors="0" failures="0" not-run="0" inconclusive="0" ignored="0" skipped="0" invalid="0" date="2015-01-29" time="19:40:47"> |
||||||
|
<environment nunit-version="2.6.0.0" clr-version="4.0.30319.17020" os-version="Unix 3.13.0.43" platform="Unix" cwd="/usr/local/google/home/jtattermusch/github/grpc/src/csharp/GrpcCoreTests" machine-name="jtattermusch.mtv.corp.google.com" user="jtattermusch" user-domain="jtattermusch.mtv.corp.google.com" /> |
||||||
|
<culture-info current-culture="en-US" current-uiculture="en-US" /> |
||||||
|
<test-suite type="Assembly" name="/usr/local/google/home/jtattermusch/github/grpc/src/csharp/GrpcCoreTests/bin/Debug/GrpcCoreTests.dll" executed="True" result="Success" success="True" time="0.172" asserts="0"> |
||||||
|
<results> |
||||||
|
<test-suite type="Namespace" name="Google" executed="True" result="Success" success="True" time="0.166" asserts="0"> |
||||||
|
<results> |
||||||
|
<test-suite type="Namespace" name="GRPC" executed="True" result="Success" success="True" time="0.166" asserts="0"> |
||||||
|
<results> |
||||||
|
<test-suite type="Namespace" name="Core" executed="True" result="Success" success="True" time="0.166" asserts="0"> |
||||||
|
<results> |
||||||
|
<test-suite type="Namespace" name="Tests" executed="True" result="Success" success="True" time="0.166" asserts="0"> |
||||||
|
<results> |
||||||
|
<test-suite type="TestFixture" name="CallsTest" executed="True" result="Success" success="True" time="0.009" asserts="0"> |
||||||
|
<results> |
||||||
|
<test-case name="Google.GRPC.Core.Tests.CallsTest.Test1" executed="True" result="Success" success="True" time="0.004" asserts="0" /> |
||||||
|
</results> |
||||||
|
</test-suite> |
||||||
|
<test-suite type="TestFixture" name="ClientServerTest" executed="True" result="Success" success="True" time="0.149" asserts="0"> |
||||||
|
<results> |
||||||
|
<test-case name="Google.GRPC.Core.Tests.ClientServerTest.EmptyCall" executed="True" result="Success" success="True" time="0.111" asserts="0" /> |
||||||
|
</results> |
||||||
|
</test-suite> |
||||||
|
<test-suite type="TestFixture" name="ServerTest" executed="True" result="Success" success="True" time="0.001" asserts="0"> |
||||||
|
<results> |
||||||
|
<test-case name="Google.GRPC.Core.Tests.ServerTest.StartAndShutdownServer" executed="True" result="Success" success="True" time="0.001" asserts="0" /> |
||||||
|
</results> |
||||||
|
</test-suite> |
||||||
|
</results> |
||||||
|
</test-suite> |
||||||
|
</results> |
||||||
|
</test-suite> |
||||||
|
</results> |
||||||
|
</test-suite> |
||||||
|
</results> |
||||||
|
</test-suite> |
||||||
|
</results> |
||||||
|
</test-suite> |
||||||
|
</test-results> |
@ -0,0 +1,43 @@ |
|||||||
|
using System; |
||||||
|
using NUnit.Framework; |
||||||
|
using Google.GRPC.Core.Internal; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core.Internal.Tests |
||||||
|
{ |
||||||
|
public class TimespecTest |
||||||
|
{ |
||||||
|
[Test] |
||||||
|
public void Now() |
||||||
|
{ |
||||||
|
var timespec = Timespec.Now; |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void Add() |
||||||
|
{ |
||||||
|
var t = new Timespec { tv_sec = 12345, tv_nsec = 123456789 }; |
||||||
|
var result = t.Add(TimeSpan.FromTicks(TimeSpan.TicksPerSecond * 10)); |
||||||
|
Assert.AreEqual(result.tv_sec, 12355); |
||||||
|
Assert.AreEqual(result.tv_nsec, 123456789); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void Add_Nanos() |
||||||
|
{ |
||||||
|
var t = new Timespec { tv_sec = 12345, tv_nsec = 123456789 }; |
||||||
|
var result = t.Add(TimeSpan.FromTicks(10)); |
||||||
|
Assert.AreEqual(result.tv_sec, 12345); |
||||||
|
Assert.AreEqual(result.tv_nsec, 123456789 + 1000); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void Add_NanosOverflow() |
||||||
|
{ |
||||||
|
var t = new Timespec { tv_sec = 12345, tv_nsec = 999999999 }; |
||||||
|
var result = t.Add(TimeSpan.FromTicks(TimeSpan.TicksPerSecond * 10 + 10)); |
||||||
|
Assert.AreEqual(result.tv_sec, 12356); |
||||||
|
Assert.AreEqual(result.tv_nsec, 999); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,51 @@ |
|||||||
|
using System; |
||||||
|
using System.Net; |
||||||
|
using System.Net.Sockets; |
||||||
|
|
||||||
|
namespace Google.GRPC.Core.Tests |
||||||
|
{ |
||||||
|
/// <summary> |
||||||
|
/// Testing utils. |
||||||
|
/// </summary> |
||||||
|
public class Utils |
||||||
|
{ |
||||||
|
static Random random = new Random(); |
||||||
|
// TODO: cleanup this code a bit |
||||||
|
public static int PickUnusedPort() |
||||||
|
{ |
||||||
|
int port; |
||||||
|
do |
||||||
|
{ |
||||||
|
port = random.Next(2000, 50000); |
||||||
|
|
||||||
|
} while(!IsPortAvailable(port)); |
||||||
|
return port; |
||||||
|
} |
||||||
|
// TODO: cleanup this code a bit |
||||||
|
public static bool IsPortAvailable(int port) |
||||||
|
{ |
||||||
|
bool available = true; |
||||||
|
|
||||||
|
TcpListener server = null; |
||||||
|
try |
||||||
|
{ |
||||||
|
IPAddress ipAddress = Dns.GetHostEntry("localhost").AddressList[0]; |
||||||
|
server = new TcpListener(ipAddress, port); |
||||||
|
server.Start(); |
||||||
|
} |
||||||
|
catch (Exception ex) |
||||||
|
{ |
||||||
|
available = false; |
||||||
|
} |
||||||
|
finally |
||||||
|
{ |
||||||
|
if (server != null) |
||||||
|
{ |
||||||
|
server.Stop(); |
||||||
|
} |
||||||
|
} |
||||||
|
return available; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1 @@ |
|||||||
|
bin |
@ -0,0 +1,52 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||||
|
<PropertyGroup> |
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
||||||
|
<Platform Condition=" '$(Platform)' == '' ">x86</Platform> |
||||||
|
<ProductVersion>10.0.0</ProductVersion> |
||||||
|
<SchemaVersion>2.0</SchemaVersion> |
||||||
|
<ProjectGuid>{61ECB8EE-0C96-4F8E-B187-8E4D227417C0}</ProjectGuid> |
||||||
|
<OutputType>Exe</OutputType> |
||||||
|
<RootNamespace>GrpcDemo</RootNamespace> |
||||||
|
<AssemblyName>GrpcDemo</AssemblyName> |
||||||
|
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> |
||||||
|
</PropertyGroup> |
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' "> |
||||||
|
<DebugSymbols>true</DebugSymbols> |
||||||
|
<DebugType>full</DebugType> |
||||||
|
<Optimize>false</Optimize> |
||||||
|
<OutputPath>bin\Debug</OutputPath> |
||||||
|
<DefineConstants>DEBUG;</DefineConstants> |
||||||
|
<ErrorReport>prompt</ErrorReport> |
||||||
|
<WarningLevel>4</WarningLevel> |
||||||
|
<Externalconsole>true</Externalconsole> |
||||||
|
<PlatformTarget>x86</PlatformTarget> |
||||||
|
</PropertyGroup> |
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' "> |
||||||
|
<DebugType>full</DebugType> |
||||||
|
<Optimize>true</Optimize> |
||||||
|
<OutputPath>bin\Release</OutputPath> |
||||||
|
<ErrorReport>prompt</ErrorReport> |
||||||
|
<WarningLevel>4</WarningLevel> |
||||||
|
<Externalconsole>true</Externalconsole> |
||||||
|
<PlatformTarget>x86</PlatformTarget> |
||||||
|
</PropertyGroup> |
||||||
|
<ItemGroup> |
||||||
|
<Reference Include="System" /> |
||||||
|
</ItemGroup> |
||||||
|
<ItemGroup> |
||||||
|
<Compile Include="Program.cs" /> |
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" /> |
||||||
|
</ItemGroup> |
||||||
|
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> |
||||||
|
<ItemGroup> |
||||||
|
<ProjectReference Include="..\GrpcApi\GrpcApi.csproj"> |
||||||
|
<Project>{7DC1433E-3225-42C7-B7EA-546D56E27A4B}</Project> |
||||||
|
<Name>GrpcApi</Name> |
||||||
|
</ProjectReference> |
||||||
|
<ProjectReference Include="..\GrpcCore\GrpcCore.csproj"> |
||||||
|
<Project>{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}</Project> |
||||||
|
<Name>GrpcCore</Name> |
||||||
|
</ProjectReference> |
||||||
|
</ItemGroup> |
||||||
|
</Project> |
@ -0,0 +1,28 @@ |
|||||||
|
using System; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
using Google.GRPC.Core; |
||||||
|
using System.Threading; |
||||||
|
using math; |
||||||
|
|
||||||
|
namespace Google.GRPC.Demo |
||||||
|
{ |
||||||
|
class MainClass |
||||||
|
{ |
||||||
|
public static void Main (string[] args) |
||||||
|
{ |
||||||
|
using (Channel channel = new Channel("127.0.0.1:23456")) |
||||||
|
{ |
||||||
|
IMathServiceClient stub = new MathServiceClientStub(channel, Timeout.InfiniteTimeSpan); |
||||||
|
Examples.DivExample(stub); |
||||||
|
|
||||||
|
Examples.FibExample(stub); |
||||||
|
|
||||||
|
Examples.SumExample(stub); |
||||||
|
|
||||||
|
Examples.DivManyExample(stub); |
||||||
|
} |
||||||
|
|
||||||
|
GrpcEnvironment.Shutdown(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
using System.Reflection; |
||||||
|
using System.Runtime.CompilerServices; |
||||||
|
|
||||||
|
// Information about this assembly is defined by the following attributes. |
||||||
|
// Change them to the values specific to your project. |
||||||
|
[assembly: AssemblyTitle ("GrpcDemo")] |
||||||
|
[assembly: AssemblyDescription ("")] |
||||||
|
[assembly: AssemblyConfiguration ("")] |
||||||
|
[assembly: AssemblyCompany ("")] |
||||||
|
[assembly: AssemblyProduct ("")] |
||||||
|
[assembly: AssemblyCopyright ("jtattermusch")] |
||||||
|
[assembly: AssemblyTrademark ("")] |
||||||
|
[assembly: AssemblyCulture ("")] |
||||||
|
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". |
||||||
|
// The form "{Major}.{Minor}.*" will automatically update the build and revision, |
||||||
|
// and "{Major}.{Minor}.{Build}.*" will update just the revision. |
||||||
|
[assembly: AssemblyVersion ("1.0.*")] |
||||||
|
// The following attributes are used to specify the signing key for the assembly, |
||||||
|
// if desired. See the Mono documentation for more information about signing. |
||||||
|
//[assembly: AssemblyDelaySign(false)] |
||||||
|
//[assembly: AssemblyKeyFile("")] |
||||||
|
|
Binary file not shown.
Loading…
Reference in new issue