diff options
93 files changed, 4198 insertions, 289 deletions
diff --git a/include/grpc++/impl/call.h b/include/grpc++/impl/call.h index 64fa5d6efb..da8ee5dd18 100644 --- a/include/grpc++/impl/call.h +++ b/include/grpc++/impl/call.h @@ -172,17 +172,34 @@ class CallOpRecvMessage { grpc_byte_buffer* recv_buf_; }; +namespace CallOpGenericRecvMessageHelper { +class DeserializeFunc { + public: + virtual Status Deserialize(grpc_byte_buffer* buf, int max_message_size) = 0; +}; + +template <class R> +class DeserializeFuncType GRPC_FINAL : public DeserializeFunc { + public: + DeserializeFuncType(R* message) : message_(message) {} + Status Deserialize(grpc_byte_buffer* buf, + int max_message_size) GRPC_OVERRIDE { + return SerializationTraits<R>::Deserialize(buf, message_, max_message_size); + } + + private: + R* message_; // Not a managed pointer because management is external to this +}; +} // namespace CallOpGenericRecvMessageHelper + class CallOpGenericRecvMessage { public: CallOpGenericRecvMessage() : got_message(false) {} template <class R> void RecvMessage(R* message) { - deserialize_ = [message](grpc_byte_buffer* buf, - int max_message_size) -> Status { - return SerializationTraits<R>::Deserialize(buf, message, - max_message_size); - }; + deserialize_.reset( + new CallOpGenericRecvMessageHelper::DeserializeFuncType<R>(message)); } bool got_message; @@ -201,7 +218,7 @@ class CallOpGenericRecvMessage { if (recv_buf_) { if (*status) { got_message = true; - *status = deserialize_(recv_buf_, max_message_size).ok(); + *status = deserialize_->Deserialize(recv_buf_, max_message_size).ok(); } else { got_message = false; grpc_byte_buffer_destroy(recv_buf_); @@ -210,12 +227,11 @@ class CallOpGenericRecvMessage { got_message = false; *status = false; } - deserialize_ = DeserializeFunc(); + deserialize_.reset(); } private: - typedef std::function<Status(grpc_byte_buffer*, int)> DeserializeFunc; - DeserializeFunc deserialize_; + std::unique_ptr<CallOpGenericRecvMessageHelper::DeserializeFunc> deserialize_; grpc_byte_buffer* recv_buf_; }; diff --git a/src/compiler/objective_c_generator.cc b/src/compiler/objective_c_generator.cc index 79a84b4a7a..2a74a3b340 100644 --- a/src/compiler/objective_c_generator.cc +++ b/src/compiler/objective_c_generator.cc @@ -57,13 +57,12 @@ void PrintProtoRpcDeclarationAsPragma(Printer *printer, vars["server_stream"] = method->server_streaming() ? "stream " : ""; printer->Print(vars, - "#pragma mark $method_name$($client_stream$$request_type$)" - " returns ($server_stream$$response_type$)\n\n"); + "#pragma mark $method_name$($client_stream$$request_type$)" + " returns ($server_stream$$response_type$)\n\n"); } -void PrintMethodSignature(Printer *printer, - const MethodDescriptor *method, - const map<string, string>& vars) { +void PrintMethodSignature(Printer *printer, const MethodDescriptor *method, + const map<string, string> &vars) { // TODO(jcanizales): Print method comments. printer->Print(vars, "- ($return_type$)$method_name$With"); @@ -75,16 +74,17 @@ void PrintMethodSignature(Printer *printer, // TODO(jcanizales): Put this on a new line and align colons. if (method->server_streaming()) { - printer->Print(vars, " eventHandler:(void(^)(BOOL done, " - "$response_class$ *response, NSError *error))eventHandler"); + printer->Print(vars, + " eventHandler:(void(^)(BOOL done, " + "$response_class$ *response, NSError *error))eventHandler"); } else { - printer->Print(vars, " handler:(void(^)($response_class$ *response, " - "NSError *error))handler"); + printer->Print(vars, + " handler:(void(^)($response_class$ *response, " + "NSError *error))handler"); } } -void PrintSimpleSignature(Printer *printer, - const MethodDescriptor *method, +void PrintSimpleSignature(Printer *printer, const MethodDescriptor *method, map<string, string> vars) { vars["method_name"] = grpc_generator::LowercaseFirstLetter(vars["method_name"]); @@ -92,8 +92,7 @@ void PrintSimpleSignature(Printer *printer, PrintMethodSignature(printer, method, vars); } -void PrintAdvancedSignature(Printer *printer, - const MethodDescriptor *method, +void PrintAdvancedSignature(Printer *printer, const MethodDescriptor *method, map<string, string> vars) { vars["method_name"] = "RPCTo" + vars["method_name"]; vars["return_type"] = "ProtoRPC *"; @@ -101,15 +100,16 @@ void PrintAdvancedSignature(Printer *printer, } inline map<string, string> GetMethodVars(const MethodDescriptor *method) { - return {{ "method_name", method->name() }, - { "request_type", method->input_type()->name() }, - { "response_type", method->output_type()->name() }, - { "request_class", ClassName(method->input_type()) }, - { "response_class", ClassName(method->output_type()) }}; + map<string, string> res; + res["method_name"] = method->name(); + res["request_type"] = method->input_type()->name(); + res["response_type"] = method->output_type()->name(); + res["request_class"] = ClassName(method->input_type()); + res["response_class"] = ClassName(method->output_type()); + return res; } -void PrintMethodDeclarations(Printer *printer, - const MethodDescriptor *method) { +void PrintMethodDeclarations(Printer *printer, const MethodDescriptor *method) { map<string, string> vars = GetMethodVars(method); PrintProtoRpcDeclarationAsPragma(printer, method, vars); @@ -120,8 +120,7 @@ void PrintMethodDeclarations(Printer *printer, printer->Print(";\n\n\n"); } -void PrintSimpleImplementation(Printer *printer, - const MethodDescriptor *method, +void PrintSimpleImplementation(Printer *printer, const MethodDescriptor *method, map<string, string> vars) { printer->Print("{\n"); printer->Print(vars, " [[self RPCTo$method_name$With"); @@ -178,7 +177,7 @@ void PrintMethodImplementations(Printer *printer, PrintAdvancedImplementation(printer, method, vars); } -} // namespace +} // namespace string GetHeader(const ServiceDescriptor *service) { string output; @@ -186,7 +185,7 @@ string GetHeader(const ServiceDescriptor *service) { // Scope the output stream so it closes and finalizes output to the string. grpc::protobuf::io::StringOutputStream output_stream(&output); Printer printer(&output_stream, '$'); - + printer.Print("@protocol GRXWriteable;\n"); printer.Print("@protocol GRXWriter;\n\n"); @@ -199,12 +198,15 @@ string GetHeader(const ServiceDescriptor *service) { } printer.Print("@end\n\n"); - printer.Print("// Basic service implementation, over gRPC, that only does" + printer.Print( + "// Basic service implementation, over gRPC, that only does" " marshalling and parsing.\n"); - printer.Print(vars, "@interface $service_class$ :" - " ProtoService<$service_class$>\n"); - printer.Print("- (instancetype)initWithHost:(NSString *)host" - " NS_DESIGNATED_INITIALIZER;\n"); + printer.Print(vars, + "@interface $service_class$ :" + " ProtoService<$service_class$>\n"); + printer.Print( + "- (instancetype)initWithHost:(NSString *)host" + " NS_DESIGNATED_INITIALIZER;\n"); printer.Print("@end\n"); } return output; @@ -222,18 +224,20 @@ string GetSource(const ServiceDescriptor *service) { {"package", service->file()->package()}}; printer.Print(vars, - "static NSString *const kPackageName = @\"$package$\";\n"); - printer.Print(vars, - "static NSString *const kServiceName = @\"$service_name$\";\n\n"); + "static NSString *const kPackageName = @\"$package$\";\n"); + printer.Print( + vars, "static NSString *const kServiceName = @\"$service_name$\";\n\n"); printer.Print(vars, "@implementation $service_class$\n\n"); - + printer.Print("// Designated initializer\n"); printer.Print("- (instancetype)initWithHost:(NSString *)host {\n"); - printer.Print(" return (self = [super initWithHost:host" + printer.Print( + " return (self = [super initWithHost:host" " packageName:kPackageName serviceName:kServiceName]);\n"); printer.Print("}\n\n"); - printer.Print("// Override superclass initializer to disallow different" + printer.Print( + "// Override superclass initializer to disallow different" " package and service names.\n"); printer.Print("- (instancetype)initWithHost:(NSString *)host\n"); printer.Print(" packageName:(NSString *)packageName\n"); @@ -250,4 +254,4 @@ string GetSource(const ServiceDescriptor *service) { return output; } -} // namespace grpc_objective_c_generator +} // namespace grpc_objective_c_generator diff --git a/src/core/transport/chttp2/frame_data.c b/src/core/transport/chttp2/frame_data.c index 0ad62a9999..7e3980159e 100644 --- a/src/core/transport/chttp2/frame_data.c +++ b/src/core/transport/chttp2/frame_data.c @@ -89,12 +89,9 @@ grpc_chttp2_parse_error grpc_chttp2_data_parser_parse( fh_0: case GRPC_CHTTP2_DATA_FH_0: p->frame_type = *cur; - if (++cur == end) { - p->state = GRPC_CHTTP2_DATA_FH_1; - return GRPC_CHTTP2_PARSE_OK; - } switch (p->frame_type) { case 0: + /* noop */ break; case 1: gpr_log(GPR_ERROR, "Compressed GRPC frames not yet supported"); @@ -103,6 +100,10 @@ grpc_chttp2_parse_error grpc_chttp2_data_parser_parse( gpr_log(GPR_ERROR, "Bad GRPC frame type 0x%02x", p->frame_type); return GRPC_CHTTP2_STREAM_ERROR; } + if (++cur == end) { + p->state = GRPC_CHTTP2_DATA_FH_1; + return GRPC_CHTTP2_PARSE_OK; + } /* fallthrough */ case GRPC_CHTTP2_DATA_FH_1: p->frame_size = ((gpr_uint32)*cur) << 24; diff --git a/src/csharp/Grpc.Auth/Grpc.Auth.csproj b/src/csharp/Grpc.Auth/Grpc.Auth.csproj index e6abbbfdf0..fdec2e7bd7 100644 --- a/src/csharp/Grpc.Auth/Grpc.Auth.csproj +++ b/src/csharp/Grpc.Auth/Grpc.Auth.csproj @@ -68,6 +68,9 @@ <Reference Include="System.Net.Http.WebRequest" /> </ItemGroup> <ItemGroup> + <Compile Include="..\Grpc.Core\Version.cs"> + <Link>Version.cs</Link> + </Compile> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="GoogleCredential.cs" /> <Compile Include="OAuth2InterceptorFactory.cs" /> @@ -81,6 +84,7 @@ </ItemGroup> <ItemGroup> <None Include="app.config" /> + <None Include="Grpc.Auth.nuspec" /> <None Include="packages.config" /> </ItemGroup> <Import Project="..\packages\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets')" /> diff --git a/src/csharp/Grpc.Auth/Grpc.Auth.nuspec b/src/csharp/Grpc.Auth/Grpc.Auth.nuspec index 978b04d70b..1262bdbdab 100644 --- a/src/csharp/Grpc.Auth/Grpc.Auth.nuspec +++ b/src/csharp/Grpc.Auth/Grpc.Auth.nuspec @@ -5,19 +5,19 @@ <title>gRPC C# Auth</title> <summary>Auth library for C# implementation of gRPC - an RPC library and framework</summary> <description>Auth library for C# implementation of gRPC - an RPC library and framework. See project site for more info.</description> - <version>0.6.0</version> + <version>$version$</version> <authors>Google Inc.</authors> <owners>grpc-packages</owners> <licenseUrl>https://github.com/grpc/grpc/blob/master/LICENSE</licenseUrl> <projectUrl>https://github.com/grpc/grpc</projectUrl> <requireLicenseAcceptance>false</requireLicenseAcceptance> - <releaseNotes>Release 0.6.0 of gRPC C#</releaseNotes> + <releaseNotes>Release $version$ of gRPC C#</releaseNotes> <copyright>Copyright 2015, Google Inc.</copyright> <tags>gRPC RPC Protocol HTTP/2 Auth OAuth2</tags> <dependencies> <dependency id="BouncyCastle" version="1.7.0" /> <dependency id="Google.Apis.Auth" version="1.9.1" /> - <dependency id="Grpc.Core" version="0.6.0" /> + <dependency id="Grpc.Core" version="$version$" /> </dependencies> </metadata> <files> diff --git a/src/csharp/Grpc.Auth/Properties/AssemblyInfo.cs b/src/csharp/Grpc.Auth/Properties/AssemblyInfo.cs index 2cdf643597..70cb32d5b2 100644 --- a/src/csharp/Grpc.Auth/Properties/AssemblyInfo.cs +++ b/src/csharp/Grpc.Auth/Properties/AssemblyInfo.cs @@ -9,6 +9,5 @@ using System.Runtime.CompilerServices; [assembly: AssemblyCopyright("Google Inc. All rights reserved.")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("0.6.*")] [assembly: InternalsVisibleTo("Grpc.Auth.Tests")] diff --git a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs index 21f94d3cf5..e797dd82f2 100644 --- a/src/csharp/Grpc.Core.Tests/ClientServerTest.cs +++ b/src/csharp/Grpc.Core.Tests/ClientServerTest.cs @@ -73,12 +73,6 @@ namespace Grpc.Core.Tests Server server; Channel channel; - [TestFixtureSetUp] - public void InitClass() - { - GrpcEnvironment.Initialize(); - } - [SetUp] public void Init() { diff --git a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj index 92e28b7d74..927954c448 100644 --- a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj +++ b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj @@ -37,6 +37,9 @@ </Reference> </ItemGroup> <ItemGroup> + <Compile Include="..\Grpc.Core\Version.cs"> + <Link>Version.cs</Link> + </Compile> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="ClientServerTest.cs" /> <Compile Include="ServerTest.cs" /> @@ -63,4 +66,4 @@ <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" /> </ItemGroup> <ItemGroup /> -</Project> +</Project>
\ No newline at end of file diff --git a/src/csharp/Grpc.Core.Tests/GrpcEnvironmentTest.cs b/src/csharp/Grpc.Core.Tests/GrpcEnvironmentTest.cs index 6a132a5b22..9ae12776f3 100644 --- a/src/csharp/Grpc.Core.Tests/GrpcEnvironmentTest.cs +++ b/src/csharp/Grpc.Core.Tests/GrpcEnvironmentTest.cs @@ -43,16 +43,17 @@ namespace Grpc.Core.Tests [Test] public void InitializeAndShutdownGrpcEnvironment() { - GrpcEnvironment.Initialize(); - Assert.IsNotNull(GrpcEnvironment.ThreadPool.CompletionQueue); + var env = GrpcEnvironment.GetInstance(); + Assert.IsNotNull(env.CompletionQueue); GrpcEnvironment.Shutdown(); } [Test] public void SubsequentInvocations() { - GrpcEnvironment.Initialize(); - GrpcEnvironment.Initialize(); + var env1 = GrpcEnvironment.GetInstance(); + var env2 = GrpcEnvironment.GetInstance(); + Assert.IsTrue(object.ReferenceEquals(env1, env2)); GrpcEnvironment.Shutdown(); GrpcEnvironment.Shutdown(); } @@ -60,15 +61,13 @@ namespace Grpc.Core.Tests [Test] public void InitializeAfterShutdown() { - GrpcEnvironment.Initialize(); - var tp1 = GrpcEnvironment.ThreadPool; + var env1 = GrpcEnvironment.GetInstance(); GrpcEnvironment.Shutdown(); - GrpcEnvironment.Initialize(); - var tp2 = GrpcEnvironment.ThreadPool; + var env2 = GrpcEnvironment.GetInstance(); GrpcEnvironment.Shutdown(); - Assert.IsFalse(object.ReferenceEquals(tp1, tp2)); + Assert.IsFalse(object.ReferenceEquals(env1, env2)); } } } diff --git a/src/csharp/Grpc.Core.Tests/PInvokeTest.cs b/src/csharp/Grpc.Core.Tests/PInvokeTest.cs index 8b3c910251..714c2f7494 100644 --- a/src/csharp/Grpc.Core.Tests/PInvokeTest.cs +++ b/src/csharp/Grpc.Core.Tests/PInvokeTest.cs @@ -53,18 +53,6 @@ namespace Grpc.Core.Tests [DllImport("grpc_csharp_ext.dll")] static extern IntPtr grpcsharp_test_nop(IntPtr ptr); - [TestFixtureSetUp] - public void Init() - { - GrpcEnvironment.Initialize(); - } - - [TestFixtureTearDown] - public void Cleanup() - { - GrpcEnvironment.Shutdown(); - } - /// <summary> /// (~1.26us .NET Windows) /// </summary> diff --git a/src/csharp/Grpc.Core.Tests/Properties/AssemblyInfo.cs b/src/csharp/Grpc.Core.Tests/Properties/AssemblyInfo.cs index d5fffb8b18..c2e5e81e91 100644 --- a/src/csharp/Grpc.Core.Tests/Properties/AssemblyInfo.cs +++ b/src/csharp/Grpc.Core.Tests/Properties/AssemblyInfo.cs @@ -9,4 +9,3 @@ using System.Runtime.CompilerServices; [assembly: AssemblyCopyright("Google Inc. All rights reserved.")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("0.6.*")] diff --git a/src/csharp/Grpc.Core.Tests/ServerTest.cs b/src/csharp/Grpc.Core.Tests/ServerTest.cs index 02c773c9cc..1119aa370e 100644 --- a/src/csharp/Grpc.Core.Tests/ServerTest.cs +++ b/src/csharp/Grpc.Core.Tests/ServerTest.cs @@ -44,13 +44,10 @@ namespace Grpc.Core.Tests [Test] public void StartAndShutdownServer() { - GrpcEnvironment.Initialize(); - Server server = new Server(); server.AddListeningPort("localhost", Server.PickUnusedPort); server.Start(); server.ShutdownAsync().Wait(); - GrpcEnvironment.Shutdown(); } } diff --git a/src/csharp/Grpc.Core/Calls.cs b/src/csharp/Grpc.Core/Calls.cs index 9f8baac684..750282258f 100644 --- a/src/csharp/Grpc.Core/Calls.cs +++ b/src/csharp/Grpc.Core/Calls.cs @@ -58,7 +58,7 @@ namespace Grpc.Core where TResponse : class { var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestMarshaller.Serializer, call.ResponseMarshaller.Deserializer); - asyncCall.Initialize(call.Channel, GetCompletionQueue(), call.Name); + asyncCall.Initialize(call.Channel, call.Channel.CompletionQueue, call.Name); var asyncResult = asyncCall.UnaryCallAsync(req, call.Headers); RegisterCancellationCallback(asyncCall, token); return await asyncResult; @@ -69,7 +69,7 @@ namespace Grpc.Core where TResponse : class { var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestMarshaller.Serializer, call.ResponseMarshaller.Deserializer); - asyncCall.Initialize(call.Channel, GetCompletionQueue(), call.Name); + asyncCall.Initialize(call.Channel, call.Channel.CompletionQueue, call.Name); asyncCall.StartServerStreamingCall(req, call.Headers); RegisterCancellationCallback(asyncCall, token); var responseStream = new ClientResponseStream<TRequest, TResponse>(asyncCall); @@ -81,7 +81,7 @@ namespace Grpc.Core where TResponse : class { var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestMarshaller.Serializer, call.ResponseMarshaller.Deserializer); - asyncCall.Initialize(call.Channel, GetCompletionQueue(), call.Name); + asyncCall.Initialize(call.Channel, call.Channel.CompletionQueue, call.Name); var resultTask = asyncCall.ClientStreamingCallAsync(call.Headers); RegisterCancellationCallback(asyncCall, token); var requestStream = new ClientRequestStream<TRequest, TResponse>(asyncCall); @@ -93,7 +93,7 @@ namespace Grpc.Core where TResponse : class { var asyncCall = new AsyncCall<TRequest, TResponse>(call.RequestMarshaller.Serializer, call.ResponseMarshaller.Deserializer); - asyncCall.Initialize(call.Channel, GetCompletionQueue(), call.Name); + asyncCall.Initialize(call.Channel, call.Channel.CompletionQueue, call.Name); asyncCall.StartDuplexStreamingCall(call.Headers); RegisterCancellationCallback(asyncCall, token); var requestStream = new ClientRequestStream<TRequest, TResponse>(asyncCall); @@ -108,13 +108,5 @@ namespace Grpc.Core token.Register(() => asyncCall.Cancel()); } } - - /// <summary> - /// Gets shared completion queue used for async calls. - /// </summary> - private static CompletionQueueSafeHandle GetCompletionQueue() - { - return GrpcEnvironment.ThreadPool.CompletionQueue; - } } } diff --git a/src/csharp/Grpc.Core/Channel.cs b/src/csharp/Grpc.Core/Channel.cs index d6bfbb7bc4..5baf260003 100644 --- a/src/csharp/Grpc.Core/Channel.cs +++ b/src/csharp/Grpc.Core/Channel.cs @@ -42,8 +42,10 @@ namespace Grpc.Core /// </summary> public class Channel : IDisposable { + readonly GrpcEnvironment environment; readonly ChannelSafeHandle handle; readonly string target; + bool disposed; /// <summary> /// Creates a channel that connects to a specific host. @@ -54,6 +56,7 @@ namespace Grpc.Core /// <param name="options">Channel options.</param> public Channel(string host, Credentials credentials = null, IEnumerable<ChannelOption> options = null) { + this.environment = GrpcEnvironment.GetInstance(); using (ChannelArgsSafeHandle nativeChannelArgs = ChannelOptions.CreateChannelArgs(options)) { if (credentials != null) @@ -105,10 +108,35 @@ namespace Grpc.Core } } + internal CompletionQueueSafeHandle CompletionQueue + { + get + { + return this.environment.CompletionQueue; + } + } + + internal CompletionRegistry CompletionRegistry + { + get + { + return this.environment.CompletionRegistry; + } + } + + internal GrpcEnvironment Environment + { + get + { + return this.environment; + } + } + protected virtual void Dispose(bool disposing) { - if (handle != null && !handle.IsInvalid) + if (disposing && handle != null && !disposed) { + disposed = true; handle.Dispose(); } } diff --git a/src/csharp/Grpc.Core/Grpc.Core.csproj b/src/csharp/Grpc.Core/Grpc.Core.csproj index 0e67da3245..cde42c3b7e 100644 --- a/src/csharp/Grpc.Core/Grpc.Core.csproj +++ b/src/csharp/Grpc.Core/Grpc.Core.csproj @@ -49,6 +49,7 @@ <Compile Include="IAsyncStreamWriter.cs" /> <Compile Include="IAsyncStreamReader.cs" /> <Compile Include="Internal\GrpcLog.cs" /> + <Compile Include="Version.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="RpcException.cs" /> <Compile Include="Calls.cs" /> @@ -104,6 +105,7 @@ <Compile Include="ChannelOptions.cs" /> </ItemGroup> <ItemGroup> + <None Include="Grpc.Core.nuspec" /> <None Include="packages.config" /> </ItemGroup> <Choose> diff --git a/src/csharp/Grpc.Core/Grpc.Core.nuspec b/src/csharp/Grpc.Core/Grpc.Core.nuspec index 457983532a..5ace6dcf89 100644 --- a/src/csharp/Grpc.Core/Grpc.Core.nuspec +++ b/src/csharp/Grpc.Core/Grpc.Core.nuspec @@ -5,19 +5,19 @@ <title>gRPC C# Core</title> <summary>Core C# implementation of gRPC - an RPC library and framework</summary> <description>Core C# implementation of gRPC - an RPC library and framework. See project site for more info.</description> - <version>0.6.0</version> + <version>$version$</version> <authors>Google Inc.</authors> <owners>grpc-packages</owners> <licenseUrl>https://github.com/grpc/grpc/blob/master/LICENSE</licenseUrl> <projectUrl>https://github.com/grpc/grpc</projectUrl> <requireLicenseAcceptance>false</requireLicenseAcceptance> - <releaseNotes>Release 0.6.0 of gRPC C#</releaseNotes> + <releaseNotes>Release $version$ of gRPC C#</releaseNotes> <copyright>Copyright 2015, Google Inc.</copyright> <tags>gRPC RPC Protocol HTTP/2</tags> <dependencies> <dependency id="System.Collections.Immutable" version="1.1.36" /> <dependency id="Ix-Async" version="1.2.3" /> - <dependency id="grpc.native.csharp_ext" version="0.10.0" /> + <dependency id="grpc.native.csharp_ext" version="$GrpcNativeCsharpExtVersion$" /> </dependencies> </metadata> <files> diff --git a/src/csharp/Grpc.Core/GrpcEnvironment.cs b/src/csharp/Grpc.Core/GrpcEnvironment.cs index 30ff289714..47d1651aab 100644 --- a/src/csharp/Grpc.Core/GrpcEnvironment.cs +++ b/src/csharp/Grpc.Core/GrpcEnvironment.cs @@ -33,7 +33,9 @@ using System; using System.Runtime.InteropServices; +using System.Threading.Tasks; using Grpc.Core.Internal; +using Grpc.Core.Utils; namespace Grpc.Core { @@ -51,20 +53,18 @@ namespace Grpc.Core static extern void grpcsharp_shutdown(); static object staticLock = new object(); - static volatile GrpcEnvironment instance; + static GrpcEnvironment instance; readonly GrpcThreadPool threadPool; readonly CompletionRegistry completionRegistry; + readonly DebugStats debugStats = new DebugStats(); bool isClosed; /// <summary> - /// Makes sure GRPC environment is initialized. Subsequent invocations don't have any - /// effect unless you call Shutdown first. - /// Although normal use cases assume you will call this just once in your application's - /// lifetime (and call Shutdown once you're done), for the sake of easier testing it's - /// allowed to initialize the environment again after it has been successfully shutdown. + /// Returns an instance of initialized gRPC environment. + /// Subsequent invocations return the same instance unless Shutdown has been called first. /// </summary> - public static void Initialize() + internal static GrpcEnvironment GetInstance() { lock (staticLock) { @@ -72,12 +72,13 @@ namespace Grpc.Core { instance = new GrpcEnvironment(); } + return instance; } } /// <summary> - /// Shuts down the GRPC environment if it was initialized before. - /// Repeated invocations have no effect. + /// Shuts down the gRPC environment if it was initialized before. + /// Blocks until the environment has been fully shutdown. /// </summary> public static void Shutdown() { @@ -87,50 +88,55 @@ namespace Grpc.Core { instance.Close(); instance = null; - - CheckDebugStats(); } } } - internal static GrpcThreadPool ThreadPool + /// <summary> + /// Creates gRPC environment. + /// </summary> + private GrpcEnvironment() + { + GrpcLog.RedirectNativeLogs(Console.Error); + grpcsharp_init(); + completionRegistry = new CompletionRegistry(this); + threadPool = new GrpcThreadPool(this, THREAD_POOL_SIZE); + threadPool.Start(); + // TODO: use proper logging here + Console.WriteLine("GRPC initialized."); + } + + /// <summary> + /// Gets the completion registry used by this gRPC environment. + /// </summary> + internal CompletionRegistry CompletionRegistry { get { - var inst = instance; - if (inst == null) - { - throw new InvalidOperationException("GRPC environment not initialized"); - } - return inst.threadPool; + return this.completionRegistry; } } - internal static CompletionRegistry CompletionRegistry + /// <summary> + /// Gets the completion queue used by this gRPC environment. + /// </summary> + internal CompletionQueueSafeHandle CompletionQueue { get { - var inst = instance; - if (inst == null) - { - throw new InvalidOperationException("GRPC environment not initialized"); - } - return inst.completionRegistry; + return this.threadPool.CompletionQueue; } } /// <summary> - /// Creates gRPC environment. + /// Gets the completion queue used by this gRPC environment. /// </summary> - private GrpcEnvironment() + internal DebugStats DebugStats { - GrpcLog.RedirectNativeLogs(Console.Error); - grpcsharp_init(); - completionRegistry = new CompletionRegistry(); - threadPool = new GrpcThreadPool(THREAD_POOL_SIZE); - threadPool.Start(); - // TODO: use proper logging here - Console.WriteLine("GRPC initialized."); + get + { + return this.debugStats; + } } /// <summary> @@ -146,32 +152,28 @@ namespace Grpc.Core grpcsharp_shutdown(); isClosed = true; + debugStats.CheckOK(); + // TODO: use proper logging here Console.WriteLine("GRPC shutdown."); } - private static void CheckDebugStats() + /// <summary> + /// Shuts down this environment asynchronously. + /// </summary> + private Task CloseAsync() { - var remainingClientCalls = DebugStats.ActiveClientCalls.Count; - if (remainingClientCalls != 0) - { - DebugWarning(string.Format("Detected {0} client calls that weren't disposed properly.", remainingClientCalls)); - } - var remainingServerCalls = DebugStats.ActiveServerCalls.Count; - if (remainingServerCalls != 0) - { - DebugWarning(string.Format("Detected {0} server calls that weren't disposed properly.", remainingServerCalls)); - } - var pendingBatchCompletions = DebugStats.PendingBatchCompletions.Count; - if (pendingBatchCompletions != 0) + return Task.Run(() => { - DebugWarning(string.Format("Detected {0} pending batch completions.", pendingBatchCompletions)); - } - } - - private static void DebugWarning(string message) - { - throw new Exception("Shutdown check: " + message); + try + { + Close(); + } + catch (Exception e) + { + Console.WriteLine("Error occured while shutting down GrpcEnvironment: " + e); + } + }); } } } diff --git a/src/csharp/Grpc.Core/Internal/AsyncCall.cs b/src/csharp/Grpc.Core/Internal/AsyncCall.cs index d350f45da6..24b75d1668 100644 --- a/src/csharp/Grpc.Core/Internal/AsyncCall.cs +++ b/src/csharp/Grpc.Core/Internal/AsyncCall.cs @@ -47,6 +47,8 @@ namespace Grpc.Core.Internal /// </summary> internal class AsyncCall<TRequest, TResponse> : AsyncCallBase<TRequest, TResponse> { + Channel channel; + // Completion of a pending unary response if not null. TaskCompletionSource<TResponse> unaryResponseTcs; @@ -61,8 +63,9 @@ namespace Grpc.Core.Internal public void Initialize(Channel channel, CompletionQueueSafeHandle cq, string methodName) { - var call = CallSafeHandle.Create(channel.Handle, cq, methodName, channel.Target, Timespec.InfFuture); - DebugStats.ActiveClientCalls.Increment(); + this.channel = channel; + var call = CallSafeHandle.Create(channel.Handle, channel.CompletionRegistry, cq, methodName, channel.Target, Timespec.InfFuture); + channel.Environment.DebugStats.ActiveClientCalls.Increment(); InitializeInternal(call); } @@ -277,7 +280,7 @@ namespace Grpc.Core.Internal protected override void OnReleaseResources() { - DebugStats.ActiveClientCalls.Decrement(); + channel.Environment.DebugStats.ActiveClientCalls.Decrement(); } /// <summary> diff --git a/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs b/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs index 4f510ba40a..309067ea9d 100644 --- a/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs +++ b/src/csharp/Grpc.Core/Internal/AsyncCallServer.cs @@ -48,14 +48,17 @@ namespace Grpc.Core.Internal internal class AsyncCallServer<TRequest, TResponse> : AsyncCallBase<TResponse, TRequest> { readonly TaskCompletionSource<object> finishedServersideTcs = new TaskCompletionSource<object>(); + readonly GrpcEnvironment environment; - public AsyncCallServer(Func<TResponse, byte[]> serializer, Func<byte[], TRequest> deserializer) : base(serializer, deserializer) + public AsyncCallServer(Func<TResponse, byte[]> serializer, Func<byte[], TRequest> deserializer, GrpcEnvironment environment) : base(serializer, deserializer) { + this.environment = Preconditions.CheckNotNull(environment); } public void Initialize(CallSafeHandle call) { - DebugStats.ActiveServerCalls.Increment(); + call.SetCompletionRegistry(environment.CompletionRegistry); + environment.DebugStats.ActiveServerCalls.Increment(); InitializeInternal(call); } @@ -114,7 +117,7 @@ namespace Grpc.Core.Internal protected override void OnReleaseResources() { - DebugStats.ActiveServerCalls.Decrement(); + environment.DebugStats.ActiveServerCalls.Decrement(); } /// <summary> diff --git a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs index ef92b44402..3b246ac01b 100644 --- a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs +++ b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs @@ -43,6 +43,7 @@ namespace Grpc.Core.Internal internal class CallSafeHandle : SafeHandleZeroIsInvalid { const uint GRPC_WRITE_BUFFER_HINT = 1; + CompletionRegistry completionRegistry; [DllImport("grpc_csharp_ext.dll")] static extern CallSafeHandle grpcsharp_channel_create_call(ChannelSafeHandle channel, CompletionQueueSafeHandle cq, string method, string host, Timespec deadline); @@ -97,15 +98,22 @@ namespace Grpc.Core.Internal { } - public static CallSafeHandle Create(ChannelSafeHandle channel, CompletionQueueSafeHandle cq, string method, string host, Timespec deadline) + public static CallSafeHandle Create(ChannelSafeHandle channel, CompletionRegistry registry, CompletionQueueSafeHandle cq, string method, string host, Timespec deadline) { - return grpcsharp_channel_create_call(channel, cq, method, host, deadline); + var result = grpcsharp_channel_create_call(channel, cq, method, host, deadline); + result.SetCompletionRegistry(registry); + return result; + } + + public void SetCompletionRegistry(CompletionRegistry completionRegistry) + { + this.completionRegistry = completionRegistry; } public void StartUnary(byte[] payload, BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray) { var ctx = BatchContextSafeHandle.Create(); - GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback); + completionRegistry.RegisterBatchCompletion(ctx, callback); grpcsharp_call_start_unary(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray) .CheckOk(); } @@ -119,56 +127,56 @@ namespace Grpc.Core.Internal public void StartClientStreaming(BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray) { var ctx = BatchContextSafeHandle.Create(); - GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback); + completionRegistry.RegisterBatchCompletion(ctx, callback); grpcsharp_call_start_client_streaming(this, ctx, metadataArray).CheckOk(); } public void StartServerStreaming(byte[] payload, BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray) { var ctx = BatchContextSafeHandle.Create(); - GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback); + completionRegistry.RegisterBatchCompletion(ctx, callback); grpcsharp_call_start_server_streaming(this, ctx, payload, new UIntPtr((ulong)payload.Length), metadataArray).CheckOk(); } public void StartDuplexStreaming(BatchCompletionDelegate callback, MetadataArraySafeHandle metadataArray) { var ctx = BatchContextSafeHandle.Create(); - GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback); + completionRegistry.RegisterBatchCompletion(ctx, callback); grpcsharp_call_start_duplex_streaming(this, ctx, metadataArray).CheckOk(); } public void StartSendMessage(byte[] payload, BatchCompletionDelegate callback) { var ctx = BatchContextSafeHandle.Create(); - GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback); + completionRegistry.RegisterBatchCompletion(ctx, callback); grpcsharp_call_send_message(this, ctx, payload, new UIntPtr((ulong)payload.Length)).CheckOk(); } public void StartSendCloseFromClient(BatchCompletionDelegate callback) { var ctx = BatchContextSafeHandle.Create(); - GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback); + completionRegistry.RegisterBatchCompletion(ctx, callback); grpcsharp_call_send_close_from_client(this, ctx).CheckOk(); } public void StartSendStatusFromServer(Status status, BatchCompletionDelegate callback) { var ctx = BatchContextSafeHandle.Create(); - GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback); + completionRegistry.RegisterBatchCompletion(ctx, callback); grpcsharp_call_send_status_from_server(this, ctx, status.StatusCode, status.Detail).CheckOk(); } public void StartReceiveMessage(BatchCompletionDelegate callback) { var ctx = BatchContextSafeHandle.Create(); - GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback); + completionRegistry.RegisterBatchCompletion(ctx, callback); grpcsharp_call_recv_message(this, ctx).CheckOk(); } public void StartServerSide(BatchCompletionDelegate callback) { var ctx = BatchContextSafeHandle.Create(); - GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback); + completionRegistry.RegisterBatchCompletion(ctx, callback); grpcsharp_call_start_serverside(this, ctx).CheckOk(); } diff --git a/src/csharp/Grpc.Core/Internal/CompletionRegistry.cs b/src/csharp/Grpc.Core/Internal/CompletionRegistry.cs index 80f006ae50..f6d8aa0600 100644 --- a/src/csharp/Grpc.Core/Internal/CompletionRegistry.cs +++ b/src/csharp/Grpc.Core/Internal/CompletionRegistry.cs @@ -45,11 +45,17 @@ namespace Grpc.Core.Internal internal class CompletionRegistry { - readonly ConcurrentDictionary<IntPtr, OpCompletionDelegate> dict = new ConcurrentDictionary<IntPtr, OpCompletionDelegate>(); + readonly GrpcEnvironment environment; + readonly ConcurrentDictionary<IntPtr, OpCompletionDelegate> dict = new ConcurrentDictionary<IntPtr, OpCompletionDelegate>(); + + public CompletionRegistry(GrpcEnvironment environment) + { + this.environment = environment; + } public void Register(IntPtr key, OpCompletionDelegate callback) { - DebugStats.PendingBatchCompletions.Increment(); + environment.DebugStats.PendingBatchCompletions.Increment(); Preconditions.CheckState(dict.TryAdd(key, callback)); } @@ -63,7 +69,7 @@ namespace Grpc.Core.Internal { OpCompletionDelegate value; Preconditions.CheckState(dict.TryRemove(key, out value)); - DebugStats.PendingBatchCompletions.Decrement(); + environment.DebugStats.PendingBatchCompletions.Decrement(); return value; } diff --git a/src/csharp/Grpc.Core/Internal/DebugStats.cs b/src/csharp/Grpc.Core/Internal/DebugStats.cs index ef9d9afe11..8793450ff3 100644 --- a/src/csharp/Grpc.Core/Internal/DebugStats.cs +++ b/src/csharp/Grpc.Core/Internal/DebugStats.cs @@ -36,12 +36,39 @@ using System.Threading; namespace Grpc.Core.Internal { - internal static class DebugStats + internal class DebugStats { - public static readonly AtomicCounter ActiveClientCalls = new AtomicCounter(); + public readonly AtomicCounter ActiveClientCalls = new AtomicCounter(); - public static readonly AtomicCounter ActiveServerCalls = new AtomicCounter(); + public readonly AtomicCounter ActiveServerCalls = new AtomicCounter(); - public static readonly AtomicCounter PendingBatchCompletions = new AtomicCounter(); + public readonly AtomicCounter PendingBatchCompletions = new AtomicCounter(); + + /// <summary> + /// Checks the debug stats and take action for any inconsistency found. + /// </summary> + public void CheckOK() + { + var remainingClientCalls = ActiveClientCalls.Count; + if (remainingClientCalls != 0) + { + DebugWarning(string.Format("Detected {0} client calls that weren't disposed properly.", remainingClientCalls)); + } + var remainingServerCalls = ActiveServerCalls.Count; + if (remainingServerCalls != 0) + { + DebugWarning(string.Format("Detected {0} server calls that weren't disposed properly.", remainingServerCalls)); + } + var pendingBatchCompletions = PendingBatchCompletions.Count; + if (pendingBatchCompletions != 0) + { + DebugWarning(string.Format("Detected {0} pending batch completions.", pendingBatchCompletions)); + } + } + + private void DebugWarning(string message) + { + throw new Exception("Shutdown check: " + message); + } } } diff --git a/src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs b/src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs index 89b44a4e2b..b77e893044 100644 --- a/src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs +++ b/src/csharp/Grpc.Core/Internal/GrpcThreadPool.cs @@ -45,14 +45,16 @@ namespace Grpc.Core.Internal /// </summary> internal class GrpcThreadPool { + readonly GrpcEnvironment environment; readonly object myLock = new object(); readonly List<Thread> threads = new List<Thread>(); readonly int poolSize; CompletionQueueSafeHandle cq; - public GrpcThreadPool(int poolSize) + public GrpcThreadPool(GrpcEnvironment environment, int poolSize) { + this.environment = environment; this.poolSize = poolSize; } @@ -80,7 +82,7 @@ namespace Grpc.Core.Internal { cq.Shutdown(); - Console.WriteLine("Waiting for GPRC threads to finish."); + Console.WriteLine("Waiting for GRPC threads to finish."); foreach (var thread in threads) { thread.Join(); @@ -122,7 +124,7 @@ namespace Grpc.Core.Internal IntPtr tag = ev.tag; try { - var callback = GrpcEnvironment.CompletionRegistry.Extract(tag); + var callback = environment.CompletionRegistry.Extract(tag); callback(success); } catch (Exception e) diff --git a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs index c0e5bae13f..594e46b159 100644 --- a/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs +++ b/src/csharp/Grpc.Core/Internal/ServerCallHandler.cs @@ -42,7 +42,7 @@ namespace Grpc.Core.Internal { internal interface IServerCallHandler { - Task HandleCall(string methodName, CallSafeHandle call, CompletionQueueSafeHandle cq); + Task HandleCall(string methodName, CallSafeHandle call, GrpcEnvironment environment); } internal class UnaryServerCallHandler<TRequest, TResponse> : IServerCallHandler @@ -58,11 +58,12 @@ namespace Grpc.Core.Internal this.handler = handler; } - public async Task HandleCall(string methodName, CallSafeHandle call, CompletionQueueSafeHandle cq) + public async Task HandleCall(string methodName, CallSafeHandle call, GrpcEnvironment environment) { var asyncCall = new AsyncCallServer<TRequest, TResponse>( method.ResponseMarshaller.Serializer, - method.RequestMarshaller.Deserializer); + method.RequestMarshaller.Deserializer, + environment); asyncCall.Initialize(call); var finishedTask = asyncCall.ServerSideCallAsync(); @@ -110,11 +111,12 @@ namespace Grpc.Core.Internal this.handler = handler; } - public async Task HandleCall(string methodName, CallSafeHandle call, CompletionQueueSafeHandle cq) + public async Task HandleCall(string methodName, CallSafeHandle call, GrpcEnvironment environment) { var asyncCall = new AsyncCallServer<TRequest, TResponse>( method.ResponseMarshaller.Serializer, - method.RequestMarshaller.Deserializer); + method.RequestMarshaller.Deserializer, + environment); asyncCall.Initialize(call); var finishedTask = asyncCall.ServerSideCallAsync(); @@ -163,11 +165,12 @@ namespace Grpc.Core.Internal this.handler = handler; } - public async Task HandleCall(string methodName, CallSafeHandle call, CompletionQueueSafeHandle cq) + public async Task HandleCall(string methodName, CallSafeHandle call, GrpcEnvironment environment) { var asyncCall = new AsyncCallServer<TRequest, TResponse>( method.ResponseMarshaller.Serializer, - method.RequestMarshaller.Deserializer); + method.RequestMarshaller.Deserializer, + environment); asyncCall.Initialize(call); var finishedTask = asyncCall.ServerSideCallAsync(); @@ -219,11 +222,12 @@ namespace Grpc.Core.Internal this.handler = handler; } - public async Task HandleCall(string methodName, CallSafeHandle call, CompletionQueueSafeHandle cq) + public async Task HandleCall(string methodName, CallSafeHandle call, GrpcEnvironment environment) { var asyncCall = new AsyncCallServer<TRequest, TResponse>( method.ResponseMarshaller.Serializer, - method.RequestMarshaller.Deserializer); + method.RequestMarshaller.Deserializer, + environment); asyncCall.Initialize(call); var finishedTask = asyncCall.ServerSideCallAsync(); @@ -255,11 +259,11 @@ namespace Grpc.Core.Internal internal class NoSuchMethodCallHandler : IServerCallHandler { - public async Task HandleCall(string methodName, CallSafeHandle call, CompletionQueueSafeHandle cq) + public async Task HandleCall(string methodName, CallSafeHandle call, GrpcEnvironment environment) { // We don't care about the payload type here. var asyncCall = new AsyncCallServer<byte[], byte[]>( - (payload) => payload, (payload) => payload); + (payload) => payload, (payload) => payload, environment); asyncCall.Initialize(call); var finishedTask = asyncCall.ServerSideCallAsync(); diff --git a/src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs b/src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs index 83dbb910aa..9e1170e6dd 100644 --- a/src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs +++ b/src/csharp/Grpc.Core/Internal/ServerSafeHandle.cs @@ -91,19 +91,19 @@ namespace Grpc.Core.Internal { grpcsharp_server_start(this); } - - public void ShutdownAndNotify(CompletionQueueSafeHandle cq, BatchCompletionDelegate callback) + + public void ShutdownAndNotify(BatchCompletionDelegate callback, GrpcEnvironment environment) { var ctx = BatchContextSafeHandle.Create(); - GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback); - grpcsharp_server_shutdown_and_notify_callback(this, cq, ctx); + environment.CompletionRegistry.RegisterBatchCompletion(ctx, callback); + grpcsharp_server_shutdown_and_notify_callback(this, environment.CompletionQueue, ctx); } - public void RequestCall(CompletionQueueSafeHandle cq, BatchCompletionDelegate callback) + public void RequestCall(BatchCompletionDelegate callback, GrpcEnvironment environment) { var ctx = BatchContextSafeHandle.Create(); - GrpcEnvironment.CompletionRegistry.RegisterBatchCompletion(ctx, callback); - grpcsharp_server_request_call(this, cq, ctx).CheckOk(); + environment.CompletionRegistry.RegisterBatchCompletion(ctx, callback); + grpcsharp_server_request_call(this, environment.CompletionQueue, ctx).CheckOk(); } protected override bool ReleaseHandle() diff --git a/src/csharp/Grpc.Core/Properties/AssemblyInfo.cs b/src/csharp/Grpc.Core/Properties/AssemblyInfo.cs index c57eef65aa..2b3d7530f2 100644 --- a/src/csharp/Grpc.Core/Properties/AssemblyInfo.cs +++ b/src/csharp/Grpc.Core/Properties/AssemblyInfo.cs @@ -9,6 +9,5 @@ using System.Runtime.CompilerServices; [assembly: AssemblyCopyright("Google Inc. All rights reserved.")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("0.6.*")] [assembly: InternalsVisibleTo("Grpc.Core.Tests")] diff --git a/src/csharp/Grpc.Core/Server.cs b/src/csharp/Grpc.Core/Server.cs index 8e818885d1..cbf77196cf 100644 --- a/src/csharp/Grpc.Core/Server.cs +++ b/src/csharp/Grpc.Core/Server.cs @@ -52,6 +52,7 @@ namespace Grpc.Core /// </summary> public const int PickUnusedPort = 0; + readonly GrpcEnvironment environment; readonly ServerSafeHandle handle; readonly object myLock = new object(); @@ -67,9 +68,10 @@ namespace Grpc.Core /// <param name="options">Channel options.</param> public Server(IEnumerable<ChannelOption> options = null) { + this.environment = GrpcEnvironment.GetInstance(); using (var channelArgs = ChannelOptions.CreateChannelArgs(options)) { - this.handle = ServerSafeHandle.NewServer(GetCompletionQueue(), channelArgs); + this.handle = ServerSafeHandle.NewServer(environment.CompletionQueue, channelArgs); } } @@ -144,7 +146,7 @@ namespace Grpc.Core shutdownRequested = true; } - handle.ShutdownAndNotify(GetCompletionQueue(), HandleServerShutdown); + handle.ShutdownAndNotify(HandleServerShutdown, environment); await shutdownTcs.Task; handle.Dispose(); } @@ -173,7 +175,7 @@ namespace Grpc.Core shutdownRequested = true; } - handle.ShutdownAndNotify(GetCompletionQueue(), HandleServerShutdown); + handle.ShutdownAndNotify(HandleServerShutdown, environment); handle.CancelAllCalls(); await shutdownTcs.Task; handle.Dispose(); @@ -208,7 +210,7 @@ namespace Grpc.Core { if (!shutdownRequested) { - handle.RequestCall(GetCompletionQueue(), HandleNewServerRpc); + handle.RequestCall(HandleNewServerRpc, environment); } } } @@ -225,7 +227,7 @@ namespace Grpc.Core { callHandler = new NoSuchMethodCallHandler(); } - await callHandler.HandleCall(method, call, GetCompletionQueue()); + await callHandler.HandleCall(method, call, environment); } catch (Exception e) { @@ -259,10 +261,5 @@ namespace Grpc.Core { shutdownTcs.SetResult(null); } - - private static CompletionQueueSafeHandle GetCompletionQueue() - { - return GrpcEnvironment.ThreadPool.CompletionQueue; - } } } diff --git a/src/csharp/Grpc.Core/Version.cs b/src/csharp/Grpc.Core/Version.cs new file mode 100644 index 0000000000..972f495bd7 --- /dev/null +++ b/src/csharp/Grpc.Core/Version.cs @@ -0,0 +1,6 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// The current version of gRPC C#. +[assembly: AssemblyVersion("0.6.0.*")] + diff --git a/src/csharp/Grpc.Examples.MathClient/Grpc.Examples.MathClient.csproj b/src/csharp/Grpc.Examples.MathClient/Grpc.Examples.MathClient.csproj index 19bb4347aa..5d5401593d 100644 --- a/src/csharp/Grpc.Examples.MathClient/Grpc.Examples.MathClient.csproj +++ b/src/csharp/Grpc.Examples.MathClient/Grpc.Examples.MathClient.csproj @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> @@ -35,6 +35,9 @@ <Reference Include="System" /> </ItemGroup> <ItemGroup> + <Compile Include="..\Grpc.Core\Version.cs"> + <Link>Version.cs</Link> + </Compile> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="MathClient.cs" /> </ItemGroup> @@ -49,4 +52,4 @@ <Name>Grpc.Examples</Name> </ProjectReference> </ItemGroup> -</Project> +</Project>
\ No newline at end of file diff --git a/src/csharp/Grpc.Examples.MathClient/MathClient.cs b/src/csharp/Grpc.Examples.MathClient/MathClient.cs index 360fe928dd..b763721460 100644 --- a/src/csharp/Grpc.Examples.MathClient/MathClient.cs +++ b/src/csharp/Grpc.Examples.MathClient/MathClient.cs @@ -39,8 +39,6 @@ namespace math { public static void Main(string[] args) { - GrpcEnvironment.Initialize(); - using (Channel channel = new Channel("127.0.0.1", 23456)) { Math.IMathClient stub = new Math.MathClient(channel); diff --git a/src/csharp/Grpc.Examples.MathClient/Properties/AssemblyInfo.cs b/src/csharp/Grpc.Examples.MathClient/Properties/AssemblyInfo.cs index a57c540215..0fb0dbd510 100644 --- a/src/csharp/Grpc.Examples.MathClient/Properties/AssemblyInfo.cs +++ b/src/csharp/Grpc.Examples.MathClient/Properties/AssemblyInfo.cs @@ -9,4 +9,3 @@ using System.Runtime.CompilerServices; [assembly: AssemblyCopyright("Google Inc. All rights reserved.")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("0.6.*")] diff --git a/src/csharp/Grpc.Examples.MathServer/Grpc.Examples.MathServer.csproj b/src/csharp/Grpc.Examples.MathServer/Grpc.Examples.MathServer.csproj index ba6586ee5e..677d87da20 100644 --- a/src/csharp/Grpc.Examples.MathServer/Grpc.Examples.MathServer.csproj +++ b/src/csharp/Grpc.Examples.MathServer/Grpc.Examples.MathServer.csproj @@ -35,6 +35,9 @@ <Reference Include="System" /> </ItemGroup> <ItemGroup> + <Compile Include="..\Grpc.Core\Version.cs"> + <Link>Version.cs</Link> + </Compile> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="MathServer.cs" /> </ItemGroup> diff --git a/src/csharp/Grpc.Examples.MathServer/MathServer.cs b/src/csharp/Grpc.Examples.MathServer/MathServer.cs index d05e3f2808..f440985112 100644 --- a/src/csharp/Grpc.Examples.MathServer/MathServer.cs +++ b/src/csharp/Grpc.Examples.MathServer/MathServer.cs @@ -42,8 +42,6 @@ namespace math { string host = "0.0.0.0"; - GrpcEnvironment.Initialize(); - Server server = new Server(); server.AddServiceDefinition(Math.BindService(new MathServiceImpl())); int port = server.AddListeningPort(host, 23456); diff --git a/src/csharp/Grpc.Examples.MathServer/Properties/AssemblyInfo.cs b/src/csharp/Grpc.Examples.MathServer/Properties/AssemblyInfo.cs index 6c772cb45c..63035b6c63 100644 --- a/src/csharp/Grpc.Examples.MathServer/Properties/AssemblyInfo.cs +++ b/src/csharp/Grpc.Examples.MathServer/Properties/AssemblyInfo.cs @@ -9,4 +9,3 @@ using System.Runtime.CompilerServices; [assembly: AssemblyCopyright("Google Inc. All rights reserved.")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("0.6.*")] diff --git a/src/csharp/Grpc.Examples.Tests/Grpc.Examples.Tests.csproj b/src/csharp/Grpc.Examples.Tests/Grpc.Examples.Tests.csproj index 6e84add42b..d59d7515d1 100644 --- a/src/csharp/Grpc.Examples.Tests/Grpc.Examples.Tests.csproj +++ b/src/csharp/Grpc.Examples.Tests/Grpc.Examples.Tests.csproj @@ -43,6 +43,9 @@ </Reference> </ItemGroup> <ItemGroup> + <Compile Include="..\Grpc.Core\Version.cs"> + <Link>Version.cs</Link> + </Compile> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="MathClientServerTests.cs" /> </ItemGroup> diff --git a/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs b/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs index aadd49f795..10dceb60aa 100644 --- a/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs +++ b/src/csharp/Grpc.Examples.Tests/MathClientServerTests.cs @@ -54,8 +54,6 @@ namespace math.Tests [TestFixtureSetUp] public void Init() { - GrpcEnvironment.Initialize(); - server = new Server(); server.AddServiceDefinition(Math.BindService(new MathServiceImpl())); int port = server.AddListeningPort(host, Server.PickUnusedPort); @@ -75,7 +73,6 @@ namespace math.Tests public void Cleanup() { channel.Dispose(); - server.ShutdownAsync().Wait(); GrpcEnvironment.Shutdown(); } diff --git a/src/csharp/Grpc.Examples.Tests/Properties/AssemblyInfo.cs b/src/csharp/Grpc.Examples.Tests/Properties/AssemblyInfo.cs index 4acaeaa438..846afb4616 100644 --- a/src/csharp/Grpc.Examples.Tests/Properties/AssemblyInfo.cs +++ b/src/csharp/Grpc.Examples.Tests/Properties/AssemblyInfo.cs @@ -9,4 +9,3 @@ using System.Runtime.CompilerServices; [assembly: AssemblyCopyright("Google Inc. All rights reserved.")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("0.6.*")] diff --git a/src/csharp/Grpc.Examples/Grpc.Examples.csproj b/src/csharp/Grpc.Examples/Grpc.Examples.csproj index 5ce490f403..eaf24a253c 100644 --- a/src/csharp/Grpc.Examples/Grpc.Examples.csproj +++ b/src/csharp/Grpc.Examples/Grpc.Examples.csproj @@ -40,6 +40,9 @@ </Reference> </ItemGroup> <ItemGroup> + <Compile Include="..\Grpc.Core\Version.cs"> + <Link>Version.cs</Link> + </Compile> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Math.cs" /> <Compile Include="MathGrpc.cs" /> diff --git a/src/csharp/Grpc.Examples/Properties/AssemblyInfo.cs b/src/csharp/Grpc.Examples/Properties/AssemblyInfo.cs index 60a7aaea13..92111389cb 100644 --- a/src/csharp/Grpc.Examples/Properties/AssemblyInfo.cs +++ b/src/csharp/Grpc.Examples/Properties/AssemblyInfo.cs @@ -9,4 +9,3 @@ using System.Runtime.CompilerServices; [assembly: AssemblyCopyright("Google Inc. All rights reserved.")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("0.6.*")] diff --git a/src/csharp/Grpc.HealthCheck.Tests/.gitignore b/src/csharp/Grpc.HealthCheck.Tests/.gitignore new file mode 100644 index 0000000000..1746e3269e --- /dev/null +++ b/src/csharp/Grpc.HealthCheck.Tests/.gitignore @@ -0,0 +1,2 @@ +bin +obj diff --git a/src/csharp/Grpc.HealthCheck.Tests/Grpc.HealthCheck.Tests.csproj b/src/csharp/Grpc.HealthCheck.Tests/Grpc.HealthCheck.Tests.csproj new file mode 100644 index 0000000000..72e110302b --- /dev/null +++ b/src/csharp/Grpc.HealthCheck.Tests/Grpc.HealthCheck.Tests.csproj @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{F8C6D937-C44B-4EE3-A431-B0FBAEACE47D}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>Grpc.HealthCheck.Tests</RootNamespace> + <AssemblyName>Grpc.HealthCheck.Tests</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="Google.ProtocolBuffers"> + <HintPath>..\packages\Google.ProtocolBuffers.2.4.1.555\lib\net40\Google.ProtocolBuffers.dll</HintPath> + </Reference> + <Reference Include="Google.ProtocolBuffers.Serialization"> + <HintPath>..\packages\Google.ProtocolBuffers.2.4.1.555\lib\net40\Google.ProtocolBuffers.Serialization.dll</HintPath> + </Reference> + <Reference Include="nunit.framework"> + <HintPath>..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="..\Grpc.Core\Version.cs"> + <Link>Version.cs</Link> + </Compile> + <Compile Include="HealthServiceImplTest.cs" /> + <Compile Include="HealthClientServerTest.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\Grpc.Core\Grpc.Core.csproj"> + <Project>{ccc4440e-49f7-4790-b0af-feabb0837ae7}</Project> + <Name>Grpc.Core</Name> + </ProjectReference> + <ProjectReference Include="..\Grpc.HealthCheck\Grpc.HealthCheck.csproj"> + <Project>{aa5e328a-8835-49d7-98ed-c29f2b3049f0}</Project> + <Name>Grpc.HealthCheck</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <None Include="packages.config" /> + </ItemGroup> + <ItemGroup> + <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" /> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project>
\ No newline at end of file diff --git a/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs b/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs new file mode 100644 index 0000000000..0ac1add8e4 --- /dev/null +++ b/src/csharp/Grpc.HealthCheck.Tests/HealthClientServerTest.cs @@ -0,0 +1,97 @@ +#region Copyright notice and license +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Grpc.Core; +using Grpc.Health.V1Alpha; +using NUnit.Framework; + +namespace Grpc.HealthCheck.Tests +{ + /// <summary> + /// Health client talks to health server. + /// </summary> + public class HealthClientServerTest + { + const string Host = "localhost"; + Server server; + Channel channel; + Grpc.Health.V1Alpha.Health.IHealthClient client; + Grpc.HealthCheck.HealthServiceImpl serviceImpl; + + [TestFixtureSetUp] + public void Init() + { + serviceImpl = new HealthServiceImpl(); + + server = new Server(); + server.AddServiceDefinition(Grpc.Health.V1Alpha.Health.BindService(serviceImpl)); + int port = server.AddListeningPort(Host, Server.PickUnusedPort); + server.Start(); + channel = new Channel(Host, port); + + client = Grpc.Health.V1Alpha.Health.NewStub(channel); + } + + [TestFixtureTearDown] + public void Cleanup() + { + channel.Dispose(); + + server.ShutdownAsync().Wait(); + GrpcEnvironment.Shutdown(); + } + + [Test] + public void ServiceIsRunning() + { + serviceImpl.SetStatus("", "", HealthCheckResponse.Types.ServingStatus.SERVING); + + var response = client.Check(HealthCheckRequest.CreateBuilder().SetHost("").SetService("").Build()); + Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.SERVING, response.Status); + } + + [Test] + public void ServiceDoesntExist() + { + // TODO(jtattermusch): currently, this returns wrong status code, because we don't enable sending arbitrary status code from + // server handlers yet. + Assert.Throws(typeof(RpcException), () => client.Check(HealthCheckRequest.CreateBuilder().SetHost("").SetService("nonexistent.service").Build())); + } + + // TODO(jtattermusch): add test with timeout once timeouts are supported + } +}
\ No newline at end of file diff --git a/src/csharp/Grpc.HealthCheck.Tests/HealthServiceImplTest.cs b/src/csharp/Grpc.HealthCheck.Tests/HealthServiceImplTest.cs new file mode 100644 index 0000000000..9b7c4f2140 --- /dev/null +++ b/src/csharp/Grpc.HealthCheck.Tests/HealthServiceImplTest.cs @@ -0,0 +1,107 @@ +#region Copyright notice and license +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Grpc.Core; +using Grpc.Health.V1Alpha; +using NUnit.Framework; + +namespace Grpc.HealthCheck.Tests +{ + /// <summary> + /// Tests for HealthCheckServiceImpl + /// </summary> + public class HealthServiceImplTest + { + [Test] + public void SetStatus() + { + var impl = new HealthServiceImpl(); + impl.SetStatus("", "", HealthCheckResponse.Types.ServingStatus.SERVING); + Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.SERVING, GetStatusHelper(impl, "", "")); + + impl.SetStatus("", "", HealthCheckResponse.Types.ServingStatus.NOT_SERVING); + Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.NOT_SERVING, GetStatusHelper(impl, "", "")); + + impl.SetStatus("virtual-host", "", HealthCheckResponse.Types.ServingStatus.UNKNOWN); + Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.UNKNOWN, GetStatusHelper(impl, "virtual-host", "")); + + impl.SetStatus("virtual-host", "grpc.test.TestService", HealthCheckResponse.Types.ServingStatus.SERVING); + Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.SERVING, GetStatusHelper(impl, "virtual-host", "grpc.test.TestService")); + } + + [Test] + public void ClearStatus() + { + var impl = new HealthServiceImpl(); + impl.SetStatus("", "", HealthCheckResponse.Types.ServingStatus.SERVING); + impl.SetStatus("virtual-host", "", HealthCheckResponse.Types.ServingStatus.UNKNOWN); + + impl.ClearStatus("", ""); + + Assert.Throws(Is.TypeOf(typeof(RpcException)).And.Property("Status").Property("StatusCode").EqualTo(StatusCode.NotFound), () => GetStatusHelper(impl, "", "")); + Assert.AreEqual(HealthCheckResponse.Types.ServingStatus.UNKNOWN, GetStatusHelper(impl, "virtual-host", "")); + } + + [Test] + public void ClearAll() + { + var impl = new HealthServiceImpl(); + impl.SetStatus("", "", HealthCheckResponse.Types.ServingStatus.SERVING); + impl.SetStatus("virtual-host", "", HealthCheckResponse.Types.ServingStatus.UNKNOWN); + + impl.ClearAll(); + Assert.Throws(typeof(RpcException), () => GetStatusHelper(impl, "", "")); + Assert.Throws(typeof(RpcException), () => GetStatusHelper(impl, "virtual-host", "")); + } + + [Test] + public void NullsRejected() + { + var impl = new HealthServiceImpl(); + Assert.Throws(typeof(NullReferenceException), () => impl.SetStatus(null, "", HealthCheckResponse.Types.ServingStatus.SERVING)); + Assert.Throws(typeof(NullReferenceException), () => impl.SetStatus("", null, HealthCheckResponse.Types.ServingStatus.SERVING)); + + Assert.Throws(typeof(NullReferenceException), () => impl.ClearStatus(null, "")); + Assert.Throws(typeof(NullReferenceException), () => impl.ClearStatus("", null)); + } + + private static HealthCheckResponse.Types.ServingStatus GetStatusHelper(HealthServiceImpl impl, string host, string service) + { + return impl.Check(null, HealthCheckRequest.CreateBuilder().SetHost(host).SetService(service).Build()).Result.Status; + } + } +} diff --git a/src/csharp/Grpc.HealthCheck.Tests/Properties/AssemblyInfo.cs b/src/csharp/Grpc.HealthCheck.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..d5660305be --- /dev/null +++ b/src/csharp/Grpc.HealthCheck.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +[assembly: AssemblyTitle("Grpc.HealthCheck.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Google Inc. All rights reserved.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] diff --git a/src/csharp/Grpc.HealthCheck.Tests/packages.config b/src/csharp/Grpc.HealthCheck.Tests/packages.config new file mode 100644 index 0000000000..050c4eaed6 --- /dev/null +++ b/src/csharp/Grpc.HealthCheck.Tests/packages.config @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="Google.ProtocolBuffers" version="2.4.1.555" targetFramework="net45" /> + <package id="NUnit" version="2.6.4" targetFramework="net45" /> +</packages>
\ No newline at end of file diff --git a/src/csharp/Grpc.HealthCheck/.gitignore b/src/csharp/Grpc.HealthCheck/.gitignore new file mode 100644 index 0000000000..1746e3269e --- /dev/null +++ b/src/csharp/Grpc.HealthCheck/.gitignore @@ -0,0 +1,2 @@ +bin +obj diff --git a/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.csproj b/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.csproj new file mode 100644 index 0000000000..4ebb6446dd --- /dev/null +++ b/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.csproj @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{AA5E328A-8835-49D7-98ED-C29F2B3049F0}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>Grpc.HealthCheck</RootNamespace> + <AssemblyName>Grpc.HealthCheck</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <DocumentationFile>bin\$(Configuration)\Grpc.HealthCheck.Xml</DocumentationFile> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="Google.ProtocolBuffers"> + <HintPath>..\packages\Google.ProtocolBuffers.2.4.1.555\lib\net40\Google.ProtocolBuffers.dll</HintPath> + </Reference> + <Reference Include="Google.ProtocolBuffers.Serialization"> + <HintPath>..\packages\Google.ProtocolBuffers.2.4.1.555\lib\net40\Google.ProtocolBuffers.Serialization.dll</HintPath> + </Reference> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.Interactive.Async"> + <HintPath>..\packages\Ix-Async.1.2.3\lib\net45\System.Interactive.Async.dll</HintPath> + </Reference> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="..\Grpc.Core\Version.cs"> + <Link>Version.cs</Link> + </Compile> + <Compile Include="HealthServiceImpl.cs" /> + <Compile Include="Health.cs" /> + <Compile Include="HealthGrpc.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="Grpc.HealthCheck.nuspec" /> + <None Include="packages.config" /> + <None Include="proto\health.proto" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\Grpc.Core\Grpc.Core.csproj"> + <Project>{ccc4440e-49f7-4790-b0af-feabb0837ae7}</Project> + <Name>Grpc.Core</Name> + </ProjectReference> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project>
\ No newline at end of file diff --git a/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.nuspec b/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.nuspec new file mode 100644 index 0000000000..ca35b36805 --- /dev/null +++ b/src/csharp/Grpc.HealthCheck/Grpc.HealthCheck.nuspec @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<package> + <metadata> + <id>Grpc.HealthCheck</id> + <title>gRPC C# Healthchecking</title> + <summary>Implementation of gRPC health service</summary> + <description>Example implementation of grpc.health.v1alpha service that can be used for health-checking.</description> + <version>$version$</version> + <authors>Google Inc.</authors> + <owners>grpc-packages</owners> + <licenseUrl>https://github.com/grpc/grpc/blob/master/LICENSE</licenseUrl> + <projectUrl>https://github.com/grpc/grpc</projectUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <copyright>Copyright 2015, Google Inc.</copyright> + <tags>gRPC health check</tags> + <dependencies> + <dependency id="Google.ProtocolBuffers" version="2.4.1.555" /> + <dependency id="Grpc.Core" version="$version$" /> + <dependency id="Ix-Async" version="1.2.3" /> + </dependencies> + </metadata> + <files> + <file src="bin/Release/Grpc.HealthCheck.dll" target="lib/net45" /> + <file src="bin/Release/Grpc.HealthCheck.pdb" target="lib/net45" /> + <file src="bin/Release/Grpc.HealthCheck.xml" target="lib/net45" /> + <file src="**\*.cs" target="src" /> + </files> +</package> diff --git a/src/csharp/Grpc.HealthCheck/Health.cs b/src/csharp/Grpc.HealthCheck/Health.cs new file mode 100644 index 0000000000..361382d4bd --- /dev/null +++ b/src/csharp/Grpc.HealthCheck/Health.cs @@ -0,0 +1,687 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: health.proto +#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 Grpc.Health.V1Alpha { + + namespace Proto { + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class Health { + + #region Extension registration + public static void RegisterAllExtensions(pb::ExtensionRegistry registry) { + } + #endregion + #region Static variables + internal static pbd::MessageDescriptor internal__static_grpc_health_v1alpha_HealthCheckRequest__Descriptor; + internal static pb::FieldAccess.FieldAccessorTable<global::Grpc.Health.V1Alpha.HealthCheckRequest, global::Grpc.Health.V1Alpha.HealthCheckRequest.Builder> internal__static_grpc_health_v1alpha_HealthCheckRequest__FieldAccessorTable; + internal static pbd::MessageDescriptor internal__static_grpc_health_v1alpha_HealthCheckResponse__Descriptor; + internal static pb::FieldAccess.FieldAccessorTable<global::Grpc.Health.V1Alpha.HealthCheckResponse, global::Grpc.Health.V1Alpha.HealthCheckResponse.Builder> internal__static_grpc_health_v1alpha_HealthCheckResponse__FieldAccessorTable; + #endregion + #region Descriptor + public static pbd::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbd::FileDescriptor descriptor; + + static Health() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CgxoZWFsdGgucHJvdG8SE2dycGMuaGVhbHRoLnYxYWxwaGEiMwoSSGVhbHRo", + "Q2hlY2tSZXF1ZXN0EgwKBGhvc3QYASABKAkSDwoHc2VydmljZRgCIAEoCSKZ", + "AQoTSGVhbHRoQ2hlY2tSZXNwb25zZRJGCgZzdGF0dXMYASABKA4yNi5ncnBj", + "LmhlYWx0aC52MWFscGhhLkhlYWx0aENoZWNrUmVzcG9uc2UuU2VydmluZ1N0", + "YXR1cyI6Cg1TZXJ2aW5nU3RhdHVzEgsKB1VOS05PV04QABILCgdTRVJWSU5H", + "EAESDwoLTk9UX1NFUlZJTkcQAjJkCgZIZWFsdGgSWgoFQ2hlY2sSJy5ncnBj", + "LmhlYWx0aC52MWFscGhhLkhlYWx0aENoZWNrUmVxdWVzdBooLmdycGMuaGVh", + "bHRoLnYxYWxwaGEuSGVhbHRoQ2hlY2tSZXNwb25zZUIWqgITR3JwYy5IZWFs", + "dGguVjFBbHBoYQ==")); + pbd::FileDescriptor.InternalDescriptorAssigner assigner = delegate(pbd::FileDescriptor root) { + descriptor = root; + internal__static_grpc_health_v1alpha_HealthCheckRequest__Descriptor = Descriptor.MessageTypes[0]; + internal__static_grpc_health_v1alpha_HealthCheckRequest__FieldAccessorTable = + new pb::FieldAccess.FieldAccessorTable<global::Grpc.Health.V1Alpha.HealthCheckRequest, global::Grpc.Health.V1Alpha.HealthCheckRequest.Builder>(internal__static_grpc_health_v1alpha_HealthCheckRequest__Descriptor, + new string[] { "Host", "Service", }); + internal__static_grpc_health_v1alpha_HealthCheckResponse__Descriptor = Descriptor.MessageTypes[1]; + internal__static_grpc_health_v1alpha_HealthCheckResponse__FieldAccessorTable = + new pb::FieldAccess.FieldAccessorTable<global::Grpc.Health.V1Alpha.HealthCheckResponse, global::Grpc.Health.V1Alpha.HealthCheckResponse.Builder>(internal__static_grpc_health_v1alpha_HealthCheckResponse__Descriptor, + new string[] { "Status", }); + 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 HealthCheckRequest : pb::GeneratedMessage<HealthCheckRequest, HealthCheckRequest.Builder> { + private HealthCheckRequest() { } + private static readonly HealthCheckRequest defaultInstance = new HealthCheckRequest().MakeReadOnly(); + private static readonly string[] _healthCheckRequestFieldNames = new string[] { "host", "service" }; + private static readonly uint[] _healthCheckRequestFieldTags = new uint[] { 10, 18 }; + public static HealthCheckRequest DefaultInstance { + get { return defaultInstance; } + } + + public override HealthCheckRequest DefaultInstanceForType { + get { return DefaultInstance; } + } + + protected override HealthCheckRequest ThisMessage { + get { return this; } + } + + public static pbd::MessageDescriptor Descriptor { + get { return global::Grpc.Health.V1Alpha.Proto.Health.internal__static_grpc_health_v1alpha_HealthCheckRequest__Descriptor; } + } + + protected override pb::FieldAccess.FieldAccessorTable<HealthCheckRequest, HealthCheckRequest.Builder> InternalFieldAccessors { + get { return global::Grpc.Health.V1Alpha.Proto.Health.internal__static_grpc_health_v1alpha_HealthCheckRequest__FieldAccessorTable; } + } + + public const int HostFieldNumber = 1; + private bool hasHost; + private string host_ = ""; + public bool HasHost { + get { return hasHost; } + } + public string Host { + get { return host_; } + } + + public const int ServiceFieldNumber = 2; + private bool hasService; + private string service_ = ""; + public bool HasService { + get { return hasService; } + } + public string Service { + get { return service_; } + } + + public override bool IsInitialized { + get { + return true; + } + } + + public override void WriteTo(pb::ICodedOutputStream output) { + CalcSerializedSize(); + string[] field_names = _healthCheckRequestFieldNames; + if (hasHost) { + output.WriteString(1, field_names[0], Host); + } + if (hasService) { + output.WriteString(2, field_names[1], Service); + } + UnknownFields.WriteTo(output); + } + + private int memoizedSerializedSize = -1; + public override int SerializedSize { + get { + int size = memoizedSerializedSize; + if (size != -1) return size; + return CalcSerializedSize(); + } + } + + private int CalcSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (hasHost) { + size += pb::CodedOutputStream.ComputeStringSize(1, Host); + } + if (hasService) { + size += pb::CodedOutputStream.ComputeStringSize(2, Service); + } + size += UnknownFields.SerializedSize; + memoizedSerializedSize = size; + return size; + } + public static HealthCheckRequest ParseFrom(pb::ByteString data) { + return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed(); + } + public static HealthCheckRequest ParseFrom(pb::ByteString data, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed(); + } + public static HealthCheckRequest ParseFrom(byte[] data) { + return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed(); + } + public static HealthCheckRequest ParseFrom(byte[] data, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed(); + } + public static HealthCheckRequest ParseFrom(global::System.IO.Stream input) { + return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed(); + } + public static HealthCheckRequest ParseFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed(); + } + public static HealthCheckRequest ParseDelimitedFrom(global::System.IO.Stream input) { + return CreateBuilder().MergeDelimitedFrom(input).BuildParsed(); + } + public static HealthCheckRequest ParseDelimitedFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) { + return CreateBuilder().MergeDelimitedFrom(input, extensionRegistry).BuildParsed(); + } + public static HealthCheckRequest ParseFrom(pb::ICodedInputStream input) { + return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed(); + } + public static HealthCheckRequest ParseFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed(); + } + private HealthCheckRequest 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(HealthCheckRequest prototype) { + return new Builder(prototype); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Builder : pb::GeneratedBuilder<HealthCheckRequest, Builder> { + protected override Builder ThisBuilder { + get { return this; } + } + public Builder() { + result = DefaultInstance; + resultIsReadOnly = true; + } + internal Builder(HealthCheckRequest cloneFrom) { + result = cloneFrom; + resultIsReadOnly = true; + } + + private bool resultIsReadOnly; + private HealthCheckRequest result; + + private HealthCheckRequest PrepareBuilder() { + if (resultIsReadOnly) { + HealthCheckRequest original = result; + result = new HealthCheckRequest(); + resultIsReadOnly = false; + MergeFrom(original); + } + return result; + } + + public override bool IsInitialized { + get { return result.IsInitialized; } + } + + protected override HealthCheckRequest 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::Grpc.Health.V1Alpha.HealthCheckRequest.Descriptor; } + } + + public override HealthCheckRequest DefaultInstanceForType { + get { return global::Grpc.Health.V1Alpha.HealthCheckRequest.DefaultInstance; } + } + + public override HealthCheckRequest BuildPartial() { + if (resultIsReadOnly) { + return result; + } + resultIsReadOnly = true; + return result.MakeReadOnly(); + } + + public override Builder MergeFrom(pb::IMessage other) { + if (other is HealthCheckRequest) { + return MergeFrom((HealthCheckRequest) other); + } else { + base.MergeFrom(other); + return this; + } + } + + public override Builder MergeFrom(HealthCheckRequest other) { + if (other == global::Grpc.Health.V1Alpha.HealthCheckRequest.DefaultInstance) return this; + PrepareBuilder(); + if (other.HasHost) { + Host = other.Host; + } + if (other.HasService) { + Service = other.Service; + } + 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(_healthCheckRequestFieldNames, field_name, global::System.StringComparer.Ordinal); + if(field_ordinal >= 0) + tag = _healthCheckRequestFieldTags[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 10: { + result.hasHost = input.ReadString(ref result.host_); + break; + } + case 18: { + result.hasService = input.ReadString(ref result.service_); + break; + } + } + } + + if (unknownFields != null) { + this.UnknownFields = unknownFields.Build(); + } + return this; + } + + + public bool HasHost { + get { return result.hasHost; } + } + public string Host { + get { return result.Host; } + set { SetHost(value); } + } + public Builder SetHost(string value) { + pb::ThrowHelper.ThrowIfNull(value, "value"); + PrepareBuilder(); + result.hasHost = true; + result.host_ = value; + return this; + } + public Builder ClearHost() { + PrepareBuilder(); + result.hasHost = false; + result.host_ = ""; + return this; + } + + public bool HasService { + get { return result.hasService; } + } + public string Service { + get { return result.Service; } + set { SetService(value); } + } + public Builder SetService(string value) { + pb::ThrowHelper.ThrowIfNull(value, "value"); + PrepareBuilder(); + result.hasService = true; + result.service_ = value; + return this; + } + public Builder ClearService() { + PrepareBuilder(); + result.hasService = false; + result.service_ = ""; + return this; + } + } + static HealthCheckRequest() { + object.ReferenceEquals(global::Grpc.Health.V1Alpha.Proto.Health.Descriptor, null); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class HealthCheckResponse : pb::GeneratedMessage<HealthCheckResponse, HealthCheckResponse.Builder> { + private HealthCheckResponse() { } + private static readonly HealthCheckResponse defaultInstance = new HealthCheckResponse().MakeReadOnly(); + private static readonly string[] _healthCheckResponseFieldNames = new string[] { "status" }; + private static readonly uint[] _healthCheckResponseFieldTags = new uint[] { 8 }; + public static HealthCheckResponse DefaultInstance { + get { return defaultInstance; } + } + + public override HealthCheckResponse DefaultInstanceForType { + get { return DefaultInstance; } + } + + protected override HealthCheckResponse ThisMessage { + get { return this; } + } + + public static pbd::MessageDescriptor Descriptor { + get { return global::Grpc.Health.V1Alpha.Proto.Health.internal__static_grpc_health_v1alpha_HealthCheckResponse__Descriptor; } + } + + protected override pb::FieldAccess.FieldAccessorTable<HealthCheckResponse, HealthCheckResponse.Builder> InternalFieldAccessors { + get { return global::Grpc.Health.V1Alpha.Proto.Health.internal__static_grpc_health_v1alpha_HealthCheckResponse__FieldAccessorTable; } + } + + #region Nested types + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + public static partial class Types { + public enum ServingStatus { + UNKNOWN = 0, + SERVING = 1, + NOT_SERVING = 2, + } + + } + #endregion + + public const int StatusFieldNumber = 1; + private bool hasStatus; + private global::Grpc.Health.V1Alpha.HealthCheckResponse.Types.ServingStatus status_ = global::Grpc.Health.V1Alpha.HealthCheckResponse.Types.ServingStatus.UNKNOWN; + public bool HasStatus { + get { return hasStatus; } + } + public global::Grpc.Health.V1Alpha.HealthCheckResponse.Types.ServingStatus Status { + get { return status_; } + } + + public override bool IsInitialized { + get { + return true; + } + } + + public override void WriteTo(pb::ICodedOutputStream output) { + CalcSerializedSize(); + string[] field_names = _healthCheckResponseFieldNames; + if (hasStatus) { + output.WriteEnum(1, field_names[0], (int) Status, Status); + } + UnknownFields.WriteTo(output); + } + + private int memoizedSerializedSize = -1; + public override int SerializedSize { + get { + int size = memoizedSerializedSize; + if (size != -1) return size; + return CalcSerializedSize(); + } + } + + private int CalcSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (hasStatus) { + size += pb::CodedOutputStream.ComputeEnumSize(1, (int) Status); + } + size += UnknownFields.SerializedSize; + memoizedSerializedSize = size; + return size; + } + public static HealthCheckResponse ParseFrom(pb::ByteString data) { + return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed(); + } + public static HealthCheckResponse ParseFrom(pb::ByteString data, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed(); + } + public static HealthCheckResponse ParseFrom(byte[] data) { + return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed(); + } + public static HealthCheckResponse ParseFrom(byte[] data, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed(); + } + public static HealthCheckResponse ParseFrom(global::System.IO.Stream input) { + return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed(); + } + public static HealthCheckResponse ParseFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed(); + } + public static HealthCheckResponse ParseDelimitedFrom(global::System.IO.Stream input) { + return CreateBuilder().MergeDelimitedFrom(input).BuildParsed(); + } + public static HealthCheckResponse ParseDelimitedFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) { + return CreateBuilder().MergeDelimitedFrom(input, extensionRegistry).BuildParsed(); + } + public static HealthCheckResponse ParseFrom(pb::ICodedInputStream input) { + return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed(); + } + public static HealthCheckResponse ParseFrom(pb::ICodedInputStream input, pb::ExtensionRegistry extensionRegistry) { + return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed(); + } + private HealthCheckResponse 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(HealthCheckResponse prototype) { + return new Builder(prototype); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + public sealed partial class Builder : pb::GeneratedBuilder<HealthCheckResponse, Builder> { + protected override Builder ThisBuilder { + get { return this; } + } + public Builder() { + result = DefaultInstance; + resultIsReadOnly = true; + } + internal Builder(HealthCheckResponse cloneFrom) { + result = cloneFrom; + resultIsReadOnly = true; + } + + private bool resultIsReadOnly; + private HealthCheckResponse result; + + private HealthCheckResponse PrepareBuilder() { + if (resultIsReadOnly) { + HealthCheckResponse original = result; + result = new HealthCheckResponse(); + resultIsReadOnly = false; + MergeFrom(original); + } + return result; + } + + public override bool IsInitialized { + get { return result.IsInitialized; } + } + + protected override HealthCheckResponse 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::Grpc.Health.V1Alpha.HealthCheckResponse.Descriptor; } + } + + public override HealthCheckResponse DefaultInstanceForType { + get { return global::Grpc.Health.V1Alpha.HealthCheckResponse.DefaultInstance; } + } + + public override HealthCheckResponse BuildPartial() { + if (resultIsReadOnly) { + return result; + } + resultIsReadOnly = true; + return result.MakeReadOnly(); + } + + public override Builder MergeFrom(pb::IMessage other) { + if (other is HealthCheckResponse) { + return MergeFrom((HealthCheckResponse) other); + } else { + base.MergeFrom(other); + return this; + } + } + + public override Builder MergeFrom(HealthCheckResponse other) { + if (other == global::Grpc.Health.V1Alpha.HealthCheckResponse.DefaultInstance) return this; + PrepareBuilder(); + if (other.HasStatus) { + Status = other.Status; + } + 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(_healthCheckResponseFieldNames, field_name, global::System.StringComparer.Ordinal); + if(field_ordinal >= 0) + tag = _healthCheckResponseFieldTags[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: { + object unknown; + if(input.ReadEnum(ref result.status_, out unknown)) { + result.hasStatus = true; + } else if(unknown is int) { + if (unknownFields == null) { + unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields); + } + unknownFields.MergeVarintField(1, (ulong)(int)unknown); + } + break; + } + } + } + + if (unknownFields != null) { + this.UnknownFields = unknownFields.Build(); + } + return this; + } + + + public bool HasStatus { + get { return result.hasStatus; } + } + public global::Grpc.Health.V1Alpha.HealthCheckResponse.Types.ServingStatus Status { + get { return result.Status; } + set { SetStatus(value); } + } + public Builder SetStatus(global::Grpc.Health.V1Alpha.HealthCheckResponse.Types.ServingStatus value) { + PrepareBuilder(); + result.hasStatus = true; + result.status_ = value; + return this; + } + public Builder ClearStatus() { + PrepareBuilder(); + result.hasStatus = false; + result.status_ = global::Grpc.Health.V1Alpha.HealthCheckResponse.Types.ServingStatus.UNKNOWN; + return this; + } + } + static HealthCheckResponse() { + object.ReferenceEquals(global::Grpc.Health.V1Alpha.Proto.Health.Descriptor, null); + } + } + + #endregion + +} + +#endregion Designer generated code diff --git a/src/csharp/Grpc.HealthCheck/HealthGrpc.cs b/src/csharp/Grpc.HealthCheck/HealthGrpc.cs new file mode 100644 index 0000000000..ed9fc4ed77 --- /dev/null +++ b/src/csharp/Grpc.HealthCheck/HealthGrpc.cs @@ -0,0 +1,78 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: health.proto +#region Designer generated code + +using System; +using System.Threading; +using System.Threading.Tasks; +using Grpc.Core; + +namespace Grpc.Health.V1Alpha { + public static class Health + { + static readonly string __ServiceName = "grpc.health.v1alpha.Health"; + + static readonly Marshaller<global::Grpc.Health.V1Alpha.HealthCheckRequest> __Marshaller_HealthCheckRequest = Marshallers.Create((arg) => arg.ToByteArray(), global::Grpc.Health.V1Alpha.HealthCheckRequest.ParseFrom); + static readonly Marshaller<global::Grpc.Health.V1Alpha.HealthCheckResponse> __Marshaller_HealthCheckResponse = Marshallers.Create((arg) => arg.ToByteArray(), global::Grpc.Health.V1Alpha.HealthCheckResponse.ParseFrom); + + static readonly Method<global::Grpc.Health.V1Alpha.HealthCheckRequest, global::Grpc.Health.V1Alpha.HealthCheckResponse> __Method_Check = new Method<global::Grpc.Health.V1Alpha.HealthCheckRequest, global::Grpc.Health.V1Alpha.HealthCheckResponse>( + MethodType.Unary, + "Check", + __Marshaller_HealthCheckRequest, + __Marshaller_HealthCheckResponse); + + // client-side stub interface + public interface IHealthClient + { + global::Grpc.Health.V1Alpha.HealthCheckResponse Check(global::Grpc.Health.V1Alpha.HealthCheckRequest request, CancellationToken token = default(CancellationToken)); + Task<global::Grpc.Health.V1Alpha.HealthCheckResponse> CheckAsync(global::Grpc.Health.V1Alpha.HealthCheckRequest request, CancellationToken token = default(CancellationToken)); + } + + // server-side interface + public interface IHealth + { + Task<global::Grpc.Health.V1Alpha.HealthCheckResponse> Check(ServerCallContext context, global::Grpc.Health.V1Alpha.HealthCheckRequest request); + } + + // client stub + public class HealthClient : AbstractStub<HealthClient, StubConfiguration>, IHealthClient + { + public HealthClient(Channel channel) : this(channel, StubConfiguration.Default) + { + } + public HealthClient(Channel channel, StubConfiguration config) : base(channel, config) + { + } + public global::Grpc.Health.V1Alpha.HealthCheckResponse Check(global::Grpc.Health.V1Alpha.HealthCheckRequest request, CancellationToken token = default(CancellationToken)) + { + var call = CreateCall(__ServiceName, __Method_Check); + return Calls.BlockingUnaryCall(call, request, token); + } + public Task<global::Grpc.Health.V1Alpha.HealthCheckResponse> CheckAsync(global::Grpc.Health.V1Alpha.HealthCheckRequest request, CancellationToken token = default(CancellationToken)) + { + var call = CreateCall(__ServiceName, __Method_Check); + return Calls.AsyncUnaryCall(call, request, token); + } + } + + // creates service definition that can be registered with a server + public static ServerServiceDefinition BindService(IHealth serviceImpl) + { + return ServerServiceDefinition.CreateBuilder(__ServiceName) + .AddMethod(__Method_Check, serviceImpl.Check).Build(); + } + + // creates a new client stub + public static IHealthClient NewStub(Channel channel) + { + return new HealthClient(channel); + } + + // creates a new client stub + public static IHealthClient NewStub(Channel channel, StubConfiguration config) + { + return new HealthClient(channel, config); + } + } +} +#endregion diff --git a/src/csharp/Grpc.HealthCheck/HealthServiceImpl.cs b/src/csharp/Grpc.HealthCheck/HealthServiceImpl.cs new file mode 100644 index 0000000000..db3a2a0942 --- /dev/null +++ b/src/csharp/Grpc.HealthCheck/HealthServiceImpl.cs @@ -0,0 +1,132 @@ +#region Copyright notice and license +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Grpc.Core; +using Grpc.Core.Utils; +using Grpc.Health.V1Alpha; + +namespace Grpc.HealthCheck +{ + /// <summary> + /// Implementation of a simple Health service. Useful for health checking. + /// + /// Registering service with a server: + /// <code> + /// var serviceImpl = new HealthServiceImpl(); + /// server = new Server(); + /// server.AddServiceDefinition(Grpc.Health.V1Alpha.Health.BindService(serviceImpl)); + /// </code> + /// </summary> + public class HealthServiceImpl : Grpc.Health.V1Alpha.Health.IHealth + { + private readonly object myLock = new object(); + private readonly Dictionary<Key, HealthCheckResponse.Types.ServingStatus> statusMap = + new Dictionary<Key, HealthCheckResponse.Types.ServingStatus>(); + + /// <summary> + /// Sets the health status for given host and service. + /// </summary> + /// <param name="host">The host. Cannot be null.</param> + /// <param name="service">The service. Cannot be null.</param> + /// <param name="status">the health status</param> + public void SetStatus(string host, string service, HealthCheckResponse.Types.ServingStatus status) + { + lock (myLock) + { + statusMap[CreateKey(host, service)] = status; + } + } + + /// <summary> + /// Clears health status for given host and service. + /// </summary> + /// <param name="host">The host. Cannot be null.</param> + /// <param name="service">The service. Cannot be null.</param> + public void ClearStatus(string host, string service) + { + lock (myLock) + { + statusMap.Remove(CreateKey(host, service)); + } + } + + /// <summary> + /// Clears statuses for all hosts and services. + /// </summary> + public void ClearAll() + { + lock (myLock) + { + statusMap.Clear(); + } + } + + public Task<HealthCheckResponse> Check(ServerCallContext context, HealthCheckRequest request) + { + lock (myLock) + { + var host = request.HasHost ? request.Host : ""; + var service = request.HasService ? request.Service : ""; + + HealthCheckResponse.Types.ServingStatus status; + if (!statusMap.TryGetValue(CreateKey(host, service), out status)) + { + // TODO(jtattermusch): returning specific status from server handler is not supported yet. + throw new RpcException(new Status(StatusCode.NotFound, "")); + } + return Task.FromResult(HealthCheckResponse.CreateBuilder().SetStatus(status).Build()); + } + } + + private static Key CreateKey(string host, string service) + { + return new Key(host, service); + } + + private struct Key + { + public Key(string host, string service) + { + this.Host = Preconditions.CheckNotNull(host); + this.Service = Preconditions.CheckNotNull(service); + } + + readonly string Host; + readonly string Service; + } + } +} diff --git a/src/csharp/Grpc.HealthCheck/Properties/AssemblyInfo.cs b/src/csharp/Grpc.HealthCheck/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..41a54a98bc --- /dev/null +++ b/src/csharp/Grpc.HealthCheck/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +[assembly: AssemblyTitle("Grpc.HealthCheck")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Google Inc. All rights reserved.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")]
\ No newline at end of file diff --git a/src/csharp/Grpc.HealthCheck/packages.config b/src/csharp/Grpc.HealthCheck/packages.config new file mode 100644 index 0000000000..094a30981e --- /dev/null +++ b/src/csharp/Grpc.HealthCheck/packages.config @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="Google.ProtocolBuffers" version="2.4.1.555" targetFramework="net45" /> + <package id="Ix-Async" version="1.2.3" targetFramework="net45" /> +</packages>
\ No newline at end of file diff --git a/src/csharp/Grpc.HealthCheck/proto/health.proto b/src/csharp/Grpc.HealthCheck/proto/health.proto new file mode 100644 index 0000000000..08df7e104e --- /dev/null +++ b/src/csharp/Grpc.HealthCheck/proto/health.proto @@ -0,0 +1,52 @@ +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// TODO(jtattermusch): switch to proto3 once C# supports that. +syntax = "proto2"; + +package grpc.health.v1alpha; +option csharp_namespace = "Grpc.Health.V1Alpha"; + +message HealthCheckRequest { + optional string host = 1; + optional string service = 2; +} + +message HealthCheckResponse { + enum ServingStatus { + UNKNOWN = 0; + SERVING = 1; + NOT_SERVING = 2; + } + optional ServingStatus status = 1; +} + +service Health { + rpc Check(HealthCheckRequest) returns (HealthCheckResponse); +}
\ No newline at end of file diff --git a/src/csharp/Grpc.IntegrationTesting.Client/Grpc.IntegrationTesting.Client.csproj b/src/csharp/Grpc.IntegrationTesting.Client/Grpc.IntegrationTesting.Client.csproj index df05c535e2..328acb5b47 100644 --- a/src/csharp/Grpc.IntegrationTesting.Client/Grpc.IntegrationTesting.Client.csproj +++ b/src/csharp/Grpc.IntegrationTesting.Client/Grpc.IntegrationTesting.Client.csproj @@ -36,6 +36,9 @@ <Reference Include="System" /> </ItemGroup> <ItemGroup> + <Compile Include="..\Grpc.Core\Version.cs"> + <Link>Version.cs</Link> + </Compile> <Compile Include="Program.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> diff --git a/src/csharp/Grpc.IntegrationTesting.Client/Properties/AssemblyInfo.cs b/src/csharp/Grpc.IntegrationTesting.Client/Properties/AssemblyInfo.cs index 9a389a1e30..f51f2796c4 100644 --- a/src/csharp/Grpc.IntegrationTesting.Client/Properties/AssemblyInfo.cs +++ b/src/csharp/Grpc.IntegrationTesting.Client/Properties/AssemblyInfo.cs @@ -9,4 +9,3 @@ using System.Runtime.CompilerServices; [assembly: AssemblyCopyright("Google Inc. All rights reserved.")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("0.6.*")] diff --git a/src/csharp/Grpc.IntegrationTesting.Server/Grpc.IntegrationTesting.Server.csproj b/src/csharp/Grpc.IntegrationTesting.Server/Grpc.IntegrationTesting.Server.csproj index 235897c888..ae184c1dc7 100644 --- a/src/csharp/Grpc.IntegrationTesting.Server/Grpc.IntegrationTesting.Server.csproj +++ b/src/csharp/Grpc.IntegrationTesting.Server/Grpc.IntegrationTesting.Server.csproj @@ -36,6 +36,9 @@ <Reference Include="System" /> </ItemGroup> <ItemGroup> + <Compile Include="..\Grpc.Core\Version.cs"> + <Link>Version.cs</Link> + </Compile> <Compile Include="Program.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> diff --git a/src/csharp/Grpc.IntegrationTesting.Server/Properties/AssemblyInfo.cs b/src/csharp/Grpc.IntegrationTesting.Server/Properties/AssemblyInfo.cs index ff31035d53..f68d9a3ddc 100644 --- a/src/csharp/Grpc.IntegrationTesting.Server/Properties/AssemblyInfo.cs +++ b/src/csharp/Grpc.IntegrationTesting.Server/Properties/AssemblyInfo.cs @@ -9,4 +9,3 @@ using System.Runtime.CompilerServices; [assembly: AssemblyCopyright("Google Inc. All rights reserved.")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("0.6.*")] diff --git a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj index a6d847ca65..af4a75a034 100644 --- a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj +++ b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj @@ -75,6 +75,9 @@ </Reference> </ItemGroup> <ItemGroup> + <Compile Include="..\Grpc.Core\Version.cs"> + <Link>Version.cs</Link> + </Compile> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Empty.cs" /> <Compile Include="Messages.cs" /> diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs index f0be522bc6..bdcb2c505c 100644 --- a/src/csharp/Grpc.IntegrationTesting/InteropClient.cs +++ b/src/csharp/Grpc.IntegrationTesting/InteropClient.cs @@ -102,8 +102,6 @@ namespace Grpc.IntegrationTesting private void Run() { - GrpcEnvironment.Initialize(); - Credentials credentials = null; if (options.useTls) { @@ -135,7 +133,6 @@ namespace Grpc.IntegrationTesting TestService.ITestServiceClient client = new TestService.TestServiceClient(channel, stubConfig); RunTestCase(options.testCase, client); } - GrpcEnvironment.Shutdown(); } diff --git a/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs b/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs index 1a733450c1..6c2da9d2ee 100644 --- a/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs +++ b/src/csharp/Grpc.IntegrationTesting/InteropClientServerTest.cs @@ -55,8 +55,6 @@ namespace Grpc.IntegrationTesting [TestFixtureSetUp] public void Init() { - GrpcEnvironment.Initialize(); - server = new Server(); server.AddServiceDefinition(TestService.BindService(new TestServiceImpl())); int port = server.AddListeningPort(host, Server.PickUnusedPort, TestCredentials.CreateTestServerCredentials()); @@ -74,7 +72,6 @@ namespace Grpc.IntegrationTesting public void Cleanup() { channel.Dispose(); - server.ShutdownAsync().Wait(); GrpcEnvironment.Shutdown(); } diff --git a/src/csharp/Grpc.IntegrationTesting/InteropServer.cs b/src/csharp/Grpc.IntegrationTesting/InteropServer.cs index 87c3cbe1d4..9475e66c40 100644 --- a/src/csharp/Grpc.IntegrationTesting/InteropServer.cs +++ b/src/csharp/Grpc.IntegrationTesting/InteropServer.cs @@ -88,8 +88,6 @@ namespace Grpc.IntegrationTesting private void Run() { - GrpcEnvironment.Initialize(); - var server = new Server(); server.AddServiceDefinition(TestService.BindService(new TestServiceImpl())); diff --git a/src/csharp/Grpc.IntegrationTesting/Properties/AssemblyInfo.cs b/src/csharp/Grpc.IntegrationTesting/Properties/AssemblyInfo.cs index 7134b04892..1beb0bbb41 100644 --- a/src/csharp/Grpc.IntegrationTesting/Properties/AssemblyInfo.cs +++ b/src/csharp/Grpc.IntegrationTesting/Properties/AssemblyInfo.cs @@ -9,4 +9,3 @@ using System.Runtime.CompilerServices; [assembly: AssemblyCopyright("Google Inc. All rights reserved.")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("0.6.*")] diff --git a/src/csharp/Grpc.Tools.nuspec b/src/csharp/Grpc.Tools.nuspec index 0f4fa79277..eabf5dc7db 100644 --- a/src/csharp/Grpc.Tools.nuspec +++ b/src/csharp/Grpc.Tools.nuspec @@ -5,13 +5,13 @@ <title>gRPC C# Tools</title> <summary>Tools for C# implementation of gRPC - an RPC library and framework</summary> <description>Precompiled Windows binaries for generating protocol buffer messages and gRPC client/server code</description> - <version>0.6.0</version> + <version>$version$</version> <authors>Google Inc.</authors> <owners>grpc-packages</owners> <licenseUrl>https://github.com/grpc/grpc/blob/master/LICENSE</licenseUrl> <projectUrl>https://github.com/grpc/grpc</projectUrl> <requireLicenseAcceptance>false</requireLicenseAcceptance> - <releaseNotes>protoc.exe - protocol buffer compiler v3.0.0-alpha-3; grpc_csharp_plugin.exe - gRPC C# protoc plugin version 0.6.0</releaseNotes> + <releaseNotes>protoc.exe - protocol buffer compiler v3.0.0-alpha-3; grpc_csharp_plugin.exe - gRPC C# protoc plugin version $version$</releaseNotes> <copyright>Copyright 2015, Google Inc.</copyright> <tags>gRPC RPC Protocol HTTP/2</tags> </metadata> diff --git a/src/csharp/Grpc.nuspec b/src/csharp/Grpc.nuspec index 70203a6203..7fbd861923 100644 --- a/src/csharp/Grpc.nuspec +++ b/src/csharp/Grpc.nuspec @@ -5,17 +5,17 @@ <title>gRPC C#</title> <summary>C# implementation of gRPC - an RPC library and framework</summary> <description>C# implementation of gRPC - an RPC library and framework. See project site for more info.</description> - <version>0.6.0</version> + <version>$version$</version> <authors>Google Inc.</authors> <owners>grpc-packages</owners> <licenseUrl>https://github.com/grpc/grpc/blob/master/LICENSE</licenseUrl> <projectUrl>https://github.com/grpc/grpc</projectUrl> <requireLicenseAcceptance>false</requireLicenseAcceptance> - <releaseNotes>Release 0.6.0 of gRPC C#</releaseNotes> + <releaseNotes>Release $version$ of gRPC C#</releaseNotes> <copyright>Copyright 2015, Google Inc.</copyright> <tags>gRPC RPC Protocol HTTP/2</tags> <dependencies> - <dependency id="Grpc.Core" version="0.6.0" /> + <dependency id="Grpc.Core" version="$version$" /> </dependencies> </metadata> <files/> diff --git a/src/csharp/Grpc.sln b/src/csharp/Grpc.sln index 978739f23a..705e4fb1c2 100644 --- a/src/csharp/Grpc.sln +++ b/src/csharp/Grpc.sln @@ -28,57 +28,68 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{B5B871 .nuget\packages.config = .nuget\packages.config
EndProjectSection
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Grpc.HealthCheck", "Grpc.HealthCheck\Grpc.HealthCheck.csproj", "{AA5E328A-8835-49D7-98ED-C29F2B3049F0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Grpc.HealthCheck.Tests", "Grpc.HealthCheck.Tests\Grpc.HealthCheck.Tests.csproj", "{F8C6D937-C44B-4EE3-A431-B0FBAEACE47D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {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
- {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
- {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
{143B1C29-C442-4BE0-BF3F-A8F92288AC9F}.Debug|x86.ActiveCfg = Debug|Any CPU
{143B1C29-C442-4BE0-BF3F-A8F92288AC9F}.Debug|x86.Build.0 = Debug|Any CPU
{143B1C29-C442-4BE0-BF3F-A8F92288AC9F}.Release|x86.ActiveCfg = Release|Any CPU
{143B1C29-C442-4BE0-BF3F-A8F92288AC9F}.Release|x86.Build.0 = Release|Any CPU
- {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
- {C61154BA-DD4A-4838-8420-0162A28925E0}.Debug|x86.ActiveCfg = Debug|x86
- {C61154BA-DD4A-4838-8420-0162A28925E0}.Debug|x86.Build.0 = Debug|x86
- {C61154BA-DD4A-4838-8420-0162A28925E0}.Release|x86.ActiveCfg = Release|x86
- {C61154BA-DD4A-4838-8420-0162A28925E0}.Release|x86.Build.0 = Release|x86
{3D166931-BA2D-416E-95A3-D36E8F6E90B9}.Debug|x86.ActiveCfg = Debug|x86
{3D166931-BA2D-416E-95A3-D36E8F6E90B9}.Debug|x86.Build.0 = Debug|x86
{3D166931-BA2D-416E-95A3-D36E8F6E90B9}.Release|x86.ActiveCfg = Release|x86
{3D166931-BA2D-416E-95A3-D36E8F6E90B9}.Release|x86.Build.0 = Release|x86
+ {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
{A654F3B8-E859-4E6A-B30D-227527DBEF0D}.Debug|x86.ActiveCfg = Debug|x86
{A654F3B8-E859-4E6A-B30D-227527DBEF0D}.Debug|x86.Build.0 = Debug|x86
{A654F3B8-E859-4E6A-B30D-227527DBEF0D}.Release|x86.ActiveCfg = Release|x86
{A654F3B8-E859-4E6A-B30D-227527DBEF0D}.Release|x86.Build.0 = Release|x86
- {BF62FE08-373A-43D6-9D73-41CAA38B7011}.Debug|x86.ActiveCfg = Debug|x86
- {BF62FE08-373A-43D6-9D73-41CAA38B7011}.Debug|x86.Build.0 = Debug|x86
- {BF62FE08-373A-43D6-9D73-41CAA38B7011}.Release|x86.ActiveCfg = Release|x86
- {BF62FE08-373A-43D6-9D73-41CAA38B7011}.Release|x86.Build.0 = Release|x86
+ {AA5E328A-8835-49D7-98ED-C29F2B3049F0}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {AA5E328A-8835-49D7-98ED-C29F2B3049F0}.Debug|x86.Build.0 = Debug|Any CPU
+ {AA5E328A-8835-49D7-98ED-C29F2B3049F0}.Release|x86.ActiveCfg = Release|Any CPU
+ {AA5E328A-8835-49D7-98ED-C29F2B3049F0}.Release|x86.Build.0 = Release|Any CPU
{AE21D0EE-9A2C-4C15-AB7F-5224EED5B0EA}.Debug|x86.ActiveCfg = Debug|Any CPU
{AE21D0EE-9A2C-4C15-AB7F-5224EED5B0EA}.Debug|x86.Build.0 = Debug|Any CPU
{AE21D0EE-9A2C-4C15-AB7F-5224EED5B0EA}.Release|x86.ActiveCfg = Release|Any CPU
{AE21D0EE-9A2C-4C15-AB7F-5224EED5B0EA}.Release|x86.Build.0 = Release|Any CPU
+ {BF62FE08-373A-43D6-9D73-41CAA38B7011}.Debug|x86.ActiveCfg = Debug|x86
+ {BF62FE08-373A-43D6-9D73-41CAA38B7011}.Debug|x86.Build.0 = Debug|x86
+ {BF62FE08-373A-43D6-9D73-41CAA38B7011}.Release|x86.ActiveCfg = Release|x86
+ {BF62FE08-373A-43D6-9D73-41CAA38B7011}.Release|x86.Build.0 = Release|x86
+ {C61154BA-DD4A-4838-8420-0162A28925E0}.Debug|x86.ActiveCfg = Debug|x86
+ {C61154BA-DD4A-4838-8420-0162A28925E0}.Debug|x86.Build.0 = Debug|x86
+ {C61154BA-DD4A-4838-8420-0162A28925E0}.Release|x86.ActiveCfg = Release|x86
+ {C61154BA-DD4A-4838-8420-0162A28925E0}.Release|x86.Build.0 = Release|x86
+ {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
+ {F8C6D937-C44B-4EE3-A431-B0FBAEACE47D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F8C6D937-C44B-4EE3-A431-B0FBAEACE47D}.Debug|x86.Build.0 = Debug|Any CPU
+ {F8C6D937-C44B-4EE3-A431-B0FBAEACE47D}.Release|x86.ActiveCfg = Release|Any CPU
+ {F8C6D937-C44B-4EE3-A431-B0FBAEACE47D}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
- GlobalSection(MonoDevelopProperties) = preSolution
- StartupItem = Grpc.Examples\Grpc.Examples.csproj
- EndGlobalSection
EndGlobal
diff --git a/src/csharp/build_packages.bat b/src/csharp/build_packages.bat index 3412129fb2..c3e5fe8817 100644 --- a/src/csharp/build_packages.bat +++ b/src/csharp/build_packages.bat @@ -1,5 +1,9 @@ @rem Builds gRPC NuGet packages +@rem Current package versions +set VERSION=0.6.0 +set CORE_VERSION=0.10.0 + @rem Adjust the location of nuget.exe set NUGET=C:\nuget\nuget.exe @@ -10,11 +14,12 @@ endlocal @call buildall.bat || goto :error -%NUGET% pack ..\..\vsprojects\nuget_package\grpc.native.csharp_ext.nuspec || goto :error -%NUGET% pack Grpc.Core\Grpc.Core.nuspec -Symbols || goto :error -%NUGET% pack Grpc.Auth\Grpc.Auth.nuspec -Symbols || goto :error -%NUGET% pack Grpc.Tools.nuspec || goto :error -%NUGET% pack Grpc.nuspec || goto :error +%NUGET% pack ..\..\vsprojects\nuget_package\grpc.native.csharp_ext.nuspec -Version %CORE_VERSION% || goto :error +%NUGET% pack Grpc.Auth\Grpc.Auth.nuspec -Symbols -Version %VERSION% || goto :error +%NUGET% pack Grpc.Core\Grpc.Core.nuspec -Symbols -Version %VERSION% -Properties GrpcNativeCsharpExtVersion=%CORE_VERSION% || goto :error +%NUGET% pack Grpc.HealthCheck\Grpc.HealthCheck.nuspec -Symbols -Version %VERSION% || goto :error +%NUGET% pack Grpc.Tools.nuspec -Version %VERSION% || goto :error +%NUGET% pack Grpc.nuspec -Version %VERSION% || goto :error goto :EOF diff --git a/src/csharp/generate_proto_csharp.sh b/src/csharp/generate_proto_csharp.sh index f980787bb7..6eb3887ea1 100755 --- a/src/csharp/generate_proto_csharp.sh +++ b/src/csharp/generate_proto_csharp.sh @@ -35,9 +35,13 @@ cd $(dirname $0) PLUGIN=protoc-gen-grpc=../../bins/opt/grpc_csharp_plugin EXAMPLES_DIR=Grpc.Examples INTEROP_DIR=Grpc.IntegrationTesting +HEALTHCHECK_DIR=Grpc.HealthCheck protoc --plugin=$PLUGIN --grpc_out=$EXAMPLES_DIR \ -I $EXAMPLES_DIR/proto $EXAMPLES_DIR/proto/math.proto protoc --plugin=$PLUGIN --grpc_out=$INTEROP_DIR \ -I $INTEROP_DIR/proto $INTEROP_DIR/proto/test.proto + +protoc --plugin=$PLUGIN --grpc_out=$HEALTHCHECK_DIR \ + -I $HEALTHCHECK_DIR/proto $HEALTHCHECK_DIR/proto/health.proto diff --git a/src/node/src/server.js b/src/node/src/server.js index 9ac428f3ee..00be400e61 100644 --- a/src/node/src/server.js +++ b/src/node/src/server.js @@ -55,7 +55,7 @@ var EventEmitter = require('events').EventEmitter; */ function handleError(call, error) { var status = { - code: grpc.status.INTERNAL, + code: grpc.status.UNKNOWN, details: 'Unknown Error', metadata: {} }; @@ -142,12 +142,12 @@ function setUpWritable(stream, serialize) { stream.on('finish', sendStatus); /** * Set the pending status to a given error status. If the error does not have - * code or details properties, the code will be set to grpc.status.INTERNAL + * code or details properties, the code will be set to grpc.status.UNKNOWN * and the details will be set to 'Unknown Error'. * @param {Error} err The error object */ function setStatus(err) { - var code = grpc.status.INTERNAL; + var code = grpc.status.UNKNOWN; var details = 'Unknown Error'; var metadata = {}; if (err.hasOwnProperty('message')) { diff --git a/src/php/bin/determine_extension_dir.sh b/src/php/bin/determine_extension_dir.sh index 6bbd934bf1..3c1fc297fa 100755 --- a/src/php/bin/determine_extension_dir.sh +++ b/src/php/bin/determine_extension_dir.sh @@ -27,12 +27,12 @@ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - set -e default_extension_dir=$(php-config --extension-dir) -if command -v brew >/dev/null && [ -d $(brew --prefix)/opt/grpc-php ]; then - # homebrew and the grpc-php formula are installed - extension_dir="-d extension_dir="$(brew --prefix)/opt/grpc-php +if command -v brew > /dev/null && \ + brew ls --versions | grep php5[56]-grpc > /dev/null; then + # the grpc php extension was installed by homebrew + : elif [ ! -e $default_extension_dir/grpc.so ]; then # the grpc extension is not found in the default PHP extension dir # try the source modules directory @@ -45,5 +45,5 @@ elif [ ! -e $default_extension_dir/grpc.so ]; then for f in $default_extension_dir/*.so; do ln -s $f $module_dir/$(basename $f) &> /dev/null || true done - extension_dir="-d extension_dir="$module_dir + extension_dir="-d extension_dir=${module_dir} -d extension=grpc.so" fi diff --git a/src/php/bin/interop_client.sh b/src/php/bin/interop_client.sh index 42e075cbe8..17b888dd4e 100755 --- a/src/php/bin/interop_client.sh +++ b/src/php/bin/interop_client.sh @@ -31,5 +31,5 @@ set -e cd $(dirname $0) source ./determine_extension_dir.sh -php $extension_dir -d extension=grpc.so \ +php $extension_dir \ ../tests/interop/interop_client.php $@ 1>&2 diff --git a/src/php/bin/run_gen_code_test.sh b/src/php/bin/run_gen_code_test.sh index 03a9101a45..6e56c7207c 100755 --- a/src/php/bin/run_gen_code_test.sh +++ b/src/php/bin/run_gen_code_test.sh @@ -31,8 +31,8 @@ set -e cd $(dirname $0) source ./determine_extension_dir.sh -export GRPC_TEST_HOST=localhost:7071 -php $extension_dir -d extension=grpc.so $(which phpunit) -v --debug --strict \ +export GRPC_TEST_HOST=localhost:50051 +php $extension_dir $(which phpunit) -v --debug --strict \ ../tests/generated_code/GeneratedCodeTest.php -php $extension_dir -d extension=grpc.so $(which phpunit) -v --debug --strict \ +php $extension_dir $(which phpunit) -v --debug --strict \ ../tests/generated_code/GeneratedCodeWithCallbackTest.php diff --git a/src/php/bin/run_tests.sh b/src/php/bin/run_tests.sh index 4c37285455..953f408ea8 100755 --- a/src/php/bin/run_tests.sh +++ b/src/php/bin/run_tests.sh @@ -33,5 +33,5 @@ set -e cd $(dirname $0) source ./determine_extension_dir.sh -php $extension_dir -d extension=grpc.so $(which phpunit) -v --debug --strict \ +php $extension_dir $(which phpunit) -v --debug --strict \ ../tests/unit_tests diff --git a/src/python/src/grpc/_links/__init__.py b/src/python/src/grpc/_links/__init__.py new file mode 100644 index 0000000000..7086519106 --- /dev/null +++ b/src/python/src/grpc/_links/__init__.py @@ -0,0 +1,30 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + diff --git a/src/python/src/grpc/_links/_lonely_invocation_link_test.py b/src/python/src/grpc/_links/_lonely_invocation_link_test.py new file mode 100644 index 0000000000..3d629f4387 --- /dev/null +++ b/src/python/src/grpc/_links/_lonely_invocation_link_test.py @@ -0,0 +1,88 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""A test of invocation-side code unconnected to an RPC server.""" + +import unittest + +from grpc._adapter import _intermediary_low +from grpc._links import invocation +from grpc.framework.common import test_constants +from grpc.framework.interfaces.links import links +from grpc.framework.interfaces.links import test_cases +from grpc.framework.interfaces.links import test_utilities + +_NULL_BEHAVIOR = lambda unused_argument: None + + +class LonelyInvocationLinkTest(unittest.TestCase): + + def testUpAndDown(self): + channel = _intermediary_low.Channel('nonexistent:54321', None) + invocation_link = invocation.invocation_link(channel, 'nonexistent', {}, {}) + + invocation_link.start() + invocation_link.stop() + + def _test_lonely_invocation_with_termination(self, termination): + test_operation_id = object() + test_group = 'test package.Test Service' + test_method = 'test method' + invocation_link_mate = test_utilities.RecordingLink() + + channel = _intermediary_low.Channel('nonexistent:54321', None) + invocation_link = invocation.invocation_link( + channel, 'nonexistent', {(test_group, test_method): _NULL_BEHAVIOR}, + {(test_group, test_method): _NULL_BEHAVIOR}) + invocation_link.join_link(invocation_link_mate) + invocation_link.start() + + ticket = links.Ticket( + test_operation_id, 0, test_group, test_method, + links.Ticket.Subscription.FULL, test_constants.SHORT_TIMEOUT, 1, None, + None, None, None, None, termination) + invocation_link.accept_ticket(ticket) + invocation_link_mate.block_until_tickets_satisfy(test_cases.terminated) + + invocation_link.stop() + + self.assertIsNot( + invocation_link_mate.tickets()[-1].termination, + links.Ticket.Termination.COMPLETION) + + def testLonelyInvocationLinkWithCommencementTicket(self): + self._test_lonely_invocation_with_termination(None) + + def testLonelyInvocationLinkWithEntireTicket(self): + self._test_lonely_invocation_with_termination( + links.Ticket.Termination.COMPLETION) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/python/src/grpc/_links/_proto_scenarios.py b/src/python/src/grpc/_links/_proto_scenarios.py new file mode 100644 index 0000000000..ccf3c29782 --- /dev/null +++ b/src/python/src/grpc/_links/_proto_scenarios.py @@ -0,0 +1,261 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Test scenarios using protocol buffers.""" + +import abc +import threading + +from grpc._junkdrawer import math_pb2 + + +class ProtoScenario(object): + """An RPC test scenario using protocol buffers.""" + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def group_and_method(self): + """Access the test group and method. + + Returns: + The test group and method as a pair. + """ + raise NotImplementedError() + + @abc.abstractmethod + def serialize_request(self, request): + """Serialize a request protocol buffer. + + Args: + request: A request protocol buffer. + + Returns: + The bytestring serialization of the given request protocol buffer. + """ + raise NotImplementedError() + + @abc.abstractmethod + def deserialize_request(self, request_bytestring): + """Deserialize a request protocol buffer. + + Args: + request_bytestring: The bytestring serialization of a request protocol + buffer. + + Returns: + The request protocol buffer deserialized from the given byte string. + """ + raise NotImplementedError() + + @abc.abstractmethod + def serialize_response(self, response): + """Serialize a response protocol buffer. + + Args: + response: A response protocol buffer. + + Returns: + The bytestring serialization of the given response protocol buffer. + """ + raise NotImplementedError() + + @abc.abstractmethod + def deserialize_response(self, response_bytestring): + """Deserialize a response protocol buffer. + + Args: + response_bytestring: The bytestring serialization of a response protocol + buffer. + + Returns: + The response protocol buffer deserialized from the given byte string. + """ + raise NotImplementedError() + + @abc.abstractmethod + def requests(self): + """Access the sequence of requests for this scenario. + + Returns: + A sequence of request protocol buffers. + """ + raise NotImplementedError() + + @abc.abstractmethod + def response_for_request(self, request): + """Access the response for a particular request. + + Args: + request: A request protocol buffer. + + Returns: + The response protocol buffer appropriate for the given request. + """ + raise NotImplementedError() + + @abc.abstractmethod + def verify_requests(self, experimental_requests): + """Verify the requests transmitted through the system under test. + + Args: + experimental_requests: The request protocol buffers transmitted through + the system under test. + + Returns: + True if the requests satisfy this test scenario; False otherwise. + """ + raise NotImplementedError() + + @abc.abstractmethod + def verify_responses(self, experimental_responses): + """Verify the responses transmitted through the system under test. + + Args: + experimental_responses: The response protocol buffers transmitted through + the system under test. + + Returns: + True if the responses satisfy this test scenario; False otherwise. + """ + raise NotImplementedError() + + +class EmptyScenario(ProtoScenario): + """A scenario that transmits no protocol buffers in either direction.""" + + def group_and_method(self): + return 'math.Math', 'DivMany' + + def serialize_request(self, request): + raise ValueError('This should not be necessary to call!') + + def deserialize_request(self, request_bytestring): + raise ValueError('This should not be necessary to call!') + + def serialize_response(self, response): + raise ValueError('This should not be necessary to call!') + + def deserialize_response(self, response_bytestring): + raise ValueError('This should not be necessary to call!') + + def requests(self): + return () + + def response_for_request(self, request): + raise ValueError('This should not be necessary to call!') + + def verify_requests(self, experimental_requests): + return not experimental_requests + + def verify_responses(self, experimental_responses): + return not experimental_responses + + +class BidirectionallyUnaryScenario(ProtoScenario): + """A scenario that transmits no protocol buffers in either direction.""" + + _DIVIDEND = 59 + _DIVISOR = 7 + _QUOTIENT = 8 + _REMAINDER = 3 + + _REQUEST = math_pb2.DivArgs(dividend=_DIVIDEND, divisor=_DIVISOR) + _RESPONSE = math_pb2.DivReply(quotient=_QUOTIENT, remainder=_REMAINDER) + + def group_and_method(self): + return 'math.Math', 'Div' + + def serialize_request(self, request): + return request.SerializeToString() + + def deserialize_request(self, request_bytestring): + return math_pb2.DivArgs.FromString(request_bytestring) + + def serialize_response(self, response): + return response.SerializeToString() + + def deserialize_response(self, response_bytestring): + return math_pb2.DivReply.FromString(response_bytestring) + + def requests(self): + return [self._REQUEST] + + def response_for_request(self, request): + return self._RESPONSE + + def verify_requests(self, experimental_requests): + return tuple(experimental_requests) == (self._REQUEST,) + + def verify_responses(self, experimental_responses): + return tuple(experimental_responses) == (self._RESPONSE,) + + +class BidirectionallyStreamingScenario(ProtoScenario): + """A scenario that transmits no protocol buffers in either direction.""" + + _STREAM_LENGTH = 200 + _REQUESTS = tuple( + math_pb2.DivArgs(dividend=59 + index, divisor=7 + index) + for index in range(_STREAM_LENGTH)) + + def __init__(self): + self._lock = threading.Lock() + self._responses = [] + + def group_and_method(self): + return 'math.Math', 'DivMany' + + def serialize_request(self, request): + return request.SerializeToString() + + def deserialize_request(self, request_bytestring): + return math_pb2.DivArgs.FromString(request_bytestring) + + def serialize_response(self, response): + return response.SerializeToString() + + def deserialize_response(self, response_bytestring): + return math_pb2.DivReply.FromString(response_bytestring) + + def requests(self): + return self._REQUESTS + + def response_for_request(self, request): + quotient, remainder = divmod(request.dividend, request.divisor) + response = math_pb2.DivReply(quotient=quotient, remainder=remainder) + with self._lock: + self._responses.append(response) + return response + + def verify_requests(self, experimental_requests): + return tuple(experimental_requests) == self._REQUESTS + + def verify_responses(self, experimental_responses): + with self._lock: + return tuple(experimental_responses) == tuple(self._responses) diff --git a/src/python/src/grpc/_links/_transmission_test.py b/src/python/src/grpc/_links/_transmission_test.py new file mode 100644 index 0000000000..c5ef1edb25 --- /dev/null +++ b/src/python/src/grpc/_links/_transmission_test.py @@ -0,0 +1,226 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Tests transmission of tickets across gRPC-on-the-wire.""" + +import unittest + +from grpc._adapter import _intermediary_low +from grpc._links import _proto_scenarios +from grpc._links import invocation +from grpc._links import service +from grpc.framework.common import test_constants +from grpc.framework.interfaces.links import links +from grpc.framework.interfaces.links import test_cases +from grpc.framework.interfaces.links import test_utilities + +_IDENTITY = lambda x: x + + +class TransmissionTest(test_cases.TransmissionTest, unittest.TestCase): + + def create_transmitting_links(self): + service_link = service.service_link( + {self.group_and_method(): self.deserialize_request}, + {self.group_and_method(): self.serialize_response}) + port = service_link.add_port(0, None) + service_link.start() + channel = _intermediary_low.Channel('localhost:%d' % port, None) + invocation_link = invocation.invocation_link( + channel, 'localhost', + {self.group_and_method(): self.serialize_request}, + {self.group_and_method(): self.deserialize_response}) + invocation_link.start() + return invocation_link, service_link + + def destroy_transmitting_links(self, invocation_side_link, service_side_link): + invocation_side_link.stop() + service_side_link.stop_gracefully() + + def create_invocation_initial_metadata(self): + return ( + ('first invocation initial metadata key', 'just a string value'), + ('second invocation initial metadata key', '0123456789'), + ('third invocation initial metadata key-bin', '\x00\x57' * 100), + ) + + def create_invocation_terminal_metadata(self): + return None + + def create_service_initial_metadata(self): + return ( + ('first service initial metadata key', 'just another string value'), + ('second service initial metadata key', '9876543210'), + ('third service initial metadata key-bin', '\x00\x59\x02' * 100), + ) + + def create_service_terminal_metadata(self): + return ( + ('first service terminal metadata key', 'yet another string value'), + ('second service terminal metadata key', 'abcdefghij'), + ('third service terminal metadata key-bin', '\x00\x37' * 100), + ) + + def create_invocation_completion(self): + return None, None + + def create_service_completion(self): + return _intermediary_low.Code.OK, 'An exuberant test "details" message!' + + def assertMetadataEqual(self, original_metadata, transmitted_metadata): + self.assertSequenceEqual(original_metadata, transmitted_metadata) + + +class RoundTripTest(unittest.TestCase): + + def testZeroMessageRoundTrip(self): + test_operation_id = object() + test_group = 'test package.Test Group' + test_method = 'test method' + identity_transformation = {(test_group, test_method): _IDENTITY} + test_code = _intermediary_low.Code.OK + test_message = 'a test message' + + service_link = service.service_link( + identity_transformation, identity_transformation) + service_mate = test_utilities.RecordingLink() + service_link.join_link(service_mate) + port = service_link.add_port(0, None) + service_link.start() + channel = _intermediary_low.Channel('localhost:%d' % port, None) + invocation_link = invocation.invocation_link( + channel, 'localhost', identity_transformation, identity_transformation) + invocation_mate = test_utilities.RecordingLink() + invocation_link.join_link(invocation_mate) + invocation_link.start() + + invocation_ticket = links.Ticket( + test_operation_id, 0, test_group, test_method, + links.Ticket.Subscription.FULL, test_constants.LONG_TIMEOUT, None, None, + None, None, None, None, links.Ticket.Termination.COMPLETION) + invocation_link.accept_ticket(invocation_ticket) + service_mate.block_until_tickets_satisfy(test_cases.terminated) + + service_ticket = links.Ticket( + service_mate.tickets()[-1].operation_id, 0, None, None, None, None, + None, None, None, None, test_code, test_message, + links.Ticket.Termination.COMPLETION) + service_link.accept_ticket(service_ticket) + invocation_mate.block_until_tickets_satisfy(test_cases.terminated) + + invocation_link.stop() + service_link.stop_gracefully() + + self.assertIs( + service_mate.tickets()[-1].termination, + links.Ticket.Termination.COMPLETION) + self.assertIs( + invocation_mate.tickets()[-1].termination, + links.Ticket.Termination.COMPLETION) + + def _perform_scenario_test(self, scenario): + test_operation_id = object() + test_group, test_method = scenario.group_and_method() + test_code = _intermediary_low.Code.OK + test_message = 'a scenario test message' + + service_link = service.service_link( + {(test_group, test_method): scenario.deserialize_request}, + {(test_group, test_method): scenario.serialize_response}) + service_mate = test_utilities.RecordingLink() + service_link.join_link(service_mate) + port = service_link.add_port(0, None) + service_link.start() + channel = _intermediary_low.Channel('localhost:%d' % port, None) + invocation_link = invocation.invocation_link( + channel, 'localhost', + {(test_group, test_method): scenario.serialize_request}, + {(test_group, test_method): scenario.deserialize_response}) + invocation_mate = test_utilities.RecordingLink() + invocation_link.join_link(invocation_mate) + invocation_link.start() + + invocation_ticket = links.Ticket( + test_operation_id, 0, test_group, test_method, + links.Ticket.Subscription.FULL, test_constants.LONG_TIMEOUT, None, None, + None, None, None, None, None) + invocation_link.accept_ticket(invocation_ticket) + requests = scenario.requests() + for request_index, request in enumerate(requests): + request_ticket = links.Ticket( + test_operation_id, 1 + request_index, None, None, None, None, 1, None, + request, None, None, None, None) + invocation_link.accept_ticket(request_ticket) + service_mate.block_until_tickets_satisfy( + test_cases.at_least_n_payloads_received_predicate(1 + request_index)) + response_ticket = links.Ticket( + service_mate.tickets()[0].operation_id, request_index, None, None, + None, None, 1, None, scenario.response_for_request(request), None, + None, None, None) + service_link.accept_ticket(response_ticket) + invocation_mate.block_until_tickets_satisfy( + test_cases.at_least_n_payloads_received_predicate(1 + request_index)) + request_count = len(requests) + invocation_completion_ticket = links.Ticket( + test_operation_id, request_count + 1, None, None, None, None, None, + None, None, None, None, None, links.Ticket.Termination.COMPLETION) + invocation_link.accept_ticket(invocation_completion_ticket) + service_mate.block_until_tickets_satisfy(test_cases.terminated) + service_completion_ticket = links.Ticket( + service_mate.tickets()[0].operation_id, request_count, None, None, None, + None, None, None, None, None, test_code, test_message, + links.Ticket.Termination.COMPLETION) + service_link.accept_ticket(service_completion_ticket) + invocation_mate.block_until_tickets_satisfy(test_cases.terminated) + + invocation_link.stop() + service_link.stop_gracefully() + + observed_requests = tuple( + ticket.payload for ticket in service_mate.tickets() + if ticket.payload is not None) + observed_responses = tuple( + ticket.payload for ticket in invocation_mate.tickets() + if ticket.payload is not None) + self.assertTrue(scenario.verify_requests(observed_requests)) + self.assertTrue(scenario.verify_responses(observed_responses)) + + def testEmptyScenario(self): + self._perform_scenario_test(_proto_scenarios.EmptyScenario()) + + def testBidirectionallyUnaryScenario(self): + self._perform_scenario_test(_proto_scenarios.BidirectionallyUnaryScenario()) + + def testBidirectionallyStreamingScenario(self): + self._perform_scenario_test( + _proto_scenarios.BidirectionallyStreamingScenario()) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/src/python/src/grpc/_links/invocation.py b/src/python/src/grpc/_links/invocation.py new file mode 100644 index 0000000000..0058ae91f8 --- /dev/null +++ b/src/python/src/grpc/_links/invocation.py @@ -0,0 +1,363 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""The RPC-invocation-side bridge between RPC Framework and GRPC-on-the-wire.""" + +import abc +import enum +import logging +import threading +import time + +from grpc._adapter import _intermediary_low +from grpc.framework.foundation import activated +from grpc.framework.foundation import logging_pool +from grpc.framework.foundation import relay +from grpc.framework.interfaces.links import links + + +@enum.unique +class _Read(enum.Enum): + AWAITING_METADATA = 'awaiting metadata' + READING = 'reading' + AWAITING_ALLOWANCE = 'awaiting allowance' + CLOSED = 'closed' + + +@enum.unique +class _HighWrite(enum.Enum): + OPEN = 'open' + CLOSED = 'closed' + + +@enum.unique +class _LowWrite(enum.Enum): + OPEN = 'OPEN' + ACTIVE = 'ACTIVE' + CLOSED = 'CLOSED' + + +class _RPCState(object): + + def __init__( + self, call, request_serializer, response_deserializer, sequence_number, + read, allowance, high_write, low_write): + self.call = call + self.request_serializer = request_serializer + self.response_deserializer = response_deserializer + self.sequence_number = sequence_number + self.read = read + self.allowance = allowance + self.high_write = high_write + self.low_write = low_write + + +class _Kernel(object): + + def __init__( + self, channel, host, request_serializers, response_deserializers, + ticket_relay): + self._lock = threading.Lock() + self._channel = channel + self._host = host + self._request_serializers = request_serializers + self._response_deserializers = response_deserializers + self._relay = ticket_relay + + self._completion_queue = None + self._rpc_states = None + self._pool = None + + def _on_write_event(self, operation_id, unused_event, rpc_state): + if rpc_state.high_write is _HighWrite.CLOSED: + rpc_state.call.complete(operation_id) + rpc_state.low_write = _LowWrite.CLOSED + else: + ticket = links.Ticket( + operation_id, rpc_state.sequence_number, None, None, None, None, 1, + None, None, None, None, None, None) + rpc_state.sequence_number += 1 + self._relay.add_value(ticket) + rpc_state.low_write = _LowWrite.OPEN + + def _on_read_event(self, operation_id, event, rpc_state): + if event.bytes is None: + rpc_state.read = _Read.CLOSED + else: + if 0 < rpc_state.allowance: + rpc_state.allowance -= 1 + rpc_state.call.read(operation_id) + else: + rpc_state.read = _Read.AWAITING_ALLOWANCE + ticket = links.Ticket( + operation_id, rpc_state.sequence_number, None, None, None, None, None, + None, rpc_state.response_deserializer(event.bytes), None, None, None, + None) + rpc_state.sequence_number += 1 + self._relay.add_value(ticket) + + def _on_metadata_event(self, operation_id, event, rpc_state): + rpc_state.allowance -= 1 + rpc_state.call.read(operation_id) + rpc_state.read = _Read.READING + ticket = links.Ticket( + operation_id, rpc_state.sequence_number, None, None, + links.Ticket.Subscription.FULL, None, None, event.metadata, None, None, + None, None, None) + rpc_state.sequence_number += 1 + self._relay.add_value(ticket) + + def _on_finish_event(self, operation_id, event, rpc_state): + self._rpc_states.pop(operation_id, None) + if event.status.code is _intermediary_low.Code.OK: + termination = links.Ticket.Termination.COMPLETION + elif event.status.code is _intermediary_low.Code.CANCELLED: + termination = links.Ticket.Termination.CANCELLATION + elif event.status.code is _intermediary_low.Code.DEADLINE_EXCEEDED: + termination = links.Ticket.Termination.EXPIRATION + else: + termination = links.Ticket.Termination.TRANSMISSION_FAILURE + ticket = links.Ticket( + operation_id, rpc_state.sequence_number, None, None, None, None, None, + None, None, event.metadata, event.status.code, event.status.details, + termination) + rpc_state.sequence_number += 1 + self._relay.add_value(ticket) + + def _spin(self, completion_queue): + while True: + event = completion_queue.get(None) + if event.kind is _intermediary_low.Event.Kind.STOP: + return + operation_id = event.tag + with self._lock: + if self._completion_queue is None: + continue + rpc_state = self._rpc_states.get(operation_id) + if rpc_state is not None: + if event.kind is _intermediary_low.Event.Kind.WRITE_ACCEPTED: + self._on_write_event(operation_id, event, rpc_state) + elif event.kind is _intermediary_low.Event.Kind.METADATA_ACCEPTED: + self._on_metadata_event(operation_id, event, rpc_state) + elif event.kind is _intermediary_low.Event.Kind.READ_ACCEPTED: + self._on_read_event(operation_id, event, rpc_state) + elif event.kind is _intermediary_low.Event.Kind.FINISH: + self._on_finish_event(operation_id, event, rpc_state) + elif event.kind is _intermediary_low.Event.Kind.COMPLETE_ACCEPTED: + pass + else: + logging.error('Illegal RPC event! %s', (event,)) + + def _invoke( + self, operation_id, group, method, initial_metadata, payload, termination, + timeout, allowance): + """Invoke an RPC. + + Args: + operation_id: Any object to be used as an operation ID for the RPC. + group: The group to which the RPC method belongs. + method: The RPC method name. + initial_metadata: The initial metadata object for the RPC. + payload: A payload object for the RPC or None if no payload was given at + invocation-time. + termination: A links.Ticket.Termination value or None indicated whether or + not more writes will follow from this side of the RPC. + timeout: A duration of time in seconds to allow for the RPC. + allowance: The number of payloads (beyond the free first one) that the + local ticket exchange mate has granted permission to be read. + """ + if termination is links.Ticket.Termination.COMPLETION: + high_write = _HighWrite.CLOSED + elif termination is None: + high_write = _HighWrite.OPEN + else: + return + + request_serializer = self._request_serializers.get((group, method)) + response_deserializer = self._response_deserializers.get((group, method)) + if request_serializer is None or response_deserializer is None: + cancellation_ticket = links.Ticket( + operation_id, 0, None, None, None, None, None, None, None, None, None, + None, links.Ticket.Termination.CANCELLATION) + self._relay.add_value(cancellation_ticket) + return + + call = _intermediary_low.Call( + self._channel, self._completion_queue, '/%s/%s' % (group, method), + self._host, time.time() + timeout) + if initial_metadata is not None: + for metadata_key, metadata_value in initial_metadata: + call.add_metadata(metadata_key, metadata_value) + call.invoke(self._completion_queue, operation_id, operation_id) + if payload is None: + if high_write is _HighWrite.CLOSED: + call.complete(operation_id) + low_write = _LowWrite.CLOSED + else: + low_write = _LowWrite.OPEN + else: + call.write(request_serializer(payload), operation_id) + low_write = _LowWrite.ACTIVE + self._rpc_states[operation_id] = _RPCState( + call, request_serializer, response_deserializer, 0, + _Read.AWAITING_METADATA, 1 if allowance is None else (1 + allowance), + high_write, low_write) + + def _advance(self, operation_id, rpc_state, payload, termination, allowance): + if payload is not None: + rpc_state.call.write(rpc_state.request_serializer(payload), operation_id) + rpc_state.low_write = _LowWrite.ACTIVE + + if allowance is not None: + if rpc_state.read is _Read.AWAITING_ALLOWANCE: + rpc_state.allowance += allowance - 1 + rpc_state.call.read(operation_id) + rpc_state.read = _Read.READING + else: + rpc_state.allowance += allowance + + if termination is links.Ticket.Termination.COMPLETION: + rpc_state.high_write = _HighWrite.CLOSED + if rpc_state.low_write is _LowWrite.OPEN: + rpc_state.call.complete(operation_id) + rpc_state.low_write = _LowWrite.CLOSED + elif termination is not None: + rpc_state.call.cancel() + + def add_ticket(self, ticket): + with self._lock: + if self._completion_queue is None: + return + if ticket.sequence_number == 0: + self._invoke( + ticket.operation_id, ticket.group, ticket.method, + ticket.initial_metadata, ticket.payload, ticket.termination, + ticket.timeout, ticket.allowance) + else: + rpc_state = self._rpc_states.get(ticket.operation_id) + if rpc_state is not None: + self._advance( + ticket.operation_id, rpc_state, ticket.payload, + ticket.termination, ticket.allowance) + + def start(self): + """Starts this object. + + This method must be called before attempting to exchange tickets with this + object. + """ + with self._lock: + self._completion_queue = _intermediary_low.CompletionQueue() + self._rpc_states = {} + self._pool = logging_pool.pool(1) + self._pool.submit(self._spin, self._completion_queue) + + def stop(self): + """Stops this object. + + This method must be called for proper termination of this object, and no + attempts to exchange tickets with this object may be made after this method + has been called. + """ + with self._lock: + self._completion_queue.stop() + self._completion_queue = None + pool = self._pool + self._pool = None + self._rpc_states = None + pool.shutdown(wait=True) + + +class InvocationLink(links.Link, activated.Activated): + """A links.Link for use on the invocation-side of a gRPC connection. + + Implementations of this interface are only valid for use when activated. + """ + __metaclass__ = abc.ABCMeta + + +class _InvocationLink(InvocationLink): + + def __init__( + self, channel, host, request_serializers, response_deserializers): + self._relay = relay.relay(None) + self._kernel = _Kernel( + channel, host, request_serializers, response_deserializers, self._relay) + + def _start(self): + self._relay.start() + self._kernel.start() + return self + + def _stop(self): + self._kernel.stop() + self._relay.stop() + + def accept_ticket(self, ticket): + """See links.Link.accept_ticket for specification.""" + self._kernel.add_ticket(ticket) + + def join_link(self, link): + """See links.Link.join_link for specification.""" + self._relay.set_behavior(link.accept_ticket) + + def __enter__(self): + """See activated.Activated.__enter__ for specification.""" + return self._start() + + def __exit__(self, exc_type, exc_val, exc_tb): + """See activated.Activated.__exit__ for specification.""" + self._stop() + return False + + def start(self): + """See activated.Activated.start for specification.""" + return self._start() + + def stop(self): + """See activated.Activated.stop for specification.""" + self._stop() + + +def invocation_link(channel, host, request_serializers, response_deserializers): + """Creates an InvocationLink. + + Args: + channel: A channel for use by the link. + host: The host to specify when invoking RPCs. + request_serializers: A dict from group-method pair to request object + serialization behavior. + response_deserializers: A dict from group-method pair to response object + deserialization behavior. + + Returns: + An InvocationLink. + """ + return _InvocationLink( + channel, host, request_serializers, response_deserializers) diff --git a/src/python/src/grpc/_links/service.py b/src/python/src/grpc/_links/service.py new file mode 100644 index 0000000000..7783e91824 --- /dev/null +++ b/src/python/src/grpc/_links/service.py @@ -0,0 +1,402 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""The RPC-service-side bridge between RPC Framework and GRPC-on-the-wire.""" + +import abc +import enum +import logging +import threading +import time + +from grpc._adapter import _intermediary_low +from grpc.framework.foundation import logging_pool +from grpc.framework.foundation import relay +from grpc.framework.interfaces.links import links + + +@enum.unique +class _Read(enum.Enum): + READING = 'reading' + AWAITING_ALLOWANCE = 'awaiting allowance' + CLOSED = 'closed' + + +@enum.unique +class _HighWrite(enum.Enum): + OPEN = 'open' + CLOSED = 'closed' + + +@enum.unique +class _LowWrite(enum.Enum): + """The possible categories of low-level write state.""" + + OPEN = 'OPEN' + ACTIVE = 'ACTIVE' + CLOSED = 'CLOSED' + + +class _RPCState(object): + + def __init__( + self, request_deserializer, response_serializer, sequence_number, read, + allowance, high_write, low_write, premetadataed, terminal_metadata, code, + message): + self.request_deserializer = request_deserializer + self.response_serializer = response_serializer + self.sequence_number = sequence_number + self.read = read + self.allowance = allowance + self.high_write = high_write + self.low_write = low_write + self.premetadataed = premetadataed + self.terminal_metadata = terminal_metadata + self.code = code + self.message = message + + +def _metadatafy(call, metadata): + for metadata_key, metadata_value in metadata: + call.add_metadata(metadata_key, metadata_value) + + +class _Kernel(object): + + def __init__(self, request_deserializers, response_serializers, ticket_relay): + self._lock = threading.Lock() + self._request_deserializers = request_deserializers + self._response_serializers = response_serializers + self._relay = ticket_relay + + self._completion_queue = None + self._server = None + self._rpc_states = {} + self._pool = None + + def _on_service_acceptance_event(self, event, server): + server.service(None) + + service_acceptance = event.service_acceptance + call = service_acceptance.call + call.accept(self._completion_queue, call) + try: + group, method = service_acceptance.method.split('/')[1:3] + except ValueError: + logging.info('Illegal path "%s"!', service_acceptance.method) + return + request_deserializer = self._request_deserializers.get((group, method)) + response_serializer = self._response_serializers.get((group, method)) + if request_deserializer is None or response_serializer is None: + # TODO(nathaniel): Terminate the RPC with code NOT_FOUND. + call.cancel() + return + + call.read(call) + self._rpc_states[call] = _RPCState( + request_deserializer, response_serializer, 1, _Read.READING, 0, + _HighWrite.OPEN, _LowWrite.OPEN, False, None, None, None) + ticket = links.Ticket( + call, 0, group, method, links.Ticket.Subscription.FULL, + service_acceptance.deadline - time.time(), None, event.metadata, None, + None, None, None, None) + self._relay.add_value(ticket) + + def _on_read_event(self, event): + call = event.tag + rpc_state = self._rpc_states.get(call, None) + if rpc_state is None: + return + + if event.bytes is None: + rpc_state.read = _Read.CLOSED + payload = None + termination = links.Ticket.Termination.COMPLETION + else: + if 0 < rpc_state.allowance: + rpc_state.allowance -= 1 + call.read(call) + else: + rpc_state.read = _Read.AWAITING_ALLOWANCE + payload = rpc_state.request_deserializer(event.bytes) + termination = None + ticket = links.Ticket( + call, rpc_state.sequence_number, None, None, None, None, None, None, + payload, None, None, None, termination) + rpc_state.sequence_number += 1 + self._relay.add_value(ticket) + + def _on_write_event(self, event): + call = event.tag + rpc_state = self._rpc_states.get(call, None) + if rpc_state is None: + return + + if rpc_state.high_write is _HighWrite.CLOSED: + if rpc_state.terminal_metadata is not None: + _metadatafy(call, rpc_state.terminal_metadata) + call.status( + _intermediary_low.Status(rpc_state.code, rpc_state.message), call) + rpc_state.low_write = _LowWrite.CLOSED + else: + ticket = links.Ticket( + call, rpc_state.sequence_number, None, None, None, None, 1, None, + None, None, None, None, None) + rpc_state.sequence_number += 1 + self._relay.add_value(ticket) + rpc_state.low_write = _LowWrite.OPEN + + def _on_finish_event(self, event): + call = event.tag + rpc_state = self._rpc_states.pop(call, None) + if rpc_state is None: + return + code = event.status.code + if code is _intermediary_low.Code.OK: + return + + if code is _intermediary_low.Code.CANCELLED: + termination = links.Ticket.Termination.CANCELLATION + elif code is _intermediary_low.Code.DEADLINE_EXCEEDED: + termination = links.Ticket.Termination.EXPIRATION + else: + termination = links.Ticket.Termination.TRANSMISSION_FAILURE + ticket = links.Ticket( + call, rpc_state.sequence_number, None, None, None, None, None, None, + None, None, None, None, termination) + rpc_state.sequence_number += 1 + self._relay.add_value(ticket) + + def _spin(self, completion_queue, server): + while True: + event = completion_queue.get(None) + if event.kind is _intermediary_low.Event.Kind.STOP: + return + with self._lock: + if self._server is None: + continue + elif event.kind is _intermediary_low.Event.Kind.SERVICE_ACCEPTED: + self._on_service_acceptance_event(event, server) + elif event.kind is _intermediary_low.Event.Kind.READ_ACCEPTED: + self._on_read_event(event) + elif event.kind is _intermediary_low.Event.Kind.WRITE_ACCEPTED: + self._on_write_event(event) + elif event.kind is _intermediary_low.Event.Kind.COMPLETE_ACCEPTED: + pass + elif event.kind is _intermediary_low.Event.Kind.FINISH: + self._on_finish_event(event) + else: + logging.error('Illegal event! %s', (event,)) + + def add_ticket(self, ticket): + with self._lock: + if self._server is None: + return + call = ticket.operation_id + rpc_state = self._rpc_states.get(call) + if rpc_state is None: + return + + if ticket.initial_metadata is not None: + _metadatafy(call, ticket.initial_metadata) + call.premetadata() + rpc_state.premetadataed = True + elif not rpc_state.premetadataed: + if (ticket.terminal_metadata is not None or + ticket.payload is not None or + ticket.termination is links.Ticket.Termination.COMPLETION or + ticket.code is not None or + ticket.message is not None): + call.premetadata() + rpc_state.premetadataed = True + + if ticket.allowance is not None: + if rpc_state.read is _Read.AWAITING_ALLOWANCE: + rpc_state.allowance += ticket.allowance - 1 + call.read(call) + rpc_state.read = _Read.READING + else: + rpc_state.allowance += ticket.allowance + + if ticket.payload is not None: + call.write(rpc_state.response_serializer(ticket.payload), call) + rpc_state.low_write = _LowWrite.ACTIVE + + if ticket.terminal_metadata is not None: + rpc_state.terminal_metadata = ticket.terminal_metadata + if ticket.code is not None: + rpc_state.code = ticket.code + if ticket.message is not None: + rpc_state.message = ticket.message + + if ticket.termination is links.Ticket.Termination.COMPLETION: + rpc_state.high_write = _HighWrite.CLOSED + if rpc_state.low_write is _LowWrite.OPEN: + if rpc_state.terminal_metadata is not None: + _metadatafy(call, rpc_state.terminal_metadata) + status = _intermediary_low.Status( + _intermediary_low.Code.OK + if rpc_state.code is None else rpc_state.code, + '' if rpc_state.message is None else rpc_state.message) + call.status(status, call) + rpc_state.low_write = _LowWrite.CLOSED + elif ticket.termination is not None: + call.cancel() + self._rpc_states.pop(call, None) + + def add_port(self, port, server_credentials): + with self._lock: + address = '[::]:%d' % port + if self._server is None: + self._completion_queue = _intermediary_low.CompletionQueue() + self._server = _intermediary_low.Server(self._completion_queue) + if server_credentials is None: + return self._server.add_http2_addr(address) + else: + return self._server.add_secure_http2_addr(address, server_credentials) + + def start(self): + with self._lock: + if self._server is None: + self._completion_queue = _intermediary_low.CompletionQueue() + self._server = _intermediary_low.Server(self._completion_queue) + self._pool = logging_pool.pool(1) + self._pool.submit(self._spin, self._completion_queue, self._server) + self._server.start() + self._server.service(None) + + def graceful_stop(self): + with self._lock: + self._server.stop() + self._server = None + self._completion_queue.stop() + self._completion_queue = None + pool = self._pool + self._pool = None + self._rpc_states = None + pool.shutdown(wait=True) + + def immediate_stop(self): + # TODO(nathaniel): Implementation. + raise NotImplementedError( + 'TODO(nathaniel): after merge of rewritten lower layers') + + +class ServiceLink(links.Link): + """A links.Link for use on the service-side of a gRPC connection. + + Implementations of this interface are only valid for use between calls to + their start method and one of their stop methods. + """ + + @abc.abstractmethod + def add_port(self, port, server_credentials): + """Adds a port on which to service RPCs after this link has been started. + + Args: + port: The port on which to service RPCs, or zero to request that a port be + automatically selected and used. + server_credentials: A ServerCredentials object, or None for insecure + service. + + Returns: + A port on which RPCs will be serviced after this link has been started. + """ + raise NotImplementedError() + + @abc.abstractmethod + def start(self): + """Starts this object. + + This method must be called before attempting to use this Link in ticket + exchange. + """ + raise NotImplementedError() + + @abc.abstractmethod + def stop_gracefully(self): + """Stops this link. + + New RPCs will be rejected as soon as this method is called, but ongoing RPCs + will be allowed to continue until they terminate. This method blocks until + all RPCs have terminated. + """ + raise NotImplementedError() + + @abc.abstractmethod + def stop_immediately(self): + """Stops this link. + + All in-progress RPCs will be terminated immediately. + """ + raise NotImplementedError() + + +class _ServiceLink(ServiceLink): + + def __init__(self, request_deserializers, response_serializers): + self._relay = relay.relay(None) + self._kernel = _Kernel( + request_deserializers, response_serializers, self._relay) + + def accept_ticket(self, ticket): + self._kernel.add_ticket(ticket) + + def join_link(self, link): + self._relay.set_behavior(link.accept_ticket) + + def add_port(self, port, server_credentials): + return self._kernel.add_port(port, server_credentials) + + def start(self): + self._relay.start() + return self._kernel.start() + + def stop_gracefully(self): + self._kernel.graceful_stop() + self._relay.stop() + + def stop_immediately(self): + self._kernel.immediate_stop() + self._relay.stop() + + +def service_link(request_deserializers, response_serializers): + """Creates a ServiceLink. + + Args: + request_deserializers: A dict from group-method pair to request object + deserialization behavior. + response_serializers: A dict from group-method pair to response ojbect + serialization behavior. + + Returns: + A ServiceLink. + """ + return _ServiceLink(request_deserializers, response_serializers) diff --git a/src/python/src/grpc/framework/common/test_constants.py b/src/python/src/grpc/framework/common/test_constants.py new file mode 100644 index 0000000000..237b8754ed --- /dev/null +++ b/src/python/src/grpc/framework/common/test_constants.py @@ -0,0 +1,37 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Constants shared among tests throughout RPC Framework.""" + +# Value for maximum duration in seconds of RPCs that may time out as part of a +# test. +SHORT_TIMEOUT = 4 +# Absurdly large value for maximum duration in seconds for should-not-time-out +# RPCs made during tests. +LONG_TIMEOUT = 3000 diff --git a/src/python/src/grpc/framework/common/test_control.py b/src/python/src/grpc/framework/common/test_control.py new file mode 100644 index 0000000000..3960c4e649 --- /dev/null +++ b/src/python/src/grpc/framework/common/test_control.py @@ -0,0 +1,87 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Code for instructing systems under test to block or fail.""" + +import abc +import contextlib +import threading + + +class Control(object): + """An object that accepts program control from a system under test. + + Systems under test passed a Control should call its control() method + frequently during execution. The control() method may block, raise an + exception, or do nothing, all according to the enclosing test's desire for + the system under test to simulate hanging, failing, or functioning. + """ + + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def control(self): + """Potentially does anything.""" + raise NotImplementedError() + + +class PauseFailControl(Control): + """A Control that can be used to pause or fail code under control.""" + + def __init__(self): + self._condition = threading.Condition() + self._paused = False + self._fail = False + + def control(self): + with self._condition: + if self._fail: + raise ValueError() + + while self._paused: + self._condition.wait() + + @contextlib.contextmanager + def pause(self): + """Pauses code under control while controlling code is in context.""" + with self._condition: + self._paused = True + yield + with self._condition: + self._paused = False + self._condition.notify_all() + + @contextlib.contextmanager + def fail(self): + """Fails code under control while controlling code is in context.""" + with self._condition: + self._fail = True + yield + with self._condition: + self._fail = False diff --git a/src/python/src/grpc/framework/common/test_coverage.py b/src/python/src/grpc/framework/common/test_coverage.py new file mode 100644 index 0000000000..a7ed3582c4 --- /dev/null +++ b/src/python/src/grpc/framework/common/test_coverage.py @@ -0,0 +1,116 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Governs coverage for tests of RPCs throughout RPC Framework.""" + +import abc + +# This code is designed for use with the unittest module. +# pylint: disable=invalid-name + + +class Coverage(object): + """Specification of test coverage.""" + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def testSuccessfulUnaryRequestUnaryResponse(self): + raise NotImplementedError() + + @abc.abstractmethod + def testSuccessfulUnaryRequestStreamResponse(self): + raise NotImplementedError() + + @abc.abstractmethod + def testSuccessfulStreamRequestUnaryResponse(self): + raise NotImplementedError() + + @abc.abstractmethod + def testSuccessfulStreamRequestStreamResponse(self): + raise NotImplementedError() + + @abc.abstractmethod + def testSequentialInvocations(self): + raise NotImplementedError() + + @abc.abstractmethod + def testParallelInvocations(self): + raise NotImplementedError() + + @abc.abstractmethod + def testWaitingForSomeButNotAllParallelInvocations(self): + raise NotImplementedError() + + @abc.abstractmethod + def testCancelledUnaryRequestUnaryResponse(self): + raise NotImplementedError() + + @abc.abstractmethod + def testCancelledUnaryRequestStreamResponse(self): + raise NotImplementedError() + + @abc.abstractmethod + def testCancelledStreamRequestUnaryResponse(self): + raise NotImplementedError() + + @abc.abstractmethod + def testCancelledStreamRequestStreamResponse(self): + raise NotImplementedError() + + @abc.abstractmethod + def testExpiredUnaryRequestUnaryResponse(self): + raise NotImplementedError() + + @abc.abstractmethod + def testExpiredUnaryRequestStreamResponse(self): + raise NotImplementedError() + + @abc.abstractmethod + def testExpiredStreamRequestUnaryResponse(self): + raise NotImplementedError() + + @abc.abstractmethod + def testExpiredStreamRequestStreamResponse(self): + raise NotImplementedError() + + @abc.abstractmethod + def testFailedUnaryRequestUnaryResponse(self): + raise NotImplementedError() + + @abc.abstractmethod + def testFailedUnaryRequestStreamResponse(self): + raise NotImplementedError() + + @abc.abstractmethod + def testFailedStreamRequestUnaryResponse(self): + raise NotImplementedError() + + @abc.abstractmethod + def testFailedStreamRequestStreamResponse(self): + raise NotImplementedError() diff --git a/src/python/src/grpc/framework/foundation/relay.py b/src/python/src/grpc/framework/foundation/relay.py new file mode 100644 index 0000000000..9c23946552 --- /dev/null +++ b/src/python/src/grpc/framework/foundation/relay.py @@ -0,0 +1,175 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Implementations of in-order work deference.""" + +import abc +import enum +import threading + +from grpc.framework.foundation import activated +from grpc.framework.foundation import logging_pool + +_NULL_BEHAVIOR = lambda unused_value: None + + +class Relay(object): + """Performs work submitted to it in another thread. + + Performs work in the order in which work was submitted to it; otherwise there + would be no reason to use an implementation of this interface instead of a + thread pool. + """ + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def add_value(self, value): + """Adds a value to be passed to the behavior registered with this Relay. + + Args: + value: A value that will be passed to a call made in another thread to the + behavior registered with this Relay. + """ + raise NotImplementedError() + + @abc.abstractmethod + def set_behavior(self, behavior): + """Sets the behavior that this Relay should call when passed values. + + Args: + behavior: The behavior that this Relay should call in another thread when + passed a value, or None to have passed values ignored. + """ + raise NotImplementedError() + + +class _PoolRelay(activated.Activated, Relay): + + @enum.unique + class _State(enum.Enum): + INACTIVE = 'inactive' + IDLE = 'idle' + SPINNING = 'spinning' + + def __init__(self, pool, behavior): + self._condition = threading.Condition() + self._pool = pool + self._own_pool = pool is None + self._state = _PoolRelay._State.INACTIVE + self._activated = False + self._spinning = False + self._values = [] + self._behavior = _NULL_BEHAVIOR if behavior is None else behavior + + def _spin(self, behavior, value): + while True: + behavior(value) + with self._condition: + if self._values: + value = self._values.pop(0) + behavior = self._behavior + else: + self._state = _PoolRelay._State.IDLE + self._condition.notify_all() + break + + def add_value(self, value): + with self._condition: + if self._state is _PoolRelay._State.INACTIVE: + raise ValueError('add_value not valid on inactive Relay!') + elif self._state is _PoolRelay._State.IDLE: + self._pool.submit(self._spin, self._behavior, value) + self._state = _PoolRelay._State.SPINNING + else: + self._values.append(value) + + def set_behavior(self, behavior): + with self._condition: + self._behavior = _NULL_BEHAVIOR if behavior is None else behavior + + def _start(self): + with self._condition: + self._state = _PoolRelay._State.IDLE + if self._own_pool: + self._pool = logging_pool.pool(1) + return self + + def _stop(self): + with self._condition: + while self._state is _PoolRelay._State.SPINNING: + self._condition.wait() + if self._own_pool: + self._pool.shutdown(wait=True) + self._state = _PoolRelay._State.INACTIVE + + def __enter__(self): + return self._start() + + def __exit__(self, exc_type, exc_val, exc_tb): + self._stop() + return False + + def start(self): + return self._start() + + def stop(self): + self._stop() + + +def relay(behavior): + """Creates a Relay. + + Args: + behavior: The behavior to be called by the created Relay, or None to have + passed values dropped until a different behavior is given to the returned + Relay later. + + Returns: + An object that is both an activated.Activated and a Relay. The object is + only valid for use as a Relay when activated. + """ + return _PoolRelay(None, behavior) + + +def pool_relay(pool, behavior): + """Creates a Relay that uses a given thread pool. + + This object will make use of at most one thread in the given pool. + + Args: + pool: A futures.ThreadPoolExecutor for use by the created Relay. + behavior: The behavior to be called by the created Relay, or None to have + passed values dropped until a different behavior is given to the returned + Relay later. + + Returns: + An object that is both an activated.Activated and a Relay. The object is + only valid for use as a Relay when activated. + """ + return _PoolRelay(pool, behavior) diff --git a/src/python/src/grpc/framework/interfaces/__init__.py b/src/python/src/grpc/framework/interfaces/__init__.py new file mode 100644 index 0000000000..7086519106 --- /dev/null +++ b/src/python/src/grpc/framework/interfaces/__init__.py @@ -0,0 +1,30 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + diff --git a/src/python/src/grpc/framework/interfaces/links/__init__.py b/src/python/src/grpc/framework/interfaces/links/__init__.py new file mode 100644 index 0000000000..7086519106 --- /dev/null +++ b/src/python/src/grpc/framework/interfaces/links/__init__.py @@ -0,0 +1,30 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + diff --git a/src/python/src/grpc/framework/interfaces/links/links.py b/src/python/src/grpc/framework/interfaces/links/links.py new file mode 100644 index 0000000000..5ebbac8a6f --- /dev/null +++ b/src/python/src/grpc/framework/interfaces/links/links.py @@ -0,0 +1,124 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""The low-level ticket-exchanging-links interface of RPC Framework.""" + +import abc +import collections +import enum + + +class Ticket( + collections.namedtuple( + 'Ticket', + ['operation_id', 'sequence_number', 'group', 'method', 'subscription', + 'timeout', 'allowance', 'initial_metadata', 'payload', + 'terminal_metadata', 'code', 'message', 'termination'])): + """A sum type for all values sent from a front to a back. + + Attributes: + operation_id: A unique-with-respect-to-equality hashable object identifying + a particular operation. + sequence_number: A zero-indexed integer sequence number identifying the + ticket's place in the stream of tickets sent in one direction for the + particular operation. + group: The group to which the method of the operation belongs. Must be + present in the first ticket from invocation side to service side. Ignored + for all other tickets exchanged during the operation. + method: The name of an operation. Must be present in the first ticket from + invocation side to service side. Ignored for all other tickets exchanged + during the operation. + subscription: A Subscription value describing the interest one side has in + receiving information from the other side. Must be present in the first + ticket from either side. Ignored for all other tickets exchanged during + the operation. + timeout: A nonzero length of time (measured from the beginning of the + operation) to allow for the entire operation. Must be present in the first + ticket from invocation side to service side. Optional for all other + tickets exchanged during the operation. Receipt of a value from the other + side of the operation indicates the value in use by that side. Setting a + value on a later ticket allows either side to request time extensions (or + even time reductions!) on in-progress operations. + allowance: A positive integer granting permission for a number of payloads + to be transmitted to the communicating side of the operation, or None if + no additional allowance is being granted with this ticket. + initial_metadata: An optional metadata value communicated from one side to + the other at the beginning of the operation. May be non-None in at most + one ticket from each side. Any non-None value must appear no later than + the first payload value. + payload: A customer payload object. May be None. + terminal_metadata: A metadata value comminicated from one side to the other + at the end of the operation. May be non-None in the same ticket as + the code and message, but must be None for all earlier tickets. + code: A value communicated at operation completion. May be None. + message: A value communicated at operation completion. May be None. + termination: A Termination value describing the end of the operation, or + None if the operation has not yet terminated. If set, no further tickets + may be sent in the same direction. + """ + + @enum.unique + class Subscription(enum.Enum): + """Identifies the level of subscription of a side of an operation.""" + + NONE = 'none' + TERMINATION = 'termination' + FULL = 'full' + + @enum.unique + class Termination(enum.Enum): + """Identifies the termination of an operation.""" + + COMPLETION = 'completion' + CANCELLATION = 'cancellation' + EXPIRATION = 'expiration' + LOCAL_SHUTDOWN = 'local shutdown' + RECEPTION_FAILURE = 'reception failure' + TRANSMISSION_FAILURE = 'transmission failure' + LOCAL_FAILURE = 'local failure' + REMOTE_FAILURE = 'remote failure' + + +class Link(object): + """Accepts and emits tickets.""" + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def accept_ticket(self, ticket): + """Accept a Ticket. + + Args: + ticket: Any Ticket. + """ + raise NotImplementedError() + + @abc.abstractmethod + def join_link(self, link): + """Mates this object with a peer with which it will exchange tickets.""" + raise NotImplementedError() diff --git a/src/python/src/grpc/framework/interfaces/links/test_cases.py b/src/python/src/grpc/framework/interfaces/links/test_cases.py new file mode 100644 index 0000000000..3ac212ebdf --- /dev/null +++ b/src/python/src/grpc/framework/interfaces/links/test_cases.py @@ -0,0 +1,332 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Tests of the links interface of RPC Framework.""" + +# unittest is referenced from specification in this module. +import abc +import unittest # pylint: disable=unused-import + +from grpc.framework.common import test_constants +from grpc.framework.interfaces.links import links +from grpc.framework.interfaces.links import test_utilities + + +def at_least_n_payloads_received_predicate(n): + def predicate(ticket_sequence): + payload_count = 0 + for ticket in ticket_sequence: + if ticket.payload is not None: + payload_count += 1 + if n <= payload_count: + return True + else: + return False + return predicate + + +def terminated(ticket_sequence): + return ticket_sequence and ticket_sequence[-1].termination is not None + +_TRANSMISSION_GROUP = 'test.Group' +_TRANSMISSION_METHOD = 'TestMethod' + + +class TransmissionTest(object): + """Tests ticket transmission between two connected links. + + This class must be mixed into a unittest.TestCase that implements the abstract + methods it provides. + """ + __metaclass__ = abc.ABCMeta + + # This is a unittest.TestCase mix-in. + # pylint: disable=invalid-name + + @abc.abstractmethod + def create_transmitting_links(self): + """Creates two connected links for use in this test. + + Returns: + Two links.Links, the first of which will be used on the invocation side + of RPCs and the second of which will be used on the service side of + RPCs. + """ + raise NotImplementedError() + + @abc.abstractmethod + def destroy_transmitting_links(self, invocation_side_link, service_side_link): + """Destroys the two connected links created for this test. + + + Args: + invocation_side_link: The link used on the invocation side of RPCs in + this test. + service_side_link: The link used on the service side of RPCs in this + test. + """ + raise NotImplementedError() + + @abc.abstractmethod + def create_invocation_initial_metadata(self): + """Creates a value for use as invocation-side initial metadata. + + Returns: + A metadata value appropriate for use as invocation-side initial metadata + or None if invocation-side initial metadata transmission is not + supported by the links under test. + """ + raise NotImplementedError() + + @abc.abstractmethod + def create_invocation_terminal_metadata(self): + """Creates a value for use as invocation-side terminal metadata. + + Returns: + A metadata value appropriate for use as invocation-side terminal + metadata or None if invocation-side terminal metadata transmission is + not supported by the links under test. + """ + raise NotImplementedError() + + @abc.abstractmethod + def create_service_initial_metadata(self): + """Creates a value for use as service-side initial metadata. + + Returns: + A metadata value appropriate for use as service-side initial metadata or + None if service-side initial metadata transmission is not supported by + the links under test. + """ + raise NotImplementedError() + + @abc.abstractmethod + def create_service_terminal_metadata(self): + """Creates a value for use as service-side terminal metadata. + + Returns: + A metadata value appropriate for use as service-side terminal metadata or + None if service-side terminal metadata transmission is not supported by + the links under test. + """ + raise NotImplementedError() + + @abc.abstractmethod + def create_invocation_completion(self): + """Creates values for use as invocation-side code and message. + + Returns: + An invocation-side code value and an invocation-side message value. + Either or both may be None if invocation-side code and/or + invocation-side message transmission is not supported by the links + under test. + """ + raise NotImplementedError() + + @abc.abstractmethod + def create_service_completion(self): + """Creates values for use as service-side code and message. + + Returns: + A service-side code value and a service-side message value. Either or + both may be None if service-side code and/or service-side message + transmission is not supported by the links under test. + """ + raise NotImplementedError() + + @abc.abstractmethod + def assertMetadataEqual(self, original_metadata, transmitted_metadata): + """Asserts that two metadata objects are equal. + + Args: + original_metadata: A metadata object used in this test. + transmitted_metadata: A metadata object obtained after transmission + through the system under test. + + Raises: + AssertionError: if the two metadata objects are not equal. + """ + raise NotImplementedError() + + def group_and_method(self): + """Returns the group and method used in this test case. + + Returns: + A pair of the group and method used in this test case. + """ + return _TRANSMISSION_GROUP, _TRANSMISSION_METHOD + + def serialize_request(self, request): + """Serializes a request value used in this test case. + + Args: + request: A request value created by this test case. + + Returns: + A bytestring that is the serialization of the given request. + """ + return request + + def deserialize_request(self, serialized_request): + """Deserializes a request value used in this test case. + + Args: + serialized_request: A bytestring that is the serialization of some request + used in this test case. + + Returns: + The request value encoded by the given bytestring. + """ + return serialized_request + + def serialize_response(self, response): + """Serializes a response value used in this test case. + + Args: + response: A response value created by this test case. + + Returns: + A bytestring that is the serialization of the given response. + """ + return response + + def deserialize_response(self, serialized_response): + """Deserializes a response value used in this test case. + + Args: + serialized_response: A bytestring that is the serialization of some + response used in this test case. + + Returns: + The response value encoded by the given bytestring. + """ + return serialized_response + + def _assert_is_valid_metadata_payload_sequence( + self, ticket_sequence, payloads, initial_metadata, terminal_metadata): + initial_metadata_seen = False + seen_payloads = [] + terminal_metadata_seen = False + + for ticket in ticket_sequence: + if ticket.initial_metadata is not None: + self.assertFalse(initial_metadata_seen) + self.assertFalse(seen_payloads) + self.assertFalse(terminal_metadata_seen) + self.assertMetadataEqual(initial_metadata, ticket.initial_metadata) + initial_metadata_seen = True + + if ticket.payload is not None: + self.assertFalse(terminal_metadata_seen) + seen_payloads.append(ticket.payload) + + if ticket.terminal_metadata is not None: + self.assertFalse(terminal_metadata_seen) + self.assertMetadataEqual(terminal_metadata, ticket.terminal_metadata) + terminal_metadata_seen = True + self.assertSequenceEqual(payloads, seen_payloads) + + def _assert_is_valid_invocation_sequence( + self, ticket_sequence, group, method, payloads, initial_metadata, + terminal_metadata, termination): + self.assertLess(0, len(ticket_sequence)) + self.assertEqual(group, ticket_sequence[0].group) + self.assertEqual(method, ticket_sequence[0].method) + self._assert_is_valid_metadata_payload_sequence( + ticket_sequence, payloads, initial_metadata, terminal_metadata) + self.assertIs(termination, ticket_sequence[-1].termination) + + def _assert_is_valid_service_sequence( + self, ticket_sequence, payloads, initial_metadata, terminal_metadata, + code, message, termination): + self.assertLess(0, len(ticket_sequence)) + self._assert_is_valid_metadata_payload_sequence( + ticket_sequence, payloads, initial_metadata, terminal_metadata) + self.assertEqual(code, ticket_sequence[-1].code) + self.assertEqual(message, ticket_sequence[-1].message) + self.assertIs(termination, ticket_sequence[-1].termination) + + def setUp(self): + self._invocation_link, self._service_link = self.create_transmitting_links() + self._invocation_mate = test_utilities.RecordingLink() + self._service_mate = test_utilities.RecordingLink() + self._invocation_link.join_link(self._invocation_mate) + self._service_link.join_link(self._service_mate) + + def tearDown(self): + self.destroy_transmitting_links(self._invocation_link, self._service_link) + + def testSimplestRoundTrip(self): + """Tests transmission of one ticket in each direction.""" + invocation_operation_id = object() + invocation_payload = b'\x07' * 1023 + timeout = test_constants.LONG_TIMEOUT + invocation_initial_metadata = self.create_invocation_initial_metadata() + invocation_terminal_metadata = self.create_invocation_terminal_metadata() + invocation_code, invocation_message = self.create_invocation_completion() + service_payload = b'\x08' * 1025 + service_initial_metadata = self.create_service_initial_metadata() + service_terminal_metadata = self.create_service_terminal_metadata() + service_code, service_message = self.create_service_completion() + + original_invocation_ticket = links.Ticket( + invocation_operation_id, 0, _TRANSMISSION_GROUP, _TRANSMISSION_METHOD, + links.Ticket.Subscription.FULL, timeout, 0, invocation_initial_metadata, + invocation_payload, invocation_terminal_metadata, invocation_code, + invocation_message, links.Ticket.Termination.COMPLETION) + self._invocation_link.accept_ticket(original_invocation_ticket) + + # TODO(nathaniel): This shouldn't be necessary. Detecting the end of the + # invocation-side ticket sequence shouldn't require granting allowance for + # another payload. + self._service_mate.block_until_tickets_satisfy( + at_least_n_payloads_received_predicate(1)) + service_operation_id = self._service_mate.tickets()[0].operation_id + self._service_link.accept_ticket( + links.Ticket( + service_operation_id, 0, None, None, links.Ticket.Subscription.FULL, + None, 1, None, None, None, None, None, None)) + + self._service_mate.block_until_tickets_satisfy(terminated) + self._assert_is_valid_invocation_sequence( + self._service_mate.tickets(), _TRANSMISSION_GROUP, _TRANSMISSION_METHOD, + (invocation_payload,), invocation_initial_metadata, + invocation_terminal_metadata, links.Ticket.Termination.COMPLETION) + + original_service_ticket = links.Ticket( + service_operation_id, 1, None, None, links.Ticket.Subscription.FULL, + timeout, 0, service_initial_metadata, service_payload, + service_terminal_metadata, service_code, service_message, + links.Ticket.Termination.COMPLETION) + self._service_link.accept_ticket(original_service_ticket) + self._invocation_mate.block_until_tickets_satisfy(terminated) + self._assert_is_valid_service_sequence( + self._invocation_mate.tickets(), (service_payload,), + service_initial_metadata, service_terminal_metadata, service_code, + service_message, links.Ticket.Termination.COMPLETION) diff --git a/src/python/src/grpc/framework/interfaces/links/test_utilities.py b/src/python/src/grpc/framework/interfaces/links/test_utilities.py new file mode 100644 index 0000000000..6c2e3346aa --- /dev/null +++ b/src/python/src/grpc/framework/interfaces/links/test_utilities.py @@ -0,0 +1,66 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""State and behavior appropriate for use in tests.""" + +import threading + +from grpc.framework.interfaces.links import links + + +class RecordingLink(links.Link): + """A Link that records every ticket passed to it.""" + + def __init__(self): + self._condition = threading.Condition() + self._tickets = [] + + def accept_ticket(self, ticket): + with self._condition: + self._tickets.append(ticket) + self._condition.notify_all() + + def join_link(self, link): + pass + + def block_until_tickets_satisfy(self, predicate): + """Blocks until the received tickets satisfy the given predicate. + + Args: + predicate: A callable that takes a sequence of tickets and returns a + boolean value. + """ + with self._condition: + while not predicate(self._tickets): + self._condition.wait() + + def tickets(self): + """Returns a copy of the list of all tickets received by this Link.""" + with self._condition: + return tuple(self._tickets) diff --git a/src/python/src/grpc/framework/interfaces/links/utilities.py b/src/python/src/grpc/framework/interfaces/links/utilities.py new file mode 100644 index 0000000000..6e4fd76d93 --- /dev/null +++ b/src/python/src/grpc/framework/interfaces/links/utilities.py @@ -0,0 +1,44 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Utilities provided as part of the links interface.""" + +from grpc.framework.interfaces.links import links + + +class _NullLink(links.Link): + """A do-nothing links.Link.""" + + def accept_ticket(self, ticket): + pass + + def join_link(self, link): + pass + +NULL_LINK = _NullLink() diff --git a/src/python/src/setup.py b/src/python/src/setup.py index b9764bdc07..a857ae98cc 100644 --- a/src/python/src/setup.py +++ b/src/python/src/setup.py @@ -76,6 +76,7 @@ _PACKAGES = ( 'grpc', 'grpc._adapter', 'grpc._junkdrawer', + 'grpc._links', 'grpc.early_adopter', 'grpc.framework', 'grpc.framework.alpha', @@ -84,12 +85,15 @@ _PACKAGES = ( 'grpc.framework.face', 'grpc.framework.face.testing', 'grpc.framework.foundation', + 'grpc.framework.interfaces', + 'grpc.framework.interfaces.links', ) _PACKAGE_DIRECTORIES = { 'grpc': 'grpc', 'grpc._adapter': 'grpc/_adapter', 'grpc._junkdrawer': 'grpc/_junkdrawer', + 'grpc._links': 'grpc/_links', 'grpc.early_adopter': 'grpc/early_adopter', 'grpc.framework': 'grpc/framework', } diff --git a/src/ruby/grpc.gemspec b/src/ruby/grpc.gemspec index 319da9470a..dd4e27df51 100755 --- a/src/ruby/grpc.gemspec +++ b/src/ruby/grpc.gemspec @@ -14,7 +14,7 @@ Gem::Specification.new do |s| s.license = 'BSD-3-Clause' s.required_ruby_version = '>= 2.0.0' - s.requirements << 'libgrpc ~> 0.9.1 needs to be installed' + s.requirements << 'libgrpc ~> 0.10.0 needs to be installed' s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- spec/*`.split("\n") diff --git a/test/cpp/end2end/end2end_test.cc b/test/cpp/end2end/end2end_test.cc index f0d9f75214..207dad5282 100644 --- a/test/cpp/end2end/end2end_test.cc +++ b/test/cpp/end2end/end2end_test.cc @@ -98,8 +98,9 @@ void CheckAuthContext(T* context) { class TestServiceImpl : public ::grpc::cpp::test::util::TestService::Service { public: - TestServiceImpl() : signal_client_(false), host_(nullptr) {} - explicit TestServiceImpl(const grpc::string& host) : signal_client_(false), host_(new grpc::string(host)) {} + TestServiceImpl() : signal_client_(false), host_() {} + explicit TestServiceImpl(const grpc::string& host) + : signal_client_(false), host_(new grpc::string(host)) {} Status Echo(ServerContext* context, const EchoRequest* request, EchoResponse* response) GRPC_OVERRIDE { @@ -224,7 +225,8 @@ class TestServiceImplDupPkg class End2endTest : public ::testing::Test { protected: - End2endTest() : kMaxMessageSize_(8192), special_service_("special"), thread_pool_(2) {} + End2endTest() + : kMaxMessageSize_(8192), special_service_("special"), thread_pool_(2) {} void SetUp() GRPC_OVERRIDE { int port = grpc_pick_unused_port_or_die(); diff --git a/tools/run_tests/python_tests.json b/tools/run_tests/python_tests.json index 77473db2ed..3d75d8de36 100755 --- a/tools/run_tests/python_tests.json +++ b/tools/run_tests/python_tests.json @@ -48,6 +48,18 @@ ] }, { + "module": "grpc._links._lonely_invocation_link_test", + "pythonVersions": [ + "2.7" + ] + }, + { + "module": "grpc._links._transmission_test", + "pythonVersions": [ + "2.7" + ] + }, + { "module": "grpc.early_adopter.implementations_test", "pythonVersions": [ "2.7" |