mirror of https://github.com/grpc/grpc.git
Merge pull request #378 from jtattermusch/csharp_prototype
Prototype of gRPC C# library (Core and API examples)pull/384/head
commit
69115529f4
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