Prototype of gRPC C# library (Core and API examples)

pull/378/head
Jan Tattermusch 10 years ago
parent ea6f6d99f4
commit a7608b081e
  1. 38
      src/csharp/Grpc.sln
  2. 2
      src/csharp/GrpcApi/.gitignore
  3. 74
      src/csharp/GrpcApi/DummyMathServiceClient.cs
  4. 97
      src/csharp/GrpcApi/Examples.cs
  5. 64
      src/csharp/GrpcApi/GrpcApi.csproj
  6. 26
      src/csharp/GrpcApi/IMathServiceClient.cs
  7. 1531
      src/csharp/GrpcApi/Math.cs
  8. 75
      src/csharp/GrpcApi/MathServiceClientStub.cs
  9. 35
      src/csharp/GrpcApi/Messages.cs
  10. 22
      src/csharp/GrpcApi/Properties/AssemblyInfo.cs
  11. 32
      src/csharp/GrpcApi/RecordingObserver.cs
  12. 50
      src/csharp/GrpcApi/math.proto
  13. 1
      src/csharp/GrpcCore/.gitignore
  14. 69
      src/csharp/GrpcCore/Call.cs
  15. 85
      src/csharp/GrpcCore/Calls.cs
  16. 59
      src/csharp/GrpcCore/Channel.cs
  17. 37
      src/csharp/GrpcCore/ClientStreamingAsyncResult.cs
  18. 62
      src/csharp/GrpcCore/GrpcCore.csproj
  19. 91
      src/csharp/GrpcCore/GrpcEnvironment.cs
  20. 485
      src/csharp/GrpcCore/Internal/AsyncCall.cs
  21. 182
      src/csharp/GrpcCore/Internal/CallSafeHandle.cs
  22. 34
      src/csharp/GrpcCore/Internal/ChannelSafeHandle.cs
  23. 66
      src/csharp/GrpcCore/Internal/CompletionQueueSafeHandle.cs
  24. 75
      src/csharp/GrpcCore/Internal/Enums.cs
  25. 191
      src/csharp/GrpcCore/Internal/Event.cs
  26. 129
      src/csharp/GrpcCore/Internal/GrpcThreadPool.cs
  27. 28
      src/csharp/GrpcCore/Internal/SafeHandleZeroIsInvalid.cs
  28. 76
      src/csharp/GrpcCore/Internal/ServerSafeHandle.cs
  29. 33
      src/csharp/GrpcCore/Internal/StreamingInputObserver.cs
  30. 67
      src/csharp/GrpcCore/Internal/Timespec.cs
  31. 24
      src/csharp/GrpcCore/Properties/AssemblyInfo.cs
  32. 27
      src/csharp/GrpcCore/RpcException.cs
  33. 141
      src/csharp/GrpcCore/Server.cs
  34. 36
      src/csharp/GrpcCore/Status.cs
  35. 150
      src/csharp/GrpcCore/StatusCode.cs
  36. 2
      src/csharp/GrpcCoreTests/.gitignore
  37. 48
      src/csharp/GrpcCoreTests/ClientServerTest.cs
  38. 53
      src/csharp/GrpcCoreTests/GrpcCoreTests.csproj
  39. 18
      src/csharp/GrpcCoreTests/GrpcEnvironmentTest.cs
  40. 22
      src/csharp/GrpcCoreTests/Properties/AssemblyInfo.cs
  41. 21
      src/csharp/GrpcCoreTests/ServerTest.cs
  42. 41
      src/csharp/GrpcCoreTests/TestResult.xml
  43. 43
      src/csharp/GrpcCoreTests/TimespecTest.cs
  44. 51
      src/csharp/GrpcCoreTests/Utils.cs
  45. 1
      src/csharp/GrpcDemo/.gitignore
  46. 52
      src/csharp/GrpcDemo/GrpcDemo.csproj
  47. 28
      src/csharp/GrpcDemo/Program.cs
  48. 22
      src/csharp/GrpcDemo/Properties/AssemblyInfo.cs
  49. 4
      src/csharp/README.md
  50. BIN
      src/csharp/lib/Google.ProtocolBuffers.dll

@ -0,0 +1,38 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrpcDemo", "GrpcDemo\GrpcDemo.csproj", "{61ECB8EE-0C96-4F8E-B187-8E4D227417C0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrpcApi", "GrpcApi\GrpcApi.csproj", "{7DC1433E-3225-42C7-B7EA-546D56E27A4B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrpcCore", "GrpcCore\GrpcCore.csproj", "{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrpcCoreTests", "GrpcCoreTests\GrpcCoreTests.csproj", "{86EC5CB4-4EA2-40A2-8057-86542A0353BB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.Debug|x86.ActiveCfg = Debug|x86
{61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.Debug|x86.Build.0 = Debug|x86
{61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.Release|x86.ActiveCfg = Release|x86
{61ECB8EE-0C96-4F8E-B187-8E4D227417C0}.Release|x86.Build.0 = Release|x86
{7DC1433E-3225-42C7-B7EA-546D56E27A4B}.Debug|x86.ActiveCfg = Debug|Any CPU
{7DC1433E-3225-42C7-B7EA-546D56E27A4B}.Debug|x86.Build.0 = Debug|Any CPU
{7DC1433E-3225-42C7-B7EA-546D56E27A4B}.Release|x86.ActiveCfg = Release|Any CPU
{7DC1433E-3225-42C7-B7EA-546D56E27A4B}.Release|x86.Build.0 = Release|Any CPU
{86EC5CB4-4EA2-40A2-8057-86542A0353BB}.Debug|x86.ActiveCfg = Debug|Any CPU
{86EC5CB4-4EA2-40A2-8057-86542A0353BB}.Debug|x86.Build.0 = Debug|Any CPU
{86EC5CB4-4EA2-40A2-8057-86542A0353BB}.Release|x86.ActiveCfg = Release|Any CPU
{86EC5CB4-4EA2-40A2-8057-86542A0353BB}.Release|x86.Build.0 = Release|Any CPU
{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.Debug|x86.ActiveCfg = Debug|Any CPU
{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.Debug|x86.Build.0 = Debug|Any CPU
{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.Release|x86.ActiveCfg = Release|Any CPU
{CCC4440E-49F7-4790-B0AF-FEABB0837AE7}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = GrpcDemo\GrpcDemo.csproj
EndGlobalSection
EndGlobal

@ -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,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,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("")]

@ -19,4 +19,8 @@ CONTENTS
- ext: - ext:
The extension library that wraps C API to be more digestible by C#. The extension library that wraps C API to be more digestible by C#.
- GrpcCore:
The main gRPC C# library.
- GrpcApi:
API examples for math.proto.

Loading…
Cancel
Save