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 00000000000..ce2f466b243 Binary files /dev/null and b/src/csharp/lib/Google.ProtocolBuffers.dll differ