From a7608b081e30f6fef5fd36935f8c8591381d4bde Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Tue, 3 Feb 2015 17:54:38 -0800 Subject: [PATCH 01/27] Prototype of gRPC C# library (Core and API examples) --- src/csharp/Grpc.sln | 38 + src/csharp/GrpcApi/.gitignore | 2 + src/csharp/GrpcApi/DummyMathServiceClient.cs | 74 + src/csharp/GrpcApi/Examples.cs | 97 ++ src/csharp/GrpcApi/GrpcApi.csproj | 64 + src/csharp/GrpcApi/IMathServiceClient.cs | 26 + src/csharp/GrpcApi/Math.cs | 1531 +++++++++++++++++ src/csharp/GrpcApi/MathServiceClientStub.cs | 75 + src/csharp/GrpcApi/Messages.cs | 35 + src/csharp/GrpcApi/Properties/AssemblyInfo.cs | 22 + src/csharp/GrpcApi/RecordingObserver.cs | 32 + src/csharp/GrpcApi/math.proto | 50 + src/csharp/GrpcCore/.gitignore | 1 + src/csharp/GrpcCore/Call.cs | 69 + src/csharp/GrpcCore/Calls.cs | 85 + src/csharp/GrpcCore/Channel.cs | 59 + .../GrpcCore/ClientStreamingAsyncResult.cs | 37 + src/csharp/GrpcCore/GrpcCore.csproj | 62 + src/csharp/GrpcCore/GrpcEnvironment.cs | 91 + src/csharp/GrpcCore/Internal/AsyncCall.cs | 485 ++++++ .../GrpcCore/Internal/CallSafeHandle.cs | 182 ++ .../GrpcCore/Internal/ChannelSafeHandle.cs | 34 + .../Internal/CompletionQueueSafeHandle.cs | 66 + src/csharp/GrpcCore/Internal/Enums.cs | 75 + src/csharp/GrpcCore/Internal/Event.cs | 191 ++ .../GrpcCore/Internal/GrpcThreadPool.cs | 129 ++ .../Internal/SafeHandleZeroIsInvalid.cs | 28 + .../GrpcCore/Internal/ServerSafeHandle.cs | 76 + .../Internal/StreamingInputObserver.cs | 33 + src/csharp/GrpcCore/Internal/Timespec.cs | 67 + .../GrpcCore/Properties/AssemblyInfo.cs | 24 + src/csharp/GrpcCore/RpcException.cs | 27 + src/csharp/GrpcCore/Server.cs | 141 ++ src/csharp/GrpcCore/Status.cs | 36 + src/csharp/GrpcCore/StatusCode.cs | 150 ++ src/csharp/GrpcCoreTests/.gitignore | 2 + src/csharp/GrpcCoreTests/ClientServerTest.cs | 48 + src/csharp/GrpcCoreTests/GrpcCoreTests.csproj | 53 + .../GrpcCoreTests/GrpcEnvironmentTest.cs | 18 + .../GrpcCoreTests/Properties/AssemblyInfo.cs | 22 + src/csharp/GrpcCoreTests/ServerTest.cs | 21 + src/csharp/GrpcCoreTests/TestResult.xml | 41 + src/csharp/GrpcCoreTests/TimespecTest.cs | 43 + src/csharp/GrpcCoreTests/Utils.cs | 51 + src/csharp/GrpcDemo/.gitignore | 1 + src/csharp/GrpcDemo/GrpcDemo.csproj | 52 + src/csharp/GrpcDemo/Program.cs | 28 + .../GrpcDemo/Properties/AssemblyInfo.cs | 22 + src/csharp/README.md | 4 + src/csharp/lib/Google.ProtocolBuffers.dll | Bin 0 -> 380416 bytes 50 files changed, 4600 insertions(+) create mode 100644 src/csharp/Grpc.sln create mode 100644 src/csharp/GrpcApi/.gitignore create mode 100644 src/csharp/GrpcApi/DummyMathServiceClient.cs create mode 100644 src/csharp/GrpcApi/Examples.cs create mode 100644 src/csharp/GrpcApi/GrpcApi.csproj create mode 100644 src/csharp/GrpcApi/IMathServiceClient.cs create mode 100644 src/csharp/GrpcApi/Math.cs create mode 100644 src/csharp/GrpcApi/MathServiceClientStub.cs create mode 100644 src/csharp/GrpcApi/Messages.cs create mode 100644 src/csharp/GrpcApi/Properties/AssemblyInfo.cs create mode 100644 src/csharp/GrpcApi/RecordingObserver.cs create mode 100755 src/csharp/GrpcApi/math.proto create mode 100644 src/csharp/GrpcCore/.gitignore create mode 100644 src/csharp/GrpcCore/Call.cs create mode 100644 src/csharp/GrpcCore/Calls.cs create mode 100644 src/csharp/GrpcCore/Channel.cs create mode 100644 src/csharp/GrpcCore/ClientStreamingAsyncResult.cs create mode 100644 src/csharp/GrpcCore/GrpcCore.csproj create mode 100644 src/csharp/GrpcCore/GrpcEnvironment.cs create mode 100644 src/csharp/GrpcCore/Internal/AsyncCall.cs create mode 100644 src/csharp/GrpcCore/Internal/CallSafeHandle.cs create mode 100644 src/csharp/GrpcCore/Internal/ChannelSafeHandle.cs create mode 100644 src/csharp/GrpcCore/Internal/CompletionQueueSafeHandle.cs create mode 100644 src/csharp/GrpcCore/Internal/Enums.cs create mode 100644 src/csharp/GrpcCore/Internal/Event.cs create mode 100644 src/csharp/GrpcCore/Internal/GrpcThreadPool.cs create mode 100644 src/csharp/GrpcCore/Internal/SafeHandleZeroIsInvalid.cs create mode 100644 src/csharp/GrpcCore/Internal/ServerSafeHandle.cs create mode 100644 src/csharp/GrpcCore/Internal/StreamingInputObserver.cs create mode 100644 src/csharp/GrpcCore/Internal/Timespec.cs create mode 100644 src/csharp/GrpcCore/Properties/AssemblyInfo.cs create mode 100644 src/csharp/GrpcCore/RpcException.cs create mode 100644 src/csharp/GrpcCore/Server.cs create mode 100644 src/csharp/GrpcCore/Status.cs create mode 100644 src/csharp/GrpcCore/StatusCode.cs create mode 100644 src/csharp/GrpcCoreTests/.gitignore create mode 100644 src/csharp/GrpcCoreTests/ClientServerTest.cs create mode 100644 src/csharp/GrpcCoreTests/GrpcCoreTests.csproj create mode 100644 src/csharp/GrpcCoreTests/GrpcEnvironmentTest.cs create mode 100644 src/csharp/GrpcCoreTests/Properties/AssemblyInfo.cs create mode 100644 src/csharp/GrpcCoreTests/ServerTest.cs create mode 100644 src/csharp/GrpcCoreTests/TestResult.xml create mode 100644 src/csharp/GrpcCoreTests/TimespecTest.cs create mode 100644 src/csharp/GrpcCoreTests/Utils.cs create mode 100644 src/csharp/GrpcDemo/.gitignore create mode 100644 src/csharp/GrpcDemo/GrpcDemo.csproj create mode 100644 src/csharp/GrpcDemo/Program.cs create mode 100644 src/csharp/GrpcDemo/Properties/AssemblyInfo.cs create mode 100755 src/csharp/lib/Google.ProtocolBuffers.dll diff --git a/src/csharp/Grpc.sln b/src/csharp/Grpc.sln new file mode 100644 index 00000000000..5890617acf5 --- /dev/null +++ b/src/csharp/Grpc.sln @@ -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 diff --git a/src/csharp/GrpcApi/.gitignore b/src/csharp/GrpcApi/.gitignore new file mode 100644 index 00000000000..2cc8cca52d0 --- /dev/null +++ b/src/csharp/GrpcApi/.gitignore @@ -0,0 +1,2 @@ +test-results +bin diff --git a/src/csharp/GrpcApi/DummyMathServiceClient.cs b/src/csharp/GrpcApi/DummyMathServiceClient.cs new file mode 100644 index 00000000000..6799109be42 --- /dev/null +++ b/src/csharp/GrpcApi/DummyMathServiceClient.cs @@ -0,0 +1,74 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Reactive.Linq; + +namespace math +{ +// /// +// /// Dummy local implementation of math service. +// /// +// public class DummyMathServiceClient : IMathServiceClient +// { +// public DivReply Div(DivArgs args, CancellationToken token = default(CancellationToken)) +// { +// // TODO: cancellation... +// return DivInternal(args); +// } +// +// public Task DivAsync(DivArgs args, CancellationToken token = default(CancellationToken)) +// { +// return Task.Factory.StartNew(() => DivInternal(args), token); +// } +// +// public IObservable 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 Sum(IObservable inputs, CancellationToken token = default(CancellationToken)) +// { +// // TODO: implement +// inputs = null; +// return Task.Factory.StartNew(() => Num.CreateBuilder().Build(), token); +// } +// +// public IObservable DivMany(IObservable inputs, CancellationToken token = default(CancellationToken)) +// { +// // TODO: implement +// inputs = null; +// return new List { }.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 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(); +// } +// } +// } +} + diff --git a/src/csharp/GrpcApi/Examples.cs b/src/csharp/GrpcApi/Examples.cs new file mode 100644 index 00000000000..d45b702708e --- /dev/null +++ b/src/csharp/GrpcApi/Examples.cs @@ -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 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 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(); + stub.Fib(new FibArgs.Builder { Limit = 5 }.Build(), recorder); + + List numbers = recorder.ToList().Result; + Console.WriteLine("Fib Result: " + string.Join("|", recorder.ToList().Result)); + } + + public static void SumExample(IMathServiceClient stub) + { + List numbers = new List{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 divArgsList = new List{ + 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(); + + 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 + { new Num.Builder{ Num_ = 1 }.Build(), + new Num.Builder{ Num_ = 2 }.Build(), new Num.Builder{ Num_ = 3 }.Build() + }; + + numberList.ToObservable(); + + //IObserver numbers; + //Task 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()); + } + } +} + diff --git a/src/csharp/GrpcApi/GrpcApi.csproj b/src/csharp/GrpcApi/GrpcApi.csproj new file mode 100644 index 00000000000..d0377828b54 --- /dev/null +++ b/src/csharp/GrpcApi/GrpcApi.csproj @@ -0,0 +1,64 @@ + + + + Debug + AnyCPU + 10.0.0 + 2.0 + {7DC1433E-3225-42C7-B7EA-546D56E27A4B} + Library + GrpcApi + GrpcApi + v4.5 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + + + full + true + bin\Release + prompt + 4 + false + + + + + False + + + + False + + + False + + + ..\lib\Google.ProtocolBuffers.dll + + + + + + + + + + + + + + + {CCC4440E-49F7-4790-B0AF-FEABB0837AE7} + GrpcCore + + + \ No newline at end of file diff --git a/src/csharp/GrpcApi/IMathServiceClient.cs b/src/csharp/GrpcApi/IMathServiceClient.cs new file mode 100644 index 00000000000..51385a328f4 --- /dev/null +++ b/src/csharp/GrpcApi/IMathServiceClient.cs @@ -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 +{ + /// + /// Hand-written stub for MathService defined in math.proto. + /// This code will be generated by gRPC codegen in the future. + /// + public interface IMathServiceClient + { + DivReply Div(DivArgs args, CancellationToken token = default(CancellationToken)); + + Task DivAsync(DivArgs args, CancellationToken token = default(CancellationToken)); + + Task Fib(FibArgs args, IObserver outputs, CancellationToken token = default(CancellationToken)); + + ClientStreamingAsyncResult Sum(CancellationToken token = default(CancellationToken)); + + IObserver DivMany(IObserver outputs, CancellationToken token = default(CancellationToken)); + } +} \ No newline at end of file diff --git a/src/csharp/GrpcApi/Math.cs b/src/csharp/GrpcApi/Math.cs new file mode 100644 index 00000000000..2d700337ac7 --- /dev/null +++ b/src/csharp/GrpcApi/Math.cs @@ -0,0 +1,1531 @@ +// Generated by ProtoGen, Version=2.4.1.521, Culture=neutral, PublicKeyToken=17b3b1f090c3ea48. DO NOT EDIT! +#pragma warning disable 1591, 0612, 3021 +#region Designer generated code + +using pb = global::Google.ProtocolBuffers; +using pbc = global::Google.ProtocolBuffers.Collections; +using pbd = global::Google.ProtocolBuffers.Descriptors; +using scg = global::System.Collections.Generic; +namespace math { + + namespace Proto { + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class Math { + + #region Extension registration + public static void RegisterAllExtensions(pb::ExtensionRegistry registry) { + } + #endregion + #region Static variables + internal static pbd::MessageDescriptor internal__static_math_DivArgs__Descriptor; + internal static pb::FieldAccess.FieldAccessorTable internal__static_math_DivArgs__FieldAccessorTable; + internal static pbd::MessageDescriptor internal__static_math_DivReply__Descriptor; + internal static pb::FieldAccess.FieldAccessorTable internal__static_math_DivReply__FieldAccessorTable; + internal static pbd::MessageDescriptor internal__static_math_FibArgs__Descriptor; + internal static pb::FieldAccess.FieldAccessorTable internal__static_math_FibArgs__FieldAccessorTable; + internal static pbd::MessageDescriptor internal__static_math_Num__Descriptor; + internal static pb::FieldAccess.FieldAccessorTable internal__static_math_Num__FieldAccessorTable; + internal static pbd::MessageDescriptor internal__static_math_FibReply__Descriptor; + internal static pb::FieldAccess.FieldAccessorTable internal__static_math_FibReply__FieldAccessorTable; + #endregion + #region Descriptor + public static pbd::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbd::FileDescriptor descriptor; + + static Math() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CgptYXRoLnByb3RvEgRtYXRoIiwKB0RpdkFyZ3MSEAoIZGl2aWRlbmQYASAB", + "KAMSDwoHZGl2aXNvchgCIAEoAyIvCghEaXZSZXBseRIQCghxdW90aWVudBgB", + "IAEoAxIRCglyZW1haW5kZXIYAiABKAMiGAoHRmliQXJncxINCgVsaW1pdBgB", + "IAEoAyISCgNOdW0SCwoDbnVtGAEgASgDIhkKCEZpYlJlcGx5Eg0KBWNvdW50", + "GAEgASgDMqQBCgRNYXRoEiYKA0RpdhINLm1hdGguRGl2QXJncxoOLm1hdGgu", + "RGl2UmVwbHkiABIuCgdEaXZNYW55Eg0ubWF0aC5EaXZBcmdzGg4ubWF0aC5E", + "aXZSZXBseSIAKAEwARIjCgNGaWISDS5tYXRoLkZpYkFyZ3MaCS5tYXRoLk51", + "bSIAMAESHwoDU3VtEgkubWF0aC5OdW0aCS5tYXRoLk51bSIAKAE=")); + pbd::FileDescriptor.InternalDescriptorAssigner assigner = delegate(pbd::FileDescriptor root) { + descriptor = root; + internal__static_math_DivArgs__Descriptor = Descriptor.MessageTypes[0]; + internal__static_math_DivArgs__FieldAccessorTable = + new pb::FieldAccess.FieldAccessorTable(internal__static_math_DivArgs__Descriptor, + new string[] { "Dividend", "Divisor", }); + internal__static_math_DivReply__Descriptor = Descriptor.MessageTypes[1]; + internal__static_math_DivReply__FieldAccessorTable = + new pb::FieldAccess.FieldAccessorTable(internal__static_math_DivReply__Descriptor, + new string[] { "Quotient", "Remainder", }); + internal__static_math_FibArgs__Descriptor = Descriptor.MessageTypes[2]; + internal__static_math_FibArgs__FieldAccessorTable = + new pb::FieldAccess.FieldAccessorTable(internal__static_math_FibArgs__Descriptor, + new string[] { "Limit", }); + internal__static_math_Num__Descriptor = Descriptor.MessageTypes[3]; + internal__static_math_Num__FieldAccessorTable = + new pb::FieldAccess.FieldAccessorTable(internal__static_math_Num__Descriptor, + new string[] { "Num_", }); + internal__static_math_FibReply__Descriptor = Descriptor.MessageTypes[4]; + internal__static_math_FibReply__FieldAccessorTable = + new pb::FieldAccess.FieldAccessorTable(internal__static_math_FibReply__Descriptor, + new string[] { "Count", }); + pb::ExtensionRegistry registry = pb::ExtensionRegistry.CreateInstance(); + RegisterAllExtensions(registry); + return registry; + }; + pbd::FileDescriptor.InternalBuildGeneratedFileFrom(descriptorData, + new pbd::FileDescriptor[] { + }, assigner); + } + #endregion + + } + } + #region Messages + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class DivArgs : pb::GeneratedMessage { + private DivArgs() { } + private static readonly DivArgs defaultInstance = new DivArgs().MakeReadOnly(); + private static readonly string[] _divArgsFieldNames = new string[] { "dividend", "divisor" }; + private static readonly uint[] _divArgsFieldTags = new uint[] { 8, 16 }; + public static DivArgs DefaultInstance { + get { return defaultInstance; } + } + + public override DivArgs DefaultInstanceForType { + get { return DefaultInstance; } + } + + protected override DivArgs ThisMessage { + get { return this; } + } + + public static pbd::MessageDescriptor Descriptor { + get { return global::math.Proto.Math.internal__static_math_DivArgs__Descriptor; } + } + + protected override pb::FieldAccess.FieldAccessorTable InternalFieldAccessors { + get { return global::math.Proto.Math.internal__static_math_DivArgs__FieldAccessorTable; } + } + + public const int DividendFieldNumber = 1; + private bool hasDividend; + private long dividend_; + public bool HasDividend { + get { return hasDividend; } + } + public long Dividend { + get { return dividend_; } + } + + public const int DivisorFieldNumber = 2; + private bool hasDivisor; + private long divisor_; + public bool HasDivisor { + get { return hasDivisor; } + } + public long Divisor { + get { return divisor_; } + } + + public override bool IsInitialized { + get { + return true; + } + } + + public override void WriteTo(pb::ICodedOutputStream output) { + int size = SerializedSize; + string[] field_names = _divArgsFieldNames; + if (hasDividend) { + output.WriteInt64(1, field_names[0], Dividend); + } + if (hasDivisor) { + output.WriteInt64(2, field_names[1], Divisor); + } + UnknownFields.WriteTo(output); + } + + private int memoizedSerializedSize = -1; + public override int SerializedSize { + get { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (hasDividend) { + size += pb::CodedOutputStream.ComputeInt64Size(1, Dividend); + } + if (hasDivisor) { + size += pb::CodedOutputStream.ComputeInt64Size(2, Divisor); + } + size += UnknownFields.SerializedSize; + memoizedSerializedSize = size; + return size; + } + } + + public static DivArgs ParseFrom(pb::ByteString data) { + return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed(); + } + public static DivArgs ParseFrom(pb::ByteString data, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed(); + } + public static DivArgs ParseFrom(byte[] data) { + return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed(); + } + public static DivArgs ParseFrom(byte[] data, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed(); + } + public static DivArgs ParseFrom(global::System.IO.Stream input) { + return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed(); + } + public static DivArgs ParseFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed(); + } + public static DivArgs ParseDelimitedFrom(global::System.IO.Stream input) { + return CreateBuilder().MergeDelimitedFrom(input).BuildParsed(); + } + public static DivArgs ParseDelimitedFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) { + return CreateBuilder().MergeDelimitedFrom(input, extensionRegistry).BuildParsed(); + } + public static DivArgs ParseFrom(pb::ICodedInputStream input) { + return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed(); + } + public static DivArgs ParseFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed(); + } + private DivArgs MakeReadOnly() { + return this; + } + + public static Builder CreateBuilder() { return new Builder(); } + public override Builder ToBuilder() { return CreateBuilder(this); } + public override Builder CreateBuilderForType() { return new Builder(); } + public static Builder CreateBuilder(DivArgs prototype) { + return new Builder(prototype); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Builder : pb::GeneratedBuilder { + protected override Builder ThisBuilder { + get { return this; } + } + public Builder() { + result = DefaultInstance; + resultIsReadOnly = true; + } + internal Builder(DivArgs cloneFrom) { + result = cloneFrom; + resultIsReadOnly = true; + } + + private bool resultIsReadOnly; + private DivArgs result; + + private DivArgs PrepareBuilder() { + if (resultIsReadOnly) { + DivArgs original = result; + result = new DivArgs(); + resultIsReadOnly = false; + MergeFrom(original); + } + return result; + } + + public override bool IsInitialized { + get { return result.IsInitialized; } + } + + protected override DivArgs MessageBeingBuilt { + get { return PrepareBuilder(); } + } + + public override Builder Clear() { + result = DefaultInstance; + resultIsReadOnly = true; + return this; + } + + public override Builder Clone() { + if (resultIsReadOnly) { + return new Builder(result); + } else { + return new Builder().MergeFrom(result); + } + } + + public override pbd::MessageDescriptor DescriptorForType { + get { return global::math.DivArgs.Descriptor; } + } + + public override DivArgs DefaultInstanceForType { + get { return global::math.DivArgs.DefaultInstance; } + } + + public override DivArgs BuildPartial() { + if (resultIsReadOnly) { + return result; + } + resultIsReadOnly = true; + return result.MakeReadOnly(); + } + + public override Builder MergeFrom(pb::IMessage other) { + if (other is DivArgs) { + return MergeFrom((DivArgs) other); + } else { + base.MergeFrom(other); + return this; + } + } + + public override Builder MergeFrom(DivArgs other) { + if (other == global::math.DivArgs.DefaultInstance) return this; + PrepareBuilder(); + if (other.HasDividend) { + Dividend = other.Dividend; + } + if (other.HasDivisor) { + Divisor = other.Divisor; + } + this.MergeUnknownFields(other.UnknownFields); + return this; + } + + public override Builder MergeFrom(pb::ICodedInputStream input) { + return MergeFrom(input, pb::ExtensionRegistry.Empty); + } + + public override Builder MergeFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) { + PrepareBuilder(); + pb::UnknownFieldSet.Builder unknownFields = null; + uint tag; + string field_name; + while (input.ReadTag(out tag, out field_name)) { + if(tag == 0 && field_name != null) { + int field_ordinal = global::System.Array.BinarySearch(_divArgsFieldNames, field_name, global::System.StringComparer.Ordinal); + if(field_ordinal >= 0) + tag = _divArgsFieldTags[field_ordinal]; + else { + if (unknownFields == null) { + unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields); + } + ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name); + continue; + } + } + switch (tag) { + case 0: { + throw pb::InvalidProtocolBufferException.InvalidTag(); + } + default: { + if (pb::WireFormat.IsEndGroupTag(tag)) { + if (unknownFields != null) { + this.UnknownFields = unknownFields.Build(); + } + return this; + } + if (unknownFields == null) { + unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields); + } + ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name); + break; + } + case 8: { + result.hasDividend = input.ReadInt64(ref result.dividend_); + break; + } + case 16: { + result.hasDivisor = input.ReadInt64(ref result.divisor_); + break; + } + } + } + + if (unknownFields != null) { + this.UnknownFields = unknownFields.Build(); + } + return this; + } + + + public bool HasDividend { + get { return result.hasDividend; } + } + public long Dividend { + get { return result.Dividend; } + set { SetDividend(value); } + } + public Builder SetDividend(long value) { + PrepareBuilder(); + result.hasDividend = true; + result.dividend_ = value; + return this; + } + public Builder ClearDividend() { + PrepareBuilder(); + result.hasDividend = false; + result.dividend_ = 0L; + return this; + } + + public bool HasDivisor { + get { return result.hasDivisor; } + } + public long Divisor { + get { return result.Divisor; } + set { SetDivisor(value); } + } + public Builder SetDivisor(long value) { + PrepareBuilder(); + result.hasDivisor = true; + result.divisor_ = value; + return this; + } + public Builder ClearDivisor() { + PrepareBuilder(); + result.hasDivisor = false; + result.divisor_ = 0L; + return this; + } + } + static DivArgs() { + object.ReferenceEquals(global::math.Proto.Math.Descriptor, null); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class DivReply : pb::GeneratedMessage { + private DivReply() { } + private static readonly DivReply defaultInstance = new DivReply().MakeReadOnly(); + private static readonly string[] _divReplyFieldNames = new string[] { "quotient", "remainder" }; + private static readonly uint[] _divReplyFieldTags = new uint[] { 8, 16 }; + public static DivReply DefaultInstance { + get { return defaultInstance; } + } + + public override DivReply DefaultInstanceForType { + get { return DefaultInstance; } + } + + protected override DivReply ThisMessage { + get { return this; } + } + + public static pbd::MessageDescriptor Descriptor { + get { return global::math.Proto.Math.internal__static_math_DivReply__Descriptor; } + } + + protected override pb::FieldAccess.FieldAccessorTable InternalFieldAccessors { + get { return global::math.Proto.Math.internal__static_math_DivReply__FieldAccessorTable; } + } + + public const int QuotientFieldNumber = 1; + private bool hasQuotient; + private long quotient_; + public bool HasQuotient { + get { return hasQuotient; } + } + public long Quotient { + get { return quotient_; } + } + + public const int RemainderFieldNumber = 2; + private bool hasRemainder; + private long remainder_; + public bool HasRemainder { + get { return hasRemainder; } + } + public long Remainder { + get { return remainder_; } + } + + public override bool IsInitialized { + get { + return true; + } + } + + public override void WriteTo(pb::ICodedOutputStream output) { + int size = SerializedSize; + string[] field_names = _divReplyFieldNames; + if (hasQuotient) { + output.WriteInt64(1, field_names[0], Quotient); + } + if (hasRemainder) { + output.WriteInt64(2, field_names[1], Remainder); + } + UnknownFields.WriteTo(output); + } + + private int memoizedSerializedSize = -1; + public override int SerializedSize { + get { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (hasQuotient) { + size += pb::CodedOutputStream.ComputeInt64Size(1, Quotient); + } + if (hasRemainder) { + size += pb::CodedOutputStream.ComputeInt64Size(2, Remainder); + } + size += UnknownFields.SerializedSize; + memoizedSerializedSize = size; + return size; + } + } + + public static DivReply ParseFrom(pb::ByteString data) { + return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed(); + } + public static DivReply ParseFrom(pb::ByteString data, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed(); + } + public static DivReply ParseFrom(byte[] data) { + return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed(); + } + public static DivReply ParseFrom(byte[] data, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed(); + } + public static DivReply ParseFrom(global::System.IO.Stream input) { + return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed(); + } + public static DivReply ParseFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed(); + } + public static DivReply ParseDelimitedFrom(global::System.IO.Stream input) { + return CreateBuilder().MergeDelimitedFrom(input).BuildParsed(); + } + public static DivReply ParseDelimitedFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) { + return CreateBuilder().MergeDelimitedFrom(input, extensionRegistry).BuildParsed(); + } + public static DivReply ParseFrom(pb::ICodedInputStream input) { + return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed(); + } + public static DivReply ParseFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed(); + } + private DivReply MakeReadOnly() { + return this; + } + + public static Builder CreateBuilder() { return new Builder(); } + public override Builder ToBuilder() { return CreateBuilder(this); } + public override Builder CreateBuilderForType() { return new Builder(); } + public static Builder CreateBuilder(DivReply prototype) { + return new Builder(prototype); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Builder : pb::GeneratedBuilder { + protected override Builder ThisBuilder { + get { return this; } + } + public Builder() { + result = DefaultInstance; + resultIsReadOnly = true; + } + internal Builder(DivReply cloneFrom) { + result = cloneFrom; + resultIsReadOnly = true; + } + + private bool resultIsReadOnly; + private DivReply result; + + private DivReply PrepareBuilder() { + if (resultIsReadOnly) { + DivReply original = result; + result = new DivReply(); + resultIsReadOnly = false; + MergeFrom(original); + } + return result; + } + + public override bool IsInitialized { + get { return result.IsInitialized; } + } + + protected override DivReply MessageBeingBuilt { + get { return PrepareBuilder(); } + } + + public override Builder Clear() { + result = DefaultInstance; + resultIsReadOnly = true; + return this; + } + + public override Builder Clone() { + if (resultIsReadOnly) { + return new Builder(result); + } else { + return new Builder().MergeFrom(result); + } + } + + public override pbd::MessageDescriptor DescriptorForType { + get { return global::math.DivReply.Descriptor; } + } + + public override DivReply DefaultInstanceForType { + get { return global::math.DivReply.DefaultInstance; } + } + + public override DivReply BuildPartial() { + if (resultIsReadOnly) { + return result; + } + resultIsReadOnly = true; + return result.MakeReadOnly(); + } + + public override Builder MergeFrom(pb::IMessage other) { + if (other is DivReply) { + return MergeFrom((DivReply) other); + } else { + base.MergeFrom(other); + return this; + } + } + + public override Builder MergeFrom(DivReply other) { + if (other == global::math.DivReply.DefaultInstance) return this; + PrepareBuilder(); + if (other.HasQuotient) { + Quotient = other.Quotient; + } + if (other.HasRemainder) { + Remainder = other.Remainder; + } + this.MergeUnknownFields(other.UnknownFields); + return this; + } + + public override Builder MergeFrom(pb::ICodedInputStream input) { + return MergeFrom(input, pb::ExtensionRegistry.Empty); + } + + public override Builder MergeFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) { + PrepareBuilder(); + pb::UnknownFieldSet.Builder unknownFields = null; + uint tag; + string field_name; + while (input.ReadTag(out tag, out field_name)) { + if(tag == 0 && field_name != null) { + int field_ordinal = global::System.Array.BinarySearch(_divReplyFieldNames, field_name, global::System.StringComparer.Ordinal); + if(field_ordinal >= 0) + tag = _divReplyFieldTags[field_ordinal]; + else { + if (unknownFields == null) { + unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields); + } + ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name); + continue; + } + } + switch (tag) { + case 0: { + throw pb::InvalidProtocolBufferException.InvalidTag(); + } + default: { + if (pb::WireFormat.IsEndGroupTag(tag)) { + if (unknownFields != null) { + this.UnknownFields = unknownFields.Build(); + } + return this; + } + if (unknownFields == null) { + unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields); + } + ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name); + break; + } + case 8: { + result.hasQuotient = input.ReadInt64(ref result.quotient_); + break; + } + case 16: { + result.hasRemainder = input.ReadInt64(ref result.remainder_); + break; + } + } + } + + if (unknownFields != null) { + this.UnknownFields = unknownFields.Build(); + } + return this; + } + + + public bool HasQuotient { + get { return result.hasQuotient; } + } + public long Quotient { + get { return result.Quotient; } + set { SetQuotient(value); } + } + public Builder SetQuotient(long value) { + PrepareBuilder(); + result.hasQuotient = true; + result.quotient_ = value; + return this; + } + public Builder ClearQuotient() { + PrepareBuilder(); + result.hasQuotient = false; + result.quotient_ = 0L; + return this; + } + + public bool HasRemainder { + get { return result.hasRemainder; } + } + public long Remainder { + get { return result.Remainder; } + set { SetRemainder(value); } + } + public Builder SetRemainder(long value) { + PrepareBuilder(); + result.hasRemainder = true; + result.remainder_ = value; + return this; + } + public Builder ClearRemainder() { + PrepareBuilder(); + result.hasRemainder = false; + result.remainder_ = 0L; + return this; + } + } + static DivReply() { + object.ReferenceEquals(global::math.Proto.Math.Descriptor, null); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class FibArgs : pb::GeneratedMessage { + private FibArgs() { } + private static readonly FibArgs defaultInstance = new FibArgs().MakeReadOnly(); + private static readonly string[] _fibArgsFieldNames = new string[] { "limit" }; + private static readonly uint[] _fibArgsFieldTags = new uint[] { 8 }; + public static FibArgs DefaultInstance { + get { return defaultInstance; } + } + + public override FibArgs DefaultInstanceForType { + get { return DefaultInstance; } + } + + protected override FibArgs ThisMessage { + get { return this; } + } + + public static pbd::MessageDescriptor Descriptor { + get { return global::math.Proto.Math.internal__static_math_FibArgs__Descriptor; } + } + + protected override pb::FieldAccess.FieldAccessorTable InternalFieldAccessors { + get { return global::math.Proto.Math.internal__static_math_FibArgs__FieldAccessorTable; } + } + + public const int LimitFieldNumber = 1; + private bool hasLimit; + private long limit_; + public bool HasLimit { + get { return hasLimit; } + } + public long Limit { + get { return limit_; } + } + + public override bool IsInitialized { + get { + return true; + } + } + + public override void WriteTo(pb::ICodedOutputStream output) { + int size = SerializedSize; + string[] field_names = _fibArgsFieldNames; + if (hasLimit) { + output.WriteInt64(1, field_names[0], Limit); + } + UnknownFields.WriteTo(output); + } + + private int memoizedSerializedSize = -1; + public override int SerializedSize { + get { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (hasLimit) { + size += pb::CodedOutputStream.ComputeInt64Size(1, Limit); + } + size += UnknownFields.SerializedSize; + memoizedSerializedSize = size; + return size; + } + } + + public static FibArgs ParseFrom(pb::ByteString data) { + return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed(); + } + public static FibArgs ParseFrom(pb::ByteString data, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed(); + } + public static FibArgs ParseFrom(byte[] data) { + return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed(); + } + public static FibArgs ParseFrom(byte[] data, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed(); + } + public static FibArgs ParseFrom(global::System.IO.Stream input) { + return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed(); + } + public static FibArgs ParseFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed(); + } + public static FibArgs ParseDelimitedFrom(global::System.IO.Stream input) { + return CreateBuilder().MergeDelimitedFrom(input).BuildParsed(); + } + public static FibArgs ParseDelimitedFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) { + return CreateBuilder().MergeDelimitedFrom(input, extensionRegistry).BuildParsed(); + } + public static FibArgs ParseFrom(pb::ICodedInputStream input) { + return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed(); + } + public static FibArgs ParseFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed(); + } + private FibArgs MakeReadOnly() { + return this; + } + + public static Builder CreateBuilder() { return new Builder(); } + public override Builder ToBuilder() { return CreateBuilder(this); } + public override Builder CreateBuilderForType() { return new Builder(); } + public static Builder CreateBuilder(FibArgs prototype) { + return new Builder(prototype); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Builder : pb::GeneratedBuilder { + protected override Builder ThisBuilder { + get { return this; } + } + public Builder() { + result = DefaultInstance; + resultIsReadOnly = true; + } + internal Builder(FibArgs cloneFrom) { + result = cloneFrom; + resultIsReadOnly = true; + } + + private bool resultIsReadOnly; + private FibArgs result; + + private FibArgs PrepareBuilder() { + if (resultIsReadOnly) { + FibArgs original = result; + result = new FibArgs(); + resultIsReadOnly = false; + MergeFrom(original); + } + return result; + } + + public override bool IsInitialized { + get { return result.IsInitialized; } + } + + protected override FibArgs MessageBeingBuilt { + get { return PrepareBuilder(); } + } + + public override Builder Clear() { + result = DefaultInstance; + resultIsReadOnly = true; + return this; + } + + public override Builder Clone() { + if (resultIsReadOnly) { + return new Builder(result); + } else { + return new Builder().MergeFrom(result); + } + } + + public override pbd::MessageDescriptor DescriptorForType { + get { return global::math.FibArgs.Descriptor; } + } + + public override FibArgs DefaultInstanceForType { + get { return global::math.FibArgs.DefaultInstance; } + } + + public override FibArgs BuildPartial() { + if (resultIsReadOnly) { + return result; + } + resultIsReadOnly = true; + return result.MakeReadOnly(); + } + + public override Builder MergeFrom(pb::IMessage other) { + if (other is FibArgs) { + return MergeFrom((FibArgs) other); + } else { + base.MergeFrom(other); + return this; + } + } + + public override Builder MergeFrom(FibArgs other) { + if (other == global::math.FibArgs.DefaultInstance) return this; + PrepareBuilder(); + if (other.HasLimit) { + Limit = other.Limit; + } + this.MergeUnknownFields(other.UnknownFields); + return this; + } + + public override Builder MergeFrom(pb::ICodedInputStream input) { + return MergeFrom(input, pb::ExtensionRegistry.Empty); + } + + public override Builder MergeFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) { + PrepareBuilder(); + pb::UnknownFieldSet.Builder unknownFields = null; + uint tag; + string field_name; + while (input.ReadTag(out tag, out field_name)) { + if(tag == 0 && field_name != null) { + int field_ordinal = global::System.Array.BinarySearch(_fibArgsFieldNames, field_name, global::System.StringComparer.Ordinal); + if(field_ordinal >= 0) + tag = _fibArgsFieldTags[field_ordinal]; + else { + if (unknownFields == null) { + unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields); + } + ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name); + continue; + } + } + switch (tag) { + case 0: { + throw pb::InvalidProtocolBufferException.InvalidTag(); + } + default: { + if (pb::WireFormat.IsEndGroupTag(tag)) { + if (unknownFields != null) { + this.UnknownFields = unknownFields.Build(); + } + return this; + } + if (unknownFields == null) { + unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields); + } + ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name); + break; + } + case 8: { + result.hasLimit = input.ReadInt64(ref result.limit_); + break; + } + } + } + + if (unknownFields != null) { + this.UnknownFields = unknownFields.Build(); + } + return this; + } + + + public bool HasLimit { + get { return result.hasLimit; } + } + public long Limit { + get { return result.Limit; } + set { SetLimit(value); } + } + public Builder SetLimit(long value) { + PrepareBuilder(); + result.hasLimit = true; + result.limit_ = value; + return this; + } + public Builder ClearLimit() { + PrepareBuilder(); + result.hasLimit = false; + result.limit_ = 0L; + return this; + } + } + static FibArgs() { + object.ReferenceEquals(global::math.Proto.Math.Descriptor, null); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Num : pb::GeneratedMessage { + private Num() { } + private static readonly Num defaultInstance = new Num().MakeReadOnly(); + private static readonly string[] _numFieldNames = new string[] { "num" }; + private static readonly uint[] _numFieldTags = new uint[] { 8 }; + public static Num DefaultInstance { + get { return defaultInstance; } + } + + public override Num DefaultInstanceForType { + get { return DefaultInstance; } + } + + protected override Num ThisMessage { + get { return this; } + } + + public static pbd::MessageDescriptor Descriptor { + get { return global::math.Proto.Math.internal__static_math_Num__Descriptor; } + } + + protected override pb::FieldAccess.FieldAccessorTable InternalFieldAccessors { + get { return global::math.Proto.Math.internal__static_math_Num__FieldAccessorTable; } + } + + public const int Num_FieldNumber = 1; + private bool hasNum_; + private long num_; + public bool HasNum_ { + get { return hasNum_; } + } + public long Num_ { + get { return num_; } + } + + public override bool IsInitialized { + get { + return true; + } + } + + public override void WriteTo(pb::ICodedOutputStream output) { + int size = SerializedSize; + string[] field_names = _numFieldNames; + if (hasNum_) { + output.WriteInt64(1, field_names[0], Num_); + } + UnknownFields.WriteTo(output); + } + + private int memoizedSerializedSize = -1; + public override int SerializedSize { + get { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (hasNum_) { + size += pb::CodedOutputStream.ComputeInt64Size(1, Num_); + } + size += UnknownFields.SerializedSize; + memoizedSerializedSize = size; + return size; + } + } + + public static Num ParseFrom(pb::ByteString data) { + return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed(); + } + public static Num ParseFrom(pb::ByteString data, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed(); + } + public static Num ParseFrom(byte[] data) { + return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed(); + } + public static Num ParseFrom(byte[] data, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed(); + } + public static Num ParseFrom(global::System.IO.Stream input) { + return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed(); + } + public static Num ParseFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed(); + } + public static Num ParseDelimitedFrom(global::System.IO.Stream input) { + return CreateBuilder().MergeDelimitedFrom(input).BuildParsed(); + } + public static Num ParseDelimitedFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) { + return CreateBuilder().MergeDelimitedFrom(input, extensionRegistry).BuildParsed(); + } + public static Num ParseFrom(pb::ICodedInputStream input) { + return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed(); + } + public static Num ParseFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed(); + } + private Num MakeReadOnly() { + return this; + } + + public static Builder CreateBuilder() { return new Builder(); } + public override Builder ToBuilder() { return CreateBuilder(this); } + public override Builder CreateBuilderForType() { return new Builder(); } + public static Builder CreateBuilder(Num prototype) { + return new Builder(prototype); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Builder : pb::GeneratedBuilder { + protected override Builder ThisBuilder { + get { return this; } + } + public Builder() { + result = DefaultInstance; + resultIsReadOnly = true; + } + internal Builder(Num cloneFrom) { + result = cloneFrom; + resultIsReadOnly = true; + } + + private bool resultIsReadOnly; + private Num result; + + private Num PrepareBuilder() { + if (resultIsReadOnly) { + Num original = result; + result = new Num(); + resultIsReadOnly = false; + MergeFrom(original); + } + return result; + } + + public override bool IsInitialized { + get { return result.IsInitialized; } + } + + protected override Num MessageBeingBuilt { + get { return PrepareBuilder(); } + } + + public override Builder Clear() { + result = DefaultInstance; + resultIsReadOnly = true; + return this; + } + + public override Builder Clone() { + if (resultIsReadOnly) { + return new Builder(result); + } else { + return new Builder().MergeFrom(result); + } + } + + public override pbd::MessageDescriptor DescriptorForType { + get { return global::math.Num.Descriptor; } + } + + public override Num DefaultInstanceForType { + get { return global::math.Num.DefaultInstance; } + } + + public override Num BuildPartial() { + if (resultIsReadOnly) { + return result; + } + resultIsReadOnly = true; + return result.MakeReadOnly(); + } + + public override Builder MergeFrom(pb::IMessage other) { + if (other is Num) { + return MergeFrom((Num) other); + } else { + base.MergeFrom(other); + return this; + } + } + + public override Builder MergeFrom(Num other) { + if (other == global::math.Num.DefaultInstance) return this; + PrepareBuilder(); + if (other.HasNum_) { + Num_ = other.Num_; + } + this.MergeUnknownFields(other.UnknownFields); + return this; + } + + public override Builder MergeFrom(pb::ICodedInputStream input) { + return MergeFrom(input, pb::ExtensionRegistry.Empty); + } + + public override Builder MergeFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) { + PrepareBuilder(); + pb::UnknownFieldSet.Builder unknownFields = null; + uint tag; + string field_name; + while (input.ReadTag(out tag, out field_name)) { + if(tag == 0 && field_name != null) { + int field_ordinal = global::System.Array.BinarySearch(_numFieldNames, field_name, global::System.StringComparer.Ordinal); + if(field_ordinal >= 0) + tag = _numFieldTags[field_ordinal]; + else { + if (unknownFields == null) { + unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields); + } + ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name); + continue; + } + } + switch (tag) { + case 0: { + throw pb::InvalidProtocolBufferException.InvalidTag(); + } + default: { + if (pb::WireFormat.IsEndGroupTag(tag)) { + if (unknownFields != null) { + this.UnknownFields = unknownFields.Build(); + } + return this; + } + if (unknownFields == null) { + unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields); + } + ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name); + break; + } + case 8: { + result.hasNum_ = input.ReadInt64(ref result.num_); + break; + } + } + } + + if (unknownFields != null) { + this.UnknownFields = unknownFields.Build(); + } + return this; + } + + + public bool HasNum_ { + get { return result.hasNum_; } + } + public long Num_ { + get { return result.Num_; } + set { SetNum_(value); } + } + public Builder SetNum_(long value) { + PrepareBuilder(); + result.hasNum_ = true; + result.num_ = value; + return this; + } + public Builder ClearNum_() { + PrepareBuilder(); + result.hasNum_ = false; + result.num_ = 0L; + return this; + } + } + static Num() { + object.ReferenceEquals(global::math.Proto.Math.Descriptor, null); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class FibReply : pb::GeneratedMessage { + private FibReply() { } + private static readonly FibReply defaultInstance = new FibReply().MakeReadOnly(); + private static readonly string[] _fibReplyFieldNames = new string[] { "count" }; + private static readonly uint[] _fibReplyFieldTags = new uint[] { 8 }; + public static FibReply DefaultInstance { + get { return defaultInstance; } + } + + public override FibReply DefaultInstanceForType { + get { return DefaultInstance; } + } + + protected override FibReply ThisMessage { + get { return this; } + } + + public static pbd::MessageDescriptor Descriptor { + get { return global::math.Proto.Math.internal__static_math_FibReply__Descriptor; } + } + + protected override pb::FieldAccess.FieldAccessorTable InternalFieldAccessors { + get { return global::math.Proto.Math.internal__static_math_FibReply__FieldAccessorTable; } + } + + public const int CountFieldNumber = 1; + private bool hasCount; + private long count_; + public bool HasCount { + get { return hasCount; } + } + public long Count { + get { return count_; } + } + + public override bool IsInitialized { + get { + return true; + } + } + + public override void WriteTo(pb::ICodedOutputStream output) { + int size = SerializedSize; + string[] field_names = _fibReplyFieldNames; + if (hasCount) { + output.WriteInt64(1, field_names[0], Count); + } + UnknownFields.WriteTo(output); + } + + private int memoizedSerializedSize = -1; + public override int SerializedSize { + get { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (hasCount) { + size += pb::CodedOutputStream.ComputeInt64Size(1, Count); + } + size += UnknownFields.SerializedSize; + memoizedSerializedSize = size; + return size; + } + } + + public static FibReply ParseFrom(pb::ByteString data) { + return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed(); + } + public static FibReply ParseFrom(pb::ByteString data, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed(); + } + public static FibReply ParseFrom(byte[] data) { + return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed(); + } + public static FibReply ParseFrom(byte[] data, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed(); + } + public static FibReply ParseFrom(global::System.IO.Stream input) { + return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed(); + } + public static FibReply ParseFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed(); + } + public static FibReply ParseDelimitedFrom(global::System.IO.Stream input) { + return CreateBuilder().MergeDelimitedFrom(input).BuildParsed(); + } + public static FibReply ParseDelimitedFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) { + return CreateBuilder().MergeDelimitedFrom(input, extensionRegistry).BuildParsed(); + } + public static FibReply ParseFrom(pb::ICodedInputStream input) { + return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed(); + } + public static FibReply ParseFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed(); + } + private FibReply MakeReadOnly() { + return this; + } + + public static Builder CreateBuilder() { return new Builder(); } + public override Builder ToBuilder() { return CreateBuilder(this); } + public override Builder CreateBuilderForType() { return new Builder(); } + public static Builder CreateBuilder(FibReply prototype) { + return new Builder(prototype); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Builder : pb::GeneratedBuilder { + protected override Builder ThisBuilder { + get { return this; } + } + public Builder() { + result = DefaultInstance; + resultIsReadOnly = true; + } + internal Builder(FibReply cloneFrom) { + result = cloneFrom; + resultIsReadOnly = true; + } + + private bool resultIsReadOnly; + private FibReply result; + + private FibReply PrepareBuilder() { + if (resultIsReadOnly) { + FibReply original = result; + result = new FibReply(); + resultIsReadOnly = false; + MergeFrom(original); + } + return result; + } + + public override bool IsInitialized { + get { return result.IsInitialized; } + } + + protected override FibReply MessageBeingBuilt { + get { return PrepareBuilder(); } + } + + public override Builder Clear() { + result = DefaultInstance; + resultIsReadOnly = true; + return this; + } + + public override Builder Clone() { + if (resultIsReadOnly) { + return new Builder(result); + } else { + return new Builder().MergeFrom(result); + } + } + + public override pbd::MessageDescriptor DescriptorForType { + get { return global::math.FibReply.Descriptor; } + } + + public override FibReply DefaultInstanceForType { + get { return global::math.FibReply.DefaultInstance; } + } + + public override FibReply BuildPartial() { + if (resultIsReadOnly) { + return result; + } + resultIsReadOnly = true; + return result.MakeReadOnly(); + } + + public override Builder MergeFrom(pb::IMessage other) { + if (other is FibReply) { + return MergeFrom((FibReply) other); + } else { + base.MergeFrom(other); + return this; + } + } + + public override Builder MergeFrom(FibReply other) { + if (other == global::math.FibReply.DefaultInstance) return this; + PrepareBuilder(); + if (other.HasCount) { + Count = other.Count; + } + this.MergeUnknownFields(other.UnknownFields); + return this; + } + + public override Builder MergeFrom(pb::ICodedInputStream input) { + return MergeFrom(input, pb::ExtensionRegistry.Empty); + } + + public override Builder MergeFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) { + PrepareBuilder(); + pb::UnknownFieldSet.Builder unknownFields = null; + uint tag; + string field_name; + while (input.ReadTag(out tag, out field_name)) { + if(tag == 0 && field_name != null) { + int field_ordinal = global::System.Array.BinarySearch(_fibReplyFieldNames, field_name, global::System.StringComparer.Ordinal); + if(field_ordinal >= 0) + tag = _fibReplyFieldTags[field_ordinal]; + else { + if (unknownFields == null) { + unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields); + } + ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name); + continue; + } + } + switch (tag) { + case 0: { + throw pb::InvalidProtocolBufferException.InvalidTag(); + } + default: { + if (pb::WireFormat.IsEndGroupTag(tag)) { + if (unknownFields != null) { + this.UnknownFields = unknownFields.Build(); + } + return this; + } + if (unknownFields == null) { + unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields); + } + ParseUnknownField(input, unknownFields, extensionRegistry, tag, field_name); + break; + } + case 8: { + result.hasCount = input.ReadInt64(ref result.count_); + break; + } + } + } + + if (unknownFields != null) { + this.UnknownFields = unknownFields.Build(); + } + return this; + } + + + public bool HasCount { + get { return result.hasCount; } + } + public long Count { + get { return result.Count; } + set { SetCount(value); } + } + public Builder SetCount(long value) { + PrepareBuilder(); + result.hasCount = true; + result.count_ = value; + return this; + } + public Builder ClearCount() { + PrepareBuilder(); + result.hasCount = false; + result.count_ = 0L; + return this; + } + } + static FibReply() { + object.ReferenceEquals(global::math.Proto.Math.Descriptor, null); + } + } + + #endregion + + #region Services + /* + * Service generation is now disabled by default, use the following option to enable: + * option (google.protobuf.csharp_file_options).service_generator_type = GENERIC; + */ + #endregion + +} + +#endregion Designer generated code diff --git a/src/csharp/GrpcApi/MathServiceClientStub.cs b/src/csharp/GrpcApi/MathServiceClientStub.cs new file mode 100644 index 00000000000..493c186b8e5 --- /dev/null +++ b/src/csharp/GrpcApi/MathServiceClientStub.cs @@ -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 +{ + /// + /// Implementation of math service stub (this is handwritten version of code + /// that will normally be generated). + /// + 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("/math.Math/Div", Serialize_DivArgs, Deserialize_DivReply, methodTimeout, channel); + return Calls.BlockingUnaryCall(call, args, token); + } + + public Task DivAsync(DivArgs args, CancellationToken token = default(CancellationToken)) + { + var call = new Google.GRPC.Core.Call("/math.Math/Div", Serialize_DivArgs, Deserialize_DivReply, methodTimeout, channel); + return Calls.AsyncUnaryCall(call, args, token); + } + + public Task Fib(FibArgs args, IObserver outputs, CancellationToken token = default(CancellationToken)) + { + var call = new Google.GRPC.Core.Call("/math.Math/Fib", Serialize_FibArgs, Deserialize_Num, methodTimeout, channel); + return Calls.AsyncServerStreamingCall(call, args, outputs, token); + } + + public ClientStreamingAsyncResult Sum(CancellationToken token = default(CancellationToken)) + { + var call = new Google.GRPC.Core.Call("/math.Math/Sum", Serialize_Num, Deserialize_Num, methodTimeout, channel); + return Calls.AsyncClientStreamingCall(call, token); + } + + public IObserver DivMany(IObserver outputs, CancellationToken token = default(CancellationToken)) + { + var call = new Google.GRPC.Core.Call("/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(); + } + } +} \ No newline at end of file diff --git a/src/csharp/GrpcApi/Messages.cs b/src/csharp/GrpcApi/Messages.cs new file mode 100644 index 00000000000..b08816bdb7f --- /dev/null +++ b/src/csharp/GrpcApi/Messages.cs @@ -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; } +// } +//} + diff --git a/src/csharp/GrpcApi/Properties/AssemblyInfo.cs b/src/csharp/GrpcApi/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..725f12c4860 --- /dev/null +++ b/src/csharp/GrpcApi/Properties/AssemblyInfo.cs @@ -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("")] + diff --git a/src/csharp/GrpcApi/RecordingObserver.cs b/src/csharp/GrpcApi/RecordingObserver.cs new file mode 100644 index 00000000000..8ba3787905a --- /dev/null +++ b/src/csharp/GrpcApi/RecordingObserver.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading.Tasks; +using System.Collections.Generic; + +namespace math +{ + public class RecordingObserver : IObserver + { + TaskCompletionSource> tcs = new TaskCompletionSource>(); + List data = new List(); + + public void OnCompleted() + { + tcs.SetResult(data); + } + + public void OnError(Exception error) + { + tcs.SetException(error); + } + + public void OnNext(T value) + { + data.Add(value); + } + + public Task> ToList() { + return tcs.Task; + } + } +} + diff --git a/src/csharp/GrpcApi/math.proto b/src/csharp/GrpcApi/math.proto new file mode 100755 index 00000000000..e98b99e002a --- /dev/null +++ b/src/csharp/GrpcApi/math.proto @@ -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) { + } +} diff --git a/src/csharp/GrpcCore/.gitignore b/src/csharp/GrpcCore/.gitignore new file mode 100644 index 00000000000..ba077a4031a --- /dev/null +++ b/src/csharp/GrpcCore/.gitignore @@ -0,0 +1 @@ +bin diff --git a/src/csharp/GrpcCore/Call.cs b/src/csharp/GrpcCore/Call.cs new file mode 100644 index 00000000000..bf257e5d598 --- /dev/null +++ b/src/csharp/GrpcCore/Call.cs @@ -0,0 +1,69 @@ +using System; +using Google.GRPC.Core.Internal; + +namespace Google.GRPC.Core +{ + public class Call + { + readonly string methodName; + readonly Func requestSerializer; + readonly Func responseDeserializer; + readonly TimeSpan timeout; + readonly Channel channel; + + // TODO: channel param should be removed in the future. + public Call(string methodName, + Func requestSerializer, + Func 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 RequestSerializer + { + get + { + return this.requestSerializer; + } + } + + public Func ResponseDeserializer + { + get + { + return this.responseDeserializer; + } + } + } +} + diff --git a/src/csharp/GrpcCore/Calls.cs b/src/csharp/GrpcCore/Calls.cs new file mode 100644 index 00000000000..c3e51cb4781 --- /dev/null +++ b/src/csharp/GrpcCore/Calls.cs @@ -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 + + /// + /// Helper methods for generated stubs to make RPC calls. + /// + public static class Calls + { + public static TResponse BlockingUnaryCall(Call 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 AsyncUnaryCall(Call call, TRequest req, CancellationToken token) + { + var asyncCall = new AsyncCall(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(Call call, TRequest req, IObserver outputs, CancellationToken token) + { + var asyncCall = new AsyncCall(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 AsyncClientStreamingCall(Call call, CancellationToken token) + { + var asyncCall = new AsyncCall(call.RequestSerializer, call.ResponseDeserializer); + asyncCall.Initialize(call.Channel, call.MethodName); + asyncCall.Start(false, GetCompletionQueue()); + + var task = asyncCall.ReadAsync(); + var inputs = new StreamingInputObserver(asyncCall); + return new ClientStreamingAsyncResult(task, inputs); + } + + public static TResponse BlockingClientStreamingCall(Call call, IObservable inputs, CancellationToken token) + { + throw new NotImplementedException(); + } + + public static IObserver DuplexStreamingCall(Call call, IObserver outputs, CancellationToken token) + { + var asyncCall = new AsyncCall(call.RequestSerializer, call.ResponseDeserializer); + asyncCall.Initialize(call.Channel, call.MethodName); + asyncCall.Start(false, GetCompletionQueue()); + + asyncCall.StartReadingToStream(outputs); + var inputs = new StreamingInputObserver(asyncCall); + return inputs; + } + + private static CompletionQueueSafeHandle GetCompletionQueue() { + return GrpcEnvironment.ThreadPool.CompletionQueue; + } + } +} + diff --git a/src/csharp/GrpcCore/Channel.cs b/src/csharp/GrpcCore/Channel.cs new file mode 100644 index 00000000000..b0d8beeb7bf --- /dev/null +++ b/src/csharp/GrpcCore/Channel.cs @@ -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 + { + /// + /// Make sure GPRC environment is initialized before any channels get used. + /// + 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(); + } + } + } +} \ No newline at end of file diff --git a/src/csharp/GrpcCore/ClientStreamingAsyncResult.cs b/src/csharp/GrpcCore/ClientStreamingAsyncResult.cs new file mode 100644 index 00000000000..9e7312c1fa0 --- /dev/null +++ b/src/csharp/GrpcCore/ClientStreamingAsyncResult.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading.Tasks; + +namespace Google.GRPC.Core +{ + /// + /// Return type for client streaming async method. + /// + public struct ClientStreamingAsyncResult + { + readonly Task task; + readonly IObserver inputs; + + public ClientStreamingAsyncResult(Task task, IObserver inputs) + { + this.task = task; + this.inputs = inputs; + } + + public Task Task + { + get + { + return this.task; + } + } + + public IObserver Inputs + { + get + { + return this.inputs; + } + } + } +} + diff --git a/src/csharp/GrpcCore/GrpcCore.csproj b/src/csharp/GrpcCore/GrpcCore.csproj new file mode 100644 index 00000000000..f0c84e78ea6 --- /dev/null +++ b/src/csharp/GrpcCore/GrpcCore.csproj @@ -0,0 +1,62 @@ + + + + Debug + AnyCPU + 10.0.0 + 2.0 + {CCC4440E-49F7-4790-B0AF-FEABB0837AE7} + Library + GrpcCore + GrpcCore + v4.5 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + + + full + true + bin\Release + prompt + 4 + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/csharp/GrpcCore/GrpcEnvironment.cs b/src/csharp/GrpcCore/GrpcEnvironment.cs new file mode 100644 index 00000000000..7a644f49619 --- /dev/null +++ b/src/csharp/GrpcCore/GrpcEnvironment.cs @@ -0,0 +1,91 @@ +using System; +using Google.GRPC.Core.Internal; +using System.Runtime.InteropServices; + +namespace Google.GRPC.Core +{ + /// + /// 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. + /// + 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); + + /// + /// Makes sure GRPC environment is initialized. + /// + public static void EnsureInitialized() { + lock(staticLock) + { + if (!initCalled) + { + initCalled = true; + GrpcInit(); + } + } + } + + /// + /// Shuts down the GRPC environment if it was initialized before. + /// Repeated invocations have no effect. + /// + public static void Shutdown() + { + lock(staticLock) + { + if (initCalled && !shutdownCalled) + { + shutdownCalled = true; + GrpcShutdown(); + } + } + + } + + /// + /// Initializes GRPC C Core library. + /// + private static void GrpcInit() + { + grpc_init(); + threadPool.Start(); + // TODO: use proper logging here + Console.WriteLine("GRPC initialized."); + } + + /// + /// Shutdown GRPC C Core library. + /// + private static void GrpcShutdown() + { + threadPool.Stop(); + grpc_shutdown(); + + // TODO: use proper logging here + Console.WriteLine("GRPC shutdown."); + } + + internal static GrpcThreadPool ThreadPool + { + get + { + return threadPool; + } + } + } +} + diff --git a/src/csharp/GrpcCore/Internal/AsyncCall.cs b/src/csharp/GrpcCore/Internal/AsyncCall.cs new file mode 100644 index 00000000000..e83ca0eaa96 --- /dev/null +++ b/src/csharp/GrpcCore/Internal/AsyncCall.cs @@ -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 +{ + /// + /// Listener for call events that can be delivered from a completion queue. + /// + 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); + } + + /// + /// Handle native call lifecycle and provides convenience methods. + /// + internal class AsyncCall: ICallEventListener, IDisposable + { + readonly Func serializer; + readonly Func 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 finishedStatus; + + TaskCompletionSource writeTcs; + TaskCompletionSource readTcs; + TaskCompletionSource halfcloseTcs = new TaskCompletionSource(); + TaskCompletionSource finishedTcs = new TaskCompletionSource(); + + IObserver readObserver; + + public AsyncCall(Func serializer, Func 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 ReadAsync() + { + return StartRead().Task; + } + + public Task Finished + { + get + { + return finishedTcs.Task; + } + } + + /// + /// Initiates reading to given observer. + /// + public void StartReadingToStream(IObserver 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 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(); + 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 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(); + 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 oldTcs = null; + IObserver 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 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); + } + } + } +} \ No newline at end of file diff --git a/src/csharp/GrpcCore/Internal/CallSafeHandle.cs b/src/csharp/GrpcCore/Internal/CallSafeHandle.cs new file mode 100644 index 00000000000..6c9c58a4c3f --- /dev/null +++ b/src/csharp/GrpcCore/Internal/CallSafeHandle.cs @@ -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); + + /// + /// grpc_call from + /// + 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() + { + } + + /// + /// Creates a client call. + /// + 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; + } + } +} \ No newline at end of file diff --git a/src/csharp/GrpcCore/Internal/ChannelSafeHandle.cs b/src/csharp/GrpcCore/Internal/ChannelSafeHandle.cs new file mode 100644 index 00000000000..3a09d8b1b64 --- /dev/null +++ b/src/csharp/GrpcCore/Internal/ChannelSafeHandle.cs @@ -0,0 +1,34 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Google.GRPC.Core.Internal +{ + /// + /// grpc_channel from + /// + 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; + } + } +} \ No newline at end of file diff --git a/src/csharp/GrpcCore/Internal/CompletionQueueSafeHandle.cs b/src/csharp/GrpcCore/Internal/CompletionQueueSafeHandle.cs new file mode 100644 index 00000000000..73dd3edde33 --- /dev/null +++ b/src/csharp/GrpcCore/Internal/CompletionQueueSafeHandle.cs @@ -0,0 +1,66 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace Google.GRPC.Core.Internal +{ + /// + /// grpc_completion_queue from + /// + 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; + } + } +} + diff --git a/src/csharp/GrpcCore/Internal/Enums.cs b/src/csharp/GrpcCore/Internal/Enums.cs new file mode 100644 index 00000000000..46e3bca6ebe --- /dev/null +++ b/src/csharp/GrpcCore/Internal/Enums.cs @@ -0,0 +1,75 @@ +using System; +using System.Runtime.InteropServices; + +namespace Google.GRPC.Core.Internal +{ + /// + /// from grpc/grpc.h + /// + 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 + } + + /// + /// grpc_completion_type from grpc/grpc.h + /// + 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 */ + } + + /// + /// grpc_op_error from grpc/grpc.h + /// + internal enum GRPCOpError + { + /* everything went ok */ + GRPC_OP_OK = 0, + /* something failed, we don't know what */ + GRPC_OP_ERROR + } +} + diff --git a/src/csharp/GrpcCore/Internal/Event.cs b/src/csharp/GrpcCore/Internal/Event.cs new file mode 100644 index 00000000000..7056005ba65 --- /dev/null +++ b/src/csharp/GrpcCore/Internal/Event.cs @@ -0,0 +1,191 @@ +using System; +using System.Runtime.InteropServices; +using Google.GRPC.Core; + +namespace Google.GRPC.Core.Internal +{ + /// + /// grpc_event from grpc/grpc.h + /// + 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! + /// + /// Not owned version of + /// grpc_event from grpc/grpc.h + /// + 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; + } + } +} \ No newline at end of file diff --git a/src/csharp/GrpcCore/Internal/GrpcThreadPool.cs b/src/csharp/GrpcCore/Internal/GrpcThreadPool.cs new file mode 100644 index 00000000000..1139e54a1d7 --- /dev/null +++ b/src/csharp/GrpcCore/Internal/GrpcThreadPool.cs @@ -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 +{ + /// + /// Pool of threads polling on the same completion queue. + /// + internal class GrpcThreadPool + { + readonly object myLock = new object(); + readonly List threads = new List(); + readonly int poolSize; + readonly Action eventHandler; + + CompletionQueueSafeHandle cq; + + public GrpcThreadPool(int poolSize) { + this.poolSize = poolSize; + } + + internal GrpcThreadPool(int poolSize, Action 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; + } + + /// + /// Body of the polling thread. + /// + 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."); + } + + /// + /// Body of the polling thread. + /// + 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."); + } + } + +} + diff --git a/src/csharp/GrpcCore/Internal/SafeHandleZeroIsInvalid.cs b/src/csharp/GrpcCore/Internal/SafeHandleZeroIsInvalid.cs new file mode 100644 index 00000000000..5a1252b8814 --- /dev/null +++ b/src/csharp/GrpcCore/Internal/SafeHandleZeroIsInvalid.cs @@ -0,0 +1,28 @@ +using System; +using System.Runtime.InteropServices; + +namespace Google.GRPC.Core.Internal +{ + /// + /// Safe handle to wrap native objects. + /// + 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; + } + } + } +} + diff --git a/src/csharp/GrpcCore/Internal/ServerSafeHandle.cs b/src/csharp/GrpcCore/Internal/ServerSafeHandle.cs new file mode 100644 index 00000000000..0d38bce63e3 --- /dev/null +++ b/src/csharp/GrpcCore/Internal/ServerSafeHandle.cs @@ -0,0 +1,76 @@ +using System; +using System.Runtime.InteropServices; +using System.Diagnostics; +using System.Collections.Concurrent; + +namespace Google.GRPC.Core.Internal +{ + /// + /// grpc_server from grpc/grpc.h + /// + 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; + } + } +} \ No newline at end of file diff --git a/src/csharp/GrpcCore/Internal/StreamingInputObserver.cs b/src/csharp/GrpcCore/Internal/StreamingInputObserver.cs new file mode 100644 index 00000000000..d483e53a2db --- /dev/null +++ b/src/csharp/GrpcCore/Internal/StreamingInputObserver.cs @@ -0,0 +1,33 @@ +using System; +using Google.GRPC.Core.Internal; + +namespace Google.GRPC.Core +{ + internal class StreamingInputObserver : IObserver + { + readonly AsyncCall call; + + public StreamingInputObserver(AsyncCall 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(); + } + } +} + diff --git a/src/csharp/GrpcCore/Internal/Timespec.cs b/src/csharp/GrpcCore/Internal/Timespec.cs new file mode 100644 index 00000000000..8ffaf70bbfb --- /dev/null +++ b/src/csharp/GrpcCore/Internal/Timespec.cs @@ -0,0 +1,67 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Google.GRPC.Core.Internal +{ + /// + /// gpr_timespec from grpc/support/time.h + /// + [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; + + /// + /// Timespec a long time in the future. + /// + 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(); + } + } + + /// + /// Creates a GPR deadline from current instant and given timeout. + /// + /// The from timeout. + 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; + } + } +} + diff --git a/src/csharp/GrpcCore/Properties/AssemblyInfo.cs b/src/csharp/GrpcCore/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..74aba257678 --- /dev/null +++ b/src/csharp/GrpcCore/Properties/AssemblyInfo.cs @@ -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")] + diff --git a/src/csharp/GrpcCore/RpcException.cs b/src/csharp/GrpcCore/RpcException.cs new file mode 100644 index 00000000000..8811c3a7c75 --- /dev/null +++ b/src/csharp/GrpcCore/RpcException.cs @@ -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; + } + } + } +} + diff --git a/src/csharp/GrpcCore/Server.cs b/src/csharp/GrpcCore/Server.cs new file mode 100644 index 00000000000..68da1a83008 --- /dev/null +++ b/src/csharp/GrpcCore/Server.cs @@ -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 +{ + /// + /// Server is implemented only to be able to do + /// in-process testing. + /// + 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 newRpcQueue = new BlockingCollection(); + 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 asyncCall = new AsyncCall( + (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; + } + } + } + } +} \ No newline at end of file diff --git a/src/csharp/GrpcCore/Status.cs b/src/csharp/GrpcCore/Status.cs new file mode 100644 index 00000000000..f1212f8d672 --- /dev/null +++ b/src/csharp/GrpcCore/Status.cs @@ -0,0 +1,36 @@ +using System; +using System.Runtime.InteropServices; + +namespace Google.GRPC.Core +{ + /// + /// Represents RPC result. + /// + 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; + } + } + } +} \ No newline at end of file diff --git a/src/csharp/GrpcCore/StatusCode.cs b/src/csharp/GrpcCore/StatusCode.cs new file mode 100644 index 00000000000..80fc8bd5815 --- /dev/null +++ b/src/csharp/GrpcCore/StatusCode.cs @@ -0,0 +1,150 @@ +using System; + +namespace Google.GRPC.Core +{ + // TODO: element names should changed to comply with C# naming conventions. + /// + /// grpc_status_code from grpc/status.h + /// + 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 + } +} + diff --git a/src/csharp/GrpcCoreTests/.gitignore b/src/csharp/GrpcCoreTests/.gitignore new file mode 100644 index 00000000000..2cc8cca52d0 --- /dev/null +++ b/src/csharp/GrpcCoreTests/.gitignore @@ -0,0 +1,2 @@ +test-results +bin diff --git a/src/csharp/GrpcCoreTests/ClientServerTest.cs b/src/csharp/GrpcCoreTests/ClientServerTest.cs new file mode 100644 index 00000000000..823ee942882 --- /dev/null +++ b/src/csharp/GrpcCoreTests/ClientServerTest.cs @@ -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 CreateCall(Channel channel) + { + return new Call("/tests.Test/EmptyCall", + (s) => System.Text.Encoding.ASCII.GetBytes(s), + (b) => System.Text.Encoding.ASCII.GetString(b), + Timeout.InfiniteTimeSpan, channel); + } + } +} + diff --git a/src/csharp/GrpcCoreTests/GrpcCoreTests.csproj b/src/csharp/GrpcCoreTests/GrpcCoreTests.csproj new file mode 100644 index 00000000000..3de0f585cda --- /dev/null +++ b/src/csharp/GrpcCoreTests/GrpcCoreTests.csproj @@ -0,0 +1,53 @@ + + + + Debug + AnyCPU + 10.0.0 + 2.0 + {86EC5CB4-4EA2-40A2-8057-86542A0353BB} + Library + GrpcCoreTests + GrpcCoreTests + v4.5 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + + + full + true + bin\Release + prompt + 4 + false + + + + + False + + + + + + + + + + + + + + {CCC4440E-49F7-4790-B0AF-FEABB0837AE7} + GrpcCore + + + \ No newline at end of file diff --git a/src/csharp/GrpcCoreTests/GrpcEnvironmentTest.cs b/src/csharp/GrpcCoreTests/GrpcEnvironmentTest.cs new file mode 100644 index 00000000000..136878d76eb --- /dev/null +++ b/src/csharp/GrpcCoreTests/GrpcEnvironmentTest.cs @@ -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(); + } + } +} diff --git a/src/csharp/GrpcCoreTests/Properties/AssemblyInfo.cs b/src/csharp/GrpcCoreTests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..565b1e2bd65 --- /dev/null +++ b/src/csharp/GrpcCoreTests/Properties/AssemblyInfo.cs @@ -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("")] + diff --git a/src/csharp/GrpcCoreTests/ServerTest.cs b/src/csharp/GrpcCoreTests/ServerTest.cs new file mode 100644 index 00000000000..b34101bbf59 --- /dev/null +++ b/src/csharp/GrpcCoreTests/ServerTest.cs @@ -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(); + } + + } +} diff --git a/src/csharp/GrpcCoreTests/TestResult.xml b/src/csharp/GrpcCoreTests/TestResult.xml new file mode 100644 index 00000000000..a5a6abd7b95 --- /dev/null +++ b/src/csharp/GrpcCoreTests/TestResult.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/csharp/GrpcCoreTests/TimespecTest.cs b/src/csharp/GrpcCoreTests/TimespecTest.cs new file mode 100644 index 00000000000..484bad7ca19 --- /dev/null +++ b/src/csharp/GrpcCoreTests/TimespecTest.cs @@ -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); + } + } +} + diff --git a/src/csharp/GrpcCoreTests/Utils.cs b/src/csharp/GrpcCoreTests/Utils.cs new file mode 100644 index 00000000000..b0c0a7b6205 --- /dev/null +++ b/src/csharp/GrpcCoreTests/Utils.cs @@ -0,0 +1,51 @@ +using System; +using System.Net; +using System.Net.Sockets; + +namespace Google.GRPC.Core.Tests +{ + /// + /// Testing utils. + /// + 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; + } + } +} + diff --git a/src/csharp/GrpcDemo/.gitignore b/src/csharp/GrpcDemo/.gitignore new file mode 100644 index 00000000000..ba077a4031a --- /dev/null +++ b/src/csharp/GrpcDemo/.gitignore @@ -0,0 +1 @@ +bin diff --git a/src/csharp/GrpcDemo/GrpcDemo.csproj b/src/csharp/GrpcDemo/GrpcDemo.csproj new file mode 100644 index 00000000000..31ce7f133b3 --- /dev/null +++ b/src/csharp/GrpcDemo/GrpcDemo.csproj @@ -0,0 +1,52 @@ + + + + Debug + x86 + 10.0.0 + 2.0 + {61ECB8EE-0C96-4F8E-B187-8E4D227417C0} + Exe + GrpcDemo + GrpcDemo + v4.5 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + true + x86 + + + full + true + bin\Release + prompt + 4 + true + x86 + + + + + + + + + + + + {7DC1433E-3225-42C7-B7EA-546D56E27A4B} + GrpcApi + + + {CCC4440E-49F7-4790-B0AF-FEABB0837AE7} + GrpcCore + + + \ No newline at end of file diff --git a/src/csharp/GrpcDemo/Program.cs b/src/csharp/GrpcDemo/Program.cs new file mode 100644 index 00000000000..258762dbb99 --- /dev/null +++ b/src/csharp/GrpcDemo/Program.cs @@ -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(); + } + } +} diff --git a/src/csharp/GrpcDemo/Properties/AssemblyInfo.cs b/src/csharp/GrpcDemo/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..b8e1406da7c --- /dev/null +++ b/src/csharp/GrpcDemo/Properties/AssemblyInfo.cs @@ -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("")] + diff --git a/src/csharp/README.md b/src/csharp/README.md index 5b56303c147..75bfb26252c 100755 --- a/src/csharp/README.md +++ b/src/csharp/README.md @@ -19,4 +19,8 @@ CONTENTS - ext: 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. diff --git a/src/csharp/lib/Google.ProtocolBuffers.dll b/src/csharp/lib/Google.ProtocolBuffers.dll new file mode 100755 index 0000000000000000000000000000000000000000..ce2f466b243da0e875764856cd99c61ca9d98628 GIT binary patch literal 380416 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�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 Date: Tue, 3 Feb 2015 22:44:13 -0800 Subject: [PATCH 02/27] Cleanup documentation --- src/core/surface/call.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/surface/call.c b/src/core/surface/call.c index f3b76364467..8221401a70b 100644 --- a/src/core/surface/call.c +++ b/src/core/surface/call.c @@ -163,11 +163,12 @@ struct grpc_call { a request, and is valid iff request_set[op] <= GRPC_IOREQ_OP_COUNT. The set fields are as per the request type specified by op. - Finally, one element of masters[op] is set per active _group_ of ioreq + Finally, one element of masters is set per active _set_ of ioreq operations. It describes work left outstanding, result status, and what work to perform upon operation completion. As one ioreq of each op type can be active at once, by convention we choose the first element - of a the group to be the master. This allows constant time allocation + of the group to be the master -- ie the master of in-progress operation + op is masters[request_set[op]]. This allows constant time allocation and a strong upper bound of a count of masters to be calculated. */ gpr_uint8 request_set[GRPC_IOREQ_OP_COUNT]; grpc_ioreq_data request_data[GRPC_IOREQ_OP_COUNT]; From 6a09ca80d62e291163409d27cc78c8371d3af32e Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Wed, 4 Feb 2015 08:30:11 -0800 Subject: [PATCH 03/27] Eliminate condvar in chttp2 transport This used to be necessary to guarantee safe deletion, but with refcounting changes in call it's no longer needed --- src/core/transport/chttp2_transport.c | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/core/transport/chttp2_transport.c b/src/core/transport/chttp2_transport.c index f5604176173..0446e0ab9eb 100644 --- a/src/core/transport/chttp2_transport.c +++ b/src/core/transport/chttp2_transport.c @@ -184,7 +184,6 @@ struct transport { gpr_uint8 is_client; gpr_mu mu; - gpr_cv cv; /* basic state management - what are we doing at the moment? */ gpr_uint8 reading; @@ -395,7 +394,6 @@ static void init_transport(transport *t, grpc_transport_setup_callback setup, /* one ref is for destroy, the other for when ep becomes NULL */ gpr_ref_init(&t->refs, 2); gpr_mu_init(&t->mu); - gpr_cv_init(&t->cv); t->metadata_context = mdctx; t->str_grpc_timeout = grpc_mdstr_from_string(t->metadata_context, "grpc-timeout"); @@ -483,7 +481,6 @@ static void init_transport(transport *t, grpc_transport_setup_callback setup, t->cb_user_data = sr.user_data; grpc_chttp2_hpack_parser_init(&t->hpack_parser, t->metadata_context); t->calling_back = 0; - gpr_cv_broadcast(&t->cv); unlock(t); unref_transport(t); } @@ -492,9 +489,6 @@ static void destroy_transport(grpc_transport *gt) { transport *t = (transport *)gt; gpr_mu_lock(&t->mu); - while (t->calling_back) { - gpr_cv_wait(&t->cv, &t->mu, gpr_inf_future); - } t->cb = NULL; gpr_mu_unlock(&t->mu); @@ -573,13 +567,6 @@ static void destroy_stream(grpc_transport *gt, grpc_stream *gs) { gpr_mu_lock(&t->mu); - /* await pending callbacks - TODO(ctiller): this could be optimized to check if this stream is getting - callbacks */ - while (t->calling_back) { - gpr_cv_wait(&t->cv, &t->mu, gpr_inf_future); - } - /* stop parsing if we're currently parsing this stream */ if (t->deframe_state == DTS_FRAME && t->incoming_stream_id == s->id && s->id != 0) { @@ -591,7 +578,6 @@ static void destroy_stream(grpc_transport *gt, grpc_stream *gs) { } remove_from_stream_map(t, s); - gpr_cv_broadcast(&t->cv); gpr_mu_unlock(&t->mu); grpc_sopb_destroy(&s->outgoing_sopb); @@ -761,7 +747,6 @@ static void unlock(transport *t) { if (perform_callbacks || call_closed || num_goaways) { lock(t); t->calling_back = 0; - gpr_cv_broadcast(&t->cv); unlock(t); unref_transport(t); } @@ -892,7 +877,6 @@ static void finish_write_common(transport *t, int success) { if (!t->reading) { grpc_endpoint_destroy(t->ep); t->ep = NULL; - gpr_cv_broadcast(&t->cv); unref_transport(t); /* safe because we'll still have the ref for write */ } unlock(t); @@ -1673,7 +1657,6 @@ static void recv_data(void *tp, gpr_slice *slices, size_t nslices, if (!t->writing && t->ep) { grpc_endpoint_destroy(t->ep); t->ep = NULL; - gpr_cv_broadcast(&t->cv); unref_transport(t); /* safe as we still have a ref for read */ } unlock(t); From 9b60fa3acd9b48441f8f0a2062cfd2df7ebeeb8b Mon Sep 17 00:00:00 2001 From: David Klempner Date: Wed, 4 Feb 2015 09:48:53 -0800 Subject: [PATCH 04/27] Make gpr_timespec no longer be a typedef for struct timespec in posix The problem is that for the typedef to work we need _POSIX_C_SOURCE to be defined properly before any file that uses gpr_timespec includes anything. This is extremely fragile unless we change CFLAGS, which probably isn't worth doing for this. --- include/grpc/support/time.h | 4 ++-- include/grpc/support/time_posix.h | 5 ++++- src/core/support/sync_posix.c | 11 ++++++++++- src/core/support/time_posix.c | 25 +++++++++++++++++++++---- 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/include/grpc/support/time.h b/include/grpc/support/time.h index 6327a2cffb6..70e4afdf6ba 100644 --- a/include/grpc/support/time.h +++ b/include/grpc/support/time.h @@ -34,8 +34,8 @@ #ifndef __GRPC_SUPPORT_TIME_H__ #define __GRPC_SUPPORT_TIME_H__ /* Time support. - We use gpr_timespec, which is typedefed to struct timespec on platforms which - have it. On some machines, absolute times may be in local time. */ + We use gpr_timespec, which is analogous to struct timespec. On some + machines, absolute times may be in local time. */ /* Platform specific header declares gpr_timespec. gpr_timespec contains: diff --git a/include/grpc/support/time_posix.h b/include/grpc/support/time_posix.h index 9ff6f7f4933..85dee5fc212 100644 --- a/include/grpc/support/time_posix.h +++ b/include/grpc/support/time_posix.h @@ -38,6 +38,9 @@ #include #include -typedef struct timespec gpr_timespec; +typedef struct gpr_timespec { + time_t tv_sec; + long tv_nsec; +} gpr_timespec; #endif /* __GRPC_SUPPORT_TIME_POSIX_H__ */ diff --git a/src/core/support/sync_posix.c b/src/core/support/sync_posix.c index 7f0e4a95a43..a28a4c6bf46 100644 --- a/src/core/support/sync_posix.c +++ b/src/core/support/sync_posix.c @@ -33,11 +33,17 @@ /* Posix gpr synchroization support code. */ +#if !defined _POSIX_C_SOURCE || _POSIX_C_SOURCE < 199309L +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 199309L +#endif + #include #ifdef GPR_POSIX_SYNC #include +#include #include #include #include @@ -67,7 +73,10 @@ int gpr_cv_wait(gpr_cv *cv, gpr_mu *mu, gpr_timespec abs_deadline) { if (gpr_time_cmp(abs_deadline, gpr_inf_future) == 0) { err = pthread_cond_wait(cv, mu); } else { - err = pthread_cond_timedwait(cv, mu, &abs_deadline); + struct timespec abs_deadline_ts; + abs_deadline_ts.tv_sec = abs_deadline.tv_sec; + abs_deadline_ts.tv_nsec = abs_deadline.tv_nsec; + err = pthread_cond_timedwait(cv, mu, &abs_deadline_ts); } GPR_ASSERT(err == 0 || err == ETIMEDOUT || err == EAGAIN); return err == ETIMEDOUT; diff --git a/src/core/support/time_posix.c b/src/core/support/time_posix.c index 9e11f8a865d..7f0f028183e 100644 --- a/src/core/support/time_posix.c +++ b/src/core/support/time_posix.c @@ -34,7 +34,8 @@ /* Posix code for gpr time support. */ /* So we get nanosleep and clock_* */ -#ifndef _POSIX_C_SOURCE +#if !defined _POSIX_C_SOURCE || _POSIX_C_SOURCE < 199309L +#undef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 199309L #endif @@ -47,11 +48,25 @@ #include #include +static struct timespec timespec_from_gpr(gpr_timespec gts) { + struct timespec rv; + rv.tv_sec = gts.tv_sec; + rv.tv_nsec = gts.tv_nsec; + return rv; +} + #if _POSIX_TIMERS > 0 +static gpr_timespec gpr_from_timespec(struct timespec ts) { + gpr_timespec rv; + rv.tv_sec = ts.tv_sec; + rv.tv_nsec = ts.tv_nsec; + return rv; +} + gpr_timespec gpr_now(void) { - gpr_timespec now; + struct timespec now; clock_gettime(CLOCK_REALTIME, &now); - return now; + return gpr_from_timespec(now); } #else /* For some reason Apple's OSes haven't implemented clock_gettime. */ @@ -69,6 +84,7 @@ gpr_timespec gpr_now(void) { void gpr_sleep_until(gpr_timespec until) { gpr_timespec now; gpr_timespec delta; + struct timespec delta_ts; for (;;) { /* We could simplify by using clock_nanosleep instead, but it might be @@ -79,7 +95,8 @@ void gpr_sleep_until(gpr_timespec until) { } delta = gpr_time_sub(until, now); - if (nanosleep(&delta, NULL) == 0) { + delta_ts = timespec_from_gpr(delta); + if (nanosleep(&delta_ts, NULL) == 0) { break; } } From 78b79920afbdc87284c49fc27358d2854ae8fe9c Mon Sep 17 00:00:00 2001 From: David Klempner Date: Wed, 4 Feb 2015 10:18:59 -0800 Subject: [PATCH 05/27] Fix up feature test macros Move all feature test macros to the start of the file and check that they aren't already defined or defined to a lower value than the file needs. Projects should be allowed to put these in CFLAGS and we shouldn't break when they do. --- src/core/iomgr/resolve_address.c | 2 ++ src/core/iomgr/socket_utils_linux.c | 2 ++ src/core/iomgr/socket_utils_posix.c | 1 - src/core/iomgr/tcp_server_posix.c | 6 +++++- src/core/support/log_linux.c | 6 ++++++ src/core/support/log_posix.c | 7 ++++++- src/core/support/string_posix.c | 3 ++- test/core/echo/echo_test.c | 3 +++ test/core/fling/fling_stream_test.c | 3 +++ test/core/fling/fling_test.c | 3 +++ 10 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/core/iomgr/resolve_address.c b/src/core/iomgr/resolve_address.c index 01681168ce4..575f884d91a 100644 --- a/src/core/iomgr/resolve_address.c +++ b/src/core/iomgr/resolve_address.c @@ -31,7 +31,9 @@ * */ +#ifndef _POSIX_SOURCE #define _POSIX_SOURCE +#endif #include "src/core/iomgr/sockaddr.h" #include "src/core/iomgr/resolve_address.h" diff --git a/src/core/iomgr/socket_utils_linux.c b/src/core/iomgr/socket_utils_linux.c index f971cb33bcc..7ef58940c24 100644 --- a/src/core/iomgr/socket_utils_linux.c +++ b/src/core/iomgr/socket_utils_linux.c @@ -31,7 +31,9 @@ * */ +#ifndef _GNU_SOURCE #define _GNU_SOURCE +#endif #include #ifdef GPR_LINUX diff --git a/src/core/iomgr/socket_utils_posix.c b/src/core/iomgr/socket_utils_posix.c index 06c5033d457..9184b2a47cf 100644 --- a/src/core/iomgr/socket_utils_posix.c +++ b/src/core/iomgr/socket_utils_posix.c @@ -35,7 +35,6 @@ #ifdef GPR_POSIX_SOCKETUTILS -#define _BSD_SOURCE #include "src/core/iomgr/socket_utils_posix.h" #include diff --git a/src/core/iomgr/tcp_server_posix.c b/src/core/iomgr/tcp_server_posix.c index d169d232718..091f0aab1a6 100644 --- a/src/core/iomgr/tcp_server_posix.c +++ b/src/core/iomgr/tcp_server_posix.c @@ -31,11 +31,15 @@ * */ +/* FIXME: "posix" files shouldn't be depending on _GNU_SOURCE */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + #include #ifdef GPR_POSIX_SOCKET -#define _GNU_SOURCE #include "src/core/iomgr/tcp_server.h" #include diff --git a/src/core/support/log_linux.c b/src/core/support/log_linux.c index a0307e1a9a4..a64faa98bd9 100644 --- a/src/core/support/log_linux.c +++ b/src/core/support/log_linux.c @@ -31,8 +31,14 @@ * */ +#ifndef _POSIX_SOURCE #define _POSIX_SOURCE +#endif + +#ifndef _GNU_SOURCE #define _GNU_SOURCE +#endif + #include #ifdef GPR_LINUX diff --git a/src/core/support/log_posix.c b/src/core/support/log_posix.c index ab2d2e5a740..05f45de1308 100644 --- a/src/core/support/log_posix.c +++ b/src/core/support/log_posix.c @@ -31,11 +31,16 @@ * */ -#ifndef _POSIX_C_SOURCE +#if !defined _POSIX_C_SOURCE || _POSIX_C_SOURCE < 200112L +#undef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200112L #endif +/* FIXME: "posix" files probably shouldn't depend on _GNU_SOURCE */ +#ifndef _GNU_SOURCE #define _GNU_SOURCE +#endif + #include #if defined(GPR_POSIX_LOG) diff --git a/src/core/support/string_posix.c b/src/core/support/string_posix.c index 57832810ad3..a6bb8058e6c 100644 --- a/src/core/support/string_posix.c +++ b/src/core/support/string_posix.c @@ -33,7 +33,8 @@ /* Posix code for gpr snprintf support. */ -#ifndef _POSIX_C_SOURCE +#if !defined _POSIX_C_SOURCE || _POSIX_C_SOURCE < 200112L +#undef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200112L #endif diff --git a/test/core/echo/echo_test.c b/test/core/echo/echo_test.c index 83b83ab7ff7..5450dfbef56 100644 --- a/test/core/echo/echo_test.c +++ b/test/core/echo/echo_test.c @@ -31,7 +31,10 @@ * */ +#ifndef _POSIX_SOURCE #define _POSIX_SOURCE +#endif + #include #include #include diff --git a/test/core/fling/fling_stream_test.c b/test/core/fling/fling_stream_test.c index 7f52fb1bad1..1db2f1a7916 100644 --- a/test/core/fling/fling_stream_test.c +++ b/test/core/fling/fling_stream_test.c @@ -31,7 +31,10 @@ * */ +#ifndef _POSIX_SOURCE #define _POSIX_SOURCE +#endif + #include #include #include diff --git a/test/core/fling/fling_test.c b/test/core/fling/fling_test.c index b2272f20c8e..4f41a21aaaf 100644 --- a/test/core/fling/fling_test.c +++ b/test/core/fling/fling_test.c @@ -31,7 +31,10 @@ * */ +#ifndef _POSIX_SOURCE #define _POSIX_SOURCE +#endif + #include #include #include From c15622b95c8162cf981aa63caf8d764ab1718b09 Mon Sep 17 00:00:00 2001 From: David Klempner Date: Wed, 4 Feb 2015 12:02:17 -0800 Subject: [PATCH 06/27] Remove timeval functions They only had one caller, which could easily be converted to use timespec instead of timeval. --- include/grpc/support/time.h | 4 ---- src/core/support/time.c | 16 ---------------- src/node/ext/timeval.cc | 5 ++--- 3 files changed, 2 insertions(+), 23 deletions(-) diff --git a/include/grpc/support/time.h b/include/grpc/support/time.h index 6327a2cffb6..f8870153b1f 100644 --- a/include/grpc/support/time.h +++ b/include/grpc/support/time.h @@ -103,10 +103,6 @@ int gpr_time_similar(gpr_timespec a, gpr_timespec b, gpr_timespec threshold); /* Sleep until at least 'until' - an absolute timeout */ void gpr_sleep_until(gpr_timespec until); -struct timeval gpr_timeval_from_timespec(gpr_timespec t); - -gpr_timespec gpr_timespec_from_timeval(struct timeval t); - double gpr_timespec_to_micros(gpr_timespec t); #ifdef __cplusplus diff --git a/src/core/support/time.c b/src/core/support/time.c index 97243318fda..268a43c6775 100644 --- a/src/core/support/time.c +++ b/src/core/support/time.c @@ -234,22 +234,6 @@ int gpr_time_similar(gpr_timespec a, gpr_timespec b, gpr_timespec threshold) { } } -struct timeval gpr_timeval_from_timespec(gpr_timespec t) { - /* TODO(klempner): Consider whether this should round up, since it is likely - to be used for delays */ - struct timeval tv; - tv.tv_sec = t.tv_sec; - tv.tv_usec = t.tv_nsec / 1000; - return tv; -} - -gpr_timespec gpr_timespec_from_timeval(struct timeval t) { - gpr_timespec ts; - ts.tv_sec = t.tv_sec; - ts.tv_nsec = t.tv_usec * 1000; - return ts; -} - gpr_int32 gpr_time_to_millis(gpr_timespec t) { if (t.tv_sec >= 2147483) { if (t.tv_sec == 2147483 && t.tv_nsec < 648 * GPR_NS_PER_MS) { diff --git a/src/node/ext/timeval.cc b/src/node/ext/timeval.cc index 687e33576b4..20d52f0963e 100644 --- a/src/node/ext/timeval.cc +++ b/src/node/ext/timeval.cc @@ -56,9 +56,8 @@ double TimespecToMilliseconds(gpr_timespec timespec) { } else if (gpr_time_cmp(timespec, gpr_inf_past) == 0) { return -std::numeric_limits::infinity(); } else { - struct timeval time = gpr_timeval_from_timespec(timespec); - return (static_cast(time.tv_sec) * 1000 + - static_cast(time.tv_usec) / 1000); + return (static_cast(timespec.tv_sec) * 1000 + + static_cast(timespec.tv_nsec) / 1000000); } } From e2b2b1fb676eef687f0ea088aa74a13b52cbf755 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Wed, 4 Feb 2015 12:50:06 -0800 Subject: [PATCH 07/27] Fix check for whether we should write to prevent infinite loop --- src/core/surface/call.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/core/surface/call.c b/src/core/surface/call.c index 382909c8652..561c24e547e 100644 --- a/src/core/surface/call.c +++ b/src/core/surface/call.c @@ -445,17 +445,16 @@ static void finish_start_step(void *pc, grpc_op_error error) { static send_action choose_send_action(grpc_call *call) { switch (call->write_state) { case WRITE_STATE_INITIAL: - if (call->request_set[GRPC_IOREQ_SEND_INITIAL_METADATA] != - REQSET_EMPTY) { + if (is_op_live(call, GRPC_IOREQ_SEND_INITIAL_METADATA)) { call->write_state = WRITE_STATE_STARTED; return SEND_INITIAL_METADATA; } return SEND_NOTHING; case WRITE_STATE_STARTED: - if (call->request_set[GRPC_IOREQ_SEND_MESSAGE] != REQSET_EMPTY) { + if (is_op_live(call, GRPC_IOREQ_SEND_MESSAGE)) { return SEND_MESSAGE; } - if (call->request_set[GRPC_IOREQ_SEND_CLOSE] != REQSET_EMPTY) { + if (is_op_live(call, GRPC_IOREQ_SEND_CLOSE)) { call->write_state = WRITE_STATE_WRITE_CLOSED; finish_ioreq_op(call, GRPC_IOREQ_SEND_TRAILING_METADATA, GRPC_OP_OK); finish_ioreq_op(call, GRPC_IOREQ_SEND_STATUS, GRPC_OP_OK); From 0c61dc52a17b33fd11e2c85ee7797da517f01df2 Mon Sep 17 00:00:00 2001 From: David Klempner Date: Wed, 4 Feb 2015 13:24:04 -0800 Subject: [PATCH 08/27] Remove the platform specific time headers --- include/grpc/support/time.h | 21 ++++---------- include/grpc/support/time_posix.h | 46 ------------------------------- include/grpc/support/time_win32.h | 46 ------------------------------- 3 files changed, 6 insertions(+), 107 deletions(-) delete mode 100644 include/grpc/support/time_posix.h delete mode 100644 include/grpc/support/time_win32.h diff --git a/include/grpc/support/time.h b/include/grpc/support/time.h index 690fbc10e7d..9fb1d0bc97b 100644 --- a/include/grpc/support/time.h +++ b/include/grpc/support/time.h @@ -37,28 +37,19 @@ We use gpr_timespec, which is analogous to struct timespec. On some machines, absolute times may be in local time. */ -/* Platform specific header declares gpr_timespec. - gpr_timespec contains: - time_t tv_sec; // seconds since start of 1970 - int tv_nsec; // nanoseconds; always in 0..999999999; never negative. - */ - #include - -#if defined(GPR_POSIX_TIME) -#include -#elif defined(GPR_WIN32) -#include -#else -#error could not determine platform for time -#endif - #include +#include #ifdef __cplusplus extern "C" { #endif +typedef struct gpr_timespec { + time_t tv_sec; + int tv_nsec; +} gpr_timespec; + /* Time constants. */ extern const gpr_timespec gpr_time_0; /* The zero time interval. */ extern const gpr_timespec gpr_inf_future; /* The far future */ diff --git a/include/grpc/support/time_posix.h b/include/grpc/support/time_posix.h deleted file mode 100644 index 85dee5fc212..00000000000 --- a/include/grpc/support/time_posix.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * - * Copyright 2014, Google Inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#ifndef __GRPC_SUPPORT_TIME_POSIX_H__ -#define __GRPC_SUPPORT_TIME_POSIX_H__ -/* Posix variant of gpr_time_platform.h */ - -#include -#include - -typedef struct gpr_timespec { - time_t tv_sec; - long tv_nsec; -} gpr_timespec; - -#endif /* __GRPC_SUPPORT_TIME_POSIX_H__ */ diff --git a/include/grpc/support/time_win32.h b/include/grpc/support/time_win32.h deleted file mode 100644 index e62ad64b8f5..00000000000 --- a/include/grpc/support/time_win32.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * - * Copyright 2014, Google Inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#ifndef __GRPC_SUPPORT_TIME_WIN32_H__ -#define __GRPC_SUPPORT_TIME_WIN32_H__ -/* Win32 variant of gpr_time_platform.h */ - -#include -#include - -typedef struct gpr_timespec { - time_t tv_sec; - long tv_nsec; -} gpr_timespec; - -#endif /* __GRPC_SUPPORT_TIME_WIN32_H__ */ From e5437de181fb0ccea7978c6dfa735169ad65cc69 Mon Sep 17 00:00:00 2001 From: David Klempner Date: Wed, 4 Feb 2015 14:06:03 -0800 Subject: [PATCH 09/27] Add a missing mdstr_unref This fixes most of the asan reported leaks. --- src/core/surface/call.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/surface/call.c b/src/core/surface/call.c index 5a24264ccec..2b6f042eb99 100644 --- a/src/core/surface/call.c +++ b/src/core/surface/call.c @@ -318,6 +318,7 @@ grpc_call_error grpc_call_cancel_with_status(grpc_call *c, maybe_set_status_code(c, status); if (details) { maybe_set_status_details(c, details); + grpc_mdstr_unref(details); } gpr_mu_unlock(&c->read_mu); return grpc_call_cancel(c); From 5ea99bb81cf34ed721e915bfabd9b974e361e382 Mon Sep 17 00:00:00 2001 From: Nicolas Noble Date: Wed, 4 Feb 2015 14:13:09 -0800 Subject: [PATCH 10/27] Let the http2 transport issue a read request before pumping bytes into it. --- src/core/transport/chttp2_transport.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/core/transport/chttp2_transport.c b/src/core/transport/chttp2_transport.c index 48a10058331..6e5095d87f5 100644 --- a/src/core/transport/chttp2_transport.c +++ b/src/core/transport/chttp2_transport.c @@ -328,6 +328,9 @@ static void maybe_start_some_streams(transport *t); static void become_skip_parser(transport *t); +static void recv_data(void *tp, gpr_slice *slices, size_t nslices, + grpc_endpoint_cb_status error); + /* * CONSTRUCTION/DESTRUCTION/REFCOUNTING */ @@ -382,8 +385,8 @@ static void ref_transport(transport *t) { gpr_ref(&t->refs); } static void init_transport(transport *t, grpc_transport_setup_callback setup, void *arg, const grpc_channel_args *channel_args, - grpc_endpoint *ep, grpc_mdctx *mdctx, - int is_client) { + grpc_endpoint *ep, gpr_slice *slices, size_t nslices, + grpc_mdctx *mdctx, int is_client) { size_t i; int j; grpc_transport_setup_result sr; @@ -422,6 +425,7 @@ static void init_transport(transport *t, grpc_transport_setup_callback setup, gpr_slice_buffer_init(&t->outbuf); gpr_slice_buffer_init(&t->qbuf); grpc_sopb_init(&t->nuke_later_sopb); + grpc_chttp2_hpack_parser_init(&t->hpack_parser, t->metadata_context); if (is_client) { gpr_slice_buffer_add(&t->qbuf, gpr_slice_from_copied_string(CLIENT_CONNECT_STRING)); @@ -476,12 +480,14 @@ static void init_transport(transport *t, grpc_transport_setup_callback setup, ref_transport(t); gpr_mu_unlock(&t->mu); + ref_transport(t); + recv_data(t, slices, nslices, GRPC_ENDPOINT_CB_OK); + sr = setup(arg, &t->base, t->metadata_context); lock(t); t->cb = sr.callbacks; t->cb_user_data = sr.user_data; - grpc_chttp2_hpack_parser_init(&t->hpack_parser, t->metadata_context); t->calling_back = 0; gpr_cv_broadcast(&t->cv); unlock(t); @@ -1769,7 +1775,6 @@ void grpc_create_chttp2_transport(grpc_transport_setup_callback setup, size_t nslices, grpc_mdctx *mdctx, int is_client) { transport *t = gpr_malloc(sizeof(transport)); - init_transport(t, setup, arg, channel_args, ep, mdctx, is_client); - ref_transport(t); - recv_data(t, slices, nslices, GRPC_ENDPOINT_CB_OK); + init_transport(t, setup, arg, channel_args, ep, slices, nslices, mdctx, + is_client); } From 6393dd36f1a3d2c0c1125f65e0c8329e1385e0b6 Mon Sep 17 00:00:00 2001 From: Nicolas Noble Date: Wed, 4 Feb 2015 14:23:39 -0800 Subject: [PATCH 11/27] Fixing build.json by removing files that are no longer present. --- Makefile | 2 -- build.json | 2 -- vsprojects/vs2013/gpr.vcxproj | 2 -- vsprojects/vs2013/gpr.vcxproj.filters | 6 ------ 4 files changed, 12 deletions(-) diff --git a/Makefile b/Makefile index 846d9772d4e..d0b5e34f3e5 100644 --- a/Makefile +++ b/Makefile @@ -1272,8 +1272,6 @@ PUBLIC_HEADERS_C += \ include/grpc/support/sync_win32.h \ include/grpc/support/thd.h \ include/grpc/support/time.h \ - include/grpc/support/time_posix.h \ - include/grpc/support/time_win32.h \ include/grpc/support/useful.h \ LIBGPR_OBJS = $(addprefix objs/$(CONFIG)/, $(addsuffix .o, $(basename $(LIBGPR_SRC)))) diff --git a/build.json b/build.json index 6fa3495aa30..d8a295f35c3 100644 --- a/build.json +++ b/build.json @@ -221,8 +221,6 @@ "include/grpc/support/sync_win32.h", "include/grpc/support/thd.h", "include/grpc/support/time.h", - "include/grpc/support/time_posix.h", - "include/grpc/support/time_win32.h", "include/grpc/support/useful.h" ], "headers": [ diff --git a/vsprojects/vs2013/gpr.vcxproj b/vsprojects/vs2013/gpr.vcxproj index c77a61d7829..0d429ab43de 100644 --- a/vsprojects/vs2013/gpr.vcxproj +++ b/vsprojects/vs2013/gpr.vcxproj @@ -92,8 +92,6 @@ - - diff --git a/vsprojects/vs2013/gpr.vcxproj.filters b/vsprojects/vs2013/gpr.vcxproj.filters index e385efcfb24..a992558654b 100644 --- a/vsprojects/vs2013/gpr.vcxproj.filters +++ b/vsprojects/vs2013/gpr.vcxproj.filters @@ -138,12 +138,6 @@ include\grpc\support - - include\grpc\support - - - include\grpc\support - include\grpc\support From 05fce429e2a6503a839ac2a7f6854dafc6d188e9 Mon Sep 17 00:00:00 2001 From: David Klempner Date: Wed, 4 Feb 2015 14:40:42 -0800 Subject: [PATCH 12/27] Fix a memory leak and a gpr_strdup/free mismatch in json_test --- test/core/json/json_test.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/core/json/json_test.c b/test/core/json/json_test.c index 11659a57161..6d0227ad39b 100644 --- a/test/core/json/json_test.c +++ b/test/core/json/json_test.c @@ -151,7 +151,7 @@ static void test_pairs() { GPR_ASSERT(!json); } - free(scratchpad); + gpr_free(scratchpad); } } @@ -166,6 +166,7 @@ static void test_atypical() { grpc_json_destroy(json->child); json->child = brother; grpc_json_destroy(json); + gpr_free(scratchpad); } int main(int argc, char **argv) { From ca8cbe40e7713ca9ff1e9d11e9fd5341c0d8a1e6 Mon Sep 17 00:00:00 2001 From: murgatroid99 Date: Wed, 4 Feb 2015 15:10:15 -0800 Subject: [PATCH 13/27] Added a lot more information to README --- src/node/README.md | 72 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 7 deletions(-) diff --git a/src/node/README.md b/src/node/README.md index 55329d8cb2f..c342b7ca575 100644 --- a/src/node/README.md +++ b/src/node/README.md @@ -1,12 +1,70 @@ -# Node.js GRPC extension +# Node.js gRPC Library -The package is built with +## Installation - node-gyp configure - node-gyp build +First, clone this repository (NPM package coming soon). Then follow the instructions in the `INSTALL` file in the root of the repository to install the C core library that this package depends on. -or, for brevity +Then, simply run `npm install` in or referencing this directory. - node-gyp configure build +## Tests -The tests can be run with `npm test` on a dev install. \ No newline at end of file +To run the test suite, simply run `npm test` in the install location. + +## API + +This library internally uses [ProtoBuf.js](https://github.com/dcodeIO/ProtoBuf.js), and some structures it exports match those exported by that library + +If you require this module, you will get an object with the following members + +```javascript +function load(filename) +``` + +Takes a filename of a [Protocol Buffer](https://developers.google.com/protocol-buffers/) file, and returns an object representing the structure of the protocol buffer in the following way: + + - Namespaces become maps from the names of their direct members to those member objects + - Service definitions become client constructors for clients for that service. They also have a `service` member that can be used for constructing servers. + - Message definitions become Message constructors like those that ProtoBuf.js would create + - Enum definitions become Enum objects like those that ProtoBuf.js would create + - Anything else becomes the relevant reflection object that ProtoBuf.js would create + + +```javascript +function loadObject(reflectionObject) +``` + +Returns the same structure that `load` returns, but takes a reflection object from `ProtoBuf.js` instead of a file name. + +```javascript +function buildServer(serviceArray) +``` + +Takes an array of service objects and returns a constructor for a server that handles requests to all of those services. + + +```javascript +status +``` + +An object mapping status names to status code numbers. + + +```javascript +callError +``` + +An object mapping call error names to codes. This is primarily useful for tracking down certain kinds of internal errors. + + +```javascript +Credentials +``` + +An object with factory methods for creating credential objects for clients. + + +```javascript +ServerCredentials +``` + +An object with factory methods fro creating credential objects for servers. From 1d0302d03df9d5bde57fb326c5df3f6de43ddd6f Mon Sep 17 00:00:00 2001 From: David Klempner Date: Wed, 4 Feb 2015 16:08:01 -0800 Subject: [PATCH 14/27] Add a tsan suppression file with OPENSSL_cleanse and use it in run_tests --- tools/run_tests/run_tests.py | 5 +++-- tools/tsan_suppressions.txt | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 tools/tsan_suppressions.txt diff --git a/tools/run_tests/run_tests.py b/tools/run_tests/run_tests.py index 280c3f05cb9..cb54c0db82c 100755 --- a/tools/run_tests/run_tests.py +++ b/tools/run_tests/run_tests.py @@ -108,10 +108,11 @@ class PythonLanguage(object): _CONFIGS = { 'dbg': SimpleConfig('dbg'), 'opt': SimpleConfig('opt'), - 'tsan': SimpleConfig('tsan'), + 'tsan': SimpleConfig('tsan', environ={ + 'TSAN_OPTIONS': 'suppressions=tools/tsan_suppressions.txt'}), 'msan': SimpleConfig('msan'), 'asan': SimpleConfig('asan', environ={ - 'ASAN_OPTIONS': 'detect_leaks=1:color=always'}), + 'ASAN_OPTIONS': 'detect_leaks=1:color=always:suppressions=tools/tsan_suppressions.txt'}), 'gcov': SimpleConfig('gcov'), 'memcheck': ValgrindConfig('valgrind', 'memcheck'), 'helgrind': ValgrindConfig('dbg', 'helgrind') diff --git a/tools/tsan_suppressions.txt b/tools/tsan_suppressions.txt new file mode 100644 index 00000000000..23d57f9fd1f --- /dev/null +++ b/tools/tsan_suppressions.txt @@ -0,0 +1,2 @@ +# OPENSSL_cleanse does racy access to a global +race:OPENSSL_cleanse From 4138a6a837c51d376352eeb691f91a350dc07fd9 Mon Sep 17 00:00:00 2001 From: Donna Dionne Date: Wed, 4 Feb 2015 20:09:40 -0800 Subject: [PATCH 15/27] Rewrote the timeout for test commands Added missing pingpong tests to cloud prod test Cleaned up test output and inserted them into a google.visualization.Datatable object --- tools/gce_setup/builder.sh | 33 ++++++++++++++++++++ tools/gce_setup/cloud_prod_runner.sh | 4 +-- tools/gce_setup/grpc_docker.sh | 22 ++++++++++++-- tools/gce_setup/interop_test_runner.sh | 42 ++++++++------------------ tools/gce_setup/post.html | 12 ++++++++ tools/gce_setup/pre.html | 14 +++++++++ 6 files changed, 94 insertions(+), 33 deletions(-) create mode 100755 tools/gce_setup/builder.sh create mode 100644 tools/gce_setup/post.html create mode 100644 tools/gce_setup/pre.html diff --git a/tools/gce_setup/builder.sh b/tools/gce_setup/builder.sh new file mode 100755 index 00000000000..ecdafe9010a --- /dev/null +++ b/tools/gce_setup/builder.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +main() { + # restart builder vm and wait for images to sync to it + source grpc_docker.sh + ./new_grpc_docker_builder.sh -igrpc-docker-builder-alt-2 -anone + cd ../../ + sleep 3600 + + # build images for all languages + languages=(cxx java go ruby node) + for lan in "${languages[@]}" + do + grpc_update_image $lan + done + + # restart client and server vm and wait for images to sync to them + cd tools/gce_setup + ./new_grpc_docker_builder.sh -igrpc-docker-testclients-donna -anone + ./new_grpc_docker_builder.sh -igrpc-docker-server-donna -anone + sleep 3600 + + # launch images for all languages on both client and server + for lan in "${languages[@]}" + do + grpc_launch_servers grpc-docker-testclients-donna $lan + grpc_launch_servers grpc-docker-server-donna $lan + done + +} + +set -x +main "$@" diff --git a/tools/gce_setup/cloud_prod_runner.sh b/tools/gce_setup/cloud_prod_runner.sh index 0c1163ad7db..200f859ede5 100755 --- a/tools/gce_setup/cloud_prod_runner.sh +++ b/tools/gce_setup/cloud_prod_runner.sh @@ -2,8 +2,8 @@ main() { source grpc_docker.sh - test_cases=(large_unary empty_unary client_streaming server_streaming) - clients=(cxx java go ruby) + test_cases=(large_unary empty_unary ping_pong client_streaming server_streaming) + clients=(cxx java go ruby node) for test_case in "${test_cases[@]}" do for client in "${clients[@]}" diff --git a/tools/gce_setup/grpc_docker.sh b/tools/gce_setup/grpc_docker.sh index 2ac75f3cc59..2e026538643 100755 --- a/tools/gce_setup/grpc_docker.sh +++ b/tools/gce_setup/grpc_docker.sh @@ -762,7 +762,16 @@ grpc_interop_test() { echo " $ssh_cmd" echo "on $host" [[ $dry_run == 1 ]] && return 0 # don't run the command on a dry run - gcloud compute $project_opt ssh $zone_opt $host --command "$cmd" + gcloud compute $project_opt ssh $zone_opt $host --command "$cmd" & + PID=$! + sleep 10 + echo "pid is $PID" + if ps -p $PID + then + kill $PID + return 1 + fi + } # Runs a test command on a docker instance. @@ -808,7 +817,16 @@ grpc_cloud_prod_test() { echo " $ssh_cmd" echo "on $host" [[ $dry_run == 1 ]] && return 0 # don't run the command on a dry run - gcloud compute $project_opt ssh $zone_opt $host --command "$cmd" + gcloud compute $project_opt ssh $zone_opt $host --command "$cmd" & + PID=$! + sleep 10 + echo "pid is $PID" + if ps -p $PID + then + kill $PID + return 1 + fi + } # Runs a test command on a docker instance. diff --git a/tools/gce_setup/interop_test_runner.sh b/tools/gce_setup/interop_test_runner.sh index 12443079128..456ad4b4722 100755 --- a/tools/gce_setup/interop_test_runner.sh +++ b/tools/gce_setup/interop_test_runner.sh @@ -1,33 +1,8 @@ #!/bin/bash thisfile=$(readlink -ne "${BASH_SOURCE[0]}") - -run_test() { - local test_case=$1 - shift - local client=$1 - shift - local server=$1 - if grpc_interop_test $test_case grpc-docker-testclients $client grpc-docker-server $server - then - echo "$test_case $client $server passed" >> /tmp/interop_result.txt - else - echo "$test_case $client $server failed" >> /tmp/interop_result.txt - fi -} - -time_out() { - local test_case=$1 - shift - local client=$1 - shift - local server=$1 - if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then - if ! timeout 20s bash -l -c "source $thisfile && run_test $test_case $client $server" - then - echo "$test_case $client $server timed out" >> /tmp/interop_result.txt - fi - fi -} +current_time=$(date "+%Y-%m-%d-%H-%M-%S") +result_file_name=interop_result.$current_time.html +echo $result_file_name main() { source grpc_docker.sh @@ -40,13 +15,22 @@ main() { do for server in "${servers[@]}" do - time_out $test_case $client $server + if grpc_interop_test $test_case grpc-docker-testclients $client grpc-docker-server $server + then + echo " ['$test_case', '$client', '$server', true]," >> /tmp/interop_result.txt + else + echo " ['$test_case', '$client', '$server', false]," >> /tmp/interop_result.txt + fi done done done if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + cat pre.html /tmp/interop_result.txt post.html > /tmp/interop_result.html gsutil cp /tmp/interop_result.txt gs://stoked-keyword-656-output/interop_result.txt + gsutil cp /tmp/interop_result.html gs://stoked-keyword-656-output/interop_result.html + gsutil cp /tmp/interop_result.html gs://stoked-keyword-656-output/result_history/$result_file_name rm /tmp/interop_result.txt + rm /tmp/interop_result.html fi } diff --git a/tools/gce_setup/post.html b/tools/gce_setup/post.html new file mode 100644 index 00000000000..57cbc8c3694 --- /dev/null +++ b/tools/gce_setup/post.html @@ -0,0 +1,12 @@ + ]); + + var table = new google.visualization.Table(document.getElementById('table_div')); + + table.draw(data, {showRowNumber: true}); + } + + + +

+ + diff --git a/tools/gce_setup/pre.html b/tools/gce_setup/pre.html new file mode 100644 index 00000000000..74ce5ce2028 --- /dev/null +++ b/tools/gce_setup/pre.html @@ -0,0 +1,14 @@ + + + +