Browse Source

Merge remote-tracking branch 'upstream/master' into lb_policy_create_channel_api_improvement

Mark D. Roth 6 years ago
parent
commit
4f3a55b73d
100 changed files with 4991 additions and 687 deletions
  1. 1 0
      doc/server_side_auth.md
  2. 1 0
      grpc.def
  3. 57 2
      include/grpc/grpc_security.h
  4. 1 2
      include/grpcpp/impl/codegen/call_op_set.h
  5. 1 0
      include/grpcpp/server_builder_impl.h
  6. 59 41
      src/compiler/csharp_generator.cc
  7. 1 1
      src/compiler/csharp_generator.h
  8. 6 1
      src/compiler/csharp_plugin.cc
  9. 2 3
      src/core/ext/filters/client_channel/backup_poller.cc
  10. 5 2
      src/core/ext/filters/client_channel/backup_poller.h
  11. 2 0
      src/core/ext/filters/client_channel/client_channel_plugin.cc
  12. 6 2
      src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.cc
  13. 6 2
      src/core/ext/filters/client_channel/lb_policy/xds/xds_load_balancer_api.cc
  14. 6 3
      src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc
  15. 0 1
      src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc
  16. 70 48
      src/core/lib/channel/channelz_registry.cc
  17. 4 2
      src/core/lib/channel/channelz_registry.h
  18. 9 0
      src/core/lib/gprpp/global_config.h
  19. 28 21
      src/core/lib/gprpp/map.h
  20. 5 0
      src/core/lib/gprpp/ref_counted.h
  21. 3 3
      src/core/lib/iomgr/iomgr.cc
  22. 3 1
      src/core/lib/iomgr/tcp_server.h
  23. 2 1
      src/core/lib/iomgr/tcp_server_posix.cc
  24. 20 2
      src/core/lib/security/credentials/ssl/ssl_credentials.cc
  25. 2 2
      src/core/lib/security/credentials/ssl/ssl_credentials.h
  26. 3 0
      src/core/lib/security/transport/auth_filters.h
  27. 13 0
      src/core/lib/security/transport/client_auth_filter.cc
  28. 49 36
      src/core/lib/surface/completion_queue.cc
  29. 2 1
      src/core/lib/surface/completion_queue.h
  30. 1 1
      src/core/lib/surface/server.cc
  31. 22 5
      src/cpp/client/secure_credentials.cc
  32. 1 1
      src/cpp/server/external_connection_acceptor_impl.cc
  33. 2 2
      src/cpp/thread_manager/thread_manager.h
  34. 97 0
      src/csharp/Grpc.Core.Api/LiteClientBase.cs
  35. 759 0
      src/csharp/Grpc.Examples/MathWithProtocOptions.cs
  36. 208 0
      src/csharp/Grpc.Examples/MathWithProtocOptionsGrpc.cs
  37. 65 0
      src/csharp/Grpc.Examples/math_with_protoc_options.proto
  38. 37 8
      src/csharp/Grpc.IntegrationTesting/EchoMessages.cs
  39. 30 0
      src/csharp/Grpc.IntegrationTesting/InteropClient.cs
  40. 2 0
      src/csharp/generate_proto_csharp.sh
  41. 15 2
      src/objective-c/GRPCClient/GRPCCall.h
  42. 87 256
      src/objective-c/GRPCClient/GRPCCall.m
  43. 16 0
      src/objective-c/GRPCClient/GRPCCallOptions.h
  44. 16 0
      src/objective-c/GRPCClient/GRPCCallOptions.m
  45. 269 0
      src/objective-c/GRPCClient/GRPCInterceptor.h
  46. 219 0
      src/objective-c/GRPCClient/GRPCInterceptor.m
  47. 36 0
      src/objective-c/GRPCClient/private/GRPCCall+V2API.h
  48. 42 0
      src/objective-c/GRPCClient/private/GRPCCallInternal.h
  49. 342 0
      src/objective-c/GRPCClient/private/GRPCCallInternal.m
  50. 4 4
      src/objective-c/ProtoRPC/ProtoRPC.m
  51. 416 0
      src/objective-c/examples/InterceptorSample/InterceptorSample.xcodeproj/project.pbxproj
  52. 25 0
      src/objective-c/examples/InterceptorSample/InterceptorSample/AppDelegate.h
  53. 23 0
      src/objective-c/examples/InterceptorSample/InterceptorSample/AppDelegate.m
  54. 98 0
      src/objective-c/examples/InterceptorSample/InterceptorSample/Assets.xcassets/AppIcon.appiconset/Contents.json
  55. 6 0
      src/objective-c/examples/InterceptorSample/InterceptorSample/Assets.xcassets/Contents.json
  56. 25 0
      src/objective-c/examples/InterceptorSample/InterceptorSample/Base.lproj/LaunchScreen.storyboard
  57. 38 0
      src/objective-c/examples/InterceptorSample/InterceptorSample/Base.lproj/Main.storyboard
  58. 92 0
      src/objective-c/examples/InterceptorSample/InterceptorSample/CacheInterceptor.h
  59. 306 0
      src/objective-c/examples/InterceptorSample/InterceptorSample/CacheInterceptor.m
  60. 45 0
      src/objective-c/examples/InterceptorSample/InterceptorSample/Info.plist
  61. 23 0
      src/objective-c/examples/InterceptorSample/InterceptorSample/ViewController.h
  62. 85 0
      src/objective-c/examples/InterceptorSample/InterceptorSample/ViewController.m
  63. 26 0
      src/objective-c/examples/InterceptorSample/InterceptorSample/main.m
  64. 31 0
      src/objective-c/examples/InterceptorSample/Podfile
  65. 527 1
      src/objective-c/tests/InteropTests/InteropTests.m
  66. 11 5
      src/php/ext/grpc/php_grpc.c
  67. 2 0
      src/php/ext/grpc/php_grpc.h
  68. 19 8
      src/python/grpcio/grpc/__init__.py
  69. 2 2
      src/python/grpcio/grpc/_cython/_cygrpc/credentials.pxd.pxi
  70. 2 2
      src/python/grpcio/grpc/_cython/_cygrpc/credentials.pyx.pxi
  71. 2 2
      src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi
  72. 2 0
      src/ruby/ext/grpc/rb_grpc_imports.generated.c
  73. 3 0
      src/ruby/ext/grpc/rb_grpc_imports.generated.h
  74. 41 35
      test/core/channel/channelz_registry_test.cc
  75. 41 21
      test/core/gprpp/map_test.cc
  76. 42 2
      test/core/surface/completion_queue_test.cc
  77. 1 0
      test/core/surface/public_headers_must_be_c89.c
  78. 28 0
      test/cpp/end2end/client_callback_end2end_test.cc
  79. 74 5
      test/cpp/end2end/client_interceptors_end2end_test.cc
  80. 4 5
      test/cpp/end2end/client_lb_end2end_test.cc
  81. 73 12
      test/cpp/end2end/end2end_test.cc
  82. 4 5
      test/cpp/end2end/grpclb_end2end_test.cc
  83. 3 3
      test/cpp/end2end/interceptors_util.cc
  84. 2 0
      test/cpp/end2end/interceptors_util.h
  85. 9 5
      test/cpp/end2end/port_sharing_end2end_test.cc
  86. 3 1
      test/cpp/end2end/server_interceptors_end2end_test.cc
  87. 4 5
      test/cpp/end2end/service_config_end2end_test.cc
  88. 4 5
      test/cpp/end2end/xds_end2end_test.cc
  89. 34 1
      test/cpp/microbenchmarks/bm_cq.cc
  90. 1 1
      test/cpp/microbenchmarks/callback_streaming_ping_pong.h
  91. 4 2
      tools/dockerfile/grpc_artifact_linux_armv6/Dockerfile
  92. 4 2
      tools/dockerfile/grpc_artifact_linux_armv7/Dockerfile
  93. 2 3
      tools/internal_ci/helper_scripts/prepare_build_interop_rc
  94. 9 0
      tools/internal_ci/linux/grpc_interop_toprod.cfg
  95. 9 0
      tools/internal_ci/linux/pull_request/grpc_interop_toprod.cfg
  96. 9 1
      tools/internal_ci/macos/grpc_interop_toprod.cfg
  97. 1 1
      tools/internal_ci/macos/grpc_interop_toprod.sh
  98. 136 95
      tools/interop_matrix/client_matrix.py
  99. 1 1
      tools/interop_matrix/create_testcases.sh
  100. 2 0
      tools/interop_matrix/run_interop_matrix_tests.py

+ 1 - 0
doc/server_side_auth.md

@@ -2,6 +2,7 @@ Server-side API for Authenticating Clients
 ==========================================
 ==========================================
 
 
 NOTE: This document describes how server-side authentication works in C-core based gRPC implementations only. In gRPC Java and Go, server side authentication is handled differently.
 NOTE: This document describes how server-side authentication works in C-core based gRPC implementations only. In gRPC Java and Go, server side authentication is handled differently.
+NOTE2: `CallCredentials` class is only valid for secure channels in C-Core. So, for connections under insecure channels, features below might not be avaiable.
 
 
 ## AuthContext
 ## AuthContext
 
 

+ 1 - 0
grpc.def

@@ -101,6 +101,7 @@ EXPORTS
     grpc_google_default_credentials_create
     grpc_google_default_credentials_create
     grpc_set_ssl_roots_override_callback
     grpc_set_ssl_roots_override_callback
     grpc_ssl_credentials_create
     grpc_ssl_credentials_create
+    grpc_ssl_credentials_create_ex
     grpc_call_credentials_release
     grpc_call_credentials_release
     grpc_composite_channel_credentials_create
     grpc_composite_channel_credentials_create
     grpc_composite_call_credentials_create
     grpc_composite_call_credentials_create

+ 57 - 2
include/grpc/grpc_security.h

@@ -163,6 +163,28 @@ typedef struct {
   const char* cert_chain;
   const char* cert_chain;
 } grpc_ssl_pem_key_cert_pair;
 } grpc_ssl_pem_key_cert_pair;
 
 
+/** Deprecated in favor of grpc_ssl_verify_peer_options. It will be removed
+  after all of its call sites are migrated to grpc_ssl_verify_peer_options.
+  Object that holds additional peer-verification options on a secure
+  channel. */
+typedef struct {
+  /** If non-NULL this callback will be invoked with the expected
+     target_name, the peer's certificate (in PEM format), and whatever
+     userdata pointer is set below. If a non-zero value is returned by this
+     callback then it is treated as a verification failure. Invocation of
+     the callback is blocking, so any implementation should be light-weight.
+     */
+  int (*verify_peer_callback)(const char* target_name, const char* peer_pem,
+                              void* userdata);
+  /** Arbitrary userdata that will be passed as the last argument to
+     verify_peer_callback. */
+  void* verify_peer_callback_userdata;
+  /** A destruct callback that will be invoked when the channel is being
+     cleaned up. The userdata argument will be passed to it. The intent is
+     to perform any cleanup associated with that userdata. */
+  void (*verify_peer_destruct)(void* userdata);
+} verify_peer_options;
+
 /** Object that holds additional peer-verification options on a secure
 /** Object that holds additional peer-verification options on a secure
    channel. */
    channel. */
 typedef struct {
 typedef struct {
@@ -181,9 +203,11 @@ typedef struct {
      cleaned up. The userdata argument will be passed to it. The intent is
      cleaned up. The userdata argument will be passed to it. The intent is
      to perform any cleanup associated with that userdata. */
      to perform any cleanup associated with that userdata. */
   void (*verify_peer_destruct)(void* userdata);
   void (*verify_peer_destruct)(void* userdata);
-} verify_peer_options;
+} grpc_ssl_verify_peer_options;
 
 
-/** Creates an SSL credentials object.
+/** Deprecated in favor of grpc_ssl_server_credentials_create_ex. It will be
+   removed after all of its call sites are migrated to
+   grpc_ssl_server_credentials_create_ex. Creates an SSL credentials object.
    - pem_root_certs is the NULL-terminated string containing the PEM encoding
    - pem_root_certs is the NULL-terminated string containing the PEM encoding
      of the server root certificates. If this parameter is NULL, the
      of the server root certificates. If this parameter is NULL, the
      implementation will first try to dereference the file pointed by the
      implementation will first try to dereference the file pointed by the
@@ -214,6 +238,37 @@ GRPCAPI grpc_channel_credentials* grpc_ssl_credentials_create(
     const char* pem_root_certs, grpc_ssl_pem_key_cert_pair* pem_key_cert_pair,
     const char* pem_root_certs, grpc_ssl_pem_key_cert_pair* pem_key_cert_pair,
     const verify_peer_options* verify_options, void* reserved);
     const verify_peer_options* verify_options, void* reserved);
 
 
+/* Creates an SSL credentials object.
+   - pem_root_certs is the NULL-terminated string containing the PEM encoding
+     of the server root certificates. If this parameter is NULL, the
+     implementation will first try to dereference the file pointed by the
+     GRPC_DEFAULT_SSL_ROOTS_FILE_PATH environment variable, and if that fails,
+     try to get the roots set by grpc_override_ssl_default_roots. Eventually,
+     if all these fail, it will try to get the roots from a well-known place on
+     disk (in the grpc install directory).
+
+     gRPC has implemented root cache if the underlying OpenSSL library supports
+     it. The gRPC root certificates cache is only applicable on the default
+     root certificates, which is used when this parameter is nullptr. If user
+     provides their own pem_root_certs, when creating an SSL credential object,
+     gRPC would not be able to cache it, and each subchannel will generate a
+     copy of the root store. So it is recommended to avoid providing large room
+     pem with pem_root_certs parameter to avoid excessive memory consumption,
+     particularly on mobile platforms such as iOS.
+   - pem_key_cert_pair is a pointer on the object containing client's private
+     key and certificate chain. This parameter can be NULL if the client does
+     not have such a key/cert pair.
+   - verify_options is an optional verify_peer_options object which holds
+     additional options controlling how peer certificates are verified. For
+     example, you can supply a callback which receives the peer's certificate
+     with which you can do additional verification. Can be NULL, in which
+     case verification will retain default behavior. Any settings in
+     verify_options are copied during this call, so the verify_options
+     object can be released afterwards. */
+GRPCAPI grpc_channel_credentials* grpc_ssl_credentials_create_ex(
+    const char* pem_root_certs, grpc_ssl_pem_key_cert_pair* pem_key_cert_pair,
+    const grpc_ssl_verify_peer_options* verify_options, void* reserved);
+
 /** --- grpc_call_credentials object.
 /** --- grpc_call_credentials object.
 
 
    A call credentials object represents a way to authenticate on a particular
    A call credentials object represents a way to authenticate on a particular

+ 1 - 2
include/grpcpp/impl/codegen/call_op_set.h

@@ -468,7 +468,6 @@ class CallOpRecvMessage {
         *status = false;
         *status = false;
       }
       }
     }
     }
-    message_ = nullptr;
   }
   }
 
 
   void SetInterceptionHookPoint(
   void SetInterceptionHookPoint(
@@ -565,7 +564,6 @@ class CallOpGenericRecvMessage {
         *status = false;
         *status = false;
       }
       }
     }
     }
-    deserialize_.reset();
   }
   }
 
 
   void SetInterceptionHookPoint(
   void SetInterceptionHookPoint(
@@ -580,6 +578,7 @@ class CallOpGenericRecvMessage {
     interceptor_methods->AddInterceptionHookPoint(
     interceptor_methods->AddInterceptionHookPoint(
         experimental::InterceptionHookPoints::POST_RECV_MESSAGE);
         experimental::InterceptionHookPoints::POST_RECV_MESSAGE);
     if (!got_message) interceptor_methods->SetRecvMessage(nullptr, nullptr);
     if (!got_message) interceptor_methods->SetRecvMessage(nullptr, nullptr);
+    deserialize_.reset();
   }
   }
   void SetHijackingState(InterceptorBatchMethodsImpl* interceptor_methods) {
   void SetHijackingState(InterceptorBatchMethodsImpl* interceptor_methods) {
     hijacked_ = true;
     hijacked_ = true;

+ 1 - 0
include/grpcpp/server_builder_impl.h

@@ -67,6 +67,7 @@ class CallbackGenericService;
 class ExternalConnectionAcceptor {
 class ExternalConnectionAcceptor {
  public:
  public:
   struct NewConnectionParameters {
   struct NewConnectionParameters {
+    int listener_fd = -1;
     int fd = -1;
     int fd = -1;
     ByteBuffer read_buffer;  // data intended for the grpc server
     ByteBuffer read_buffer;  // data intended for the grpc server
   };
   };

+ 59 - 41
src/compiler/csharp_generator.cc

@@ -414,24 +414,34 @@ void GenerateServerClass(Printer* out, const ServiceDescriptor* service) {
   out->Print("\n");
   out->Print("\n");
 }
 }
 
 
-void GenerateClientStub(Printer* out, const ServiceDescriptor* service) {
-  out->Print("/// <summary>Client for $servicename$</summary>\n", "servicename",
-             GetServiceClassName(service));
-  out->Print("public partial class $name$ : grpc::ClientBase<$name$>\n", "name",
-             GetClientClassName(service));
+void GenerateClientStub(Printer* out, const ServiceDescriptor* service,
+                        bool lite_client) {
+  if (!lite_client) {
+    out->Print("/// <summary>Client for $servicename$</summary>\n",
+               "servicename", GetServiceClassName(service));
+    out->Print("public partial class $name$ : grpc::ClientBase<$name$>\n",
+               "name", GetClientClassName(service));
+  } else {
+    out->Print("/// <summary>Lite client for $servicename$</summary>\n",
+               "servicename", GetServiceClassName(service));
+    out->Print("public partial class $name$ : grpc::LiteClientBase\n", "name",
+               GetClientClassName(service));
+  }
   out->Print("{\n");
   out->Print("{\n");
   out->Indent();
   out->Indent();
 
 
   // constructors
   // constructors
-  out->Print(
-      "/// <summary>Creates a new client for $servicename$</summary>\n"
-      "/// <param name=\"channel\">The channel to use to make remote "
-      "calls.</param>\n",
-      "servicename", GetServiceClassName(service));
-  out->Print("public $name$(grpc::Channel channel) : base(channel)\n", "name",
-             GetClientClassName(service));
-  out->Print("{\n");
-  out->Print("}\n");
+  if (!lite_client) {
+    out->Print(
+        "/// <summary>Creates a new client for $servicename$</summary>\n"
+        "/// <param name=\"channel\">The channel to use to make remote "
+        "calls.</param>\n",
+        "servicename", GetServiceClassName(service));
+    out->Print("public $name$(grpc::Channel channel) : base(channel)\n", "name",
+               GetClientClassName(service));
+    out->Print("{\n");
+    out->Print("}\n");
+  }
   out->Print(
   out->Print(
       "/// <summary>Creates a new client for $servicename$ that uses a custom "
       "/// <summary>Creates a new client for $servicename$ that uses a custom "
       "<c>CallInvoker</c>.</summary>\n"
       "<c>CallInvoker</c>.</summary>\n"
@@ -450,16 +460,20 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor* service) {
              GetClientClassName(service));
              GetClientClassName(service));
   out->Print("{\n");
   out->Print("{\n");
   out->Print("}\n");
   out->Print("}\n");
-  out->Print(
-      "/// <summary>Protected constructor to allow creation of configured "
-      "clients.</summary>\n"
-      "/// <param name=\"configuration\">The client configuration.</param>\n");
-  out->Print(
-      "protected $name$(ClientBaseConfiguration configuration)"
-      " : base(configuration)\n",
-      "name", GetClientClassName(service));
-  out->Print("{\n");
-  out->Print("}\n\n");
+  if (!lite_client) {
+    out->Print(
+        "/// <summary>Protected constructor to allow creation of configured "
+        "clients.</summary>\n"
+        "/// <param name=\"configuration\">The client "
+        "configuration.</param>\n");
+    out->Print(
+        "protected $name$(ClientBaseConfiguration configuration)"
+        " : base(configuration)\n",
+        "name", GetClientClassName(service));
+    out->Print("{\n");
+    out->Print("}\n");
+  }
+  out->Print("\n");
 
 
   for (int i = 0; i < service->method_count(); i++) {
   for (int i = 0; i < service->method_count(); i++) {
     const MethodDescriptor* method = service->method(i);
     const MethodDescriptor* method = service->method(i);
@@ -577,19 +591,21 @@ void GenerateClientStub(Printer* out, const ServiceDescriptor* service) {
   }
   }
 
 
   // override NewInstance method
   // override NewInstance method
-  out->Print(
-      "/// <summary>Creates a new instance of client from given "
-      "<c>ClientBaseConfiguration</c>.</summary>\n");
-  out->Print(
-      "protected override $name$ NewInstance(ClientBaseConfiguration "
-      "configuration)\n",
-      "name", GetClientClassName(service));
-  out->Print("{\n");
-  out->Indent();
-  out->Print("return new $name$(configuration);\n", "name",
-             GetClientClassName(service));
-  out->Outdent();
-  out->Print("}\n");
+  if (!lite_client) {
+    out->Print(
+        "/// <summary>Creates a new instance of client from given "
+        "<c>ClientBaseConfiguration</c>.</summary>\n");
+    out->Print(
+        "protected override $name$ NewInstance(ClientBaseConfiguration "
+        "configuration)\n",
+        "name", GetClientClassName(service));
+    out->Print("{\n");
+    out->Indent();
+    out->Print("return new $name$(configuration);\n", "name",
+               GetClientClassName(service));
+    out->Outdent();
+    out->Print("}\n");
+  }
 
 
   out->Outdent();
   out->Outdent();
   out->Print("}\n");
   out->Print("}\n");
@@ -671,7 +687,7 @@ void GenerateBindServiceWithBinderMethod(Printer* out,
 
 
 void GenerateService(Printer* out, const ServiceDescriptor* service,
 void GenerateService(Printer* out, const ServiceDescriptor* service,
                      bool generate_client, bool generate_server,
                      bool generate_client, bool generate_server,
-                     bool internal_access) {
+                     bool internal_access, bool lite_client) {
   GenerateDocCommentBody(out, service);
   GenerateDocCommentBody(out, service);
   out->Print("$access_level$ static partial class $classname$\n",
   out->Print("$access_level$ static partial class $classname$\n",
              "access_level", GetAccessLevel(internal_access), "classname",
              "access_level", GetAccessLevel(internal_access), "classname",
@@ -693,8 +709,9 @@ void GenerateService(Printer* out, const ServiceDescriptor* service,
     GenerateServerClass(out, service);
     GenerateServerClass(out, service);
   }
   }
   if (generate_client) {
   if (generate_client) {
-    GenerateClientStub(out, service);
+    GenerateClientStub(out, service, lite_client);
   }
   }
+
   if (generate_server) {
   if (generate_server) {
     GenerateBindServiceMethod(out, service);
     GenerateBindServiceMethod(out, service);
     GenerateBindServiceWithBinderMethod(out, service);
     GenerateBindServiceWithBinderMethod(out, service);
@@ -707,7 +724,8 @@ void GenerateService(Printer* out, const ServiceDescriptor* service,
 }  // anonymous namespace
 }  // anonymous namespace
 
 
 grpc::string GetServices(const FileDescriptor* file, bool generate_client,
 grpc::string GetServices(const FileDescriptor* file, bool generate_client,
-                         bool generate_server, bool internal_access) {
+                         bool generate_server, bool internal_access,
+                         bool lite_client) {
   grpc::string output;
   grpc::string output;
   {
   {
     // Scope the output stream so it closes and finalizes output to the string.
     // Scope the output stream so it closes and finalizes output to the string.
@@ -749,7 +767,7 @@ grpc::string GetServices(const FileDescriptor* file, bool generate_client,
     }
     }
     for (int i = 0; i < file->service_count(); i++) {
     for (int i = 0; i < file->service_count(); i++) {
       GenerateService(&out, file->service(i), generate_client, generate_server,
       GenerateService(&out, file->service(i), generate_client, generate_server,
-                      internal_access);
+                      internal_access, lite_client);
     }
     }
     if (file_namespace != "") {
     if (file_namespace != "") {
       out.Outdent();
       out.Outdent();

+ 1 - 1
src/compiler/csharp_generator.h

@@ -27,7 +27,7 @@ namespace grpc_csharp_generator {
 
 
 grpc::string GetServices(const grpc::protobuf::FileDescriptor* file,
 grpc::string GetServices(const grpc::protobuf::FileDescriptor* file,
                          bool generate_client, bool generate_server,
                          bool generate_client, bool generate_server,
-                         bool internal_access);
+                         bool internal_access, bool lite_client);
 
 
 }  // namespace grpc_csharp_generator
 }  // namespace grpc_csharp_generator
 
 

+ 6 - 1
src/compiler/csharp_plugin.cc

@@ -39,6 +39,7 @@ class CSharpGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator {
     bool generate_client = true;
     bool generate_client = true;
     bool generate_server = true;
     bool generate_server = true;
     bool internal_access = false;
     bool internal_access = false;
+    bool lite_client = false;
     for (size_t i = 0; i < options.size(); i++) {
     for (size_t i = 0; i < options.size(); i++) {
       if (options[i].first == "no_client") {
       if (options[i].first == "no_client") {
         generate_client = false;
         generate_client = false;
@@ -46,6 +47,10 @@ class CSharpGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator {
         generate_server = false;
         generate_server = false;
       } else if (options[i].first == "internal_access") {
       } else if (options[i].first == "internal_access") {
         internal_access = true;
         internal_access = true;
+      } else if (options[i].first == "lite_client") {
+        // will only be used if generate_client is true.
+        // NOTE: experimental option, can be removed in future release
+        lite_client = true;
       } else {
       } else {
         *error = "Unknown generator option: " + options[i].first;
         *error = "Unknown generator option: " + options[i].first;
         return false;
         return false;
@@ -53,7 +58,7 @@ class CSharpGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator {
     }
     }
 
 
     grpc::string code = grpc_csharp_generator::GetServices(
     grpc::string code = grpc_csharp_generator::GetServices(
-        file, generate_client, generate_server, internal_access);
+        file, generate_client, generate_server, internal_access, lite_client);
     if (code.size() == 0) {
     if (code.size() == 0) {
       return true;  // don't generate a file if there are no services
       return true;  // don't generate a file if there are no services
     }
     }

+ 2 - 3
src/core/ext/filters/client_channel/backup_poller.cc

@@ -65,8 +65,8 @@ GPR_GLOBAL_CONFIG_DEFINE_INT32(
     "idleness), so that the next RPC on this channel won't fail. Set to 0 to "
     "idleness), so that the next RPC on this channel won't fail. Set to 0 to "
     "turn off the backup polls.");
     "turn off the backup polls.");
 
 
-static void init_globals() {
-  gpr_mu_init(&g_poller_mu);
+void grpc_client_channel_global_init_backup_polling() {
+  gpr_once_init(&g_once, [] { gpr_mu_init(&g_poller_mu); });
   int32_t poll_interval_ms =
   int32_t poll_interval_ms =
       GPR_GLOBAL_CONFIG_GET(grpc_client_channel_backup_poll_interval_ms);
       GPR_GLOBAL_CONFIG_GET(grpc_client_channel_backup_poll_interval_ms);
   if (poll_interval_ms < 0) {
   if (poll_interval_ms < 0) {
@@ -153,7 +153,6 @@ static void g_poller_init_locked() {
 
 
 void grpc_client_channel_start_backup_polling(
 void grpc_client_channel_start_backup_polling(
     grpc_pollset_set* interested_parties) {
     grpc_pollset_set* interested_parties) {
-  gpr_once_init(&g_once, init_globals);
   if (g_poll_interval_ms == 0) {
   if (g_poll_interval_ms == 0) {
     return;
     return;
   }
   }

+ 5 - 2
src/core/ext/filters/client_channel/backup_poller.h

@@ -27,11 +27,14 @@
 
 
 GPR_GLOBAL_CONFIG_DECLARE_INT32(grpc_client_channel_backup_poll_interval_ms);
 GPR_GLOBAL_CONFIG_DECLARE_INT32(grpc_client_channel_backup_poll_interval_ms);
 
 
-/* Start polling \a interested_parties periodically in the timer thread  */
+/* Initializes backup polling. */
+void grpc_client_channel_global_init_backup_polling();
+
+/* Starts polling \a interested_parties periodically in the timer thread. */
 void grpc_client_channel_start_backup_polling(
 void grpc_client_channel_start_backup_polling(
     grpc_pollset_set* interested_parties);
     grpc_pollset_set* interested_parties);
 
 
-/* Stop polling \a interested_parties */
+/* Stops polling \a interested_parties. */
 void grpc_client_channel_stop_backup_polling(
 void grpc_client_channel_stop_backup_polling(
     grpc_pollset_set* interested_parties);
     grpc_pollset_set* interested_parties);
 
 

+ 2 - 0
src/core/ext/filters/client_channel/client_channel_plugin.cc

@@ -24,6 +24,7 @@
 
 
 #include <grpc/support/alloc.h>
 #include <grpc/support/alloc.h>
 
 
+#include "src/core/ext/filters/client_channel/backup_poller.h"
 #include "src/core/ext/filters/client_channel/client_channel.h"
 #include "src/core/ext/filters/client_channel/client_channel.h"
 #include "src/core/ext/filters/client_channel/client_channel_channelz.h"
 #include "src/core/ext/filters/client_channel/client_channel_channelz.h"
 #include "src/core/ext/filters/client_channel/global_subchannel_pool.h"
 #include "src/core/ext/filters/client_channel/global_subchannel_pool.h"
@@ -62,6 +63,7 @@ void grpc_client_channel_init(void) {
       GRPC_CLIENT_CHANNEL, GRPC_CHANNEL_INIT_BUILTIN_PRIORITY, append_filter,
       GRPC_CLIENT_CHANNEL, GRPC_CHANNEL_INIT_BUILTIN_PRIORITY, append_filter,
       (void*)&grpc_client_channel_filter);
       (void*)&grpc_client_channel_filter);
   grpc_http_connect_register_handshaker_factory();
   grpc_http_connect_register_handshaker_factory();
+  grpc_client_channel_global_init_backup_polling();
 }
 }
 
 
 void grpc_client_channel_shutdown(void) {
 void grpc_client_channel_shutdown(void) {

+ 6 - 2
src/core/ext/filters/client_channel/lb_policy/grpclb/load_balancer_api.cc

@@ -67,8 +67,12 @@ grpc_grpclb_request* grpc_grpclb_request_create(const char* lb_service_name) {
   req->has_client_stats = false;
   req->has_client_stats = false;
   req->has_initial_request = true;
   req->has_initial_request = true;
   req->initial_request.has_name = true;
   req->initial_request.has_name = true;
-  strncpy(req->initial_request.name, lb_service_name,
-          GRPC_GRPCLB_SERVICE_NAME_MAX_LENGTH);
+  // GCC warns (-Wstringop-truncation) because the destination
+  // buffer size is identical to max-size, leading to a potential
+  // char[] with no null terminator.  nanopb can handle it fine,
+  // and parantheses around strncpy silence that compiler warning.
+  (strncpy(req->initial_request.name, lb_service_name,
+           GRPC_GRPCLB_SERVICE_NAME_MAX_LENGTH));
   return req;
   return req;
 }
 }
 
 

+ 6 - 2
src/core/ext/filters/client_channel/lb_policy/xds/xds_load_balancer_api.cc

@@ -67,8 +67,12 @@ xds_grpclb_request* xds_grpclb_request_create(const char* lb_service_name) {
   req->has_client_stats = false;
   req->has_client_stats = false;
   req->has_initial_request = true;
   req->has_initial_request = true;
   req->initial_request.has_name = true;
   req->initial_request.has_name = true;
-  strncpy(req->initial_request.name, lb_service_name,
-          XDS_SERVICE_NAME_MAX_LENGTH);
+  // GCC warns (-Wstringop-truncation) because the destination
+  // buffer size is identical to max-size, leading to a potential
+  // char[] with no null terminator.  nanopb can handle it fine,
+  // and parantheses around strncpy silence that compiler warning.
+  (strncpy(req->initial_request.name, lb_service_name,
+           XDS_SERVICE_NAME_MAX_LENGTH));
   return req;
   return req;
 }
 }
 
 

+ 6 - 3
src/core/ext/filters/client_channel/resolver/dns/c_ares/dns_resolver_ares.cc

@@ -473,10 +473,13 @@ static bool should_use_ares(const char* resolver_env) {
 }
 }
 #endif /* GRPC_UV */
 #endif /* GRPC_UV */
 
 
+static bool g_use_ares_dns_resolver;
+
 void grpc_resolver_dns_ares_init() {
 void grpc_resolver_dns_ares_init() {
   grpc_core::UniquePtr<char> resolver =
   grpc_core::UniquePtr<char> resolver =
       GPR_GLOBAL_CONFIG_GET(grpc_dns_resolver);
       GPR_GLOBAL_CONFIG_GET(grpc_dns_resolver);
   if (should_use_ares(resolver.get())) {
   if (should_use_ares(resolver.get())) {
+    g_use_ares_dns_resolver = true;
     gpr_log(GPR_DEBUG, "Using ares dns resolver");
     gpr_log(GPR_DEBUG, "Using ares dns resolver");
     address_sorting_init();
     address_sorting_init();
     grpc_error* error = grpc_ares_init();
     grpc_error* error = grpc_ares_init();
@@ -491,13 +494,13 @@ void grpc_resolver_dns_ares_init() {
     grpc_core::ResolverRegistry::Builder::RegisterResolverFactory(
     grpc_core::ResolverRegistry::Builder::RegisterResolverFactory(
         grpc_core::UniquePtr<grpc_core::ResolverFactory>(
         grpc_core::UniquePtr<grpc_core::ResolverFactory>(
             grpc_core::New<grpc_core::AresDnsResolverFactory>()));
             grpc_core::New<grpc_core::AresDnsResolverFactory>()));
+  } else {
+    g_use_ares_dns_resolver = false;
   }
   }
 }
 }
 
 
 void grpc_resolver_dns_ares_shutdown() {
 void grpc_resolver_dns_ares_shutdown() {
-  grpc_core::UniquePtr<char> resolver =
-      GPR_GLOBAL_CONFIG_GET(grpc_dns_resolver);
-  if (should_use_ares(resolver.get())) {
+  if (g_use_ares_dns_resolver) {
     address_sorting_shutdown();
     address_sorting_shutdown();
     grpc_ares_cleanup();
     grpc_ares_cleanup();
   }
   }

+ 0 - 1
src/core/ext/filters/client_channel/resolver/dns/c_ares/grpc_ares_ev_driver_windows.cc

@@ -231,7 +231,6 @@ class GrpcPolledFdWindows : public GrpcPolledFd {
 
 
   ares_ssize_t TrySendWriteBufSyncNonBlocking() {
   ares_ssize_t TrySendWriteBufSyncNonBlocking() {
     GPR_ASSERT(write_state_ == WRITE_IDLE);
     GPR_ASSERT(write_state_ == WRITE_IDLE);
-    ares_ssize_t total_sent;
     DWORD bytes_sent = 0;
     DWORD bytes_sent = 0;
     if (SendWriteBuf(&bytes_sent, nullptr) != 0) {
     if (SendWriteBuf(&bytes_sent, nullptr) != 0) {
       int wsa_last_error = WSAGetLastError();
       int wsa_last_error = WSAGetLastError();

+ 70 - 48
src/core/lib/channel/channelz_registry.cc

@@ -117,36 +117,46 @@ void ChannelzRegistry::InternalUnregister(intptr_t uuid) {
   MaybePerformCompactionLocked();
   MaybePerformCompactionLocked();
 }
 }
 
 
-BaseNode* ChannelzRegistry::InternalGet(intptr_t uuid) {
+RefCountedPtr<BaseNode> ChannelzRegistry::InternalGet(intptr_t uuid) {
   MutexLock lock(&mu_);
   MutexLock lock(&mu_);
   if (uuid < 1 || uuid > uuid_generator_) {
   if (uuid < 1 || uuid > uuid_generator_) {
     return nullptr;
     return nullptr;
   }
   }
   int idx = FindByUuidLocked(uuid, true);
   int idx = FindByUuidLocked(uuid, true);
-  return idx < 0 ? nullptr : entities_[idx];
+  if (idx < 0 || entities_[idx] == nullptr) return nullptr;
+  // Found node.  Return only if its refcount is not zero (i.e., when we
+  // know that there is no other thread about to destroy it).
+  if (!entities_[idx]->RefIfNonZero()) return nullptr;
+  return RefCountedPtr<BaseNode>(entities_[idx]);
 }
 }
 
 
 char* ChannelzRegistry::InternalGetTopChannels(intptr_t start_channel_id) {
 char* ChannelzRegistry::InternalGetTopChannels(intptr_t start_channel_id) {
-  MutexLock lock(&mu_);
   grpc_json* top_level_json = grpc_json_create(GRPC_JSON_OBJECT);
   grpc_json* top_level_json = grpc_json_create(GRPC_JSON_OBJECT);
   grpc_json* json = top_level_json;
   grpc_json* json = top_level_json;
   grpc_json* json_iterator = nullptr;
   grpc_json* json_iterator = nullptr;
-  InlinedVector<BaseNode*, 10> top_level_channels;
-  bool reached_pagination_limit = false;
-  int start_idx = GPR_MAX(FindByUuidLocked(start_channel_id, false), 0);
-  for (size_t i = start_idx; i < entities_.size(); ++i) {
-    if (entities_[i] != nullptr &&
-        entities_[i]->type() ==
-            grpc_core::channelz::BaseNode::EntityType::kTopLevelChannel &&
-        entities_[i]->uuid() >= start_channel_id) {
-      // check if we are over pagination limit to determine if we need to set
-      // the "end" element. If we don't go through this block, we know that
-      // when the loop terminates, we have <= to kPaginationLimit.
-      if (top_level_channels.size() == kPaginationLimit) {
-        reached_pagination_limit = true;
-        break;
+  InlinedVector<RefCountedPtr<BaseNode>, 10> top_level_channels;
+  RefCountedPtr<BaseNode> node_after_pagination_limit;
+  {
+    MutexLock lock(&mu_);
+    const int start_idx = GPR_MAX(FindByUuidLocked(start_channel_id, false), 0);
+    for (size_t i = start_idx; i < entities_.size(); ++i) {
+      if (entities_[i] != nullptr &&
+          entities_[i]->type() ==
+              grpc_core::channelz::BaseNode::EntityType::kTopLevelChannel &&
+          entities_[i]->uuid() >= start_channel_id &&
+          entities_[i]->RefIfNonZero()) {
+        // Check if we are over pagination limit to determine if we need to set
+        // the "end" element. If we don't go through this block, we know that
+        // when the loop terminates, we have <= to kPaginationLimit.
+        // Note that because we have already increased this node's
+        // refcount, we need to decrease it, but we can't unref while
+        // holding the lock, because this may lead to a deadlock.
+        if (top_level_channels.size() == kPaginationLimit) {
+          node_after_pagination_limit.reset(entities_[i]);
+          break;
+        }
+        top_level_channels.emplace_back(entities_[i]);
       }
       }
-      top_level_channels.push_back(entities_[i]);
     }
     }
   }
   }
   if (!top_level_channels.empty()) {
   if (!top_level_channels.empty()) {
@@ -159,7 +169,7 @@ char* ChannelzRegistry::InternalGetTopChannels(intptr_t start_channel_id) {
           grpc_json_link_child(array_parent, channel_json, json_iterator);
           grpc_json_link_child(array_parent, channel_json, json_iterator);
     }
     }
   }
   }
-  if (!reached_pagination_limit) {
+  if (node_after_pagination_limit == nullptr) {
     grpc_json_create_child(nullptr, json, "end", nullptr, GRPC_JSON_TRUE,
     grpc_json_create_child(nullptr, json, "end", nullptr, GRPC_JSON_TRUE,
                            false);
                            false);
   }
   }
@@ -169,26 +179,32 @@ char* ChannelzRegistry::InternalGetTopChannels(intptr_t start_channel_id) {
 }
 }
 
 
 char* ChannelzRegistry::InternalGetServers(intptr_t start_server_id) {
 char* ChannelzRegistry::InternalGetServers(intptr_t start_server_id) {
-  MutexLock lock(&mu_);
   grpc_json* top_level_json = grpc_json_create(GRPC_JSON_OBJECT);
   grpc_json* top_level_json = grpc_json_create(GRPC_JSON_OBJECT);
   grpc_json* json = top_level_json;
   grpc_json* json = top_level_json;
   grpc_json* json_iterator = nullptr;
   grpc_json* json_iterator = nullptr;
-  InlinedVector<BaseNode*, 10> servers;
-  bool reached_pagination_limit = false;
-  int start_idx = GPR_MAX(FindByUuidLocked(start_server_id, false), 0);
-  for (size_t i = start_idx; i < entities_.size(); ++i) {
-    if (entities_[i] != nullptr &&
-        entities_[i]->type() ==
-            grpc_core::channelz::BaseNode::EntityType::kServer &&
-        entities_[i]->uuid() >= start_server_id) {
-      // check if we are over pagination limit to determine if we need to set
-      // the "end" element. If we don't go through this block, we know that
-      // when the loop terminates, we have <= to kPaginationLimit.
-      if (servers.size() == kPaginationLimit) {
-        reached_pagination_limit = true;
-        break;
+  InlinedVector<RefCountedPtr<BaseNode>, 10> servers;
+  RefCountedPtr<BaseNode> node_after_pagination_limit;
+  {
+    MutexLock lock(&mu_);
+    const int start_idx = GPR_MAX(FindByUuidLocked(start_server_id, false), 0);
+    for (size_t i = start_idx; i < entities_.size(); ++i) {
+      if (entities_[i] != nullptr &&
+          entities_[i]->type() ==
+              grpc_core::channelz::BaseNode::EntityType::kServer &&
+          entities_[i]->uuid() >= start_server_id &&
+          entities_[i]->RefIfNonZero()) {
+        // Check if we are over pagination limit to determine if we need to set
+        // the "end" element. If we don't go through this block, we know that
+        // when the loop terminates, we have <= to kPaginationLimit.
+        // Note that because we have already increased this node's
+        // refcount, we need to decrease it, but we can't unref while
+        // holding the lock, because this may lead to a deadlock.
+        if (servers.size() == kPaginationLimit) {
+          node_after_pagination_limit.reset(entities_[i]);
+          break;
+        }
+        servers.emplace_back(entities_[i]);
       }
       }
-      servers.push_back(entities_[i]);
     }
     }
   }
   }
   if (!servers.empty()) {
   if (!servers.empty()) {
@@ -201,7 +217,7 @@ char* ChannelzRegistry::InternalGetServers(intptr_t start_server_id) {
           grpc_json_link_child(array_parent, server_json, json_iterator);
           grpc_json_link_child(array_parent, server_json, json_iterator);
     }
     }
   }
   }
-  if (!reached_pagination_limit) {
+  if (node_after_pagination_limit == nullptr) {
     grpc_json_create_child(nullptr, json, "end", nullptr, GRPC_JSON_TRUE,
     grpc_json_create_child(nullptr, json, "end", nullptr, GRPC_JSON_TRUE,
                            false);
                            false);
   }
   }
@@ -211,14 +227,20 @@ char* ChannelzRegistry::InternalGetServers(intptr_t start_server_id) {
 }
 }
 
 
 void ChannelzRegistry::InternalLogAllEntities() {
 void ChannelzRegistry::InternalLogAllEntities() {
-  MutexLock lock(&mu_);
-  for (size_t i = 0; i < entities_.size(); ++i) {
-    if (entities_[i] != nullptr) {
-      char* json = entities_[i]->RenderJsonString();
-      gpr_log(GPR_INFO, "%s", json);
-      gpr_free(json);
+  InlinedVector<RefCountedPtr<BaseNode>, 10> nodes;
+  {
+    MutexLock lock(&mu_);
+    for (size_t i = 0; i < entities_.size(); ++i) {
+      if (entities_[i] != nullptr && entities_[i]->RefIfNonZero()) {
+        nodes.emplace_back(entities_[i]);
+      }
     }
     }
   }
   }
+  for (size_t i = 0; i < nodes.size(); ++i) {
+    char* json = nodes[i]->RenderJsonString();
+    gpr_log(GPR_INFO, "%s", json);
+    gpr_free(json);
+  }
 }
 }
 
 
 }  // namespace channelz
 }  // namespace channelz
@@ -234,7 +256,7 @@ char* grpc_channelz_get_servers(intptr_t start_server_id) {
 }
 }
 
 
 char* grpc_channelz_get_server(intptr_t server_id) {
 char* grpc_channelz_get_server(intptr_t server_id) {
-  grpc_core::channelz::BaseNode* server_node =
+  grpc_core::RefCountedPtr<grpc_core::channelz::BaseNode> server_node =
       grpc_core::channelz::ChannelzRegistry::Get(server_id);
       grpc_core::channelz::ChannelzRegistry::Get(server_id);
   if (server_node == nullptr ||
   if (server_node == nullptr ||
       server_node->type() !=
       server_node->type() !=
@@ -254,7 +276,7 @@ char* grpc_channelz_get_server(intptr_t server_id) {
 char* grpc_channelz_get_server_sockets(intptr_t server_id,
 char* grpc_channelz_get_server_sockets(intptr_t server_id,
                                        intptr_t start_socket_id,
                                        intptr_t start_socket_id,
                                        intptr_t max_results) {
                                        intptr_t max_results) {
-  grpc_core::channelz::BaseNode* base_node =
+  grpc_core::RefCountedPtr<grpc_core::channelz::BaseNode> base_node =
       grpc_core::channelz::ChannelzRegistry::Get(server_id);
       grpc_core::channelz::ChannelzRegistry::Get(server_id);
   if (base_node == nullptr ||
   if (base_node == nullptr ||
       base_node->type() != grpc_core::channelz::BaseNode::EntityType::kServer) {
       base_node->type() != grpc_core::channelz::BaseNode::EntityType::kServer) {
@@ -263,12 +285,12 @@ char* grpc_channelz_get_server_sockets(intptr_t server_id,
   // This cast is ok since we have just checked to make sure base_node is
   // This cast is ok since we have just checked to make sure base_node is
   // actually a server node
   // actually a server node
   grpc_core::channelz::ServerNode* server_node =
   grpc_core::channelz::ServerNode* server_node =
-      static_cast<grpc_core::channelz::ServerNode*>(base_node);
+      static_cast<grpc_core::channelz::ServerNode*>(base_node.get());
   return server_node->RenderServerSockets(start_socket_id, max_results);
   return server_node->RenderServerSockets(start_socket_id, max_results);
 }
 }
 
 
 char* grpc_channelz_get_channel(intptr_t channel_id) {
 char* grpc_channelz_get_channel(intptr_t channel_id) {
-  grpc_core::channelz::BaseNode* channel_node =
+  grpc_core::RefCountedPtr<grpc_core::channelz::BaseNode> channel_node =
       grpc_core::channelz::ChannelzRegistry::Get(channel_id);
       grpc_core::channelz::ChannelzRegistry::Get(channel_id);
   if (channel_node == nullptr ||
   if (channel_node == nullptr ||
       (channel_node->type() !=
       (channel_node->type() !=
@@ -288,7 +310,7 @@ char* grpc_channelz_get_channel(intptr_t channel_id) {
 }
 }
 
 
 char* grpc_channelz_get_subchannel(intptr_t subchannel_id) {
 char* grpc_channelz_get_subchannel(intptr_t subchannel_id) {
-  grpc_core::channelz::BaseNode* subchannel_node =
+  grpc_core::RefCountedPtr<grpc_core::channelz::BaseNode> subchannel_node =
       grpc_core::channelz::ChannelzRegistry::Get(subchannel_id);
       grpc_core::channelz::ChannelzRegistry::Get(subchannel_id);
   if (subchannel_node == nullptr ||
   if (subchannel_node == nullptr ||
       subchannel_node->type() !=
       subchannel_node->type() !=
@@ -306,7 +328,7 @@ char* grpc_channelz_get_subchannel(intptr_t subchannel_id) {
 }
 }
 
 
 char* grpc_channelz_get_socket(intptr_t socket_id) {
 char* grpc_channelz_get_socket(intptr_t socket_id) {
-  grpc_core::channelz::BaseNode* socket_node =
+  grpc_core::RefCountedPtr<grpc_core::channelz::BaseNode> socket_node =
       grpc_core::channelz::ChannelzRegistry::Get(socket_id);
       grpc_core::channelz::ChannelzRegistry::Get(socket_id);
   if (socket_node == nullptr ||
   if (socket_node == nullptr ||
       socket_node->type() !=
       socket_node->type() !=

+ 4 - 2
src/core/lib/channel/channelz_registry.h

@@ -48,7 +48,9 @@ class ChannelzRegistry {
     return Default()->InternalRegister(node);
     return Default()->InternalRegister(node);
   }
   }
   static void Unregister(intptr_t uuid) { Default()->InternalUnregister(uuid); }
   static void Unregister(intptr_t uuid) { Default()->InternalUnregister(uuid); }
-  static BaseNode* Get(intptr_t uuid) { return Default()->InternalGet(uuid); }
+  static RefCountedPtr<BaseNode> Get(intptr_t uuid) {
+    return Default()->InternalGet(uuid);
+  }
 
 
   // Returns the allocated JSON string that represents the proto
   // Returns the allocated JSON string that represents the proto
   // GetTopChannelsResponse as per channelz.proto.
   // GetTopChannelsResponse as per channelz.proto.
@@ -86,7 +88,7 @@ class ChannelzRegistry {
 
 
   // if object with uuid has previously been registered as the correct type,
   // if object with uuid has previously been registered as the correct type,
   // returns the void* associated with that uuid. Else returns nullptr.
   // returns the void* associated with that uuid. Else returns nullptr.
-  BaseNode* InternalGet(intptr_t uuid);
+  RefCountedPtr<BaseNode> InternalGet(intptr_t uuid);
 
 
   char* InternalGetTopChannels(intptr_t start_channel_id);
   char* InternalGetTopChannels(intptr_t start_channel_id);
   char* InternalGetServers(intptr_t start_server_id);
   char* InternalGetServers(intptr_t start_server_id);

+ 9 - 0
src/core/lib/gprpp/global_config.h

@@ -59,6 +59,15 @@
 //
 //
 // Declaring config variables for other modules to access:
 // Declaring config variables for other modules to access:
 //   GPR_GLOBAL_CONFIG_DECLARE_*TYPE*(name)
 //   GPR_GLOBAL_CONFIG_DECLARE_*TYPE*(name)
+//
+// * Caveat for setting global configs at runtime
+//
+// Setting global configs at runtime multiple times is safe but it doesn't
+// mean that it will have a valid effect on the module depending configs.
+// In unit tests, it may be unpredictable to set different global configs
+// between test cases because grpc init and shutdown can ignore changes.
+// It's considered safe to set global configs before the first call to
+// grpc_init().
 
 
 // --------------------------------------------------------------------
 // --------------------------------------------------------------------
 // How to customize the global configuration system:
 // How to customize the global configuration system:

+ 28 - 21
src/core/lib/gprpp/map.h

@@ -112,7 +112,10 @@ class Map {
   // inserted entry and the second value being the new root of the subtree
   // inserted entry and the second value being the new root of the subtree
   // after a rebalance
   // after a rebalance
   Pair<iterator, Entry*> InsertRecursive(Entry* root, value_type&& p);
   Pair<iterator, Entry*> InsertRecursive(Entry* root, value_type&& p);
-  static Entry* RemoveRecursive(Entry* root, const key_type& k);
+  // Returns a pair with the first value being an iterator pointing to the
+  // successor of the deleted entry and the second value being the new root of
+  // the subtree after a rebalance
+  Pair<iterator, Entry*> RemoveRecursive(Entry* root, const key_type& k);
   // Return 0 if lhs = rhs
   // Return 0 if lhs = rhs
   //        1 if lhs > rhs
   //        1 if lhs > rhs
   //       -1 if lhs < rhs
   //       -1 if lhs < rhs
@@ -233,10 +236,10 @@ typename Map<Key, T, Compare>::iterator Map<Key, T, Compare>::erase(
     iterator iter) {
     iterator iter) {
   if (iter == end()) return iter;
   if (iter == end()) return iter;
   key_type& del_key = iter->first;
   key_type& del_key = iter->first;
-  iter++;
-  root_ = RemoveRecursive(root_, del_key);
+  Pair<iterator, Entry*> ret = RemoveRecursive(root_, del_key);
+  root_ = ret.second;
   size_--;
   size_--;
-  return iter;
+  return ret.first;
 }
 }
 
 
 template <class Key, class T, class Compare>
 template <class Key, class T, class Compare>
@@ -373,34 +376,38 @@ Map<Key, T, Compare>::RebalanceTreeAfterDeletion(Entry* root) {
 }
 }
 
 
 template <class Key, class T, class Compare>
 template <class Key, class T, class Compare>
-typename Map<Key, T, Compare>::Entry* Map<Key, T, Compare>::RemoveRecursive(
-    Entry* root, const key_type& k) {
-  if (root == nullptr) return root;
+typename ::grpc_core::Pair<typename Map<Key, T, Compare>::iterator,
+                           typename Map<Key, T, Compare>::Entry*>
+Map<Key, T, Compare>::RemoveRecursive(Entry* root, const key_type& k) {
+  Pair<iterator, Entry*> ret = MakePair(end(), root);
+  if (root == nullptr) return ret;
   int comp = CompareKeys(root->pair.first, k);
   int comp = CompareKeys(root->pair.first, k);
   if (comp > 0) {
   if (comp > 0) {
-    root->left = RemoveRecursive(root->left, k);
+    ret = RemoveRecursive(root->left, k);
+    root->left = ret.second;
   } else if (comp < 0) {
   } else if (comp < 0) {
-    root->right = RemoveRecursive(root->right, k);
+    ret = RemoveRecursive(root->right, k);
+    root->right = ret.second;
   } else {
   } else {
-    Entry* ret;
+    Entry* entry;
+    Entry* successor = InOrderSuccessor(root);
     if (root->left == nullptr) {
     if (root->left == nullptr) {
-      ret = root->right;
+      entry = root->right;
       Delete(root);
       Delete(root);
-      return ret;
+      return MakePair(iterator(this, successor), entry);
     } else if (root->right == nullptr) {
     } else if (root->right == nullptr) {
-      ret = root->left;
+      entry = root->left;
       Delete(root);
       Delete(root);
-      return ret;
+      return MakePair(iterator(this, successor), entry);
     } else {
     } else {
-      ret = root->right;
-      while (ret->left != nullptr) {
-        ret = ret->left;
-      }
-      root->pair.swap(ret->pair);
-      root->right = RemoveRecursive(root->right, ret->pair.first);
+      entry = successor;
+      root->pair.swap(entry->pair);
+      ret = RemoveRecursive(root->right, entry->pair.first);
+      root->right = ret.second;
+      ret.first = iterator(this, root);
     }
     }
   }
   }
-  return RebalanceTreeAfterDeletion(root);
+  return MakePair(ret.first, RebalanceTreeAfterDeletion(root));
 }
 }
 
 
 template <class Key, class T, class Compare>
 template <class Key, class T, class Compare>

+ 5 - 0
src/core/lib/gprpp/ref_counted.h

@@ -221,6 +221,11 @@ class RefCounted : public Impl {
     }
     }
   }
   }
 
 
+  bool RefIfNonZero() { return refs_.RefIfNonZero(); }
+  bool RefIfNonZero(const DebugLocation& location, const char* reason) {
+    return refs_.RefIfNonZero(location, reason);
+  }
+
   // Not copyable nor movable.
   // Not copyable nor movable.
   RefCounted(const RefCounted&) = delete;
   RefCounted(const RefCounted&) = delete;
   RefCounted& operator=(const RefCounted&) = delete;
   RefCounted& operator=(const RefCounted&) = delete;

+ 3 - 3
src/core/lib/iomgr/iomgr.cc

@@ -49,6 +49,7 @@ static gpr_mu g_mu;
 static gpr_cv g_rcv;
 static gpr_cv g_rcv;
 static int g_shutdown;
 static int g_shutdown;
 static grpc_iomgr_object g_root_object;
 static grpc_iomgr_object g_root_object;
+static bool g_grpc_abort_on_leaks;
 
 
 void grpc_iomgr_init() {
 void grpc_iomgr_init() {
   grpc_core::ExecCtx exec_ctx;
   grpc_core::ExecCtx exec_ctx;
@@ -62,6 +63,7 @@ void grpc_iomgr_init() {
   grpc_iomgr_platform_init();
   grpc_iomgr_platform_init();
   grpc_timer_list_init();
   grpc_timer_list_init();
   grpc_core::grpc_errqueue_init();
   grpc_core::grpc_errqueue_init();
+  g_grpc_abort_on_leaks = GPR_GLOBAL_CONFIG_GET(grpc_abort_on_leaks);
 }
 }
 
 
 void grpc_iomgr_start() { grpc_timer_manager_init(); }
 void grpc_iomgr_start() { grpc_timer_manager_init(); }
@@ -189,6 +191,4 @@ void grpc_iomgr_unregister_object(grpc_iomgr_object* obj) {
   gpr_free(obj->name);
   gpr_free(obj->name);
 }
 }
 
 
-bool grpc_iomgr_abort_on_leaks(void) {
-  return GPR_GLOBAL_CONFIG_GET(grpc_abort_on_leaks);
-}
+bool grpc_iomgr_abort_on_leaks(void) { return g_grpc_abort_on_leaks; }

+ 3 - 1
src/core/lib/iomgr/tcp_server.h

@@ -41,6 +41,7 @@ typedef struct grpc_tcp_server_acceptor {
   unsigned fd_index;
   unsigned fd_index;
   /* Data when the connection is passed to tcp_server from external. */
   /* Data when the connection is passed to tcp_server from external. */
   bool external_connection;
   bool external_connection;
+  int listener_fd;
   grpc_byte_buffer* pending_data;
   grpc_byte_buffer* pending_data;
 } grpc_tcp_server_acceptor;
 } grpc_tcp_server_acceptor;
 
 
@@ -55,7 +56,8 @@ namespace grpc_core {
 class TcpServerFdHandler {
 class TcpServerFdHandler {
  public:
  public:
   virtual ~TcpServerFdHandler() = default;
   virtual ~TcpServerFdHandler() = default;
-  virtual void Handle(int fd, grpc_byte_buffer* pending_read) GRPC_ABSTRACT;
+  virtual void Handle(int listener_fd, int fd,
+                      grpc_byte_buffer* pending_read) GRPC_ABSTRACT;
 
 
   GRPC_ABSTRACT_BASE_CLASS;
   GRPC_ABSTRACT_BASE_CLASS;
 };
 };

+ 2 - 1
src/core/lib/iomgr/tcp_server_posix.cc

@@ -572,7 +572,7 @@ class ExternalConnectionHandler : public grpc_core::TcpServerFdHandler {
   explicit ExternalConnectionHandler(grpc_tcp_server* s) : s_(s) {}
   explicit ExternalConnectionHandler(grpc_tcp_server* s) : s_(s) {}
 
 
   // TODO(yangg) resolve duplicate code with on_read
   // TODO(yangg) resolve duplicate code with on_read
-  void Handle(int fd, grpc_byte_buffer* buf) override {
+  void Handle(int listener_fd, int fd, grpc_byte_buffer* buf) override {
     grpc_pollset* read_notifier_pollset;
     grpc_pollset* read_notifier_pollset;
     grpc_resolved_address addr;
     grpc_resolved_address addr;
     char* addr_str;
     char* addr_str;
@@ -606,6 +606,7 @@ class ExternalConnectionHandler : public grpc_core::TcpServerFdHandler {
     acceptor->port_index = -1;
     acceptor->port_index = -1;
     acceptor->fd_index = -1;
     acceptor->fd_index = -1;
     acceptor->external_connection = true;
     acceptor->external_connection = true;
+    acceptor->listener_fd = listener_fd;
     acceptor->pending_data = buf;
     acceptor->pending_data = buf;
     s_->on_accept_cb(s_->on_accept_cb_arg,
     s_->on_accept_cb(s_->on_accept_cb_arg,
                      grpc_tcp_create(fdobj, s_->channel_args, addr_str),
                      grpc_tcp_create(fdobj, s_->channel_args, addr_str),

+ 20 - 2
src/core/lib/security/credentials/ssl/ssl_credentials.cc

@@ -46,7 +46,7 @@ void grpc_tsi_ssl_pem_key_cert_pairs_destroy(tsi_ssl_pem_key_cert_pair* kp,
 
 
 grpc_ssl_credentials::grpc_ssl_credentials(
 grpc_ssl_credentials::grpc_ssl_credentials(
     const char* pem_root_certs, grpc_ssl_pem_key_cert_pair* pem_key_cert_pair,
     const char* pem_root_certs, grpc_ssl_pem_key_cert_pair* pem_key_cert_pair,
-    const verify_peer_options* verify_options)
+    const grpc_ssl_verify_peer_options* verify_options)
     : grpc_channel_credentials(GRPC_CHANNEL_CREDENTIALS_TYPE_SSL) {
     : grpc_channel_credentials(GRPC_CHANNEL_CREDENTIALS_TYPE_SSL) {
   build_config(pem_root_certs, pem_key_cert_pair, verify_options);
   build_config(pem_root_certs, pem_key_cert_pair, verify_options);
 }
 }
@@ -94,7 +94,7 @@ grpc_ssl_credentials::create_security_connector(
 
 
 void grpc_ssl_credentials::build_config(
 void grpc_ssl_credentials::build_config(
     const char* pem_root_certs, grpc_ssl_pem_key_cert_pair* pem_key_cert_pair,
     const char* pem_root_certs, grpc_ssl_pem_key_cert_pair* pem_key_cert_pair,
-    const verify_peer_options* verify_options) {
+    const grpc_ssl_verify_peer_options* verify_options) {
   config_.pem_root_certs = gpr_strdup(pem_root_certs);
   config_.pem_root_certs = gpr_strdup(pem_root_certs);
   if (pem_key_cert_pair != nullptr) {
   if (pem_key_cert_pair != nullptr) {
     GPR_ASSERT(pem_key_cert_pair->private_key != nullptr);
     GPR_ASSERT(pem_key_cert_pair->private_key != nullptr);
@@ -117,6 +117,8 @@ void grpc_ssl_credentials::build_config(
   }
   }
 }
 }
 
 
+/* Deprecated in favor of grpc_ssl_credentials_create_ex. Will be removed
+ * once all of its call sites are migrated to grpc_ssl_credentials_create_ex. */
 grpc_channel_credentials* grpc_ssl_credentials_create(
 grpc_channel_credentials* grpc_ssl_credentials_create(
     const char* pem_root_certs, grpc_ssl_pem_key_cert_pair* pem_key_cert_pair,
     const char* pem_root_certs, grpc_ssl_pem_key_cert_pair* pem_key_cert_pair,
     const verify_peer_options* verify_options, void* reserved) {
     const verify_peer_options* verify_options, void* reserved) {
@@ -128,6 +130,22 @@ grpc_channel_credentials* grpc_ssl_credentials_create(
       4, (pem_root_certs, pem_key_cert_pair, verify_options, reserved));
       4, (pem_root_certs, pem_key_cert_pair, verify_options, reserved));
   GPR_ASSERT(reserved == nullptr);
   GPR_ASSERT(reserved == nullptr);
 
 
+  return grpc_core::New<grpc_ssl_credentials>(
+      pem_root_certs, pem_key_cert_pair,
+      reinterpret_cast<const grpc_ssl_verify_peer_options*>(verify_options));
+}
+
+grpc_channel_credentials* grpc_ssl_credentials_create_ex(
+    const char* pem_root_certs, grpc_ssl_pem_key_cert_pair* pem_key_cert_pair,
+    const grpc_ssl_verify_peer_options* verify_options, void* reserved) {
+  GRPC_API_TRACE(
+      "grpc_ssl_credentials_create(pem_root_certs=%s, "
+      "pem_key_cert_pair=%p, "
+      "verify_options=%p, "
+      "reserved=%p)",
+      4, (pem_root_certs, pem_key_cert_pair, verify_options, reserved));
+  GPR_ASSERT(reserved == nullptr);
+
   return grpc_core::New<grpc_ssl_credentials>(pem_root_certs, pem_key_cert_pair,
   return grpc_core::New<grpc_ssl_credentials>(pem_root_certs, pem_key_cert_pair,
                                               verify_options);
                                               verify_options);
 }
 }

+ 2 - 2
src/core/lib/security/credentials/ssl/ssl_credentials.h

@@ -28,7 +28,7 @@ class grpc_ssl_credentials : public grpc_channel_credentials {
  public:
  public:
   grpc_ssl_credentials(const char* pem_root_certs,
   grpc_ssl_credentials(const char* pem_root_certs,
                        grpc_ssl_pem_key_cert_pair* pem_key_cert_pair,
                        grpc_ssl_pem_key_cert_pair* pem_key_cert_pair,
-                       const verify_peer_options* verify_options);
+                       const grpc_ssl_verify_peer_options* verify_options);
 
 
   ~grpc_ssl_credentials() override;
   ~grpc_ssl_credentials() override;
 
 
@@ -41,7 +41,7 @@ class grpc_ssl_credentials : public grpc_channel_credentials {
  private:
  private:
   void build_config(const char* pem_root_certs,
   void build_config(const char* pem_root_certs,
                     grpc_ssl_pem_key_cert_pair* pem_key_cert_pair,
                     grpc_ssl_pem_key_cert_pair* pem_key_cert_pair,
-                    const verify_peer_options* verify_options);
+                    const grpc_ssl_verify_peer_options* verify_options);
 
 
   grpc_ssl_config config_;
   grpc_ssl_config config_;
 };
 };

+ 3 - 0
src/core/lib/security/transport/auth_filters.h

@@ -32,6 +32,9 @@ void grpc_auth_metadata_context_build(
     const grpc_slice& call_method, grpc_auth_context* auth_context,
     const grpc_slice& call_method, grpc_auth_context* auth_context,
     grpc_auth_metadata_context* auth_md_context);
     grpc_auth_metadata_context* auth_md_context);
 
 
+void grpc_auth_metadata_context_copy(grpc_auth_metadata_context* from,
+                                     grpc_auth_metadata_context* to);
+
 void grpc_auth_metadata_context_reset(grpc_auth_metadata_context* context);
 void grpc_auth_metadata_context_reset(grpc_auth_metadata_context* context);
 
 
 #endif /* GRPC_CORE_LIB_SECURITY_TRANSPORT_AUTH_FILTERS_H */
 #endif /* GRPC_CORE_LIB_SECURITY_TRANSPORT_AUTH_FILTERS_H */

+ 13 - 0
src/core/lib/security/transport/client_auth_filter.cc

@@ -112,6 +112,19 @@ struct call_data {
 
 
 }  // namespace
 }  // namespace
 
 
+void grpc_auth_metadata_context_copy(grpc_auth_metadata_context* from,
+                                     grpc_auth_metadata_context* to) {
+  grpc_auth_metadata_context_reset(to);
+  to->channel_auth_context = from->channel_auth_context;
+  if (to->channel_auth_context != nullptr) {
+    const_cast<grpc_auth_context*>(to->channel_auth_context)
+        ->Ref(DEBUG_LOCATION, "grpc_auth_metadata_context_copy")
+        .release();
+  }
+  to->service_url = gpr_strdup(from->service_url);
+  to->method_name = gpr_strdup(from->method_name);
+}
+
 void grpc_auth_metadata_context_reset(
 void grpc_auth_metadata_context_reset(
     grpc_auth_metadata_context* auth_md_context) {
     grpc_auth_metadata_context* auth_md_context) {
   if (auth_md_context->service_url != nullptr) {
   if (auth_md_context->service_url != nullptr) {

+ 49 - 36
src/core/lib/surface/completion_queue.cc

@@ -34,6 +34,7 @@
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gpr/string.h"
 #include "src/core/lib/gpr/tls.h"
 #include "src/core/lib/gpr/tls.h"
 #include "src/core/lib/gprpp/atomic.h"
 #include "src/core/lib/gprpp/atomic.h"
+#include "src/core/lib/iomgr/executor.h"
 #include "src/core/lib/iomgr/pollset.h"
 #include "src/core/lib/iomgr/pollset.h"
 #include "src/core/lib/iomgr/timer.h"
 #include "src/core/lib/iomgr/timer.h"
 #include "src/core/lib/profiling/timers.h"
 #include "src/core/lib/profiling/timers.h"
@@ -200,7 +201,7 @@ struct cq_vtable {
   bool (*begin_op)(grpc_completion_queue* cq, void* tag);
   bool (*begin_op)(grpc_completion_queue* cq, void* tag);
   void (*end_op)(grpc_completion_queue* cq, void* tag, grpc_error* error,
   void (*end_op)(grpc_completion_queue* cq, void* tag, grpc_error* error,
                  void (*done)(void* done_arg, grpc_cq_completion* storage),
                  void (*done)(void* done_arg, grpc_cq_completion* storage),
-                 void* done_arg, grpc_cq_completion* storage);
+                 void* done_arg, grpc_cq_completion* storage, bool internal);
   grpc_event (*next)(grpc_completion_queue* cq, gpr_timespec deadline,
   grpc_event (*next)(grpc_completion_queue* cq, gpr_timespec deadline,
                      void* reserved);
                      void* reserved);
   grpc_event (*pluck)(grpc_completion_queue* cq, void* tag,
   grpc_event (*pluck)(grpc_completion_queue* cq, void* tag,
@@ -354,23 +355,20 @@ static bool cq_begin_op_for_callback(grpc_completion_queue* cq, void* tag);
 // queue. The done argument is a callback that will be invoked when it is
 // queue. The done argument is a callback that will be invoked when it is
 // safe to free up that storage. The storage MUST NOT be freed until the
 // safe to free up that storage. The storage MUST NOT be freed until the
 // done callback is invoked.
 // done callback is invoked.
-static void cq_end_op_for_next(grpc_completion_queue* cq, void* tag,
-                               grpc_error* error,
-                               void (*done)(void* done_arg,
-                                            grpc_cq_completion* storage),
-                               void* done_arg, grpc_cq_completion* storage);
-
-static void cq_end_op_for_pluck(grpc_completion_queue* cq, void* tag,
-                                grpc_error* error,
-                                void (*done)(void* done_arg,
-                                             grpc_cq_completion* storage),
-                                void* done_arg, grpc_cq_completion* storage);
-
-static void cq_end_op_for_callback(grpc_completion_queue* cq, void* tag,
-                                   grpc_error* error,
-                                   void (*done)(void* done_arg,
-                                                grpc_cq_completion* storage),
-                                   void* done_arg, grpc_cq_completion* storage);
+static void cq_end_op_for_next(
+    grpc_completion_queue* cq, void* tag, grpc_error* error,
+    void (*done)(void* done_arg, grpc_cq_completion* storage), void* done_arg,
+    grpc_cq_completion* storage, bool internal);
+
+static void cq_end_op_for_pluck(
+    grpc_completion_queue* cq, void* tag, grpc_error* error,
+    void (*done)(void* done_arg, grpc_cq_completion* storage), void* done_arg,
+    grpc_cq_completion* storage, bool internal);
+
+static void cq_end_op_for_callback(
+    grpc_completion_queue* cq, void* tag, grpc_error* error,
+    void (*done)(void* done_arg, grpc_cq_completion* storage), void* done_arg,
+    grpc_cq_completion* storage, bool internal);
 
 
 static grpc_event cq_next(grpc_completion_queue* cq, gpr_timespec deadline,
 static grpc_event cq_next(grpc_completion_queue* cq, gpr_timespec deadline,
                           void* reserved);
                           void* reserved);
@@ -674,11 +672,10 @@ bool grpc_cq_begin_op(grpc_completion_queue* cq, void* tag) {
 /* Queue a GRPC_OP_COMPLETED operation to a completion queue (with a
 /* Queue a GRPC_OP_COMPLETED operation to a completion queue (with a
  * completion
  * completion
  * type of GRPC_CQ_NEXT) */
  * type of GRPC_CQ_NEXT) */
-static void cq_end_op_for_next(grpc_completion_queue* cq, void* tag,
-                               grpc_error* error,
-                               void (*done)(void* done_arg,
-                                            grpc_cq_completion* storage),
-                               void* done_arg, grpc_cq_completion* storage) {
+static void cq_end_op_for_next(
+    grpc_completion_queue* cq, void* tag, grpc_error* error,
+    void (*done)(void* done_arg, grpc_cq_completion* storage), void* done_arg,
+    grpc_cq_completion* storage, bool internal) {
   GPR_TIMER_SCOPE("cq_end_op_for_next", 0);
   GPR_TIMER_SCOPE("cq_end_op_for_next", 0);
 
 
   if (GRPC_TRACE_FLAG_ENABLED(grpc_api_trace) ||
   if (GRPC_TRACE_FLAG_ENABLED(grpc_api_trace) ||
@@ -754,11 +751,10 @@ static void cq_end_op_for_next(grpc_completion_queue* cq, void* tag,
 /* Queue a GRPC_OP_COMPLETED operation to a completion queue (with a
 /* Queue a GRPC_OP_COMPLETED operation to a completion queue (with a
  * completion
  * completion
  * type of GRPC_CQ_PLUCK) */
  * type of GRPC_CQ_PLUCK) */
-static void cq_end_op_for_pluck(grpc_completion_queue* cq, void* tag,
-                                grpc_error* error,
-                                void (*done)(void* done_arg,
-                                             grpc_cq_completion* storage),
-                                void* done_arg, grpc_cq_completion* storage) {
+static void cq_end_op_for_pluck(
+    grpc_completion_queue* cq, void* tag, grpc_error* error,
+    void (*done)(void* done_arg, grpc_cq_completion* storage), void* done_arg,
+    grpc_cq_completion* storage, bool internal) {
   GPR_TIMER_SCOPE("cq_end_op_for_pluck", 0);
   GPR_TIMER_SCOPE("cq_end_op_for_pluck", 0);
 
 
   cq_pluck_data* cqd = static_cast<cq_pluck_data*> DATA_FROM_CQ(cq);
   cq_pluck_data* cqd = static_cast<cq_pluck_data*> DATA_FROM_CQ(cq);
@@ -821,15 +817,19 @@ static void cq_end_op_for_pluck(grpc_completion_queue* cq, void* tag,
   GRPC_ERROR_UNREF(error);
   GRPC_ERROR_UNREF(error);
 }
 }
 
 
+static void functor_callback(void* arg, grpc_error* error) {
+  auto* functor = static_cast<grpc_experimental_completion_queue_functor*>(arg);
+  functor->functor_run(functor, error == GRPC_ERROR_NONE);
+}
+
 /* Complete an event on a completion queue of type GRPC_CQ_CALLBACK */
 /* Complete an event on a completion queue of type GRPC_CQ_CALLBACK */
 static void cq_end_op_for_callback(
 static void cq_end_op_for_callback(
     grpc_completion_queue* cq, void* tag, grpc_error* error,
     grpc_completion_queue* cq, void* tag, grpc_error* error,
     void (*done)(void* done_arg, grpc_cq_completion* storage), void* done_arg,
     void (*done)(void* done_arg, grpc_cq_completion* storage), void* done_arg,
-    grpc_cq_completion* storage) {
+    grpc_cq_completion* storage, bool internal) {
   GPR_TIMER_SCOPE("cq_end_op_for_callback", 0);
   GPR_TIMER_SCOPE("cq_end_op_for_callback", 0);
 
 
   cq_callback_data* cqd = static_cast<cq_callback_data*> DATA_FROM_CQ(cq);
   cq_callback_data* cqd = static_cast<cq_callback_data*> DATA_FROM_CQ(cq);
-  bool is_success = (error == GRPC_ERROR_NONE);
 
 
   if (GRPC_TRACE_FLAG_ENABLED(grpc_api_trace) ||
   if (GRPC_TRACE_FLAG_ENABLED(grpc_api_trace) ||
       (GRPC_TRACE_FLAG_ENABLED(grpc_trace_operation_failures) &&
       (GRPC_TRACE_FLAG_ENABLED(grpc_trace_operation_failures) &&
@@ -856,16 +856,25 @@ static void cq_end_op_for_callback(
     cq_finish_shutdown_callback(cq);
     cq_finish_shutdown_callback(cq);
   }
   }
 
 
-  GRPC_ERROR_UNREF(error);
-
   auto* functor = static_cast<grpc_experimental_completion_queue_functor*>(tag);
   auto* functor = static_cast<grpc_experimental_completion_queue_functor*>(tag);
-  grpc_core::ApplicationCallbackExecCtx::Enqueue(functor, is_success);
+  if (internal) {
+    grpc_core::ApplicationCallbackExecCtx::Enqueue(functor,
+                                                   (error == GRPC_ERROR_NONE));
+    GRPC_ERROR_UNREF(error);
+  } else {
+    GRPC_CLOSURE_SCHED(
+        GRPC_CLOSURE_CREATE(
+            functor_callback, functor,
+            grpc_core::Executor::Scheduler(grpc_core::ExecutorJobType::SHORT)),
+        error);
+  }
 }
 }
 
 
 void grpc_cq_end_op(grpc_completion_queue* cq, void* tag, grpc_error* error,
 void grpc_cq_end_op(grpc_completion_queue* cq, void* tag, grpc_error* error,
                     void (*done)(void* done_arg, grpc_cq_completion* storage),
                     void (*done)(void* done_arg, grpc_cq_completion* storage),
-                    void* done_arg, grpc_cq_completion* storage) {
-  cq->vtable->end_op(cq, tag, error, done, done_arg, storage);
+                    void* done_arg, grpc_cq_completion* storage,
+                    bool internal) {
+  cq->vtable->end_op(cq, tag, error, done, done_arg, storage, internal);
 }
 }
 
 
 typedef struct {
 typedef struct {
@@ -1343,7 +1352,11 @@ static void cq_finish_shutdown_callback(grpc_completion_queue* cq) {
   GPR_ASSERT(cqd->shutdown_called);
   GPR_ASSERT(cqd->shutdown_called);
 
 
   cq->poller_vtable->shutdown(POLLSET_FROM_CQ(cq), &cq->pollset_shutdown_done);
   cq->poller_vtable->shutdown(POLLSET_FROM_CQ(cq), &cq->pollset_shutdown_done);
-  grpc_core::ApplicationCallbackExecCtx::Enqueue(callback, true);
+  GRPC_CLOSURE_SCHED(
+      GRPC_CLOSURE_CREATE(
+          functor_callback, callback,
+          grpc_core::Executor::Scheduler(grpc_core::ExecutorJobType::SHORT)),
+      GRPC_ERROR_NONE);
 }
 }
 
 
 static void cq_shutdown_callback(grpc_completion_queue* cq) {
 static void cq_shutdown_callback(grpc_completion_queue* cq) {

+ 2 - 1
src/core/lib/surface/completion_queue.h

@@ -77,7 +77,8 @@ bool grpc_cq_begin_op(grpc_completion_queue* cc, void* tag);
    grpc_cq_begin_op */
    grpc_cq_begin_op */
 void grpc_cq_end_op(grpc_completion_queue* cc, void* tag, grpc_error* error,
 void grpc_cq_end_op(grpc_completion_queue* cc, void* tag, grpc_error* error,
                     void (*done)(void* done_arg, grpc_cq_completion* storage),
                     void (*done)(void* done_arg, grpc_cq_completion* storage),
-                    void* done_arg, grpc_cq_completion* storage);
+                    void* done_arg, grpc_cq_completion* storage,
+                    bool internal = false);
 
 
 grpc_pollset* grpc_cq_pollset(grpc_completion_queue* cc);
 grpc_pollset* grpc_cq_pollset(grpc_completion_queue* cc);
 
 

+ 1 - 1
src/core/lib/surface/server.cc

@@ -513,7 +513,7 @@ static void publish_call(grpc_server* server, call_data* calld, size_t cq_idx,
   }
   }
 
 
   grpc_cq_end_op(calld->cq_new, rc->tag, GRPC_ERROR_NONE, done_request_event,
   grpc_cq_end_op(calld->cq_new, rc->tag, GRPC_ERROR_NONE, done_request_event,
-                 rc, &rc->completion);
+                 rc, &rc->completion, true);
 }
 }
 
 
 static void publish_new_rpc(void* arg, grpc_error* error) {
 static void publish_new_rpc(void* arg, grpc_error* error) {

+ 22 - 5
src/cpp/client/secure_credentials.cc

@@ -22,6 +22,8 @@
 #include <grpcpp/channel.h>
 #include <grpcpp/channel.h>
 #include <grpcpp/impl/grpc_library.h>
 #include <grpcpp/impl/grpc_library.h>
 #include <grpcpp/support/channel_arguments.h>
 #include <grpcpp/support/channel_arguments.h>
+#include "src/core/lib/iomgr/executor.h"
+#include "src/core/lib/security/transport/auth_filters.h"
 #include "src/cpp/client/create_channel_internal.h"
 #include "src/cpp/client/create_channel_internal.h"
 #include "src/cpp/common/secure_auth_context.h"
 #include "src/cpp/common/secure_auth_context.h"
 
 
@@ -223,13 +225,23 @@ std::shared_ptr<grpc_impl::CallCredentials> MetadataCredentialsFromPlugin(
 }  // namespace grpc_impl
 }  // namespace grpc_impl
 
 
 namespace grpc {
 namespace grpc {
-
-void MetadataCredentialsPluginWrapper::Destroy(void* wrapper) {
-  if (wrapper == nullptr) return;
+namespace {
+void DeleteWrapper(void* wrapper, grpc_error* ignored) {
   MetadataCredentialsPluginWrapper* w =
   MetadataCredentialsPluginWrapper* w =
       static_cast<MetadataCredentialsPluginWrapper*>(wrapper);
       static_cast<MetadataCredentialsPluginWrapper*>(wrapper);
   delete w;
   delete w;
 }
 }
+}  // namespace
+
+void MetadataCredentialsPluginWrapper::Destroy(void* wrapper) {
+  if (wrapper == nullptr) return;
+  grpc_core::ApplicationCallbackExecCtx callback_exec_ctx;
+  grpc_core::ExecCtx exec_ctx;
+  GRPC_CLOSURE_RUN(GRPC_CLOSURE_CREATE(DeleteWrapper, wrapper,
+                                       grpc_core::Executor::Scheduler(
+                                           grpc_core::ExecutorJobType::SHORT)),
+                   GRPC_ERROR_NONE);
+}
 
 
 int MetadataCredentialsPluginWrapper::GetMetadata(
 int MetadataCredentialsPluginWrapper::GetMetadata(
     void* wrapper, grpc_auth_metadata_context context,
     void* wrapper, grpc_auth_metadata_context context,
@@ -247,10 +259,15 @@ int MetadataCredentialsPluginWrapper::GetMetadata(
     return true;
     return true;
   }
   }
   if (w->plugin_->IsBlocking()) {
   if (w->plugin_->IsBlocking()) {
+    // The internals of context may be destroyed if GetMetadata is cancelled.
+    // Make a copy for InvokePlugin.
+    grpc_auth_metadata_context context_copy = grpc_auth_metadata_context();
+    grpc_auth_metadata_context_copy(&context, &context_copy);
     // Asynchronous return.
     // Asynchronous return.
-    w->thread_pool_->Add([w, context, cb, user_data] {
+    w->thread_pool_->Add([w, context_copy, cb, user_data]() mutable {
       w->MetadataCredentialsPluginWrapper::InvokePlugin(
       w->MetadataCredentialsPluginWrapper::InvokePlugin(
-          context, cb, user_data, nullptr, nullptr, nullptr, nullptr);
+          context_copy, cb, user_data, nullptr, nullptr, nullptr, nullptr);
+      grpc_auth_metadata_context_reset(&context_copy);
     });
     });
     return 0;
     return 0;
   } else {
   } else {

+ 1 - 1
src/cpp/server/external_connection_acceptor_impl.cc

@@ -71,7 +71,7 @@ void ExternalConnectionAcceptorImpl::HandleNewConnection(
     return;
     return;
   }
   }
   if (handler_) {
   if (handler_) {
-    handler_->Handle(p->fd, p->read_buffer.c_buffer());
+    handler_->Handle(p->listener_fd, p->fd, p->read_buffer.c_buffer());
   }
   }
 }
 }
 
 

+ 2 - 2
src/cpp/thread_manager/thread_manager.h

@@ -56,7 +56,7 @@ class ThreadManager {
   //    DoWork()
   //    DoWork()
   //
   //
   // If the return value is SHUTDOWN:,
   // If the return value is SHUTDOWN:,
-  //  - ThreadManager WILL NOT call DoWork() and terminates the thead
+  //  - ThreadManager WILL NOT call DoWork() and terminates the thread
   //
   //
   // If the return value is TIMEOUT:,
   // If the return value is TIMEOUT:,
   //  - ThreadManager WILL NOT call DoWork()
   //  - ThreadManager WILL NOT call DoWork()
@@ -133,7 +133,7 @@ class ThreadManager {
     grpc_core::Thread thd_;
     grpc_core::Thread thd_;
   };
   };
 
 
-  // The main funtion in ThreadManager
+  // The main function in ThreadManager
   void MainWorkLoop();
   void MainWorkLoop();
 
 
   void MarkAsCompleted(WorkerThread* thd);
   void MarkAsCompleted(WorkerThread* thd);

+ 97 - 0
src/csharp/Grpc.Core.Api/LiteClientBase.cs

@@ -0,0 +1,97 @@
+#region Copyright notice and license
+
+// Copyright 2019 The gRPC Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#endregion
+
+using System;
+using Grpc.Core.Utils;
+
+namespace Grpc.Core
+{
+    /// <summary>
+    /// Base class for lightweight client-side stubs.
+    /// All calls are invoked via a <c>CallInvoker</c>.
+    /// Lite client stubs have no configuration knobs, all configuration
+    /// is provided by decorating the call invoker.
+    /// Note: experimental API that can change or be removed without any prior notice.
+    /// </summary>
+    public abstract class LiteClientBase
+    {
+        readonly CallInvoker callInvoker;
+
+        /// <summary>
+        /// Initializes a new instance of <c>LiteClientBase</c> class that
+        /// throws <c>NotImplementedException</c> upon invocation of any RPC.
+        /// This constructor is only provided to allow creation of test doubles
+        /// for client classes (e.g. mocking requires a parameterless constructor).
+        /// </summary>
+        protected LiteClientBase() : this(new UnimplementedCallInvoker())
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of <c>ClientBase</c> class.
+        /// </summary>
+        /// <param name="callInvoker">The <c>CallInvoker</c> for remote call invocation.</param>
+        public LiteClientBase(CallInvoker callInvoker)
+        {
+            this.callInvoker = GrpcPreconditions.CheckNotNull(callInvoker, nameof(callInvoker));
+        }
+
+        /// <summary>
+        /// Gets the call invoker.
+        /// </summary>
+        protected CallInvoker CallInvoker
+        {
+            get { return this.callInvoker; }
+        }
+
+        /// <summary>
+        /// Call invoker that throws <c>NotImplementedException</c> for all requests.
+        /// </summary>
+        private class UnimplementedCallInvoker : CallInvoker
+        {
+            public UnimplementedCallInvoker()
+            {
+            }
+
+            public override TResponse BlockingUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
+            {
+                throw new NotImplementedException();
+            }
+
+            public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
+            {
+                throw new NotImplementedException();
+            }
+
+            public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request)
+            {
+                throw new NotImplementedException();
+            }
+
+            public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
+            {
+                throw new NotImplementedException();
+            }
+
+            public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options)
+            {
+                throw new NotImplementedException();
+            }
+        }
+    }
+}

+ 759 - 0
src/csharp/Grpc.Examples/MathWithProtocOptions.cs

@@ -0,0 +1,759 @@
+// <auto-generated>
+//     Generated by the protocol buffer compiler.  DO NOT EDIT!
+//     source: math_with_protoc_options.proto
+// </auto-generated>
+#pragma warning disable 1591, 0612, 3021
+#region Designer generated code
+
+using pb = global::Google.Protobuf;
+using pbc = global::Google.Protobuf.Collections;
+using pbr = global::Google.Protobuf.Reflection;
+using scg = global::System.Collections.Generic;
+namespace MathWithProtocOptions {
+
+  /// <summary>Holder for reflection information generated from math_with_protoc_options.proto</summary>
+  public static partial class MathWithProtocOptionsReflection {
+
+    #region Descriptor
+    /// <summary>File descriptor for math_with_protoc_options.proto</summary>
+    public static pbr::FileDescriptor Descriptor {
+      get { return descriptor; }
+    }
+    private static pbr::FileDescriptor descriptor;
+
+    static MathWithProtocOptionsReflection() {
+      byte[] descriptorData = global::System.Convert.FromBase64String(
+          string.Concat(
+            "Ch5tYXRoX3dpdGhfcHJvdG9jX29wdGlvbnMucHJvdG8SGG1hdGhfd2l0aF9w",
+            "cm90b2Nfb3B0aW9ucyIsCgdEaXZBcmdzEhAKCGRpdmlkZW5kGAEgASgDEg8K",
+            "B2Rpdmlzb3IYAiABKAMiLwoIRGl2UmVwbHkSEAoIcXVvdGllbnQYASABKAMS",
+            "EQoJcmVtYWluZGVyGAIgASgDIhgKB0ZpYkFyZ3MSDQoFbGltaXQYASABKAMi",
+            "EgoDTnVtEgsKA251bRgBIAEoAyIZCghGaWJSZXBseRINCgVjb3VudBgBIAEo",
+            "AzLEAgoETWF0aBJOCgNEaXYSIS5tYXRoX3dpdGhfcHJvdG9jX29wdGlvbnMu",
+            "RGl2QXJncxoiLm1hdGhfd2l0aF9wcm90b2Nfb3B0aW9ucy5EaXZSZXBseSIA",
+            "ElYKB0Rpdk1hbnkSIS5tYXRoX3dpdGhfcHJvdG9jX29wdGlvbnMuRGl2QXJn",
+            "cxoiLm1hdGhfd2l0aF9wcm90b2Nfb3B0aW9ucy5EaXZSZXBseSIAKAEwARJL",
+            "CgNGaWISIS5tYXRoX3dpdGhfcHJvdG9jX29wdGlvbnMuRmliQXJncxodLm1h",
+            "dGhfd2l0aF9wcm90b2Nfb3B0aW9ucy5OdW0iADABEkcKA1N1bRIdLm1hdGhf",
+            "d2l0aF9wcm90b2Nfb3B0aW9ucy5OdW0aHS5tYXRoX3dpdGhfcHJvdG9jX29w",
+            "dGlvbnMuTnVtIgAoAWIGcHJvdG8z"));
+      descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
+          new pbr::FileDescriptor[] { },
+          new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] {
+            new pbr::GeneratedClrTypeInfo(typeof(global::MathWithProtocOptions.DivArgs), global::MathWithProtocOptions.DivArgs.Parser, new[]{ "Dividend", "Divisor" }, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::MathWithProtocOptions.DivReply), global::MathWithProtocOptions.DivReply.Parser, new[]{ "Quotient", "Remainder" }, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::MathWithProtocOptions.FibArgs), global::MathWithProtocOptions.FibArgs.Parser, new[]{ "Limit" }, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::MathWithProtocOptions.Num), global::MathWithProtocOptions.Num.Parser, new[]{ "Num_" }, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::MathWithProtocOptions.FibReply), global::MathWithProtocOptions.FibReply.Parser, new[]{ "Count" }, null, null, null)
+          }));
+    }
+    #endregion
+
+  }
+  #region Messages
+  public sealed partial class DivArgs : pb::IMessage<DivArgs> {
+    private static readonly pb::MessageParser<DivArgs> _parser = new pb::MessageParser<DivArgs>(() => new DivArgs());
+    private pb::UnknownFieldSet _unknownFields;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<DivArgs> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::MathWithProtocOptions.MathWithProtocOptionsReflection.Descriptor.MessageTypes[0]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public DivArgs() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public DivArgs(DivArgs other) : this() {
+      dividend_ = other.dividend_;
+      divisor_ = other.divisor_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public DivArgs Clone() {
+      return new DivArgs(this);
+    }
+
+    /// <summary>Field number for the "dividend" field.</summary>
+    public const int DividendFieldNumber = 1;
+    private long dividend_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public long Dividend {
+      get { return dividend_; }
+      set {
+        dividend_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "divisor" field.</summary>
+    public const int DivisorFieldNumber = 2;
+    private long divisor_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public long Divisor {
+      get { return divisor_; }
+      set {
+        divisor_ = value;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as DivArgs);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(DivArgs other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (Dividend != other.Dividend) return false;
+      if (Divisor != other.Divisor) return false;
+      return Equals(_unknownFields, other._unknownFields);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      if (Dividend != 0L) hash ^= Dividend.GetHashCode();
+      if (Divisor != 0L) hash ^= Divisor.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+      if (Dividend != 0L) {
+        output.WriteRawTag(8);
+        output.WriteInt64(Dividend);
+      }
+      if (Divisor != 0L) {
+        output.WriteRawTag(16);
+        output.WriteInt64(Divisor);
+      }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      if (Dividend != 0L) {
+        size += 1 + pb::CodedOutputStream.ComputeInt64Size(Dividend);
+      }
+      if (Divisor != 0L) {
+        size += 1 + pb::CodedOutputStream.ComputeInt64Size(Divisor);
+      }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(DivArgs other) {
+      if (other == null) {
+        return;
+      }
+      if (other.Dividend != 0L) {
+        Dividend = other.Dividend;
+      }
+      if (other.Divisor != 0L) {
+        Divisor = other.Divisor;
+      }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+            break;
+          case 8: {
+            Dividend = input.ReadInt64();
+            break;
+          }
+          case 16: {
+            Divisor = input.ReadInt64();
+            break;
+          }
+        }
+      }
+    }
+
+  }
+
+  public sealed partial class DivReply : pb::IMessage<DivReply> {
+    private static readonly pb::MessageParser<DivReply> _parser = new pb::MessageParser<DivReply>(() => new DivReply());
+    private pb::UnknownFieldSet _unknownFields;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<DivReply> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::MathWithProtocOptions.MathWithProtocOptionsReflection.Descriptor.MessageTypes[1]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public DivReply() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public DivReply(DivReply other) : this() {
+      quotient_ = other.quotient_;
+      remainder_ = other.remainder_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public DivReply Clone() {
+      return new DivReply(this);
+    }
+
+    /// <summary>Field number for the "quotient" field.</summary>
+    public const int QuotientFieldNumber = 1;
+    private long quotient_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public long Quotient {
+      get { return quotient_; }
+      set {
+        quotient_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "remainder" field.</summary>
+    public const int RemainderFieldNumber = 2;
+    private long remainder_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public long Remainder {
+      get { return remainder_; }
+      set {
+        remainder_ = value;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as DivReply);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(DivReply other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (Quotient != other.Quotient) return false;
+      if (Remainder != other.Remainder) return false;
+      return Equals(_unknownFields, other._unknownFields);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      if (Quotient != 0L) hash ^= Quotient.GetHashCode();
+      if (Remainder != 0L) hash ^= Remainder.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+      if (Quotient != 0L) {
+        output.WriteRawTag(8);
+        output.WriteInt64(Quotient);
+      }
+      if (Remainder != 0L) {
+        output.WriteRawTag(16);
+        output.WriteInt64(Remainder);
+      }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      if (Quotient != 0L) {
+        size += 1 + pb::CodedOutputStream.ComputeInt64Size(Quotient);
+      }
+      if (Remainder != 0L) {
+        size += 1 + pb::CodedOutputStream.ComputeInt64Size(Remainder);
+      }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(DivReply other) {
+      if (other == null) {
+        return;
+      }
+      if (other.Quotient != 0L) {
+        Quotient = other.Quotient;
+      }
+      if (other.Remainder != 0L) {
+        Remainder = other.Remainder;
+      }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+            break;
+          case 8: {
+            Quotient = input.ReadInt64();
+            break;
+          }
+          case 16: {
+            Remainder = input.ReadInt64();
+            break;
+          }
+        }
+      }
+    }
+
+  }
+
+  public sealed partial class FibArgs : pb::IMessage<FibArgs> {
+    private static readonly pb::MessageParser<FibArgs> _parser = new pb::MessageParser<FibArgs>(() => new FibArgs());
+    private pb::UnknownFieldSet _unknownFields;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<FibArgs> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::MathWithProtocOptions.MathWithProtocOptionsReflection.Descriptor.MessageTypes[2]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public FibArgs() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public FibArgs(FibArgs other) : this() {
+      limit_ = other.limit_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public FibArgs Clone() {
+      return new FibArgs(this);
+    }
+
+    /// <summary>Field number for the "limit" field.</summary>
+    public const int LimitFieldNumber = 1;
+    private long limit_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public long Limit {
+      get { return limit_; }
+      set {
+        limit_ = value;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as FibArgs);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(FibArgs other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (Limit != other.Limit) return false;
+      return Equals(_unknownFields, other._unknownFields);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      if (Limit != 0L) hash ^= Limit.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+      if (Limit != 0L) {
+        output.WriteRawTag(8);
+        output.WriteInt64(Limit);
+      }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      if (Limit != 0L) {
+        size += 1 + pb::CodedOutputStream.ComputeInt64Size(Limit);
+      }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(FibArgs other) {
+      if (other == null) {
+        return;
+      }
+      if (other.Limit != 0L) {
+        Limit = other.Limit;
+      }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+            break;
+          case 8: {
+            Limit = input.ReadInt64();
+            break;
+          }
+        }
+      }
+    }
+
+  }
+
+  public sealed partial class Num : pb::IMessage<Num> {
+    private static readonly pb::MessageParser<Num> _parser = new pb::MessageParser<Num>(() => new Num());
+    private pb::UnknownFieldSet _unknownFields;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<Num> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::MathWithProtocOptions.MathWithProtocOptionsReflection.Descriptor.MessageTypes[3]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public Num() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public Num(Num other) : this() {
+      num_ = other.num_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public Num Clone() {
+      return new Num(this);
+    }
+
+    /// <summary>Field number for the "num" field.</summary>
+    public const int Num_FieldNumber = 1;
+    private long num_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public long Num_ {
+      get { return num_; }
+      set {
+        num_ = value;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as Num);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(Num other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (Num_ != other.Num_) return false;
+      return Equals(_unknownFields, other._unknownFields);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      if (Num_ != 0L) hash ^= Num_.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+      if (Num_ != 0L) {
+        output.WriteRawTag(8);
+        output.WriteInt64(Num_);
+      }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      if (Num_ != 0L) {
+        size += 1 + pb::CodedOutputStream.ComputeInt64Size(Num_);
+      }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(Num other) {
+      if (other == null) {
+        return;
+      }
+      if (other.Num_ != 0L) {
+        Num_ = other.Num_;
+      }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+            break;
+          case 8: {
+            Num_ = input.ReadInt64();
+            break;
+          }
+        }
+      }
+    }
+
+  }
+
+  public sealed partial class FibReply : pb::IMessage<FibReply> {
+    private static readonly pb::MessageParser<FibReply> _parser = new pb::MessageParser<FibReply>(() => new FibReply());
+    private pb::UnknownFieldSet _unknownFields;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<FibReply> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::MathWithProtocOptions.MathWithProtocOptionsReflection.Descriptor.MessageTypes[4]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public FibReply() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public FibReply(FibReply other) : this() {
+      count_ = other.count_;
+      _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public FibReply Clone() {
+      return new FibReply(this);
+    }
+
+    /// <summary>Field number for the "count" field.</summary>
+    public const int CountFieldNumber = 1;
+    private long count_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public long Count {
+      get { return count_; }
+      set {
+        count_ = value;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as FibReply);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(FibReply other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (Count != other.Count) return false;
+      return Equals(_unknownFields, other._unknownFields);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      if (Count != 0L) hash ^= Count.GetHashCode();
+      if (_unknownFields != null) {
+        hash ^= _unknownFields.GetHashCode();
+      }
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+      if (Count != 0L) {
+        output.WriteRawTag(8);
+        output.WriteInt64(Count);
+      }
+      if (_unknownFields != null) {
+        _unknownFields.WriteTo(output);
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      if (Count != 0L) {
+        size += 1 + pb::CodedOutputStream.ComputeInt64Size(Count);
+      }
+      if (_unknownFields != null) {
+        size += _unknownFields.CalculateSize();
+      }
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(FibReply other) {
+      if (other == null) {
+        return;
+      }
+      if (other.Count != 0L) {
+        Count = other.Count;
+      }
+      _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
+            break;
+          case 8: {
+            Count = input.ReadInt64();
+            break;
+          }
+        }
+      }
+    }
+
+  }
+
+  #endregion
+
+}
+
+#endregion Designer generated code

+ 208 - 0
src/csharp/Grpc.Examples/MathWithProtocOptionsGrpc.cs

@@ -0,0 +1,208 @@
+// <auto-generated>
+//     Generated by the protocol buffer compiler.  DO NOT EDIT!
+//     source: math_with_protoc_options.proto
+// </auto-generated>
+// Original file comments:
+// Copyright 2015 gRPC authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+#pragma warning disable 0414, 1591
+#region Designer generated code
+
+using grpc = global::Grpc.Core;
+
+namespace MathWithProtocOptions {
+  public static partial class Math
+  {
+    static readonly string __ServiceName = "math_with_protoc_options.Math";
+
+    static readonly grpc::Marshaller<global::MathWithProtocOptions.DivArgs> __Marshaller_math_with_protoc_options_DivArgs = grpc::Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::MathWithProtocOptions.DivArgs.Parser.ParseFrom);
+    static readonly grpc::Marshaller<global::MathWithProtocOptions.DivReply> __Marshaller_math_with_protoc_options_DivReply = grpc::Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::MathWithProtocOptions.DivReply.Parser.ParseFrom);
+    static readonly grpc::Marshaller<global::MathWithProtocOptions.FibArgs> __Marshaller_math_with_protoc_options_FibArgs = grpc::Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::MathWithProtocOptions.FibArgs.Parser.ParseFrom);
+    static readonly grpc::Marshaller<global::MathWithProtocOptions.Num> __Marshaller_math_with_protoc_options_Num = grpc::Marshallers.Create((arg) => global::Google.Protobuf.MessageExtensions.ToByteArray(arg), global::MathWithProtocOptions.Num.Parser.ParseFrom);
+
+    static readonly grpc::Method<global::MathWithProtocOptions.DivArgs, global::MathWithProtocOptions.DivReply> __Method_Div = new grpc::Method<global::MathWithProtocOptions.DivArgs, global::MathWithProtocOptions.DivReply>(
+        grpc::MethodType.Unary,
+        __ServiceName,
+        "Div",
+        __Marshaller_math_with_protoc_options_DivArgs,
+        __Marshaller_math_with_protoc_options_DivReply);
+
+    static readonly grpc::Method<global::MathWithProtocOptions.DivArgs, global::MathWithProtocOptions.DivReply> __Method_DivMany = new grpc::Method<global::MathWithProtocOptions.DivArgs, global::MathWithProtocOptions.DivReply>(
+        grpc::MethodType.DuplexStreaming,
+        __ServiceName,
+        "DivMany",
+        __Marshaller_math_with_protoc_options_DivArgs,
+        __Marshaller_math_with_protoc_options_DivReply);
+
+    static readonly grpc::Method<global::MathWithProtocOptions.FibArgs, global::MathWithProtocOptions.Num> __Method_Fib = new grpc::Method<global::MathWithProtocOptions.FibArgs, global::MathWithProtocOptions.Num>(
+        grpc::MethodType.ServerStreaming,
+        __ServiceName,
+        "Fib",
+        __Marshaller_math_with_protoc_options_FibArgs,
+        __Marshaller_math_with_protoc_options_Num);
+
+    static readonly grpc::Method<global::MathWithProtocOptions.Num, global::MathWithProtocOptions.Num> __Method_Sum = new grpc::Method<global::MathWithProtocOptions.Num, global::MathWithProtocOptions.Num>(
+        grpc::MethodType.ClientStreaming,
+        __ServiceName,
+        "Sum",
+        __Marshaller_math_with_protoc_options_Num,
+        __Marshaller_math_with_protoc_options_Num);
+
+    /// <summary>Service descriptor</summary>
+    public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor
+    {
+      get { return global::MathWithProtocOptions.MathWithProtocOptionsReflection.Descriptor.Services[0]; }
+    }
+
+    /// <summary>Lite client for Math</summary>
+    public partial class MathClient : grpc::LiteClientBase
+    {
+      /// <summary>Creates a new client for Math that uses a custom <c>CallInvoker</c>.</summary>
+      /// <param name="callInvoker">The callInvoker to use to make remote calls.</param>
+      public MathClient(grpc::CallInvoker callInvoker) : base(callInvoker)
+      {
+      }
+      /// <summary>Protected parameterless constructor to allow creation of test doubles.</summary>
+      protected MathClient() : base()
+      {
+      }
+
+      /// <summary>
+      /// Div divides DivArgs.dividend by DivArgs.divisor and returns the quotient
+      /// and remainder.
+      /// </summary>
+      /// <param name="request">The request to send to the server.</param>
+      /// <param name="headers">The initial metadata to send with the call. This parameter is optional.</param>
+      /// <param name="deadline">An optional deadline for the call. The call will be cancelled if deadline is hit.</param>
+      /// <param name="cancellationToken">An optional token for canceling the call.</param>
+      /// <returns>The response received from the server.</returns>
+      public virtual global::MathWithProtocOptions.DivReply Div(global::MathWithProtocOptions.DivArgs request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))
+      {
+        return Div(request, new grpc::CallOptions(headers, deadline, cancellationToken));
+      }
+      /// <summary>
+      /// Div divides DivArgs.dividend by DivArgs.divisor and returns the quotient
+      /// and remainder.
+      /// </summary>
+      /// <param name="request">The request to send to the server.</param>
+      /// <param name="options">The options for the call.</param>
+      /// <returns>The response received from the server.</returns>
+      public virtual global::MathWithProtocOptions.DivReply Div(global::MathWithProtocOptions.DivArgs request, grpc::CallOptions options)
+      {
+        return CallInvoker.BlockingUnaryCall(__Method_Div, null, options, request);
+      }
+      /// <summary>
+      /// Div divides DivArgs.dividend by DivArgs.divisor and returns the quotient
+      /// and remainder.
+      /// </summary>
+      /// <param name="request">The request to send to the server.</param>
+      /// <param name="headers">The initial metadata to send with the call. This parameter is optional.</param>
+      /// <param name="deadline">An optional deadline for the call. The call will be cancelled if deadline is hit.</param>
+      /// <param name="cancellationToken">An optional token for canceling the call.</param>
+      /// <returns>The call object.</returns>
+      public virtual grpc::AsyncUnaryCall<global::MathWithProtocOptions.DivReply> DivAsync(global::MathWithProtocOptions.DivArgs request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))
+      {
+        return DivAsync(request, new grpc::CallOptions(headers, deadline, cancellationToken));
+      }
+      /// <summary>
+      /// Div divides DivArgs.dividend by DivArgs.divisor and returns the quotient
+      /// and remainder.
+      /// </summary>
+      /// <param name="request">The request to send to the server.</param>
+      /// <param name="options">The options for the call.</param>
+      /// <returns>The call object.</returns>
+      public virtual grpc::AsyncUnaryCall<global::MathWithProtocOptions.DivReply> DivAsync(global::MathWithProtocOptions.DivArgs request, grpc::CallOptions options)
+      {
+        return CallInvoker.AsyncUnaryCall(__Method_Div, null, options, request);
+      }
+      /// <summary>
+      /// DivMany accepts an arbitrary number of division args from the client stream
+      /// and sends back the results in the reply stream.  The stream continues until
+      /// the client closes its end; the server does the same after sending all the
+      /// replies.  The stream ends immediately if either end aborts.
+      /// </summary>
+      /// <param name="headers">The initial metadata to send with the call. This parameter is optional.</param>
+      /// <param name="deadline">An optional deadline for the call. The call will be cancelled if deadline is hit.</param>
+      /// <param name="cancellationToken">An optional token for canceling the call.</param>
+      /// <returns>The call object.</returns>
+      public virtual grpc::AsyncDuplexStreamingCall<global::MathWithProtocOptions.DivArgs, global::MathWithProtocOptions.DivReply> DivMany(grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))
+      {
+        return DivMany(new grpc::CallOptions(headers, deadline, cancellationToken));
+      }
+      /// <summary>
+      /// DivMany accepts an arbitrary number of division args from the client stream
+      /// and sends back the results in the reply stream.  The stream continues until
+      /// the client closes its end; the server does the same after sending all the
+      /// replies.  The stream ends immediately if either end aborts.
+      /// </summary>
+      /// <param name="options">The options for the call.</param>
+      /// <returns>The call object.</returns>
+      public virtual grpc::AsyncDuplexStreamingCall<global::MathWithProtocOptions.DivArgs, global::MathWithProtocOptions.DivReply> DivMany(grpc::CallOptions options)
+      {
+        return CallInvoker.AsyncDuplexStreamingCall(__Method_DivMany, null, options);
+      }
+      /// <summary>
+      /// Fib generates numbers in the Fibonacci sequence.  If FibArgs.limit > 0, Fib
+      /// generates up to limit numbers; otherwise it continues until the call is
+      /// canceled.  Unlike Fib above, Fib has no final FibReply.
+      /// </summary>
+      /// <param name="request">The request to send to the server.</param>
+      /// <param name="headers">The initial metadata to send with the call. This parameter is optional.</param>
+      /// <param name="deadline">An optional deadline for the call. The call will be cancelled if deadline is hit.</param>
+      /// <param name="cancellationToken">An optional token for canceling the call.</param>
+      /// <returns>The call object.</returns>
+      public virtual grpc::AsyncServerStreamingCall<global::MathWithProtocOptions.Num> Fib(global::MathWithProtocOptions.FibArgs request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))
+      {
+        return Fib(request, new grpc::CallOptions(headers, deadline, cancellationToken));
+      }
+      /// <summary>
+      /// Fib generates numbers in the Fibonacci sequence.  If FibArgs.limit > 0, Fib
+      /// generates up to limit numbers; otherwise it continues until the call is
+      /// canceled.  Unlike Fib above, Fib has no final FibReply.
+      /// </summary>
+      /// <param name="request">The request to send to the server.</param>
+      /// <param name="options">The options for the call.</param>
+      /// <returns>The call object.</returns>
+      public virtual grpc::AsyncServerStreamingCall<global::MathWithProtocOptions.Num> Fib(global::MathWithProtocOptions.FibArgs request, grpc::CallOptions options)
+      {
+        return CallInvoker.AsyncServerStreamingCall(__Method_Fib, null, options, request);
+      }
+      /// <summary>
+      /// Sum sums a stream of numbers, returning the final result once the stream
+      /// is closed.
+      /// </summary>
+      /// <param name="headers">The initial metadata to send with the call. This parameter is optional.</param>
+      /// <param name="deadline">An optional deadline for the call. The call will be cancelled if deadline is hit.</param>
+      /// <param name="cancellationToken">An optional token for canceling the call.</param>
+      /// <returns>The call object.</returns>
+      public virtual grpc::AsyncClientStreamingCall<global::MathWithProtocOptions.Num, global::MathWithProtocOptions.Num> Sum(grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))
+      {
+        return Sum(new grpc::CallOptions(headers, deadline, cancellationToken));
+      }
+      /// <summary>
+      /// Sum sums a stream of numbers, returning the final result once the stream
+      /// is closed.
+      /// </summary>
+      /// <param name="options">The options for the call.</param>
+      /// <returns>The call object.</returns>
+      public virtual grpc::AsyncClientStreamingCall<global::MathWithProtocOptions.Num, global::MathWithProtocOptions.Num> Sum(grpc::CallOptions options)
+      {
+        return CallInvoker.AsyncClientStreamingCall(__Method_Sum, null, options);
+      }
+    }
+
+  }
+}
+#endregion

+ 65 - 0
src/csharp/Grpc.Examples/math_with_protoc_options.proto

@@ -0,0 +1,65 @@
+
+// Copyright 2015 gRPC authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto3";
+
+package math_with_protoc_options;
+
+message DivArgs {
+  int64 dividend = 1;
+  int64 divisor = 2;
+}
+
+message DivReply {
+  int64 quotient = 1;
+  int64 remainder = 2;
+}
+
+message FibArgs {
+  int64 limit = 1;
+}
+
+message Num {
+  int64 num = 1;
+}
+
+message FibReply {
+  int64 count = 1;
+}
+
+service Math {
+  // Div divides DivArgs.dividend by DivArgs.divisor and returns the quotient
+  // and remainder.
+  rpc Div (DivArgs) returns (DivReply) {
+  }
+
+  // DivMany accepts an arbitrary number of division args from the client stream
+  // and sends back the results in the reply stream.  The stream continues until
+  // the client closes its end; the server does the same after sending all the
+  // replies.  The stream ends immediately if either end aborts.
+  rpc DivMany (stream DivArgs) returns (stream DivReply) {
+  }
+
+  // Fib generates numbers in the Fibonacci sequence.  If FibArgs.limit > 0, Fib
+  // generates up to limit numbers; otherwise it continues until the call is
+  // canceled.  Unlike Fib above, Fib has no final FibReply.
+  rpc Fib (FibArgs) returns (stream Num) {
+  }
+
+  // Sum sums a stream of numbers, returning the final result once the stream
+  // is closed.
+  rpc Sum (stream Num) returns (Num) {
+  }
+}

+ 37 - 8
src/csharp/Grpc.IntegrationTesting/EchoMessages.cs

@@ -28,7 +28,7 @@ namespace Grpc.Testing {
             "DGdycGMudGVzdGluZyIyCglEZWJ1Z0luZm8SFQoNc3RhY2tfZW50cmllcxgB",
             "DGdycGMudGVzdGluZyIyCglEZWJ1Z0luZm8SFQoNc3RhY2tfZW50cmllcxgB",
             "IAMoCRIOCgZkZXRhaWwYAiABKAkiUAoLRXJyb3JTdGF0dXMSDAoEY29kZRgB",
             "IAMoCRIOCgZkZXRhaWwYAiABKAkiUAoLRXJyb3JTdGF0dXMSDAoEY29kZRgB",
             "IAEoBRIVCg1lcnJvcl9tZXNzYWdlGAIgASgJEhwKFGJpbmFyeV9lcnJvcl9k",
             "IAEoBRIVCg1lcnJvcl9tZXNzYWdlGAIgASgJEhwKFGJpbmFyeV9lcnJvcl9k",
-            "ZXRhaWxzGAMgASgJIv8DCg1SZXF1ZXN0UGFyYW1zEhUKDWVjaG9fZGVhZGxp",
+            "ZXRhaWxzGAMgASgJIqAECg1SZXF1ZXN0UGFyYW1zEhUKDWVjaG9fZGVhZGxp",
             "bmUYASABKAgSHgoWY2xpZW50X2NhbmNlbF9hZnRlcl91cxgCIAEoBRIeChZz",
             "bmUYASABKAgSHgoWY2xpZW50X2NhbmNlbF9hZnRlcl91cxgCIAEoBRIeChZz",
             "ZXJ2ZXJfY2FuY2VsX2FmdGVyX3VzGAMgASgFEhUKDWVjaG9fbWV0YWRhdGEY",
             "ZXJ2ZXJfY2FuY2VsX2FmdGVyX3VzGAMgASgFEhUKDWVjaG9fbWV0YWRhdGEY",
             "BCABKAgSGgoSY2hlY2tfYXV0aF9jb250ZXh0GAUgASgIEh8KF3Jlc3BvbnNl",
             "BCABKAgSGgoSY2hlY2tfYXV0aF9jb250ZXh0GAUgASgIEh8KF3Jlc3BvbnNl",
@@ -39,18 +39,19 @@ namespace Grpc.Testing {
             "Zy5EZWJ1Z0luZm8SEgoKc2VydmVyX2RpZRgMIAEoCBIcChRiaW5hcnlfZXJy",
             "Zy5EZWJ1Z0luZm8SEgoKc2VydmVyX2RpZRgMIAEoCBIcChRiaW5hcnlfZXJy",
             "b3JfZGV0YWlscxgNIAEoCRIxCg5leHBlY3RlZF9lcnJvchgOIAEoCzIZLmdy",
             "b3JfZGV0YWlscxgNIAEoCRIxCg5leHBlY3RlZF9lcnJvchgOIAEoCzIZLmdy",
             "cGMudGVzdGluZy5FcnJvclN0YXR1cxIXCg9zZXJ2ZXJfc2xlZXBfdXMYDyAB",
             "cGMudGVzdGluZy5FcnJvclN0YXR1cxIXCg9zZXJ2ZXJfc2xlZXBfdXMYDyAB",
-            "KAUSGwoTYmFja2VuZF9jaGFubmVsX2lkeBgQIAEoBSJKCgtFY2hvUmVxdWVz",
-            "dBIPCgdtZXNzYWdlGAEgASgJEioKBXBhcmFtGAIgASgLMhsuZ3JwYy50ZXN0",
-            "aW5nLlJlcXVlc3RQYXJhbXMiRgoOUmVzcG9uc2VQYXJhbXMSGAoQcmVxdWVz",
-            "dF9kZWFkbGluZRgBIAEoAxIMCgRob3N0GAIgASgJEgwKBHBlZXIYAyABKAki",
-            "TAoMRWNob1Jlc3BvbnNlEg8KB21lc3NhZ2UYASABKAkSKwoFcGFyYW0YAiAB",
-            "KAsyHC5ncnBjLnRlc3RpbmcuUmVzcG9uc2VQYXJhbXNiBnByb3RvMw=="));
+            "KAUSGwoTYmFja2VuZF9jaGFubmVsX2lkeBgQIAEoBRIfChdlY2hvX21ldGFk",
+            "YXRhX2luaXRpYWxseRgRIAEoCCJKCgtFY2hvUmVxdWVzdBIPCgdtZXNzYWdl",
+            "GAEgASgJEioKBXBhcmFtGAIgASgLMhsuZ3JwYy50ZXN0aW5nLlJlcXVlc3RQ",
+            "YXJhbXMiRgoOUmVzcG9uc2VQYXJhbXMSGAoQcmVxdWVzdF9kZWFkbGluZRgB",
+            "IAEoAxIMCgRob3N0GAIgASgJEgwKBHBlZXIYAyABKAkiTAoMRWNob1Jlc3Bv",
+            "bnNlEg8KB21lc3NhZ2UYASABKAkSKwoFcGFyYW0YAiABKAsyHC5ncnBjLnRl",
+            "c3RpbmcuUmVzcG9uc2VQYXJhbXNCA/gBAWIGcHJvdG8z"));
       descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
       descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
           new pbr::FileDescriptor[] { },
           new pbr::FileDescriptor[] { },
           new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] {
           new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] {
             new pbr::GeneratedClrTypeInfo(typeof(global::Grpc.Testing.DebugInfo), global::Grpc.Testing.DebugInfo.Parser, new[]{ "StackEntries", "Detail" }, null, null, null),
             new pbr::GeneratedClrTypeInfo(typeof(global::Grpc.Testing.DebugInfo), global::Grpc.Testing.DebugInfo.Parser, new[]{ "StackEntries", "Detail" }, null, null, null),
             new pbr::GeneratedClrTypeInfo(typeof(global::Grpc.Testing.ErrorStatus), global::Grpc.Testing.ErrorStatus.Parser, new[]{ "Code", "ErrorMessage", "BinaryErrorDetails" }, null, null, null),
             new pbr::GeneratedClrTypeInfo(typeof(global::Grpc.Testing.ErrorStatus), global::Grpc.Testing.ErrorStatus.Parser, new[]{ "Code", "ErrorMessage", "BinaryErrorDetails" }, null, null, null),
-            new pbr::GeneratedClrTypeInfo(typeof(global::Grpc.Testing.RequestParams), global::Grpc.Testing.RequestParams.Parser, new[]{ "EchoDeadline", "ClientCancelAfterUs", "ServerCancelAfterUs", "EchoMetadata", "CheckAuthContext", "ResponseMessageLength", "EchoPeer", "ExpectedClientIdentity", "SkipCancelledCheck", "ExpectedTransportSecurityType", "DebugInfo", "ServerDie", "BinaryErrorDetails", "ExpectedError", "ServerSleepUs", "BackendChannelIdx" }, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::Grpc.Testing.RequestParams), global::Grpc.Testing.RequestParams.Parser, new[]{ "EchoDeadline", "ClientCancelAfterUs", "ServerCancelAfterUs", "EchoMetadata", "CheckAuthContext", "ResponseMessageLength", "EchoPeer", "ExpectedClientIdentity", "SkipCancelledCheck", "ExpectedTransportSecurityType", "DebugInfo", "ServerDie", "BinaryErrorDetails", "ExpectedError", "ServerSleepUs", "BackendChannelIdx", "EchoMetadataInitially" }, null, null, null),
             new pbr::GeneratedClrTypeInfo(typeof(global::Grpc.Testing.EchoRequest), global::Grpc.Testing.EchoRequest.Parser, new[]{ "Message", "Param" }, null, null, null),
             new pbr::GeneratedClrTypeInfo(typeof(global::Grpc.Testing.EchoRequest), global::Grpc.Testing.EchoRequest.Parser, new[]{ "Message", "Param" }, null, null, null),
             new pbr::GeneratedClrTypeInfo(typeof(global::Grpc.Testing.ResponseParams), global::Grpc.Testing.ResponseParams.Parser, new[]{ "RequestDeadline", "Host", "Peer" }, null, null, null),
             new pbr::GeneratedClrTypeInfo(typeof(global::Grpc.Testing.ResponseParams), global::Grpc.Testing.ResponseParams.Parser, new[]{ "RequestDeadline", "Host", "Peer" }, null, null, null),
             new pbr::GeneratedClrTypeInfo(typeof(global::Grpc.Testing.EchoResponse), global::Grpc.Testing.EchoResponse.Parser, new[]{ "Message", "Param" }, null, null, null)
             new pbr::GeneratedClrTypeInfo(typeof(global::Grpc.Testing.EchoResponse), global::Grpc.Testing.EchoResponse.Parser, new[]{ "Message", "Param" }, null, null, null)
@@ -441,6 +442,7 @@ namespace Grpc.Testing {
       expectedError_ = other.expectedError_ != null ? other.expectedError_.Clone() : null;
       expectedError_ = other.expectedError_ != null ? other.expectedError_.Clone() : null;
       serverSleepUs_ = other.serverSleepUs_;
       serverSleepUs_ = other.serverSleepUs_;
       backendChannelIdx_ = other.backendChannelIdx_;
       backendChannelIdx_ = other.backendChannelIdx_;
+      echoMetadataInitially_ = other.echoMetadataInitially_;
       _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
       _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
     }
     }
 
 
@@ -637,6 +639,17 @@ namespace Grpc.Testing {
       }
       }
     }
     }
 
 
+    /// <summary>Field number for the "echo_metadata_initially" field.</summary>
+    public const int EchoMetadataInitiallyFieldNumber = 17;
+    private bool echoMetadataInitially_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool EchoMetadataInitially {
+      get { return echoMetadataInitially_; }
+      set {
+        echoMetadataInitially_ = value;
+      }
+    }
+
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public override bool Equals(object other) {
     public override bool Equals(object other) {
       return Equals(other as RequestParams);
       return Equals(other as RequestParams);
@@ -666,6 +679,7 @@ namespace Grpc.Testing {
       if (!object.Equals(ExpectedError, other.ExpectedError)) return false;
       if (!object.Equals(ExpectedError, other.ExpectedError)) return false;
       if (ServerSleepUs != other.ServerSleepUs) return false;
       if (ServerSleepUs != other.ServerSleepUs) return false;
       if (BackendChannelIdx != other.BackendChannelIdx) return false;
       if (BackendChannelIdx != other.BackendChannelIdx) return false;
+      if (EchoMetadataInitially != other.EchoMetadataInitially) return false;
       return Equals(_unknownFields, other._unknownFields);
       return Equals(_unknownFields, other._unknownFields);
     }
     }
 
 
@@ -688,6 +702,7 @@ namespace Grpc.Testing {
       if (expectedError_ != null) hash ^= ExpectedError.GetHashCode();
       if (expectedError_ != null) hash ^= ExpectedError.GetHashCode();
       if (ServerSleepUs != 0) hash ^= ServerSleepUs.GetHashCode();
       if (ServerSleepUs != 0) hash ^= ServerSleepUs.GetHashCode();
       if (BackendChannelIdx != 0) hash ^= BackendChannelIdx.GetHashCode();
       if (BackendChannelIdx != 0) hash ^= BackendChannelIdx.GetHashCode();
+      if (EchoMetadataInitially != false) hash ^= EchoMetadataInitially.GetHashCode();
       if (_unknownFields != null) {
       if (_unknownFields != null) {
         hash ^= _unknownFields.GetHashCode();
         hash ^= _unknownFields.GetHashCode();
       }
       }
@@ -765,6 +780,10 @@ namespace Grpc.Testing {
         output.WriteRawTag(128, 1);
         output.WriteRawTag(128, 1);
         output.WriteInt32(BackendChannelIdx);
         output.WriteInt32(BackendChannelIdx);
       }
       }
+      if (EchoMetadataInitially != false) {
+        output.WriteRawTag(136, 1);
+        output.WriteBool(EchoMetadataInitially);
+      }
       if (_unknownFields != null) {
       if (_unknownFields != null) {
         _unknownFields.WriteTo(output);
         _unknownFields.WriteTo(output);
       }
       }
@@ -821,6 +840,9 @@ namespace Grpc.Testing {
       if (BackendChannelIdx != 0) {
       if (BackendChannelIdx != 0) {
         size += 2 + pb::CodedOutputStream.ComputeInt32Size(BackendChannelIdx);
         size += 2 + pb::CodedOutputStream.ComputeInt32Size(BackendChannelIdx);
       }
       }
+      if (EchoMetadataInitially != false) {
+        size += 2 + 1;
+      }
       if (_unknownFields != null) {
       if (_unknownFields != null) {
         size += _unknownFields.CalculateSize();
         size += _unknownFields.CalculateSize();
       }
       }
@@ -886,6 +908,9 @@ namespace Grpc.Testing {
       if (other.BackendChannelIdx != 0) {
       if (other.BackendChannelIdx != 0) {
         BackendChannelIdx = other.BackendChannelIdx;
         BackendChannelIdx = other.BackendChannelIdx;
       }
       }
+      if (other.EchoMetadataInitially != false) {
+        EchoMetadataInitially = other.EchoMetadataInitially;
+      }
       _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
       _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
     }
     }
 
 
@@ -967,6 +992,10 @@ namespace Grpc.Testing {
             BackendChannelIdx = input.ReadInt32();
             BackendChannelIdx = input.ReadInt32();
             break;
             break;
           }
           }
+          case 136: {
+            EchoMetadataInitially = input.ReadBool();
+            break;
+          }
         }
         }
       }
       }
     }
     }

+ 30 - 0
src/csharp/Grpc.IntegrationTesting/InteropClient.cs

@@ -185,6 +185,9 @@ namespace Grpc.IntegrationTesting
                 case "unimplemented_service":
                 case "unimplemented_service":
                     RunUnimplementedService(new UnimplementedService.UnimplementedServiceClient(channel));
                     RunUnimplementedService(new UnimplementedService.UnimplementedServiceClient(channel));
                     break;
                     break;
+                case "special_status_message":
+                    await RunSpecialStatusMessageAsync(client);
+                    break;
                 case "unimplemented_method":
                 case "unimplemented_method":
                     RunUnimplementedMethod(client);
                     RunUnimplementedMethod(client);
                     break;
                     break;
@@ -567,6 +570,33 @@ namespace Grpc.IntegrationTesting
             Console.WriteLine("Passed!");
             Console.WriteLine("Passed!");
         }
         }
 
 
+        private static async Task RunSpecialStatusMessageAsync(TestService.TestServiceClient client)
+        {
+            Console.WriteLine("running special_status_message");
+
+            var echoStatus = new EchoStatus
+            {
+                Code = 2,
+                Message = "\t\ntest with whitespace\r\nand Unicode BMP ☺ and non-BMP 😈\t\n"
+            };
+
+            try
+            {
+                await client.UnaryCallAsync(new SimpleRequest
+                {
+                    ResponseStatus = echoStatus
+                });
+                Assert.Fail();
+            }
+            catch (RpcException e)
+            {
+                Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode);
+                Assert.AreEqual(echoStatus.Message, e.Status.Detail);
+            }
+
+            Console.WriteLine("Passed!");
+        }
+
         public static void RunUnimplementedService(UnimplementedService.UnimplementedServiceClient client)
         public static void RunUnimplementedService(UnimplementedService.UnimplementedServiceClient client)
         {
         {
             Console.WriteLine("running unimplemented_service");
             Console.WriteLine("running unimplemented_service");

+ 2 - 0
src/csharp/generate_proto_csharp.sh

@@ -26,6 +26,8 @@ TESTING_DIR=src/csharp/Grpc.IntegrationTesting
 
 
 $PROTOC --plugin=$PLUGIN --csharp_out=$EXAMPLES_DIR --grpc_out=$EXAMPLES_DIR \
 $PROTOC --plugin=$PLUGIN --csharp_out=$EXAMPLES_DIR --grpc_out=$EXAMPLES_DIR \
     -I src/proto src/proto/math/math.proto
     -I src/proto src/proto/math/math.proto
+$PROTOC --plugin=$PLUGIN --csharp_out=$EXAMPLES_DIR --grpc_out=$EXAMPLES_DIR --grpc_opt=lite_client,no_server \
+    -I src/csharp/Grpc.Examples src/csharp/Grpc.Examples/math_with_protoc_options.proto
 
 
 $PROTOC --plugin=$PLUGIN --csharp_out=$HEALTHCHECK_DIR --grpc_out=$HEALTHCHECK_DIR \
 $PROTOC --plugin=$PLUGIN --csharp_out=$HEALTHCHECK_DIR --grpc_out=$HEALTHCHECK_DIR \
     -I src/proto src/proto/grpc/health/v1/health.proto
     -I src/proto src/proto/grpc/health/v1/health.proto

+ 15 - 2
src/objective-c/GRPCClient/GRPCCall.h

@@ -170,11 +170,23 @@ extern NSString *const kGRPCTrailersKey;
 - (void)didReceiveInitialMetadata:(nullable NSDictionary *)initialMetadata;
 - (void)didReceiveInitialMetadata:(nullable NSDictionary *)initialMetadata;
 
 
 /**
 /**
+ * This method is deprecated and does not work with interceptors. To use GRPCCall2 interface with
+ * interceptor, implement didReceiveData: instead. To implement an interceptor, please leave this
+ * method unimplemented and implement didReceiveData: method instead. If this method and
+ * didReceiveRawMessage are implemented at the same time, implementation of this method will be
+ * ignored.
+ *
  * Issued when a message is received from the server. The message is the raw data received from the
  * Issued when a message is received from the server. The message is the raw data received from the
  * server, with decompression and without proto deserialization.
  * server, with decompression and without proto deserialization.
  */
  */
 - (void)didReceiveRawMessage:(nullable NSData *)message;
 - (void)didReceiveRawMessage:(nullable NSData *)message;
 
 
+/**
+ * Issued when a decompressed message is received from the server. The message is decompressed, and
+ * deserialized if a marshaller is provided to the call (marshaller is work in progress).
+ */
+- (void)didReceiveData:(id)data;
+
 /**
 /**
  * Issued when a call finished. If the call finished successfully, \a error is nil and \a
  * Issued when a call finished. If the call finished successfully, \a error is nil and \a
  * trainingMetadata consists any trailing metadata received from the server. Otherwise, \a error
  * trainingMetadata consists any trailing metadata received from the server. Otherwise, \a error
@@ -260,9 +272,10 @@ extern NSString *const kGRPCTrailersKey;
 - (void)cancel;
 - (void)cancel;
 
 
 /**
 /**
- * Send a message to the server. Data are sent as raw bytes in gRPC message frames.
+ * Send a message to the server. The data is subject to marshaller serialization and compression
+ * (marshaller is work in progress).
  */
  */
-- (void)writeData:(NSData *)data;
+- (void)writeData:(id)data;
 
 
 /**
 /**
  * Finish the RPC request and half-close the call. The server may still send messages and/or
  * Finish the RPC request and half-close the call. The server may still send messages and/or

+ 87 - 256
src/objective-c/GRPCClient/GRPCCall.m

@@ -17,8 +17,9 @@
  */
  */
 
 
 #import "GRPCCall.h"
 #import "GRPCCall.h"
-
 #import "GRPCCall+OAuth2.h"
 #import "GRPCCall+OAuth2.h"
+#import "GRPCCallOptions.h"
+#import "GRPCInterceptor.h"
 
 
 #import <RxLibrary/GRXBufferedPipe.h>
 #import <RxLibrary/GRXBufferedPipe.h>
 #import <RxLibrary/GRXConcurrentWriteable.h>
 #import <RxLibrary/GRXConcurrentWriteable.h>
@@ -27,7 +28,8 @@
 #include <grpc/grpc.h>
 #include <grpc/grpc.h>
 #include <grpc/support/time.h>
 #include <grpc/support/time.h>
 
 
-#import "GRPCCallOptions.h"
+#import "private/GRPCCall+V2API.h"
+#import "private/GRPCCallInternal.h"
 #import "private/GRPCChannelPool.h"
 #import "private/GRPCChannelPool.h"
 #import "private/GRPCCompletionQueue.h"
 #import "private/GRPCCompletionQueue.h"
 #import "private/GRPCConnectivityMonitor.h"
 #import "private/GRPCConnectivityMonitor.h"
@@ -57,11 +59,7 @@ const char *kCFStreamVarName = "grpc_cfstream";
 @property(atomic, strong) NSDictionary *responseHeaders;
 @property(atomic, strong) NSDictionary *responseHeaders;
 @property(atomic, strong) NSDictionary *responseTrailers;
 @property(atomic, strong) NSDictionary *responseTrailers;
 
 
-- (instancetype)initWithHost:(NSString *)host
-                        path:(NSString *)path
-                  callSafety:(GRPCCallSafety)safety
-              requestsWriter:(GRXWriter *)requestsWriter
-                 callOptions:(GRPCCallOptions *)callOptions;
+- (void)receiveNextMessages:(NSUInteger)numberOfMessages;
 
 
 - (instancetype)initWithHost:(NSString *)host
 - (instancetype)initWithHost:(NSString *)host
                         path:(NSString *)path
                         path:(NSString *)path
@@ -70,8 +68,6 @@ const char *kCFStreamVarName = "grpc_cfstream";
                  callOptions:(GRPCCallOptions *)callOptions
                  callOptions:(GRPCCallOptions *)callOptions
                    writeDone:(void (^)(void))writeDone;
                    writeDone:(void (^)(void))writeDone;
 
 
-- (void)receiveNextMessages:(NSUInteger)numberOfMessages;
-
 @end
 @end
 
 
 @implementation GRPCRequestOptions
 @implementation GRPCRequestOptions
@@ -98,32 +94,23 @@ const char *kCFStreamVarName = "grpc_cfstream";
 
 
 @end
 @end
 
 
+/**
+ * This class acts as a wrapper for interceptors
+ */
 @implementation GRPCCall2 {
 @implementation GRPCCall2 {
-  /** Options for the call. */
-  GRPCCallOptions *_callOptions;
   /** The handler of responses. */
   /** The handler of responses. */
-  id<GRPCResponseHandler> _handler;
+  id<GRPCResponseHandler> _responseHandler;
 
 
-  // Thread safety of ivars below are protected by _dispatchQueue.
+  /**
+   * Points to the first interceptor in the interceptor chain.
+   */
+  id<GRPCInterceptorInterface> _firstInterceptor;
 
 
   /**
   /**
-   * Make use of legacy GRPCCall to make calls. Nullified when call is finished.
+   * The actual call options being used by this call. It is different from the user-provided
+   * call options when the user provided a NULL call options object.
    */
    */
-  GRPCCall *_call;
-  /** Flags whether initial metadata has been published to response handler. */
-  BOOL _initialMetadataPublished;
-  /** Streaming call writeable to the underlying call. */
-  GRXBufferedPipe *_pipe;
-  /** Serial dispatch queue for tasks inside the call. */
-  dispatch_queue_t _dispatchQueue;
-  /** Flags whether call has started. */
-  BOOL _started;
-  /** Flags whether call has been canceled. */
-  BOOL _canceled;
-  /** Flags whether call has been finished. */
-  BOOL _finished;
-  /** The number of pending messages receiving requests. */
-  NSUInteger _pendingReceiveNextMessages;
+  GRPCCallOptions *_actualCallOptions;
 }
 }
 
 
 - (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions
 - (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions
@@ -145,30 +132,43 @@ const char *kCFStreamVarName = "grpc_cfstream";
 
 
   if ((self = [super init])) {
   if ((self = [super init])) {
     _requestOptions = [requestOptions copy];
     _requestOptions = [requestOptions copy];
-    if (callOptions == nil) {
-      _callOptions = [[GRPCCallOptions alloc] init];
+    _callOptions = [callOptions copy];
+    if (!_callOptions) {
+      _actualCallOptions = [[GRPCCallOptions alloc] init];
     } else {
     } else {
-      _callOptions = [callOptions copy];
+      _actualCallOptions = [callOptions copy];
     }
     }
-    _handler = responseHandler;
-    _initialMetadataPublished = NO;
-    _pipe = [GRXBufferedPipe pipe];
-    // Set queue QoS only when iOS version is 8.0 or above and Xcode version is 9.0 or above
-#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 || __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
-    if (@available(iOS 8.0, macOS 10.10, *)) {
-      _dispatchQueue = dispatch_queue_create(
-          NULL,
-          dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0));
+    _responseHandler = responseHandler;
+
+    // Initialize the interceptor chain
+    GRPCCall2Internal *internalCall = [[GRPCCall2Internal alloc] init];
+    id<GRPCInterceptorInterface> nextInterceptor = internalCall;
+    GRPCInterceptorManager *nextManager = nil;
+    NSArray *interceptorFactories = _actualCallOptions.interceptorFactories;
+    if (interceptorFactories.count == 0) {
+      [internalCall setResponseHandler:_responseHandler];
     } else {
     } else {
-#else
-    {
-#endif
-      _dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
+      for (int i = (int)interceptorFactories.count - 1; i >= 0; i--) {
+        GRPCInterceptorManager *manager =
+            [[GRPCInterceptorManager alloc] initWithNextInterceptor:nextInterceptor];
+        GRPCInterceptor *interceptor =
+            [interceptorFactories[i] createInterceptorWithManager:manager];
+        NSAssert(interceptor != nil, @"Failed to create interceptor");
+        if (interceptor == nil) {
+          return nil;
+        }
+        if (i == (int)interceptorFactories.count - 1) {
+          [internalCall setResponseHandler:interceptor];
+        } else {
+          [nextManager setPreviousInterceptor:interceptor];
+        }
+        nextInterceptor = interceptor;
+        nextManager = manager;
+      }
+
+      [nextManager setPreviousInterceptor:_responseHandler];
     }
     }
-    dispatch_set_target_queue(_dispatchQueue, responseHandler.dispatchQueue);
-    _started = NO;
-    _canceled = NO;
-    _finished = NO;
+    _firstInterceptor = nextInterceptor;
   }
   }
 
 
   return self;
   return self;
@@ -181,236 +181,65 @@ const char *kCFStreamVarName = "grpc_cfstream";
 }
 }
 
 
 - (void)start {
 - (void)start {
-  GRPCCall *copiedCall = nil;
+  id<GRPCInterceptorInterface> copiedFirstInterceptor;
   @synchronized(self) {
   @synchronized(self) {
-    NSAssert(!_started, @"Call already started.");
-    NSAssert(!_canceled, @"Call already canceled.");
-    if (_started) {
-      return;
-    }
-    if (_canceled) {
-      return;
-    }
-
-    _started = YES;
-    if (!_callOptions) {
-      _callOptions = [[GRPCCallOptions alloc] init];
-    }
-
-    _call = [[GRPCCall alloc] initWithHost:_requestOptions.host
-                                      path:_requestOptions.path
-                                callSafety:_requestOptions.safety
-                            requestsWriter:_pipe
-                               callOptions:_callOptions
-                                 writeDone:^{
-                                   @synchronized(self) {
-                                     if (self->_handler) {
-                                       [self issueDidWriteData];
-                                     }
-                                   }
-                                 }];
-    [_call setResponseDispatchQueue:_dispatchQueue];
-    if (_callOptions.initialMetadata) {
-      [_call.requestHeaders addEntriesFromDictionary:_callOptions.initialMetadata];
-    }
-    if (_pendingReceiveNextMessages > 0) {
-      [_call receiveNextMessages:_pendingReceiveNextMessages];
-      _pendingReceiveNextMessages = 0;
-    }
-    copiedCall = _call;
+    copiedFirstInterceptor = _firstInterceptor;
   }
   }
-
-  void (^valueHandler)(id value) = ^(id value) {
-    @synchronized(self) {
-      if (self->_handler) {
-        if (!self->_initialMetadataPublished) {
-          self->_initialMetadataPublished = YES;
-          [self issueInitialMetadata:self->_call.responseHeaders];
-        }
-        if (value) {
-          [self issueMessage:value];
-        }
-      }
-    }
-  };
-  void (^completionHandler)(NSError *errorOrNil) = ^(NSError *errorOrNil) {
-    @synchronized(self) {
-      if (self->_handler) {
-        if (!self->_initialMetadataPublished) {
-          self->_initialMetadataPublished = YES;
-          [self issueInitialMetadata:self->_call.responseHeaders];
-        }
-        [self issueClosedWithTrailingMetadata:self->_call.responseTrailers error:errorOrNil];
-      }
-      // Clearing _call must happen *after* dispatching close in order to get trailing
-      // metadata from _call.
-      if (self->_call) {
-        // Clean up the request writers. This should have no effect to _call since its
-        // response writeable is already nullified.
-        [self->_pipe writesFinishedWithError:nil];
-        self->_call = nil;
-        self->_pipe = nil;
-      }
-    }
-  };
-  id<GRXWriteable> responseWriteable =
-      [[GRXWriteable alloc] initWithValueHandler:valueHandler completionHandler:completionHandler];
-  [copiedCall startWithWriteable:responseWriteable];
-}
-
-- (void)cancel {
-  GRPCCall *copiedCall = nil;
-  @synchronized(self) {
-    if (_canceled) {
-      return;
-    }
-
-    _canceled = YES;
-
-    copiedCall = _call;
-    _call = nil;
-    _pipe = nil;
-
-    if ([_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
-      dispatch_async(_dispatchQueue, ^{
-        // Copy to local so that block is freed after cancellation completes.
-        id<GRPCResponseHandler> copiedHandler = nil;
-        @synchronized(self) {
-          copiedHandler = self->_handler;
-          self->_handler = nil;
-        }
-
-        [copiedHandler didCloseWithTrailingMetadata:nil
-                                              error:[NSError errorWithDomain:kGRPCErrorDomain
-                                                                        code:GRPCErrorCodeCancelled
-                                                                    userInfo:@{
-                                                                      NSLocalizedDescriptionKey :
-                                                                          @"Canceled by app"
-                                                                    }]];
-      });
-    } else {
-      _handler = nil;
-    }
+  GRPCRequestOptions *requestOptions = [_requestOptions copy];
+  GRPCCallOptions *callOptions = [_actualCallOptions copy];
+  if ([copiedFirstInterceptor respondsToSelector:@selector(startWithRequestOptions:callOptions:)]) {
+    dispatch_async(copiedFirstInterceptor.requestDispatchQueue, ^{
+      [copiedFirstInterceptor startWithRequestOptions:requestOptions callOptions:callOptions];
+    });
   }
   }
-  [copiedCall cancel];
 }
 }
 
 
-- (void)writeData:(NSData *)data {
-  GRXBufferedPipe *copiedPipe = nil;
+- (void)cancel {
+  id<GRPCInterceptorInterface> copiedFirstInterceptor;
   @synchronized(self) {
   @synchronized(self) {
-    NSAssert(!_canceled, @"Call already canceled.");
-    NSAssert(!_finished, @"Call is half-closed before sending data.");
-    if (_canceled) {
-      return;
-    }
-    if (_finished) {
-      return;
-    }
-
-    if (_pipe) {
-      copiedPipe = _pipe;
-    }
+    copiedFirstInterceptor = _firstInterceptor;
   }
   }
-  [copiedPipe writeValue:data];
-}
-
-- (void)finish {
-  GRXBufferedPipe *copiedPipe = nil;
-  @synchronized(self) {
-    NSAssert(_started, @"Call not started.");
-    NSAssert(!_canceled, @"Call already canceled.");
-    NSAssert(!_finished, @"Call already half-closed.");
-    if (!_started) {
-      return;
-    }
-    if (_canceled) {
-      return;
-    }
-    if (_finished) {
-      return;
-    }
-
-    if (_pipe) {
-      copiedPipe = _pipe;
-      _pipe = nil;
-    }
-    _finished = YES;
+  if ([copiedFirstInterceptor respondsToSelector:@selector(cancel)]) {
+    dispatch_async(copiedFirstInterceptor.requestDispatchQueue, ^{
+      [copiedFirstInterceptor cancel];
+    });
   }
   }
-  [copiedPipe writesFinishedWithError:nil];
 }
 }
 
 
-- (void)issueInitialMetadata:(NSDictionary *)initialMetadata {
+- (void)writeData:(id)data {
+  id<GRPCInterceptorInterface> copiedFirstInterceptor;
   @synchronized(self) {
   @synchronized(self) {
-    if (initialMetadata != nil &&
-        [_handler respondsToSelector:@selector(didReceiveInitialMetadata:)]) {
-      dispatch_async(_dispatchQueue, ^{
-        id<GRPCResponseHandler> copiedHandler = nil;
-        @synchronized(self) {
-          copiedHandler = self->_handler;
-        }
-        [copiedHandler didReceiveInitialMetadata:initialMetadata];
-      });
-    }
+    copiedFirstInterceptor = _firstInterceptor;
   }
   }
-}
-
-- (void)issueMessage:(id)message {
-  @synchronized(self) {
-    if (message != nil && [_handler respondsToSelector:@selector(didReceiveRawMessage:)]) {
-      dispatch_async(_dispatchQueue, ^{
-        id<GRPCResponseHandler> copiedHandler = nil;
-        @synchronized(self) {
-          copiedHandler = self->_handler;
-        }
-        [copiedHandler didReceiveRawMessage:message];
-      });
-    }
+  if ([copiedFirstInterceptor respondsToSelector:@selector(writeData:)]) {
+    dispatch_async(copiedFirstInterceptor.requestDispatchQueue, ^{
+      [copiedFirstInterceptor writeData:data];
+    });
   }
   }
 }
 }
 
 
-- (void)issueClosedWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
+- (void)finish {
+  id<GRPCInterceptorInterface> copiedFirstInterceptor;
   @synchronized(self) {
   @synchronized(self) {
-    if ([_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
-      dispatch_async(_dispatchQueue, ^{
-        id<GRPCResponseHandler> copiedHandler = nil;
-        @synchronized(self) {
-          copiedHandler = self->_handler;
-          // Clean up _handler so that no more responses are reported to the handler.
-          self->_handler = nil;
-        }
-        [copiedHandler didCloseWithTrailingMetadata:trailingMetadata error:error];
-      });
-    } else {
-      _handler = nil;
-    }
+    copiedFirstInterceptor = _firstInterceptor;
   }
   }
-}
-
-- (void)issueDidWriteData {
-  @synchronized(self) {
-    if (_callOptions.flowControlEnabled && [_handler respondsToSelector:@selector(didWriteData)]) {
-      dispatch_async(_dispatchQueue, ^{
-        id<GRPCResponseHandler> copiedHandler = nil;
-        @synchronized(self) {
-          copiedHandler = self->_handler;
-        };
-        [copiedHandler didWriteData];
-      });
-    }
+  if ([copiedFirstInterceptor respondsToSelector:@selector(finish)]) {
+    dispatch_async(copiedFirstInterceptor.requestDispatchQueue, ^{
+      [copiedFirstInterceptor finish];
+    });
   }
   }
 }
 }
 
 
 - (void)receiveNextMessages:(NSUInteger)numberOfMessages {
 - (void)receiveNextMessages:(NSUInteger)numberOfMessages {
-  // branching based on _callOptions.flowControlEnabled is handled inside _call
-  GRPCCall *copiedCall = nil;
+  id<GRPCInterceptorInterface> copiedFirstInterceptor;
   @synchronized(self) {
   @synchronized(self) {
-    copiedCall = _call;
-    if (copiedCall == nil) {
-      _pendingReceiveNextMessages += numberOfMessages;
-      return;
-    }
+    copiedFirstInterceptor = _firstInterceptor;
+  }
+  if ([copiedFirstInterceptor respondsToSelector:@selector(receiveNextMessages:)]) {
+    dispatch_async(copiedFirstInterceptor.requestDispatchQueue, ^{
+      [copiedFirstInterceptor receiveNextMessages:numberOfMessages];
+    });
   }
   }
-  [copiedCall receiveNextMessages:numberOfMessages];
 }
 }
 
 
 @end
 @end
@@ -648,6 +477,8 @@ const char *kCFStreamVarName = "grpc_cfstream";
 }
 }
 
 
 - (void)dealloc {
 - (void)dealloc {
+  [GRPCConnectivityMonitor unregisterObserver:self];
+
   __block GRPCWrappedCall *wrappedCall = _wrappedCall;
   __block GRPCWrappedCall *wrappedCall = _wrappedCall;
   dispatch_async(_callQueue, ^{
   dispatch_async(_callQueue, ^{
     wrappedCall = nil;
     wrappedCall = nil;

+ 16 - 0
src/objective-c/GRPCClient/GRPCCallOptions.h

@@ -98,6 +98,14 @@ typedef NS_ENUM(NSUInteger, GRPCTransportType) {
  */
  */
 @property(readonly) BOOL flowControlEnabled;
 @property(readonly) BOOL flowControlEnabled;
 
 
+/**
+ * An array of interceptor factories. When a call starts, interceptors are created
+ * by these factories and chained together with the same order as the factories in
+ * this array. This parameter should not be modified by any interceptor and will
+ * not take effect if done so.
+ */
+@property(copy, readonly) NSArray *interceptorFactories;
+
 // OAuth2 parameters. Users of gRPC may specify one of the following two parameters.
 // OAuth2 parameters. Users of gRPC may specify one of the following two parameters.
 
 
 /**
 /**
@@ -253,6 +261,14 @@ typedef NS_ENUM(NSUInteger, GRPCTransportType) {
  */
  */
 @property(readwrite) BOOL flowControlEnabled;
 @property(readwrite) BOOL flowControlEnabled;
 
 
+/**
+ * An array of interceptor factories. When a call starts, interceptors are created
+ * by these factories and chained together with the same order as the factories in
+ * this array. This parameter should not be modified by any interceptor and will
+ * not take effect if done so.
+ */
+@property(copy, readwrite) NSArray *interceptorFactories;
+
 // OAuth2 parameters. Users of gRPC may specify one of the following two parameters.
 // OAuth2 parameters. Users of gRPC may specify one of the following two parameters.
 
 
 /**
 /**

+ 16 - 0
src/objective-c/GRPCClient/GRPCCallOptions.m

@@ -23,6 +23,7 @@
 static NSString *const kDefaultServerAuthority = nil;
 static NSString *const kDefaultServerAuthority = nil;
 static const NSTimeInterval kDefaultTimeout = 0;
 static const NSTimeInterval kDefaultTimeout = 0;
 static const BOOL kDefaultFlowControlEnabled = NO;
 static const BOOL kDefaultFlowControlEnabled = NO;
+static NSArray *const kDefaultInterceptorFactories = nil;
 static NSDictionary *const kDefaultInitialMetadata = nil;
 static NSDictionary *const kDefaultInitialMetadata = nil;
 static NSString *const kDefaultUserAgentPrefix = nil;
 static NSString *const kDefaultUserAgentPrefix = nil;
 static const NSUInteger kDefaultResponseSizeLimit = 0;
 static const NSUInteger kDefaultResponseSizeLimit = 0;
@@ -61,6 +62,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
   NSString *_serverAuthority;
   NSString *_serverAuthority;
   NSTimeInterval _timeout;
   NSTimeInterval _timeout;
   BOOL _flowControlEnabled;
   BOOL _flowControlEnabled;
+  NSArray *_interceptorFactories;
   NSString *_oauth2AccessToken;
   NSString *_oauth2AccessToken;
   id<GRPCAuthorizationProtocol> _authTokenProvider;
   id<GRPCAuthorizationProtocol> _authTokenProvider;
   NSDictionary *_initialMetadata;
   NSDictionary *_initialMetadata;
@@ -87,6 +89,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
 @synthesize serverAuthority = _serverAuthority;
 @synthesize serverAuthority = _serverAuthority;
 @synthesize timeout = _timeout;
 @synthesize timeout = _timeout;
 @synthesize flowControlEnabled = _flowControlEnabled;
 @synthesize flowControlEnabled = _flowControlEnabled;
+@synthesize interceptorFactories = _interceptorFactories;
 @synthesize oauth2AccessToken = _oauth2AccessToken;
 @synthesize oauth2AccessToken = _oauth2AccessToken;
 @synthesize authTokenProvider = _authTokenProvider;
 @synthesize authTokenProvider = _authTokenProvider;
 @synthesize initialMetadata = _initialMetadata;
 @synthesize initialMetadata = _initialMetadata;
@@ -113,6 +116,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
   return [self initWithServerAuthority:kDefaultServerAuthority
   return [self initWithServerAuthority:kDefaultServerAuthority
                                timeout:kDefaultTimeout
                                timeout:kDefaultTimeout
                     flowControlEnabled:kDefaultFlowControlEnabled
                     flowControlEnabled:kDefaultFlowControlEnabled
+                  interceptorFactories:kDefaultInterceptorFactories
                      oauth2AccessToken:kDefaultOauth2AccessToken
                      oauth2AccessToken:kDefaultOauth2AccessToken
                      authTokenProvider:kDefaultAuthTokenProvider
                      authTokenProvider:kDefaultAuthTokenProvider
                        initialMetadata:kDefaultInitialMetadata
                        initialMetadata:kDefaultInitialMetadata
@@ -139,6 +143,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
 - (instancetype)initWithServerAuthority:(NSString *)serverAuthority
 - (instancetype)initWithServerAuthority:(NSString *)serverAuthority
                                 timeout:(NSTimeInterval)timeout
                                 timeout:(NSTimeInterval)timeout
                      flowControlEnabled:(BOOL)flowControlEnabled
                      flowControlEnabled:(BOOL)flowControlEnabled
+                   interceptorFactories:(NSArray *)interceptorFactories
                       oauth2AccessToken:(NSString *)oauth2AccessToken
                       oauth2AccessToken:(NSString *)oauth2AccessToken
                       authTokenProvider:(id<GRPCAuthorizationProtocol>)authTokenProvider
                       authTokenProvider:(id<GRPCAuthorizationProtocol>)authTokenProvider
                         initialMetadata:(NSDictionary *)initialMetadata
                         initialMetadata:(NSDictionary *)initialMetadata
@@ -164,6 +169,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
     _serverAuthority = [serverAuthority copy];
     _serverAuthority = [serverAuthority copy];
     _timeout = timeout < 0 ? 0 : timeout;
     _timeout = timeout < 0 ? 0 : timeout;
     _flowControlEnabled = flowControlEnabled;
     _flowControlEnabled = flowControlEnabled;
+    _interceptorFactories = interceptorFactories;
     _oauth2AccessToken = [oauth2AccessToken copy];
     _oauth2AccessToken = [oauth2AccessToken copy];
     _authTokenProvider = authTokenProvider;
     _authTokenProvider = authTokenProvider;
     _initialMetadata =
     _initialMetadata =
@@ -200,6 +206,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
       [[GRPCCallOptions allocWithZone:zone] initWithServerAuthority:_serverAuthority
       [[GRPCCallOptions allocWithZone:zone] initWithServerAuthority:_serverAuthority
                                                             timeout:_timeout
                                                             timeout:_timeout
                                                  flowControlEnabled:_flowControlEnabled
                                                  flowControlEnabled:_flowControlEnabled
+                                               interceptorFactories:_interceptorFactories
                                                   oauth2AccessToken:_oauth2AccessToken
                                                   oauth2AccessToken:_oauth2AccessToken
                                                   authTokenProvider:_authTokenProvider
                                                   authTokenProvider:_authTokenProvider
                                                     initialMetadata:_initialMetadata
                                                     initialMetadata:_initialMetadata
@@ -229,6 +236,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
       initWithServerAuthority:[_serverAuthority copy]
       initWithServerAuthority:[_serverAuthority copy]
                       timeout:_timeout
                       timeout:_timeout
            flowControlEnabled:_flowControlEnabled
            flowControlEnabled:_flowControlEnabled
+         interceptorFactories:_interceptorFactories
             oauth2AccessToken:[_oauth2AccessToken copy]
             oauth2AccessToken:[_oauth2AccessToken copy]
             authTokenProvider:_authTokenProvider
             authTokenProvider:_authTokenProvider
               initialMetadata:[[NSDictionary alloc] initWithDictionary:_initialMetadata
               initialMetadata:[[NSDictionary alloc] initWithDictionary:_initialMetadata
@@ -310,6 +318,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
 @dynamic serverAuthority;
 @dynamic serverAuthority;
 @dynamic timeout;
 @dynamic timeout;
 @dynamic flowControlEnabled;
 @dynamic flowControlEnabled;
+@dynamic interceptorFactories;
 @dynamic oauth2AccessToken;
 @dynamic oauth2AccessToken;
 @dynamic authTokenProvider;
 @dynamic authTokenProvider;
 @dynamic initialMetadata;
 @dynamic initialMetadata;
@@ -336,6 +345,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
   return [self initWithServerAuthority:kDefaultServerAuthority
   return [self initWithServerAuthority:kDefaultServerAuthority
                                timeout:kDefaultTimeout
                                timeout:kDefaultTimeout
                     flowControlEnabled:kDefaultFlowControlEnabled
                     flowControlEnabled:kDefaultFlowControlEnabled
+                  interceptorFactories:kDefaultInterceptorFactories
                      oauth2AccessToken:kDefaultOauth2AccessToken
                      oauth2AccessToken:kDefaultOauth2AccessToken
                      authTokenProvider:kDefaultAuthTokenProvider
                      authTokenProvider:kDefaultAuthTokenProvider
                        initialMetadata:kDefaultInitialMetadata
                        initialMetadata:kDefaultInitialMetadata
@@ -364,6 +374,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
       [[GRPCCallOptions allocWithZone:zone] initWithServerAuthority:_serverAuthority
       [[GRPCCallOptions allocWithZone:zone] initWithServerAuthority:_serverAuthority
                                                             timeout:_timeout
                                                             timeout:_timeout
                                                  flowControlEnabled:_flowControlEnabled
                                                  flowControlEnabled:_flowControlEnabled
+                                               interceptorFactories:_interceptorFactories
                                                   oauth2AccessToken:_oauth2AccessToken
                                                   oauth2AccessToken:_oauth2AccessToken
                                                   authTokenProvider:_authTokenProvider
                                                   authTokenProvider:_authTokenProvider
                                                     initialMetadata:_initialMetadata
                                                     initialMetadata:_initialMetadata
@@ -393,6 +404,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
       initWithServerAuthority:_serverAuthority
       initWithServerAuthority:_serverAuthority
                       timeout:_timeout
                       timeout:_timeout
            flowControlEnabled:_flowControlEnabled
            flowControlEnabled:_flowControlEnabled
+         interceptorFactories:_interceptorFactories
             oauth2AccessToken:_oauth2AccessToken
             oauth2AccessToken:_oauth2AccessToken
             authTokenProvider:_authTokenProvider
             authTokenProvider:_authTokenProvider
               initialMetadata:_initialMetadata
               initialMetadata:_initialMetadata
@@ -433,6 +445,10 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
   _flowControlEnabled = flowControlEnabled;
   _flowControlEnabled = flowControlEnabled;
 }
 }
 
 
+- (void)setInterceptorFactories:(NSArray *)interceptorFactories {
+  _interceptorFactories = interceptorFactories;
+}
+
 - (void)setOauth2AccessToken:(NSString *)oauth2AccessToken {
 - (void)setOauth2AccessToken:(NSString *)oauth2AccessToken {
   _oauth2AccessToken = [oauth2AccessToken copy];
   _oauth2AccessToken = [oauth2AccessToken copy];
 }
 }

+ 269 - 0
src/objective-c/GRPCClient/GRPCInterceptor.h

@@ -0,0 +1,269 @@
+/*
+ *
+ * Copyright 2019 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/**
+ * API for interceptors implementation. This feature is currently EXPERIMENTAL and is subject to
+ * breaking changes without prior notice.
+ *
+ * The interceptors in the gRPC system forms a chain. When a call is made by the user, each
+ * interceptor on the chain has chances to react to events of the call and make necessary
+ * modifications to the call's parameters, data, metadata, or flow.
+ *
+ *
+ *                                   -----------
+ *                                  | GRPCCall2 |
+ *                                   -----------
+ *                                        |
+ *                                        |
+ *                           --------------------------
+ *                          | GRPCInterceptorManager 1 |
+ *                           --------------------------
+ *                          | GRPCInterceptor 1        |
+ *                           --------------------------
+ *                                        |
+ *                                       ...
+ *                                        |
+ *                           --------------------------
+ *                          | GRPCInterceptorManager N |
+ *                           --------------------------
+ *                          | GRPCInterceptor N        |
+ *                           --------------------------
+ *                                        |
+ *                                        |
+ *                               ------------------
+ *                              | GRPCCallInternal |
+ *                               ------------------
+ *
+ * The chain of interceptors is initialized when the corresponding GRPCCall2 object or proto call
+ * object (GRPCUnaryProtoCall and GRPCStreamingProtoCall) is initialized. The initialization of the
+ * chain is controlled by the property interceptorFactories in the callOptions parameter of the
+ * corresponding call object. Property interceptorFactories is an array of
+ * id<GRPCInterceptorFactory> objects provided by the user. When a call object is initialized, each
+ * interceptor factory generates an interceptor object for the call. gRPC internally links the
+ * interceptors with each other and with the actual call object. The order of the interceptors in
+ * the chain is exactly the same as the order of factory objects in interceptorFactories property.
+ * All requests (start, write, finish, cancel, receive next) initiated by the user will be processed
+ * in the order of interceptors, and all responses (initial metadata, data, trailing metadata, write
+ * data done) are processed in the reverse order.
+ *
+ * Each interceptor in the interceptor chain should behave as a user of the next interceptor, and at
+ * the same time behave as a call to the previous interceptor. Therefore interceptor implementations
+ * must follow the state transition of gRPC calls and must also forward events that are consistent
+ * with the current state of the next/previous interceptor. They should also make sure that the
+ * events they forwarded to the next and previous interceptors will, in the end, make the neighbour
+ * interceptor terminate correctly and reaches "finished" state. The diagram below shows the state
+ * transitions. Any event not appearing on the diagram means the event is not permitted for that
+ * particular state.
+ *
+ *                                      writeData
+ *                                  receiveNextMessages
+ *                               didReceiveInitialMetadata
+ *                                    didReceiveData
+ *                                     didWriteData                   receiveNextmessages
+ *           writeData  -----             -----                 ----  didReceiveInitialMetadata
+ * receiveNextMessages |     |           |     |               |    | didReceiveData
+ *                     |     V           |     V               |    V didWriteData
+ *               -------------  start   ---------   finish    ------------
+ *              | initialized | -----> | started | --------> | half-close |
+ *               -------------          ---------             ------------
+ *                     |                     |                      |
+ *                     |                     | didClose             | didClose
+ *                     |cancel               | cancel               | cancel
+ *                     |                     V                      |
+ *                     |                 ----------                 |
+ *                      --------------> | finished | <--------------
+ *                                       ----------
+ *                                        |      ^ writeData
+ *                                        |      | finish
+ *                                         ------  cancel
+ *                                                 receiveNextMessages
+ *
+ * Events of requests and responses are dispatched to interceptor objects using the interceptor's
+ * dispatch queue. The dispatch queue should be serial queue to make sure the events are processed
+ * in order. Interceptor implementations must derive from GRPCInterceptor class. The class makes
+ * some basic implementation of all methods responding to an event of a call. If an interceptor does
+ * not care about a particular event, it can use the basic implementation of the GRPCInterceptor
+ * class, which simply forward the event to the next or previous interceptor in the chain.
+ *
+ * The interceptor object should be unique for each call since the call context is not passed to the
+ * interceptor object in a call event. However, the interceptors can be implemented to share states
+ * by receiving state sharing object from the factory upon construction.
+ */
+
+#import "GRPCCall.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class GRPCInterceptorManager;
+@class GRPCInterceptor;
+
+/**
+ * The GRPCInterceptorInterface defines the request events that can occur to an interceptr.
+ */
+@protocol GRPCInterceptorInterface<NSObject>
+
+/**
+ * The queue on which all methods of this interceptor should be dispatched on. The queue must be a
+ * serial queue.
+ */
+@property(readonly) dispatch_queue_t requestDispatchQueue;
+
+/**
+ * To start the call. This method will only be called once for each instance.
+ */
+- (void)startWithRequestOptions:(GRPCRequestOptions *)requestOptions
+                    callOptions:(GRPCCallOptions *)callOptions;
+
+/**
+ * To write data to the call.
+ */
+- (void)writeData:(id)data;
+
+/**
+ * To finish the stream of requests.
+ */
+- (void)finish;
+
+/**
+ * To cancel the call.
+ */
+- (void)cancel;
+
+/**
+ * To indicate the call that the previous interceptor is ready to receive more messages.
+ */
+- (void)receiveNextMessages:(NSUInteger)numberOfMessages;
+
+@end
+
+/**
+ * An interceptor factory object should be used to create interceptor object for the call at the
+ * call start time.
+ */
+@protocol GRPCInterceptorFactory
+
+/**
+ * Create an interceptor object. gRPC uses the returned object as the interceptor for the current
+ * call
+ */
+- (GRPCInterceptor *)createInterceptorWithManager:(GRPCInterceptorManager *)interceptorManager;
+
+@end
+
+/**
+ * The interceptor manager object retains reference to the next and previous interceptor object in
+ * the interceptor chain, and forward corresponding events to them. When a call terminates, it must
+ * invoke shutDown method of its corresponding manager so that references to other interceptors can
+ * be released.
+ */
+@interface GRPCInterceptorManager : NSObject
+
+- (instancetype)init NS_UNAVAILABLE;
+
++ (instancetype) new NS_UNAVAILABLE;
+
+- (nullable instancetype)initWithNextInterceptor:(id<GRPCInterceptorInterface>)nextInterceptor
+    NS_DESIGNATED_INITIALIZER;
+
+/** Set the previous interceptor in the chain. Can only be set once. */
+- (void)setPreviousInterceptor:(id<GRPCResponseHandler>)previousInterceptor;
+
+/** Indicate shutdown of the interceptor; release the reference to other interceptors */
+- (void)shutDown;
+
+// Methods to forward GRPCInterceptorInterface calls to the next interceptor
+
+/** Notify the next interceptor in the chain to start the call and pass arguments */
+- (void)startNextInterceptorWithRequest:(GRPCRequestOptions *)requestOptions
+                            callOptions:(GRPCCallOptions *)callOptions;
+
+/** Pass a message to be sent to the next interceptor in the chain */
+- (void)writeNextInterceptorWithData:(id)data;
+
+/** Notify the next interceptor in the chain to finish the call */
+- (void)finishNextInterceptor;
+
+/** Notify the next interceptor in the chain to cancel the call */
+- (void)cancelNextInterceptor;
+
+/** Notify the next interceptor in the chain to receive more messages */
+- (void)receiveNextInterceptorMessages:(NSUInteger)numberOfMessages;
+
+// Methods to forward GRPCResponseHandler callbacks to the previous object
+
+/** Forward initial metadata to the previous interceptor in the chain */
+- (void)forwardPreviousInterceptorWithInitialMetadata:(nullable NSDictionary *)initialMetadata;
+
+/** Forward a received message to the previous interceptor in the chain */
+- (void)forwardPreviousInterceptorWithData:(nullable id)data;
+
+/** Forward call close and trailing metadata to the previous interceptor in the chain */
+- (void)forwardPreviousInterceptorCloseWithTrailingMetadata:
+            (nullable NSDictionary *)trailingMetadata
+                                                      error:(nullable NSError *)error;
+
+/** Forward write completion to the previous interceptor in the chain */
+- (void)forwardPreviousInterceptorDidWriteData;
+
+@end
+
+/**
+ * Base class for a gRPC interceptor. The implementation of the base class provides default behavior
+ * of an interceptor, which is simply forward a request/callback to the next/previous interceptor in
+ * the chain. The base class implementation uses the same dispatch queue for both requests and
+ * callbacks.
+ *
+ * An interceptor implementation should inherit from this base class and initialize the base class
+ * with [super initWithInterceptorManager:dispatchQueue:] for the default implementation to function
+ * properly.
+ */
+@interface GRPCInterceptor : NSObject<GRPCInterceptorInterface, GRPCResponseHandler>
+
+- (instancetype)init NS_UNAVAILABLE;
+
++ (instancetype) new NS_UNAVAILABLE;
+
+/**
+ * Initialize the interceptor with the next interceptor in the chain, and provide the dispatch queue
+ * that this interceptor's methods are dispatched onto.
+ */
+- (nullable instancetype)initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager
+                               requestDispatchQueue:(dispatch_queue_t)requestDispatchQueue
+                              responseDispatchQueue:(dispatch_queue_t)responseDispatchQueue
+    NS_DESIGNATED_INITIALIZER;
+
+// Default implementation of GRPCInterceptorInterface
+
+- (void)startWithRequestOptions:(GRPCRequestOptions *)requestOptions
+                    callOptions:(GRPCCallOptions *)callOptions;
+- (void)writeData:(id)data;
+- (void)finish;
+- (void)cancel;
+- (void)receiveNextMessages:(NSUInteger)numberOfMessages;
+
+// Default implementation of GRPCResponeHandler
+
+- (void)didReceiveInitialMetadata:(nullable NSDictionary *)initialMetadata;
+- (void)didReceiveData:(id)data;
+- (void)didCloseWithTrailingMetadata:(nullable NSDictionary *)trailingMetadata
+                               error:(nullable NSError *)error;
+- (void)didWriteData;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 219 - 0
src/objective-c/GRPCClient/GRPCInterceptor.m

@@ -0,0 +1,219 @@
+/*
+ *
+ * Copyright 2019 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "GRPCInterceptor.h"
+
+@implementation GRPCInterceptorManager {
+  id<GRPCInterceptorInterface> _nextInterceptor;
+  id<GRPCResponseHandler> _previousInterceptor;
+}
+
+- (instancetype)initWithNextInterceptor:(id<GRPCInterceptorInterface>)nextInterceptor {
+  if ((self = [super init])) {
+    _nextInterceptor = nextInterceptor;
+  }
+
+  return self;
+}
+
+- (void)setPreviousInterceptor:(id<GRPCResponseHandler>)previousInterceptor {
+  _previousInterceptor = previousInterceptor;
+}
+
+- (void)shutDown {
+  _nextInterceptor = nil;
+  _previousInterceptor = nil;
+}
+
+- (void)startNextInterceptorWithRequest:(GRPCRequestOptions *)requestOptions
+                            callOptions:(GRPCCallOptions *)callOptions {
+  if (_nextInterceptor != nil) {
+    id<GRPCInterceptorInterface> copiedNextInterceptor = _nextInterceptor;
+    dispatch_async(copiedNextInterceptor.requestDispatchQueue, ^{
+      [copiedNextInterceptor startWithRequestOptions:requestOptions callOptions:callOptions];
+    });
+  }
+}
+
+- (void)writeNextInterceptorWithData:(id)data {
+  if (_nextInterceptor != nil) {
+    id<GRPCInterceptorInterface> copiedNextInterceptor = _nextInterceptor;
+    dispatch_async(copiedNextInterceptor.requestDispatchQueue, ^{
+      [copiedNextInterceptor writeData:data];
+    });
+  }
+}
+
+- (void)finishNextInterceptor {
+  if (_nextInterceptor != nil) {
+    id<GRPCInterceptorInterface> copiedNextInterceptor = _nextInterceptor;
+    dispatch_async(copiedNextInterceptor.requestDispatchQueue, ^{
+      [copiedNextInterceptor finish];
+    });
+  }
+}
+
+- (void)cancelNextInterceptor {
+  if (_nextInterceptor != nil) {
+    id<GRPCInterceptorInterface> copiedNextInterceptor = _nextInterceptor;
+    dispatch_async(copiedNextInterceptor.requestDispatchQueue, ^{
+      [copiedNextInterceptor cancel];
+    });
+  }
+}
+
+/** Notify the next interceptor in the chain to receive more messages */
+- (void)receiveNextInterceptorMessages:(NSUInteger)numberOfMessages {
+  if (_nextInterceptor != nil) {
+    id<GRPCInterceptorInterface> copiedNextInterceptor = _nextInterceptor;
+    dispatch_async(copiedNextInterceptor.requestDispatchQueue, ^{
+      [copiedNextInterceptor receiveNextMessages:numberOfMessages];
+    });
+  }
+}
+
+// Methods to forward GRPCResponseHandler callbacks to the previous object
+
+/** Forward initial metadata to the previous interceptor in the chain */
+- (void)forwardPreviousInterceptorWithInitialMetadata:(nullable NSDictionary *)initialMetadata {
+  if ([_previousInterceptor respondsToSelector:@selector(didReceiveInitialMetadata:)]) {
+    id<GRPCResponseHandler> copiedPreviousInterceptor = _previousInterceptor;
+    dispatch_async(copiedPreviousInterceptor.dispatchQueue, ^{
+      [copiedPreviousInterceptor didReceiveInitialMetadata:initialMetadata];
+    });
+  }
+}
+
+/** Forward a received message to the previous interceptor in the chain */
+- (void)forwardPreviousInterceptorWithData:(id)data {
+  if ([_previousInterceptor respondsToSelector:@selector(didReceiveData:)]) {
+    id<GRPCResponseHandler> copiedPreviousInterceptor = _previousInterceptor;
+    dispatch_async(copiedPreviousInterceptor.dispatchQueue, ^{
+      [copiedPreviousInterceptor didReceiveData:data];
+    });
+  }
+}
+
+/** Forward call close and trailing metadata to the previous interceptor in the chain */
+- (void)forwardPreviousInterceptorCloseWithTrailingMetadata:
+            (nullable NSDictionary *)trailingMetadata
+                                                      error:(nullable NSError *)error {
+  if ([_previousInterceptor respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
+    id<GRPCResponseHandler> copiedPreviousInterceptor = _previousInterceptor;
+    dispatch_async(copiedPreviousInterceptor.dispatchQueue, ^{
+      [copiedPreviousInterceptor didCloseWithTrailingMetadata:trailingMetadata error:error];
+    });
+  }
+}
+
+/** Forward write completion to the previous interceptor in the chain */
+- (void)forwardPreviousInterceptorDidWriteData {
+  if ([_previousInterceptor respondsToSelector:@selector(didWriteData)]) {
+    id<GRPCResponseHandler> copiedPreviousInterceptor = _previousInterceptor;
+    dispatch_async(copiedPreviousInterceptor.dispatchQueue, ^{
+      [copiedPreviousInterceptor didWriteData];
+    });
+  }
+}
+
+@end
+
+@implementation GRPCInterceptor {
+  GRPCInterceptorManager *_manager;
+  dispatch_queue_t _requestDispatchQueue;
+  dispatch_queue_t _responseDispatchQueue;
+}
+
+- (instancetype)initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager
+                      requestDispatchQueue:(dispatch_queue_t)requestDispatchQueue
+                     responseDispatchQueue:(dispatch_queue_t)responseDispatchQueue {
+  if ((self = [super init])) {
+    _manager = interceptorManager;
+    _requestDispatchQueue = requestDispatchQueue;
+    _responseDispatchQueue = responseDispatchQueue;
+  }
+
+  return self;
+}
+
+- (dispatch_queue_t)requestDispatchQueue {
+  return _requestDispatchQueue;
+}
+
+- (dispatch_queue_t)dispatchQueue {
+  return _responseDispatchQueue;
+}
+
+- (void)startWithRequestOptions:(GRPCRequestOptions *)requestOptions
+                    callOptions:(GRPCCallOptions *)callOptions {
+  [_manager startNextInterceptorWithRequest:requestOptions callOptions:callOptions];
+}
+
+- (void)writeData:(id)data {
+  [_manager writeNextInterceptorWithData:data];
+}
+
+- (void)finish {
+  [_manager finishNextInterceptor];
+}
+
+- (void)cancel {
+  [_manager cancelNextInterceptor];
+  [_manager
+      forwardPreviousInterceptorCloseWithTrailingMetadata:nil
+                                                    error:[NSError
+                                                              errorWithDomain:kGRPCErrorDomain
+                                                                         code:GRPCErrorCodeCancelled
+                                                                     userInfo:@{
+                                                                       NSLocalizedDescriptionKey :
+                                                                           @"Canceled"
+                                                                     }]];
+  [_manager shutDown];
+}
+
+- (void)receiveNextMessages:(NSUInteger)numberOfMessages {
+  [_manager receiveNextInterceptorMessages:numberOfMessages];
+}
+
+- (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata {
+  [_manager forwardPreviousInterceptorWithInitialMetadata:initialMetadata];
+}
+
+- (void)didReceiveRawMessage:(id)message {
+  NSAssert(NO,
+           @"The method didReceiveRawMessage is deprecated and cannot be used with interceptor");
+  NSLog(@"The method didReceiveRawMessage is deprecated and cannot be used with interceptor");
+  abort();
+}
+
+- (void)didReceiveData:(id)data {
+  [_manager forwardPreviousInterceptorWithData:data];
+}
+
+- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
+  [_manager forwardPreviousInterceptorCloseWithTrailingMetadata:trailingMetadata error:error];
+  [_manager shutDown];
+}
+
+- (void)didWriteData {
+  [_manager forwardPreviousInterceptorDidWriteData];
+}
+
+@end

+ 36 - 0
src/objective-c/GRPCClient/private/GRPCCall+V2API.h

@@ -0,0 +1,36 @@
+/*
+ *
+ * Copyright 2019 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+@interface GRPCCall (V2API)
+
+- (instancetype)initWithHost:(NSString *)host
+                        path:(NSString *)path
+                  callSafety:(GRPCCallSafety)safety
+              requestsWriter:(GRXWriter *)requestsWriter
+                 callOptions:(GRPCCallOptions *)callOptions;
+
+- (instancetype)initWithHost:(NSString *)host
+                        path:(NSString *)path
+                  callSafety:(GRPCCallSafety)safety
+              requestsWriter:(GRXWriter *)requestsWriter
+                 callOptions:(GRPCCallOptions *)callOptions
+                   writeDone:(void (^)(void))writeDone;
+
+- (void)receiveNextMessages:(NSUInteger)numberOfMessages;
+
+@end

+ 42 - 0
src/objective-c/GRPCClient/private/GRPCCallInternal.h

@@ -0,0 +1,42 @@
+/*
+ *
+ * Copyright 2019 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#import <GRPCClient/GRPCInterceptor.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface GRPCCall2Internal : NSObject<GRPCInterceptorInterface>
+
+- (instancetype)init;
+
+- (void)setResponseHandler:(id<GRPCResponseHandler>)responseHandler;
+
+- (void)startWithRequestOptions:(GRPCRequestOptions *)requestOptions
+                    callOptions:(nullable GRPCCallOptions *)callOptions;
+
+- (void)writeData:(NSData *)data;
+
+- (void)finish;
+
+- (void)cancel;
+
+- (void)receiveNextMessages:(NSUInteger)numberOfMessages;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 342 - 0
src/objective-c/GRPCClient/private/GRPCCallInternal.m

@@ -0,0 +1,342 @@
+/*
+ *
+ * Copyright 2019 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#import "GRPCCallInternal.h"
+
+#import <GRPCClient/GRPCCall.h>
+#import <RxLibrary/GRXBufferedPipe.h>
+
+#import "GRPCCall+V2API.h"
+
+@implementation GRPCCall2Internal {
+  /** Request for the call. */
+  GRPCRequestOptions *_requestOptions;
+  /** Options for the call. */
+  GRPCCallOptions *_callOptions;
+  /** The handler of responses. */
+  id<GRPCResponseHandler> _handler;
+
+  /**
+   * Make use of legacy GRPCCall to make calls. Nullified when call is finished.
+   */
+  GRPCCall *_call;
+  /** Flags whether initial metadata has been published to response handler. */
+  BOOL _initialMetadataPublished;
+  /** Streaming call writeable to the underlying call. */
+  GRXBufferedPipe *_pipe;
+  /** Serial dispatch queue for tasks inside the call. */
+  dispatch_queue_t _dispatchQueue;
+  /** Flags whether call has started. */
+  BOOL _started;
+  /** Flags whether call has been canceled. */
+  BOOL _canceled;
+  /** Flags whether call has been finished. */
+  BOOL _finished;
+  /** The number of pending messages receiving requests. */
+  NSUInteger _pendingReceiveNextMessages;
+}
+
+- (instancetype)init {
+  if ((self = [super init])) {
+  // Set queue QoS only when iOS version is 8.0 or above and Xcode version is 9.0 or above
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 || __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
+    if (@available(iOS 8.0, macOS 10.10, *)) {
+      _dispatchQueue = dispatch_queue_create(
+          NULL,
+          dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0));
+    } else {
+#else
+    {
+#endif
+      _dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
+    }
+    _pipe = [GRXBufferedPipe pipe];
+  }
+  return self;
+}
+
+- (void)setResponseHandler:(id<GRPCResponseHandler>)responseHandler {
+  @synchronized(self) {
+    NSAssert(!_started, @"Call already started.");
+    if (_started) {
+      return;
+    }
+    _handler = responseHandler;
+    _initialMetadataPublished = NO;
+    _started = NO;
+    _canceled = NO;
+    _finished = NO;
+  }
+}
+
+- (dispatch_queue_t)requestDispatchQueue {
+  return _dispatchQueue;
+}
+
+- (void)startWithRequestOptions:(GRPCRequestOptions *)requestOptions
+                    callOptions:(GRPCCallOptions *)callOptions {
+  NSAssert(requestOptions.host.length != 0 && requestOptions.path.length != 0,
+           @"Neither host nor path can be nil.");
+  NSAssert(requestOptions.safety <= GRPCCallSafetyCacheableRequest, @"Invalid call safety value.");
+  if (requestOptions.host.length == 0 || requestOptions.path.length == 0) {
+    NSLog(@"Invalid host and path.");
+    return;
+  }
+  if (requestOptions.safety > GRPCCallSafetyCacheableRequest) {
+    NSLog(@"Invalid call safety.");
+    return;
+  }
+
+  @synchronized(self) {
+    NSAssert(_handler != nil, @"Response handler required.");
+    if (_handler == nil) {
+      NSLog(@"Invalid response handler.");
+      return;
+    }
+    _requestOptions = requestOptions;
+    if (callOptions == nil) {
+      _callOptions = [[GRPCCallOptions alloc] init];
+    } else {
+      _callOptions = [callOptions copy];
+    }
+  }
+
+  [self start];
+}
+
+- (void)start {
+  GRPCCall *copiedCall = nil;
+  @synchronized(self) {
+    NSAssert(!_started, @"Call already started.");
+    NSAssert(!_canceled, @"Call already canceled.");
+    if (_started) {
+      return;
+    }
+    if (_canceled) {
+      return;
+    }
+
+    _started = YES;
+
+    _call = [[GRPCCall alloc] initWithHost:_requestOptions.host
+                                      path:_requestOptions.path
+                                callSafety:_requestOptions.safety
+                            requestsWriter:_pipe
+                               callOptions:_callOptions
+                                 writeDone:^{
+                                   @synchronized(self) {
+                                     if (self->_handler) {
+                                       [self issueDidWriteData];
+                                     }
+                                   }
+                                 }];
+    [_call setResponseDispatchQueue:_dispatchQueue];
+    if (_callOptions.initialMetadata) {
+      [_call.requestHeaders addEntriesFromDictionary:_callOptions.initialMetadata];
+    }
+    if (_pendingReceiveNextMessages > 0) {
+      [_call receiveNextMessages:_pendingReceiveNextMessages];
+      _pendingReceiveNextMessages = 0;
+    }
+    copiedCall = _call;
+  }
+
+  void (^valueHandler)(id value) = ^(id value) {
+    @synchronized(self) {
+      if (self->_handler) {
+        if (!self->_initialMetadataPublished) {
+          self->_initialMetadataPublished = YES;
+          [self issueInitialMetadata:self->_call.responseHeaders];
+        }
+        if (value) {
+          [self issueMessage:value];
+        }
+      }
+    }
+  };
+  void (^completionHandler)(NSError *errorOrNil) = ^(NSError *errorOrNil) {
+    @synchronized(self) {
+      if (self->_handler) {
+        if (!self->_initialMetadataPublished) {
+          self->_initialMetadataPublished = YES;
+          [self issueInitialMetadata:self->_call.responseHeaders];
+        }
+        [self issueClosedWithTrailingMetadata:self->_call.responseTrailers error:errorOrNil];
+      }
+      // Clearing _call must happen *after* dispatching close in order to get trailing
+      // metadata from _call.
+      if (self->_call) {
+        // Clean up the request writers. This should have no effect to _call since its
+        // response writeable is already nullified.
+        [self->_pipe writesFinishedWithError:nil];
+        self->_call = nil;
+        self->_pipe = nil;
+      }
+    }
+  };
+  id<GRXWriteable> responseWriteable =
+      [[GRXWriteable alloc] initWithValueHandler:valueHandler completionHandler:completionHandler];
+  [copiedCall startWithWriteable:responseWriteable];
+}
+
+- (void)cancel {
+  GRPCCall *copiedCall = nil;
+  @synchronized(self) {
+    if (_canceled) {
+      return;
+    }
+
+    _canceled = YES;
+
+    copiedCall = _call;
+    _call = nil;
+    _pipe = nil;
+
+    if ([_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
+      id<GRPCResponseHandler> copiedHandler = _handler;
+      _handler = nil;
+      dispatch_async(copiedHandler.dispatchQueue, ^{
+        [copiedHandler didCloseWithTrailingMetadata:nil
+                                              error:[NSError errorWithDomain:kGRPCErrorDomain
+                                                                        code:GRPCErrorCodeCancelled
+                                                                    userInfo:@{
+                                                                      NSLocalizedDescriptionKey :
+                                                                          @"Canceled by app"
+                                                                    }]];
+      });
+    } else {
+      _handler = nil;
+    }
+  }
+  [copiedCall cancel];
+}
+
+- (void)writeData:(id)data {
+  GRXBufferedPipe *copiedPipe = nil;
+  @synchronized(self) {
+    NSAssert(!_canceled, @"Call already canceled.");
+    NSAssert(!_finished, @"Call is half-closed before sending data.");
+    if (_canceled) {
+      return;
+    }
+    if (_finished) {
+      return;
+    }
+
+    if (_pipe) {
+      copiedPipe = _pipe;
+    }
+  }
+  [copiedPipe writeValue:data];
+}
+
+- (void)finish {
+  GRXBufferedPipe *copiedPipe = nil;
+  @synchronized(self) {
+    NSAssert(_started, @"Call not started.");
+    NSAssert(!_canceled, @"Call already canceled.");
+    NSAssert(!_finished, @"Call already half-closed.");
+    if (!_started) {
+      return;
+    }
+    if (_canceled) {
+      return;
+    }
+    if (_finished) {
+      return;
+    }
+
+    if (_pipe) {
+      copiedPipe = _pipe;
+      _pipe = nil;
+    }
+    _finished = YES;
+  }
+  [copiedPipe writesFinishedWithError:nil];
+}
+
+- (void)issueInitialMetadata:(NSDictionary *)initialMetadata {
+  @synchronized(self) {
+    if (initialMetadata != nil &&
+        [_handler respondsToSelector:@selector(didReceiveInitialMetadata:)]) {
+      id<GRPCResponseHandler> copiedHandler = _handler;
+      dispatch_async(_handler.dispatchQueue, ^{
+        [copiedHandler didReceiveInitialMetadata:initialMetadata];
+      });
+    }
+  }
+}
+
+- (void)issueMessage:(id)message {
+  @synchronized(self) {
+    if (message != nil) {
+      if ([_handler respondsToSelector:@selector(didReceiveData:)]) {
+        id<GRPCResponseHandler> copiedHandler = _handler;
+        dispatch_async(_handler.dispatchQueue, ^{
+          [copiedHandler didReceiveData:message];
+        });
+      } else if ([_handler respondsToSelector:@selector(didReceiveRawMessage:)]) {
+        id<GRPCResponseHandler> copiedHandler = _handler;
+        dispatch_async(_handler.dispatchQueue, ^{
+          [copiedHandler didReceiveRawMessage:message];
+        });
+      }
+    }
+  }
+}
+
+- (void)issueClosedWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
+  @synchronized(self) {
+    if ([_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
+      id<GRPCResponseHandler> copiedHandler = _handler;
+      // Clean up _handler so that no more responses are reported to the handler.
+      _handler = nil;
+      dispatch_async(copiedHandler.dispatchQueue, ^{
+        [copiedHandler didCloseWithTrailingMetadata:trailingMetadata error:error];
+      });
+    } else {
+      _handler = nil;
+    }
+  }
+}
+
+- (void)issueDidWriteData {
+  @synchronized(self) {
+    if (_callOptions.flowControlEnabled && [_handler respondsToSelector:@selector(didWriteData)]) {
+      id<GRPCResponseHandler> copiedHandler = _handler;
+      dispatch_async(copiedHandler.dispatchQueue, ^{
+        [copiedHandler didWriteData];
+      });
+    }
+  }
+}
+
+- (void)receiveNextMessages:(NSUInteger)numberOfMessages {
+  // branching based on _callOptions.flowControlEnabled is handled inside _call
+  GRPCCall *copiedCall = nil;
+  @synchronized(self) {
+    copiedCall = _call;
+    if (copiedCall == nil) {
+      _pendingReceiveNextMessages += numberOfMessages;
+      return;
+    }
+  }
+  [copiedCall receiveNextMessages:numberOfMessages];
+}
+
+@end

+ 4 - 4
src/objective-c/ProtoRPC/ProtoRPC.m

@@ -224,11 +224,11 @@ static NSError *ErrorForBadProto(id proto, Class expectedClass, NSError *parsing
   }
   }
 }
 }
 
 
-- (void)didReceiveRawMessage:(NSData *)message {
-  if (message == nil) return;
+- (void)didReceiveData:(id)data {
+  if (data == nil) return;
 
 
   NSError *error = nil;
   NSError *error = nil;
-  GPBMessage *parsed = [_responseClass parseFromData:message error:&error];
+  GPBMessage *parsed = [_responseClass parseFromData:data error:&error];
   @synchronized(self) {
   @synchronized(self) {
     if (parsed && [_handler respondsToSelector:@selector(didReceiveProtoMessage:)]) {
     if (parsed && [_handler respondsToSelector:@selector(didReceiveProtoMessage:)]) {
       dispatch_async(_dispatchQueue, ^{
       dispatch_async(_dispatchQueue, ^{
@@ -248,7 +248,7 @@ static NSError *ErrorForBadProto(id proto, Class expectedClass, NSError *parsing
         }
         }
         [copiedHandler
         [copiedHandler
             didCloseWithTrailingMetadata:nil
             didCloseWithTrailingMetadata:nil
-                                   error:ErrorForBadProto(message, self->_responseClass, error)];
+                                   error:ErrorForBadProto(data, self->_responseClass, error)];
       });
       });
       [_call cancel];
       [_call cancel];
       _call = nil;
       _call = nil;

+ 416 - 0
src/objective-c/examples/InterceptorSample/InterceptorSample.xcodeproj/project.pbxproj

@@ -0,0 +1,416 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 50;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		1C4854A76EEB56F8096DBDEF /* libPods-InterceptorSample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CB7A7A5B91FC976FCF4637AE /* libPods-InterceptorSample.a */; };
+		5EE960FB2266768A0044A74F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EE960FA2266768A0044A74F /* AppDelegate.m */; };
+		5EE960FE2266768A0044A74F /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EE960FD2266768A0044A74F /* ViewController.m */; };
+		5EE961012266768A0044A74F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5EE960FF2266768A0044A74F /* Main.storyboard */; };
+		5EE961032266768C0044A74F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5EE961022266768C0044A74F /* Assets.xcassets */; };
+		5EE961062266768C0044A74F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5EE961042266768C0044A74F /* LaunchScreen.storyboard */; };
+		5EE961092266768C0044A74F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EE961082266768C0044A74F /* main.m */; };
+		5EE9611222668CF20044A74F /* CacheInterceptor.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EE9611122668CF20044A74F /* CacheInterceptor.m */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+		09457A264AAE5323BF50B1F8 /* Pods-InterceptorSample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InterceptorSample.debug.xcconfig"; path = "Target Support Files/Pods-InterceptorSample/Pods-InterceptorSample.debug.xcconfig"; sourceTree = "<group>"; };
+		5EE960F62266768A0044A74F /* InterceptorSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = InterceptorSample.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		5EE960FA2266768A0044A74F /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
+		5EE960FC2266768A0044A74F /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
+		5EE960FD2266768A0044A74F /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
+		5EE961002266768A0044A74F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
+		5EE961022266768C0044A74F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+		5EE961052266768C0044A74F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
+		5EE961072266768C0044A74F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		5EE961082266768C0044A74F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+		5EE9610F2266774C0044A74F /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
+		5EE9611022668CE20044A74F /* CacheInterceptor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CacheInterceptor.h; sourceTree = "<group>"; };
+		5EE9611122668CF20044A74F /* CacheInterceptor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CacheInterceptor.m; sourceTree = "<group>"; };
+		A0789280A4035D0F22F96BE6 /* Pods-InterceptorSample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-InterceptorSample.release.xcconfig"; path = "Target Support Files/Pods-InterceptorSample/Pods-InterceptorSample.release.xcconfig"; sourceTree = "<group>"; };
+		CB7A7A5B91FC976FCF4637AE /* libPods-InterceptorSample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-InterceptorSample.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		5EE960F32266768A0044A74F /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				1C4854A76EEB56F8096DBDEF /* libPods-InterceptorSample.a in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		5EE960ED2266768A0044A74F = {
+			isa = PBXGroup;
+			children = (
+				5EE960F82266768A0044A74F /* InterceptorSample */,
+				5EE960F72266768A0044A74F /* Products */,
+				9D49DB75F3BEDAFDE7028B51 /* Pods */,
+				BD7184728351C7DDAFBA5FA2 /* Frameworks */,
+			);
+			sourceTree = "<group>";
+		};
+		5EE960F72266768A0044A74F /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				5EE960F62266768A0044A74F /* InterceptorSample.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		5EE960F82266768A0044A74F /* InterceptorSample */ = {
+			isa = PBXGroup;
+			children = (
+				5EE960FA2266768A0044A74F /* AppDelegate.m */,
+				5EE960FC2266768A0044A74F /* ViewController.h */,
+				5EE960FD2266768A0044A74F /* ViewController.m */,
+				5EE960FF2266768A0044A74F /* Main.storyboard */,
+				5EE961022266768C0044A74F /* Assets.xcassets */,
+				5EE961042266768C0044A74F /* LaunchScreen.storyboard */,
+				5EE961072266768C0044A74F /* Info.plist */,
+				5EE961082266768C0044A74F /* main.m */,
+				5EE9610F2266774C0044A74F /* AppDelegate.h */,
+				5EE9611022668CE20044A74F /* CacheInterceptor.h */,
+				5EE9611122668CF20044A74F /* CacheInterceptor.m */,
+			);
+			path = InterceptorSample;
+			sourceTree = "<group>";
+		};
+		9D49DB75F3BEDAFDE7028B51 /* Pods */ = {
+			isa = PBXGroup;
+			children = (
+				09457A264AAE5323BF50B1F8 /* Pods-InterceptorSample.debug.xcconfig */,
+				A0789280A4035D0F22F96BE6 /* Pods-InterceptorSample.release.xcconfig */,
+			);
+			path = Pods;
+			sourceTree = "<group>";
+		};
+		BD7184728351C7DDAFBA5FA2 /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+				CB7A7A5B91FC976FCF4637AE /* libPods-InterceptorSample.a */,
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		5EE960F52266768A0044A74F /* InterceptorSample */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 5EE9610C2266768C0044A74F /* Build configuration list for PBXNativeTarget "InterceptorSample" */;
+			buildPhases = (
+				7531607F028A04DAAF5E97B5 /* [CP] Check Pods Manifest.lock */,
+				5EE960F22266768A0044A74F /* Sources */,
+				5EE960F32266768A0044A74F /* Frameworks */,
+				5EE960F42266768A0044A74F /* Resources */,
+				17700C95BAEBB27F7A3D1B01 /* [CP] Copy Pods Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = InterceptorSample;
+			productName = InterceptorSample;
+			productReference = 5EE960F62266768A0044A74F /* InterceptorSample.app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		5EE960EE2266768A0044A74F /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastUpgradeCheck = 1010;
+				ORGANIZATIONNAME = gRPC;
+				TargetAttributes = {
+					5EE960F52266768A0044A74F = {
+						CreatedOnToolsVersion = 10.1;
+					};
+				};
+			};
+			buildConfigurationList = 5EE960F12266768A0044A74F /* Build configuration list for PBXProject "InterceptorSample" */;
+			compatibilityVersion = "Xcode 9.3";
+			developmentRegion = en;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+				Base,
+			);
+			mainGroup = 5EE960ED2266768A0044A74F;
+			productRefGroup = 5EE960F72266768A0044A74F /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				5EE960F52266768A0044A74F /* InterceptorSample */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		5EE960F42266768A0044A74F /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				5EE961062266768C0044A74F /* LaunchScreen.storyboard in Resources */,
+				5EE961032266768C0044A74F /* Assets.xcassets in Resources */,
+				5EE961012266768A0044A74F /* Main.storyboard in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+		17700C95BAEBB27F7A3D1B01 /* [CP] Copy Pods Resources */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+				"${PODS_ROOT}/Target Support Files/Pods-InterceptorSample/Pods-InterceptorSample-resources-${CONFIGURATION}-input-files.xcfilelist",
+			);
+			name = "[CP] Copy Pods Resources";
+			outputFileListPaths = (
+				"${PODS_ROOT}/Target Support Files/Pods-InterceptorSample/Pods-InterceptorSample-resources-${CONFIGURATION}-output-files.xcfilelist",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-InterceptorSample/Pods-InterceptorSample-resources.sh\"\n";
+			showEnvVarsInLog = 0;
+		};
+		7531607F028A04DAAF5E97B5 /* [CP] Check Pods Manifest.lock */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+				"${PODS_ROOT}/Manifest.lock",
+			);
+			name = "[CP] Check Pods Manifest.lock";
+			outputFileListPaths = (
+			);
+			outputPaths = (
+				"$(DERIVED_FILE_DIR)/Pods-InterceptorSample-checkManifestLockResult.txt",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+			showEnvVarsInLog = 0;
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		5EE960F22266768A0044A74F /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				5EE9611222668CF20044A74F /* CacheInterceptor.m in Sources */,
+				5EE960FE2266768A0044A74F /* ViewController.m in Sources */,
+				5EE961092266768C0044A74F /* main.m in Sources */,
+				5EE960FB2266768A0044A74F /* AppDelegate.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+		5EE960FF2266768A0044A74F /* Main.storyboard */ = {
+			isa = PBXVariantGroup;
+			children = (
+				5EE961002266768A0044A74F /* Base */,
+			);
+			name = Main.storyboard;
+			sourceTree = "<group>";
+		};
+		5EE961042266768C0044A74F /* LaunchScreen.storyboard */ = {
+			isa = PBXVariantGroup;
+			children = (
+				5EE961052266768C0044A74F /* Base */,
+			);
+			name = LaunchScreen.storyboard;
+			sourceTree = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		5EE9610A2266768C0044A74F /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_IDENTITY = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 12.1;
+				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+				MTL_FAST_MATH = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = iphoneos;
+			};
+			name = Debug;
+		};
+		5EE9610B2266768C0044A74F /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				CODE_SIGN_IDENTITY = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 12.1;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				MTL_FAST_MATH = YES;
+				SDKROOT = iphoneos;
+				VALIDATE_PRODUCT = YES;
+			};
+			name = Release;
+		};
+		5EE9610D2266768C0044A74F /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 09457A264AAE5323BF50B1F8 /* Pods-InterceptorSample.debug.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CODE_SIGN_STYLE = Automatic;
+				INFOPLIST_FILE = InterceptorSample/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = io.grpc.InterceptorSample;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Debug;
+		};
+		5EE9610E2266768C0044A74F /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = A0789280A4035D0F22F96BE6 /* Pods-InterceptorSample.release.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CODE_SIGN_STYLE = Automatic;
+				INFOPLIST_FILE = InterceptorSample/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = io.grpc.InterceptorSample;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		5EE960F12266768A0044A74F /* Build configuration list for PBXProject "InterceptorSample" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				5EE9610A2266768C0044A74F /* Debug */,
+				5EE9610B2266768C0044A74F /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		5EE9610C2266768C0044A74F /* Build configuration list for PBXNativeTarget "InterceptorSample" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				5EE9610D2266768C0044A74F /* Debug */,
+				5EE9610E2266768C0044A74F /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 5EE960EE2266768A0044A74F /* Project object */;
+}

+ 25 - 0
src/objective-c/examples/InterceptorSample/InterceptorSample/AppDelegate.h

@@ -0,0 +1,25 @@
+/*
+ *
+ * Copyright 2019 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#import <UIKit/UIKit.h>
+
+@interface AppDelegate : UIResponder<UIApplicationDelegate>
+
+@property(strong, nonatomic) UIWindow* window;
+
+@end

+ 23 - 0
src/objective-c/examples/InterceptorSample/InterceptorSample/AppDelegate.m

@@ -0,0 +1,23 @@
+/*
+ *
+ * Copyright 2019 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#import "AppDelegate.h"
+
+@implementation AppDelegate
+
+@end

+ 98 - 0
src/objective-c/examples/InterceptorSample/InterceptorSample/Assets.xcassets/AppIcon.appiconset/Contents.json

@@ -0,0 +1,98 @@
+{
+  "images" : [
+    {
+      "idiom" : "iphone",
+      "size" : "20x20",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "20x20",
+      "scale" : "3x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "29x29",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "29x29",
+      "scale" : "3x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "40x40",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "40x40",
+      "scale" : "3x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "60x60",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "60x60",
+      "scale" : "3x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "20x20",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "20x20",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "29x29",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "29x29",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "40x40",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "40x40",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "76x76",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "76x76",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "83.5x83.5",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "ios-marketing",
+      "size" : "1024x1024",
+      "scale" : "1x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}

+ 6 - 0
src/objective-c/examples/InterceptorSample/InterceptorSample/Assets.xcassets/Contents.json

@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}

+ 25 - 0
src/objective-c/examples/InterceptorSample/InterceptorSample/Base.lproj/LaunchScreen.storyboard

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
+    <dependencies>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="EHf-IW-A2E">
+            <objects>
+                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="53" y="375"/>
+        </scene>
+    </scenes>
+</document>

+ 38 - 0
src/objective-c/examples/InterceptorSample/InterceptorSample/Base.lproj/Main.storyboard

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
+    <device id="retina4_7" orientation="portrait">
+        <adaptation id="fullscreen"/>
+    </device>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="tne-QT-ifu">
+            <objects>
+                <viewController id="BYZ-38-t0r" customClass="ViewController" sceneMemberID="viewController">
+                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="XDI-qX-FfC">
+                                <rect key="frame" x="172" y="182" width="30" height="30"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                <state key="normal" title="Call"/>
+                                <connections>
+                                    <action selector="tapCall:" destination="BYZ-38-t0r" eventType="touchUpInside" id="qEz-Hb-ReK"/>
+                                </connections>
+                            </button>
+                        </subviews>
+                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+            </objects>
+        </scene>
+    </scenes>
+</document>

+ 92 - 0
src/objective-c/examples/InterceptorSample/InterceptorSample/CacheInterceptor.h

@@ -0,0 +1,92 @@
+/*
+ *
+ * Copyright 2019 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#import <GRPCClient/GRPCInterceptor.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface RequestCacheEntry : NSObject<NSCopying>
+
+@property(readonly, copy, nullable) NSString *path;
+@property(readonly, copy, nullable) id message;
+
+@end
+
+@interface MutableRequestCacheEntry : RequestCacheEntry
+
+@property(copy, nullable) NSString *path;
+@property(copy, nullable) id<NSObject> message;
+
+@end
+
+@interface ResponseCacheEntry : NSObject<NSCopying>
+
+@property(readonly, copy, nullable) NSDate *deadline;
+
+@property(readonly, copy, nullable) NSDictionary *headers;
+@property(readonly, copy, nullable) id message;
+@property(readonly, copy, nullable) NSDictionary *trailers;
+
+@end
+
+@interface MutableResponseCacheEntry : ResponseCacheEntry
+
+@property(copy, nullable) NSDate *deadline;
+
+@property(copy, nullable) NSDictionary *headers;
+@property(copy, nullable) id message;
+@property(copy, nullable) NSDictionary *trailers;
+
+@end
+
+@interface CacheContext : NSObject<GRPCInterceptorFactory>
+
+- (nullable instancetype)init;
+
+- (nullable ResponseCacheEntry *)getCachedResponseForRequest:(RequestCacheEntry *)request;
+
+- (void)setCachedResponse:(ResponseCacheEntry *)response forRequest:(RequestCacheEntry *)request;
+
+@end
+
+@interface CacheInterceptor : GRPCInterceptor
+
+- (instancetype)init NS_UNAVAILABLE;
+
++ (instancetype) new NS_UNAVAILABLE;
+
+- (nullable instancetype)initWithInterceptorManager:
+                             (GRPCInterceptorManager *_Nonnull)intercepterManager
+                                       cacheContext:(CacheContext *_Nonnull)cacheContext
+    NS_DESIGNATED_INITIALIZER;
+
+// implementation of GRPCInterceptorInterface
+- (void)startWithRequestOptions:(GRPCRequestOptions *)requestOptions
+                    callOptions:(GRPCCallOptions *)callOptions;
+- (void)writeData:(id)data;
+- (void)finish;
+
+// implementation of GRPCResponseHandler
+- (void)didReceiveInitialMetadata:(nullable NSDictionary *)initialMetadata;
+- (void)didReceiveData:(id)data;
+- (void)didCloseWithTrailingMetadata:(nullable NSDictionary *)trailingMetadata
+                               error:(nullable NSError *)error;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 306 - 0
src/objective-c/examples/InterceptorSample/InterceptorSample/CacheInterceptor.m

@@ -0,0 +1,306 @@
+/*
+ *
+ * Copyright 2019 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#import "CacheInterceptor.h"
+
+@implementation RequestCacheEntry {
+ @protected
+  NSString *_path;
+  id<NSObject> _message;
+}
+
+@synthesize path = _path;
+@synthesize message = _message;
+
+- (instancetype)initWithPath:(NSString *)path message:(id)message {
+  if ((self = [super init])) {
+    _path = [path copy];
+    _message = [message copy];
+  }
+  return self;
+}
+
+- (id)copyWithZone:(NSZone *)zone {
+  return [[RequestCacheEntry allocWithZone:zone] initWithPath:_path message:_message];
+}
+
+- (BOOL)isEqual:(id)object {
+  if ([self class] != [object class]) return NO;
+  RequestCacheEntry *rhs = (RequestCacheEntry *)object;
+  return ([_path isEqualToString:rhs.path] && [_message isEqual:rhs.message]);
+}
+
+- (NSUInteger)hash {
+  return _path.hash ^ _message.hash;
+}
+
+@end
+
+@implementation MutableRequestCacheEntry
+
+@dynamic path;
+@dynamic message;
+
+- (void)setPath:(NSString *)path {
+  _path = [path copy];
+}
+
+- (void)setMessage:(id)message {
+  _message = [message copy];
+}
+
+@end
+
+@implementation ResponseCacheEntry {
+ @protected
+  NSDate *_deadline;
+  NSDictionary *_headers;
+  id _message;
+  NSDictionary *_trailers;
+}
+
+@synthesize deadline = _deadline;
+@synthesize headers = _headers;
+@synthesize message = _message;
+@synthesize trailers = _trailers;
+
+- (instancetype)initWithDeadline:(NSDate *)deadline
+                         headers:(NSDictionary *)headers
+                         message:(id)message
+                        trailers:(NSDictionary *)trailers {
+  if (([super init])) {
+    _deadline = [deadline copy];
+    _headers = [[NSDictionary alloc] initWithDictionary:headers copyItems:YES];
+    _message = [message copy];
+    _trailers = [[NSDictionary alloc] initWithDictionary:trailers copyItems:YES];
+  }
+  return self;
+}
+
+- (id)copyWithZone:(NSZone *)zone {
+  return [[ResponseCacheEntry allocWithZone:zone] initWithDeadline:_deadline
+                                                           headers:_headers
+                                                           message:_message
+                                                          trailers:_trailers];
+}
+
+@end
+
+@implementation MutableResponseCacheEntry
+
+@dynamic deadline;
+@dynamic headers;
+@dynamic message;
+@dynamic trailers;
+
+- (void)setDeadline:(NSDate *)deadline {
+  _deadline = [deadline copy];
+}
+
+- (void)setHeaders:(NSDictionary *)headers {
+  _headers = [[NSDictionary alloc] initWithDictionary:headers copyItems:YES];
+}
+
+- (void)setMessage:(id)message {
+  _message = [message copy];
+}
+
+- (void)setTrailers:(NSDictionary *)trailers {
+  _trailers = [[NSDictionary alloc] initWithDictionary:trailers copyItems:YES];
+}
+
+@end
+
+@implementation CacheContext {
+  NSCache<RequestCacheEntry *, ResponseCacheEntry *> *_cache;
+}
+
+- (instancetype)init {
+  if ((self = [super init])) {
+    _cache = [[NSCache alloc] init];
+  }
+  return self;
+}
+
+- (GRPCInterceptor *)createInterceptorWithManager:(GRPCInterceptorManager *)interceptorManager {
+  return [[CacheInterceptor alloc] initWithInterceptorManager:interceptorManager cacheContext:self];
+}
+
+- (ResponseCacheEntry *)getCachedResponseForRequest:(RequestCacheEntry *)request {
+  ResponseCacheEntry *response = nil;
+  @synchronized(self) {
+    response = [_cache objectForKey:request];
+    if ([response.deadline timeIntervalSinceNow] < 0) {
+      [_cache removeObjectForKey:request];
+      response = nil;
+    }
+  }
+  return response;
+}
+
+- (void)setCachedResponse:(ResponseCacheEntry *)response forRequest:(RequestCacheEntry *)request {
+  @synchronized(self) {
+    [_cache setObject:response forKey:request];
+  }
+}
+
+@end
+
+@implementation CacheInterceptor {
+  GRPCInterceptorManager *_manager;
+  CacheContext *_context;
+  dispatch_queue_t _dispatchQueue;
+
+  BOOL _cacheable;
+  BOOL _writeMessageSeen;
+  BOOL _readMessageSeen;
+  GRPCCallOptions *_callOptions;
+  GRPCRequestOptions *_requestOptions;
+  id _requestMessage;
+  MutableRequestCacheEntry *_request;
+  MutableResponseCacheEntry *_response;
+}
+
+- (dispatch_queue_t)requestDispatchQueue {
+  return _dispatchQueue;
+}
+
+- (dispatch_queue_t)dispatchQueue {
+  return _dispatchQueue;
+}
+
+- (instancetype)initWithInterceptorManager:(GRPCInterceptorManager *_Nonnull)intercepterManager
+                              cacheContext:(CacheContext *_Nonnull)cacheContext {
+  if ((self = [super initWithInterceptorManager:intercepterManager
+                           requestDispatchQueue:dispatch_get_main_queue()
+                          responseDispatchQueue:dispatch_get_main_queue()])) {
+    _manager = intercepterManager;
+    _context = cacheContext;
+    _dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
+
+    _cacheable = YES;
+    _writeMessageSeen = NO;
+    _readMessageSeen = NO;
+    _request = nil;
+    _response = nil;
+  }
+  return self;
+}
+
+- (void)startWithRequestOptions:(GRPCRequestOptions *)requestOptions
+                    callOptions:(GRPCCallOptions *)callOptions {
+  if (requestOptions.safety != GRPCCallSafetyCacheableRequest) {
+    _cacheable = NO;
+    [_manager startNextInterceptorWithRequest:requestOptions callOptions:callOptions];
+  } else {
+    _requestOptions = [requestOptions copy];
+    _callOptions = [callOptions copy];
+  }
+}
+
+- (void)writeData:(id)data {
+  if (!_cacheable) {
+    [_manager writeNextInterceptorWithData:data];
+  } else {
+    NSAssert(!_writeMessageSeen, @"CacheInterceptor does not support streaming call");
+    if (_writeMessageSeen) {
+      NSLog(@"CacheInterceptor does not support streaming call");
+    }
+    _writeMessageSeen = YES;
+    _requestMessage = [data copy];
+  }
+}
+
+- (void)finish {
+  if (!_cacheable) {
+    [_manager finishNextInterceptor];
+  } else {
+    _request = [[MutableRequestCacheEntry alloc] init];
+    _request.path = _requestOptions.path;
+    _request.message = [_requestMessage copy];
+    _response = [[_context getCachedResponseForRequest:_request] copy];
+    if (!_response) {
+      [_manager startNextInterceptorWithRequest:_requestOptions callOptions:_callOptions];
+      [_manager writeNextInterceptorWithData:_requestMessage];
+      [_manager finishNextInterceptor];
+    } else {
+      [_manager forwardPreviousInterceptorWithInitialMetadata:_response.headers];
+      [_manager forwardPreviousInterceptorWithData:_response.message];
+      [_manager forwardPreviousInterceptorCloseWithTrailingMetadata:_response.trailers error:nil];
+      [_manager shutDown];
+    }
+  }
+}
+
+- (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata {
+  if (_cacheable) {
+    NSDate *deadline = nil;
+    for (NSString *key in initialMetadata) {
+      if ([key.lowercaseString isEqualToString:@"cache-control"]) {
+        NSArray *cacheControls = [initialMetadata[key] componentsSeparatedByString:@","];
+        for (NSString *option in cacheControls) {
+          NSString *trimmedOption =
+              [option stringByTrimmingCharactersInSet:[NSCharacterSet
+                                                          characterSetWithCharactersInString:@" "]];
+          if ([trimmedOption.lowercaseString isEqualToString:@"no-cache"] ||
+              [trimmedOption.lowercaseString isEqualToString:@"no-store"] ||
+              [trimmedOption.lowercaseString isEqualToString:@"no-transform"]) {
+            _cacheable = NO;
+            break;
+          } else if ([trimmedOption.lowercaseString hasPrefix:@"max-age="]) {
+            NSArray<NSString *> *components = [trimmedOption componentsSeparatedByString:@"="];
+            if (components.count == 2) {
+              NSUInteger maxAge = components[1].intValue;
+              deadline = [NSDate dateWithTimeIntervalSinceNow:maxAge];
+            }
+          }
+        }
+      }
+    }
+    if (_cacheable) {
+      _response = [[MutableResponseCacheEntry alloc] init];
+      _response.headers = [initialMetadata copy];
+      _response.deadline = deadline;
+    }
+  }
+  [_manager forwardPreviousInterceptorWithInitialMetadata:initialMetadata];
+}
+
+- (void)didReceiveData:(id)data {
+  if (_cacheable) {
+    NSAssert(!_readMessageSeen, @"CacheInterceptor does not support streaming call");
+    if (_readMessageSeen) {
+      NSLog(@"CacheInterceptor does not support streaming call");
+    }
+    _readMessageSeen = YES;
+    _response.message = [data copy];
+  }
+  [_manager forwardPreviousInterceptorWithData:data];
+}
+
+- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
+  if (error == nil && _cacheable) {
+    _response.trailers = [trailingMetadata copy];
+    [_context setCachedResponse:_response forRequest:_request];
+    NSLog(@"Write cache for %@", _request);
+  }
+  [_manager forwardPreviousInterceptorCloseWithTrailingMetadata:trailingMetadata error:error];
+  [_manager shutDown];
+}
+
+@end

+ 45 - 0
src/objective-c/examples/InterceptorSample/InterceptorSample/Info.plist

@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+	<key>LSRequiresIPhoneOS</key>
+	<true/>
+	<key>UILaunchStoryboardName</key>
+	<string>LaunchScreen</string>
+	<key>UIMainStoryboardFile</key>
+	<string>Main</string>
+	<key>UIRequiredDeviceCapabilities</key>
+	<array>
+		<string>armv7</string>
+	</array>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UISupportedInterfaceOrientations~ipad</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+</dict>
+</plist>

+ 23 - 0
src/objective-c/examples/InterceptorSample/InterceptorSample/ViewController.h

@@ -0,0 +1,23 @@
+/*
+ *
+ * Copyright 2019 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#import <UIKit/UIKit.h>
+
+@interface ViewController : UIViewController
+
+@end

+ 85 - 0
src/objective-c/examples/InterceptorSample/InterceptorSample/ViewController.m

@@ -0,0 +1,85 @@
+/*
+ *
+ * Copyright 2019 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#import "ViewController.h"
+
+#import <GRPCClient/GRPCCall.h>
+#import <RemoteTest/Messages.pbobjc.h>
+#import <RemoteTest/Test.pbrpc.h>
+
+#import "CacheInterceptor.h"
+
+static NSString *const kPackage = @"grpc.testing";
+static NSString *const kService = @"TestService";
+
+@interface ViewController ()<GRPCResponseHandler>
+
+@end
+
+@implementation ViewController {
+  GRPCCallOptions *_options;
+}
+
+- (void)viewDidLoad {
+  [super viewDidLoad];
+
+  GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+
+  id<GRPCInterceptorFactory> factory = [[CacheContext alloc] init];
+  options.interceptorFactories = @[ factory ];
+  _options = options;
+}
+
+- (IBAction)tapCall:(id)sender {
+  GRPCProtoMethod *kUnaryCallMethod =
+      [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"UnaryCall"];
+
+  GRPCRequestOptions *requestOptions =
+      [[GRPCRequestOptions alloc] initWithHost:@"grpc-test.sandbox.googleapis.com"
+                                          path:kUnaryCallMethod.HTTPPath
+                                        safety:GRPCCallSafetyCacheableRequest];
+
+  GRPCCall2 *call = [[GRPCCall2 alloc] initWithRequestOptions:requestOptions
+                                              responseHandler:self
+                                                  callOptions:_options];
+
+  RMTSimpleRequest *request = [RMTSimpleRequest message];
+  request.responseSize = 100;
+
+  [call start];
+  [call writeData:[request data]];
+  [call finish];
+}
+
+- (dispatch_queue_t)dispatchQueue {
+  return dispatch_get_main_queue();
+}
+
+- (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata {
+  NSLog(@"Header: %@", initialMetadata);
+}
+
+- (void)didReceiveData:(id)data {
+  NSLog(@"Message: %@", data);
+}
+
+- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
+  NSLog(@"Trailer: %@\nError: %@", trailingMetadata, error);
+}
+
+@end

+ 26 - 0
src/objective-c/examples/InterceptorSample/InterceptorSample/main.m

@@ -0,0 +1,26 @@
+/*
+ *
+ * Copyright 2019 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#import <UIKit/UIKit.h>
+#import "AppDelegate.h"
+
+int main(int argc, char* argv[]) {
+  @autoreleasepool {
+    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+  }
+}

+ 31 - 0
src/objective-c/examples/InterceptorSample/Podfile

@@ -0,0 +1,31 @@
+platform :ios, '8.0'
+
+install! 'cocoapods', :deterministic_uuids => false
+
+ROOT_DIR = '../../../..'
+
+target 'InterceptorSample' do
+  pod 'gRPC-ProtoRPC', :path => ROOT_DIR
+  pod 'gRPC', :path => ROOT_DIR
+  pod 'gRPC-Core', :path => ROOT_DIR
+  pod 'gRPC-RxLibrary', :path => ROOT_DIR
+  pod 'RemoteTest', :path => "../RemoteTestClient"
+  pod '!ProtoCompiler-gRPCPlugin', :path => "#{ROOT_DIR}/src/objective-c"
+end
+
+pre_install do |installer|
+  grpc_core_spec = installer.pod_targets.find{|t| t.name.start_with?('gRPC-Core')}.root_spec
+
+  src_root = "$(PODS_TARGET_SRCROOT)"
+  grpc_core_spec.pod_target_xcconfig = {
+    'GRPC_SRC_ROOT' => src_root,
+    'HEADER_SEARCH_PATHS' => '"$(inherited)" "$(GRPC_SRC_ROOT)/include"',
+    'USER_HEADER_SEARCH_PATHS' => '"$(GRPC_SRC_ROOT)"',
+    # If we don't set these two settings, `include/grpc/support/time.h` and
+    # `src/core/lib/gpr/string.h` shadow the system `<time.h>` and `<string.h>`, breaking the
+    # build.
+    'USE_HEADERMAP' => 'NO',
+    'ALWAYS_SEARCH_USER_PATHS' => 'NO',
+  }
+end
+

+ 527 - 1
src/objective-c/tests/InteropTests/InteropTests.m

@@ -26,6 +26,7 @@
 #import <GRPCClient/GRPCCall+ChannelArg.h>
 #import <GRPCClient/GRPCCall+ChannelArg.h>
 #import <GRPCClient/GRPCCall+Cronet.h>
 #import <GRPCClient/GRPCCall+Cronet.h>
 #import <GRPCClient/GRPCCall+Tests.h>
 #import <GRPCClient/GRPCCall+Tests.h>
+#import <GRPCClient/GRPCInterceptor.h>
 #import <GRPCClient/internal_testing/GRPCCall+InternalTests.h>
 #import <GRPCClient/internal_testing/GRPCCall+InternalTests.h>
 #import <ProtoRPC/ProtoRPC.h>
 #import <ProtoRPC/ProtoRPC.h>
 #import <RemoteTest/Messages.pbobjc.h>
 #import <RemoteTest/Messages.pbobjc.h>
@@ -79,6 +80,240 @@ BOOL isRemoteInteropTest(NSString *host) {
   return [host isEqualToString:@"grpc-test.sandbox.googleapis.com"];
   return [host isEqualToString:@"grpc-test.sandbox.googleapis.com"];
 }
 }
 
 
+@interface DefaultInterceptorFactory : NSObject<GRPCInterceptorFactory>
+
+- (GRPCInterceptor *)createInterceptorWithManager:(GRPCInterceptorManager *)interceptorManager;
+
+@end
+
+@implementation DefaultInterceptorFactory
+
+- (GRPCInterceptor *)createInterceptorWithManager:(GRPCInterceptorManager *)interceptorManager {
+  dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
+  return [[GRPCInterceptor alloc] initWithInterceptorManager:interceptorManager
+                                        requestDispatchQueue:queue
+                                       responseDispatchQueue:queue];
+}
+
+@end
+
+@interface HookInterceptorFactory : NSObject<GRPCInterceptorFactory>
+
+- (instancetype)
+initWithRequestDispatchQueue:(dispatch_queue_t)requestDispatchQueue
+       responseDispatchQueue:(dispatch_queue_t)responseDispatchQueue
+                   startHook:(void (^)(GRPCRequestOptions *requestOptions,
+                                       GRPCCallOptions *callOptions,
+                                       GRPCInterceptorManager *manager))startHook
+               writeDataHook:(void (^)(id data, GRPCInterceptorManager *manager))writeDataHook
+                  finishHook:(void (^)(GRPCInterceptorManager *manager))finishHook
+     receiveNextMessagesHook:(void (^)(NSUInteger numberOfMessages,
+                                       GRPCInterceptorManager *manager))receiveNextMessagesHook
+          responseHeaderHook:(void (^)(NSDictionary *initialMetadata,
+                                       GRPCInterceptorManager *manager))responseHeaderHook
+            responseDataHook:(void (^)(id data, GRPCInterceptorManager *manager))responseDataHook
+           responseCloseHook:(void (^)(NSDictionary *trailingMetadata, NSError *error,
+                                       GRPCInterceptorManager *manager))responseCloseHook
+            didWriteDataHook:(void (^)(GRPCInterceptorManager *manager))didWriteDataHook;
+
+- (GRPCInterceptor *)createInterceptorWithManager:(GRPCInterceptorManager *)interceptorManager;
+
+@end
+
+@interface HookIntercetpor : GRPCInterceptor
+
+- (instancetype)
+initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager
+      requestDispatchQueue:(dispatch_queue_t)requestDispatchQueue
+     responseDispatchQueue:(dispatch_queue_t)responseDispatchQueue
+                 startHook:(void (^)(GRPCRequestOptions *requestOptions,
+                                     GRPCCallOptions *callOptions,
+                                     GRPCInterceptorManager *manager))startHook
+             writeDataHook:(void (^)(id data, GRPCInterceptorManager *manager))writeDataHook
+                finishHook:(void (^)(GRPCInterceptorManager *manager))finishHook
+   receiveNextMessagesHook:(void (^)(NSUInteger numberOfMessages,
+                                     GRPCInterceptorManager *manager))receiveNextMessagesHook
+        responseHeaderHook:(void (^)(NSDictionary *initialMetadata,
+                                     GRPCInterceptorManager *manager))responseHeaderHook
+          responseDataHook:(void (^)(id data, GRPCInterceptorManager *manager))responseDataHook
+         responseCloseHook:(void (^)(NSDictionary *trailingMetadata, NSError *error,
+                                     GRPCInterceptorManager *manager))responseCloseHook
+          didWriteDataHook:(void (^)(GRPCInterceptorManager *manager))didWriteDataHook;
+
+@end
+
+@implementation HookInterceptorFactory {
+  void (^_startHook)(GRPCRequestOptions *requestOptions, GRPCCallOptions *callOptions,
+                     GRPCInterceptorManager *manager);
+  void (^_writeDataHook)(id data, GRPCInterceptorManager *manager);
+  void (^_finishHook)(GRPCInterceptorManager *manager);
+  void (^_receiveNextMessagesHook)(NSUInteger numberOfMessages, GRPCInterceptorManager *manager);
+  void (^_responseHeaderHook)(NSDictionary *initialMetadata, GRPCInterceptorManager *manager);
+  void (^_responseDataHook)(id data, GRPCInterceptorManager *manager);
+  void (^_responseCloseHook)(NSDictionary *trailingMetadata, NSError *error,
+                             GRPCInterceptorManager *manager);
+  void (^_didWriteDataHook)(GRPCInterceptorManager *manager);
+  dispatch_queue_t _requestDispatchQueue;
+  dispatch_queue_t _responseDispatchQueue;
+}
+
+- (instancetype)
+initWithRequestDispatchQueue:(dispatch_queue_t)requestDispatchQueue
+       responseDispatchQueue:(dispatch_queue_t)responseDispatchQueue
+                   startHook:(void (^)(GRPCRequestOptions *requestOptions,
+                                       GRPCCallOptions *callOptions,
+                                       GRPCInterceptorManager *manager))startHook
+               writeDataHook:(void (^)(id data, GRPCInterceptorManager *manager))writeDataHook
+                  finishHook:(void (^)(GRPCInterceptorManager *manager))finishHook
+     receiveNextMessagesHook:(void (^)(NSUInteger numberOfMessages,
+                                       GRPCInterceptorManager *manager))receiveNextMessagesHook
+          responseHeaderHook:(void (^)(NSDictionary *initialMetadata,
+                                       GRPCInterceptorManager *manager))responseHeaderHook
+            responseDataHook:(void (^)(id data, GRPCInterceptorManager *manager))responseDataHook
+           responseCloseHook:(void (^)(NSDictionary *trailingMetadata, NSError *error,
+                                       GRPCInterceptorManager *manager))responseCloseHook
+            didWriteDataHook:(void (^)(GRPCInterceptorManager *manager))didWriteDataHook {
+  if ((self = [super init])) {
+    _requestDispatchQueue = requestDispatchQueue;
+    _responseDispatchQueue = responseDispatchQueue;
+    _startHook = startHook;
+    _writeDataHook = writeDataHook;
+    _finishHook = finishHook;
+    _receiveNextMessagesHook = receiveNextMessagesHook;
+    _responseHeaderHook = responseHeaderHook;
+    _responseDataHook = responseDataHook;
+    _responseCloseHook = responseCloseHook;
+    _didWriteDataHook = didWriteDataHook;
+  }
+  return self;
+}
+
+- (GRPCInterceptor *)createInterceptorWithManager:(GRPCInterceptorManager *)interceptorManager {
+  return [[HookIntercetpor alloc] initWithInterceptorManager:interceptorManager
+                                        requestDispatchQueue:_requestDispatchQueue
+                                       responseDispatchQueue:_responseDispatchQueue
+                                                   startHook:_startHook
+                                               writeDataHook:_writeDataHook
+                                                  finishHook:_finishHook
+                                     receiveNextMessagesHook:_receiveNextMessagesHook
+                                          responseHeaderHook:_responseHeaderHook
+                                            responseDataHook:_responseDataHook
+                                           responseCloseHook:_responseCloseHook
+                                            didWriteDataHook:_didWriteDataHook];
+}
+
+@end
+
+@implementation HookIntercetpor {
+  void (^_startHook)(GRPCRequestOptions *requestOptions, GRPCCallOptions *callOptions,
+                     GRPCInterceptorManager *manager);
+  void (^_writeDataHook)(id data, GRPCInterceptorManager *manager);
+  void (^_finishHook)(GRPCInterceptorManager *manager);
+  void (^_receiveNextMessagesHook)(NSUInteger numberOfMessages, GRPCInterceptorManager *manager);
+  void (^_responseHeaderHook)(NSDictionary *initialMetadata, GRPCInterceptorManager *manager);
+  void (^_responseDataHook)(id data, GRPCInterceptorManager *manager);
+  void (^_responseCloseHook)(NSDictionary *trailingMetadata, NSError *error,
+                             GRPCInterceptorManager *manager);
+  void (^_didWriteDataHook)(GRPCInterceptorManager *manager);
+  GRPCInterceptorManager *_manager;
+  dispatch_queue_t _requestDispatchQueue;
+  dispatch_queue_t _responseDispatchQueue;
+}
+
+- (dispatch_queue_t)requestDispatchQueue {
+  return _requestDispatchQueue;
+}
+
+- (dispatch_queue_t)dispatchQueue {
+  return _responseDispatchQueue;
+}
+
+- (instancetype)
+initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager
+      requestDispatchQueue:(dispatch_queue_t)requestDispatchQueue
+     responseDispatchQueue:(dispatch_queue_t)responseDispatchQueue
+                 startHook:(void (^)(GRPCRequestOptions *requestOptions,
+                                     GRPCCallOptions *callOptions,
+                                     GRPCInterceptorManager *manager))startHook
+             writeDataHook:(void (^)(id data, GRPCInterceptorManager *manager))writeDataHook
+                finishHook:(void (^)(GRPCInterceptorManager *manager))finishHook
+   receiveNextMessagesHook:(void (^)(NSUInteger numberOfMessages,
+                                     GRPCInterceptorManager *manager))receiveNextMessagesHook
+        responseHeaderHook:(void (^)(NSDictionary *initialMetadata,
+                                     GRPCInterceptorManager *manager))responseHeaderHook
+          responseDataHook:(void (^)(id data, GRPCInterceptorManager *manager))responseDataHook
+         responseCloseHook:(void (^)(NSDictionary *trailingMetadata, NSError *error,
+                                     GRPCInterceptorManager *manager))responseCloseHook
+          didWriteDataHook:(void (^)(GRPCInterceptorManager *manager))didWriteDataHook {
+  if ((self = [super initWithInterceptorManager:interceptorManager
+                           requestDispatchQueue:requestDispatchQueue
+                          responseDispatchQueue:responseDispatchQueue])) {
+    _startHook = startHook;
+    _writeDataHook = writeDataHook;
+    _finishHook = finishHook;
+    _receiveNextMessagesHook = receiveNextMessagesHook;
+    _responseHeaderHook = responseHeaderHook;
+    _responseDataHook = responseDataHook;
+    _responseCloseHook = responseCloseHook;
+    _didWriteDataHook = didWriteDataHook;
+    _requestDispatchQueue = requestDispatchQueue;
+    _responseDispatchQueue = responseDispatchQueue;
+    _manager = interceptorManager;
+  }
+  return self;
+}
+
+- (void)startWithRequestOptions:(GRPCRequestOptions *)requestOptions
+                    callOptions:(GRPCCallOptions *)callOptions {
+  if (_startHook) {
+    _startHook(requestOptions, callOptions, _manager);
+  }
+}
+
+- (void)writeData:(id)data {
+  if (_writeDataHook) {
+    _writeDataHook(data, _manager);
+  }
+}
+
+- (void)finish {
+  if (_finishHook) {
+    _finishHook(_manager);
+  }
+}
+
+- (void)receiveNextMessages:(NSUInteger)numberOfMessages {
+  if (_receiveNextMessagesHook) {
+    _receiveNextMessagesHook(numberOfMessages, _manager);
+  }
+}
+
+- (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata {
+  if (_responseHeaderHook) {
+    _responseHeaderHook(initialMetadata, _manager);
+  }
+}
+
+- (void)didReceiveData:(id)data {
+  if (_responseDataHook) {
+    _responseDataHook(data, _manager);
+  }
+}
+
+- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
+  if (_responseCloseHook) {
+    _responseCloseHook(trailingMetadata, error, _manager);
+  }
+}
+
+- (void)didWriteData {
+  if (_didWriteDataHook) {
+    _didWriteDataHook(_manager);
+  }
+}
+
+@end
+
 #pragma mark Tests
 #pragma mark Tests
 
 
 @implementation InteropTests {
 @implementation InteropTests {
@@ -113,7 +348,6 @@ BOOL isRemoteInteropTest(NSString *host) {
 }
 }
 
 
 + (void)setUp {
 + (void)setUp {
-  NSLog(@"InteropTest Started, class: %@", [[self class] description]);
 #ifdef GRPC_COMPILE_WITH_CRONET
 #ifdef GRPC_COMPILE_WITH_CRONET
   configureCronet();
   configureCronet();
   if ([self useCronet]) {
   if ([self useCronet]) {
@@ -988,4 +1222,296 @@ BOOL isRemoteInteropTest(NSString *host) {
 }
 }
 #endif
 #endif
 
 
+- (void)testDefaultInterceptor {
+  XCTAssertNotNil([[self class] host]);
+  __weak XCTestExpectation *expectation = [self expectationWithDescription:@"PingPongWithV2API"];
+
+  NSArray *requests = @[ @27182, @8, @1828, @45904 ];
+  NSArray *responses = @[ @31415, @9, @2653, @58979 ];
+
+  __block int index = 0;
+
+  id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:requests[index]
+                                               requestedResponseSize:responses[index]];
+  GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  options.transportType = [[self class] transportType];
+  options.PEMRootCertificates = [[self class] PEMRootCertificates];
+  options.hostNameOverride = [[self class] hostNameOverride];
+  options.interceptorFactories = @[ [[DefaultInterceptorFactory alloc] init] ];
+
+  __block GRPCStreamingProtoCall *call = [_service
+      fullDuplexCallWithResponseHandler:[[InteropTestsBlockCallbacks alloc]
+                                            initWithInitialMetadataCallback:nil
+                                            messageCallback:^(id message) {
+                                              XCTAssertLessThan(index, 4,
+                                                                @"More than 4 responses received.");
+                                              id expected = [RMTStreamingOutputCallResponse
+                                                  messageWithPayloadSize:responses[index]];
+                                              XCTAssertEqualObjects(message, expected);
+                                              index += 1;
+                                              if (index < 4) {
+                                                id request = [RMTStreamingOutputCallRequest
+                                                    messageWithPayloadSize:requests[index]
+                                                     requestedResponseSize:responses[index]];
+                                                [call writeMessage:request];
+                                              } else {
+                                                [call finish];
+                                              }
+                                            }
+                                            closeCallback:^(NSDictionary *trailingMetadata,
+                                                            NSError *error) {
+                                              XCTAssertNil(error,
+                                                           @"Finished with unexpected error: %@",
+                                                           error);
+                                              XCTAssertEqual(index, 4,
+                                                             @"Received %i responses instead of 4.",
+                                                             index);
+                                              [expectation fulfill];
+                                            }]
+                            callOptions:options];
+  [call start];
+  [call writeMessage:request];
+
+  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
+}
+
+- (void)testLoggingInterceptor {
+  XCTAssertNotNil([[self class] host]);
+  __weak XCTestExpectation *expectation = [self expectationWithDescription:@"PingPongWithV2API"];
+
+  __block NSUInteger startCount = 0;
+  __block NSUInteger writeDataCount = 0;
+  __block NSUInteger finishCount = 0;
+  __block NSUInteger receiveNextMessageCount = 0;
+  __block NSUInteger responseHeaderCount = 0;
+  __block NSUInteger responseDataCount = 0;
+  __block NSUInteger responseCloseCount = 0;
+  __block NSUInteger didWriteDataCount = 0;
+  id<GRPCInterceptorFactory> factory = [[HookInterceptorFactory alloc]
+      initWithRequestDispatchQueue:dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL)
+      responseDispatchQueue:dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL)
+      startHook:^(GRPCRequestOptions *requestOptions, GRPCCallOptions *callOptions,
+                  GRPCInterceptorManager *manager) {
+        startCount++;
+        XCTAssertEqualObjects(requestOptions.host, [[self class] host]);
+        XCTAssertEqualObjects(requestOptions.path, @"/grpc.testing.TestService/FullDuplexCall");
+        XCTAssertEqual(requestOptions.safety, GRPCCallSafetyDefault);
+        [manager startNextInterceptorWithRequest:[requestOptions copy]
+                                     callOptions:[callOptions copy]];
+      }
+      writeDataHook:^(id data, GRPCInterceptorManager *manager) {
+        writeDataCount++;
+        [manager writeNextInterceptorWithData:data];
+      }
+      finishHook:^(GRPCInterceptorManager *manager) {
+        finishCount++;
+        [manager finishNextInterceptor];
+      }
+      receiveNextMessagesHook:^(NSUInteger numberOfMessages, GRPCInterceptorManager *manager) {
+        receiveNextMessageCount++;
+        [manager receiveNextInterceptorMessages:numberOfMessages];
+      }
+      responseHeaderHook:^(NSDictionary *initialMetadata, GRPCInterceptorManager *manager) {
+        responseHeaderCount++;
+        [manager forwardPreviousInterceptorWithInitialMetadata:initialMetadata];
+      }
+      responseDataHook:^(id data, GRPCInterceptorManager *manager) {
+        responseDataCount++;
+        [manager forwardPreviousInterceptorWithData:data];
+      }
+      responseCloseHook:^(NSDictionary *trailingMetadata, NSError *error,
+                          GRPCInterceptorManager *manager) {
+        responseCloseCount++;
+        [manager forwardPreviousInterceptorCloseWithTrailingMetadata:trailingMetadata error:error];
+      }
+      didWriteDataHook:^(GRPCInterceptorManager *manager) {
+        didWriteDataCount++;
+        [manager forwardPreviousInterceptorDidWriteData];
+      }];
+
+  NSArray *requests = @[ @1, @2, @3, @4 ];
+  NSArray *responses = @[ @1, @2, @3, @4 ];
+
+  __block int index = 0;
+
+  id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:requests[index]
+                                               requestedResponseSize:responses[index]];
+  GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  options.transportType = [[self class] transportType];
+  options.PEMRootCertificates = [[self class] PEMRootCertificates];
+  options.hostNameOverride = [[self class] hostNameOverride];
+  options.flowControlEnabled = YES;
+  options.interceptorFactories = @[ factory ];
+  __block BOOL canWriteData = NO;
+
+  __block GRPCStreamingProtoCall *call = [_service
+      fullDuplexCallWithResponseHandler:[[InteropTestsBlockCallbacks alloc]
+                                            initWithInitialMetadataCallback:nil
+                                            messageCallback:^(id message) {
+                                              XCTAssertLessThan(index, 4,
+                                                                @"More than 4 responses received.");
+                                              id expected = [RMTStreamingOutputCallResponse
+                                                  messageWithPayloadSize:responses[index]];
+                                              XCTAssertEqualObjects(message, expected);
+                                              index += 1;
+                                              if (index < 4) {
+                                                id request = [RMTStreamingOutputCallRequest
+                                                    messageWithPayloadSize:requests[index]
+                                                     requestedResponseSize:responses[index]];
+                                                XCTAssertTrue(canWriteData);
+                                                canWriteData = NO;
+                                                [call writeMessage:request];
+                                                [call receiveNextMessage];
+                                              } else {
+                                                [call finish];
+                                              }
+                                            }
+                                            closeCallback:^(NSDictionary *trailingMetadata,
+                                                            NSError *error) {
+                                              XCTAssertNil(error,
+                                                           @"Finished with unexpected error: %@",
+                                                           error);
+                                              XCTAssertEqual(index, 4,
+                                                             @"Received %i responses instead of 4.",
+                                                             index);
+                                              [expectation fulfill];
+                                            }
+                                            writeMessageCallback:^{
+                                              canWriteData = YES;
+                                            }]
+                            callOptions:options];
+  [call start];
+  [call receiveNextMessage];
+  [call writeMessage:request];
+
+  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
+  XCTAssertEqual(startCount, 1);
+  XCTAssertEqual(writeDataCount, 4);
+  XCTAssertEqual(finishCount, 1);
+  XCTAssertEqual(receiveNextMessageCount, 4);
+  XCTAssertEqual(responseHeaderCount, 1);
+  XCTAssertEqual(responseDataCount, 4);
+  XCTAssertEqual(responseCloseCount, 1);
+  XCTAssertEqual(didWriteDataCount, 4);
+}
+
+// Chain a default interceptor and a hook interceptor which, after two writes, cancels the call
+// under the hood but forward further data to the user.
+- (void)testHijackingInterceptor {
+  NSUInteger kCancelAfterWrites = 2;
+  XCTAssertNotNil([[self class] host]);
+  __weak XCTestExpectation *expectation = [self expectationWithDescription:@"PingPongWithV2API"];
+
+  NSArray *responses = @[ @1, @2, @3, @4 ];
+  __block int index = 0;
+
+  __block NSUInteger startCount = 0;
+  __block NSUInteger writeDataCount = 0;
+  __block NSUInteger finishCount = 0;
+  __block NSUInteger responseHeaderCount = 0;
+  __block NSUInteger responseDataCount = 0;
+  __block NSUInteger responseCloseCount = 0;
+  id<GRPCInterceptorFactory> factory = [[HookInterceptorFactory alloc]
+      initWithRequestDispatchQueue:dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL)
+      responseDispatchQueue:dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL)
+      startHook:^(GRPCRequestOptions *requestOptions, GRPCCallOptions *callOptions,
+                  GRPCInterceptorManager *manager) {
+        startCount++;
+        [manager startNextInterceptorWithRequest:[requestOptions copy]
+                                     callOptions:[callOptions copy]];
+      }
+      writeDataHook:^(id data, GRPCInterceptorManager *manager) {
+        writeDataCount++;
+        if (index < kCancelAfterWrites) {
+          [manager writeNextInterceptorWithData:data];
+        } else if (index == kCancelAfterWrites) {
+          [manager cancelNextInterceptor];
+          [manager forwardPreviousInterceptorWithData:[[RMTStreamingOutputCallResponse
+                                                          messageWithPayloadSize:responses[index]]
+                                                          data]];
+        } else {  // (index > kCancelAfterWrites)
+          [manager forwardPreviousInterceptorWithData:[[RMTStreamingOutputCallResponse
+                                                          messageWithPayloadSize:responses[index]]
+                                                          data]];
+        }
+      }
+      finishHook:^(GRPCInterceptorManager *manager) {
+        finishCount++;
+        // finish must happen after the hijacking, so directly reply with a close
+        [manager forwardPreviousInterceptorCloseWithTrailingMetadata:@{@"grpc-status" : @"0"}
+                                                               error:nil];
+      }
+      receiveNextMessagesHook:nil
+      responseHeaderHook:^(NSDictionary *initialMetadata, GRPCInterceptorManager *manager) {
+        responseHeaderCount++;
+        [manager forwardPreviousInterceptorWithInitialMetadata:initialMetadata];
+      }
+      responseDataHook:^(id data, GRPCInterceptorManager *manager) {
+        responseDataCount++;
+        [manager forwardPreviousInterceptorWithData:data];
+      }
+      responseCloseHook:^(NSDictionary *trailingMetadata, NSError *error,
+                          GRPCInterceptorManager *manager) {
+        responseCloseCount++;
+        // since we canceled the call, it should return cancel error
+        XCTAssertNil(trailingMetadata);
+        XCTAssertNotNil(error);
+        XCTAssertEqual(error.code, GRPC_STATUS_CANCELLED);
+      }
+      didWriteDataHook:nil];
+
+  NSArray *requests = @[ @1, @2, @3, @4 ];
+
+  id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:requests[index]
+                                               requestedResponseSize:responses[index]];
+  GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  options.transportType = [[self class] transportType];
+  options.PEMRootCertificates = [[self class] PEMRootCertificates];
+  options.hostNameOverride = [[self class] hostNameOverride];
+  options.interceptorFactories = @[ [[DefaultInterceptorFactory alloc] init], factory ];
+
+  __block GRPCStreamingProtoCall *call = [_service
+      fullDuplexCallWithResponseHandler:[[InteropTestsBlockCallbacks alloc]
+                                            initWithInitialMetadataCallback:nil
+                                            messageCallback:^(id message) {
+                                              XCTAssertLessThan(index, 4,
+                                                                @"More than 4 responses received.");
+                                              id expected = [RMTStreamingOutputCallResponse
+                                                  messageWithPayloadSize:responses[index]];
+                                              XCTAssertEqualObjects(message, expected);
+                                              index += 1;
+                                              if (index < 4) {
+                                                id request = [RMTStreamingOutputCallRequest
+                                                    messageWithPayloadSize:requests[index]
+                                                     requestedResponseSize:responses[index]];
+                                                [call writeMessage:request];
+                                                [call receiveNextMessage];
+                                              } else {
+                                                [call finish];
+                                              }
+                                            }
+                                            closeCallback:^(NSDictionary *trailingMetadata,
+                                                            NSError *error) {
+                                              XCTAssertNil(error,
+                                                           @"Finished with unexpected error: %@",
+                                                           error);
+                                              XCTAssertEqual(index, 4,
+                                                             @"Received %i responses instead of 4.",
+                                                             index);
+                                              [expectation fulfill];
+                                            }]
+                            callOptions:options];
+  [call start];
+  [call receiveNextMessage];
+  [call writeMessage:request];
+
+  [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
+  XCTAssertEqual(startCount, 1);
+  XCTAssertEqual(writeDataCount, 4);
+  XCTAssertEqual(finishCount, 1);
+  XCTAssertEqual(responseHeaderCount, 1);
+  XCTAssertEqual(responseDataCount, 2);
+  XCTAssertEqual(responseCloseCount, 1);
+}
+
 @end
 @end

+ 11 - 5
src/php/ext/grpc/php_grpc.c

@@ -42,6 +42,8 @@ const zend_function_entry grpc_functions[] = {
 };
 };
 /* }}} */
 /* }}} */
 
 
+ZEND_DECLARE_MODULE_GLOBALS(grpc);
+
 /* {{{ grpc_module_entry
 /* {{{ grpc_module_entry
  */
  */
 zend_module_entry grpc_module_entry = {
 zend_module_entry grpc_module_entry = {
@@ -77,10 +79,13 @@ ZEND_GET_MODULE(grpc)
 
 
 /* {{{ php_grpc_init_globals
 /* {{{ php_grpc_init_globals
  */
  */
-static void php_grpc_init_globals(zend_grpc_globals *grpc_globals) {
-  grpc_globals->enable_fork_support = 0;
-  grpc_globals->poll_strategy = NULL;
-}
+/* Uncomment this function if you have INI entries
+   static void php_grpc_init_globals(zend_grpc_globals *grpc_globals)
+   {
+     grpc_globals->global_value = 0;
+     grpc_globals->global_string = NULL;
+   }
+*/
 /* }}} */
 /* }}} */
 
 
 void create_new_channel(
 void create_new_channel(
@@ -222,7 +227,6 @@ void apply_ini_settings(TSRMLS_D) {
 /* {{{ PHP_MINIT_FUNCTION
 /* {{{ PHP_MINIT_FUNCTION
  */
  */
 PHP_MINIT_FUNCTION(grpc) {
 PHP_MINIT_FUNCTION(grpc) {
-  ZEND_INIT_MODULE_GLOBALS(grpc, php_grpc_init_globals, NULL);
   REGISTER_INI_ENTRIES();
   REGISTER_INI_ENTRIES();
 
 
   /* Register call error constants */
   /* Register call error constants */
@@ -406,6 +410,8 @@ PHP_RINIT_FUNCTION(grpc) {
  */
  */
 static PHP_GINIT_FUNCTION(grpc) {
 static PHP_GINIT_FUNCTION(grpc) {
   grpc_globals->initialized = 0;
   grpc_globals->initialized = 0;
+  grpc_globals->enable_fork_support = 0;
+  grpc_globals->poll_strategy = NULL;
 }
 }
 /* }}} */
 /* }}} */
 
 

+ 2 - 0
src/php/ext/grpc/php_grpc.h

@@ -70,6 +70,8 @@ ZEND_BEGIN_MODULE_GLOBALS(grpc)
   char *poll_strategy;
   char *poll_strategy;
 ZEND_END_MODULE_GLOBALS(grpc)
 ZEND_END_MODULE_GLOBALS(grpc)
 
 
+ZEND_EXTERN_MODULE_GLOBALS(grpc);
+
 /* In every utility function you add that needs to use variables
 /* In every utility function you add that needs to use variables
    in php_grpc_globals, call TSRMLS_FETCH(); after declaring other
    in php_grpc_globals, call TSRMLS_FETCH(); after declaring other
    variables used by that function, or better yet, pass in TSRMLS_CC
    variables used by that function, or better yet, pass in TSRMLS_CC

+ 19 - 8
src/python/grpcio/grpc/__init__.py

@@ -584,6 +584,9 @@ class ChannelCredentials(object):
 class CallCredentials(object):
 class CallCredentials(object):
     """An encapsulation of the data required to assert an identity over a call.
     """An encapsulation of the data required to assert an identity over a call.
 
 
+    A CallCredentials has to be used with secure Channel, otherwise the
+    metadata will not be transmitted to the server.
+
     A CallCredentials may be composed with ChannelCredentials to always assert
     A CallCredentials may be composed with ChannelCredentials to always assert
     identity for every call over that Channel.
     identity for every call over that Channel.
 
 
@@ -682,7 +685,8 @@ class UnaryUnaryMultiCallable(six.with_metaclass(abc.ABCMeta)):
             for the RPC.
             for the RPC.
           metadata: Optional :term:`metadata` to be transmitted to the
           metadata: Optional :term:`metadata` to be transmitted to the
             service-side of the RPC.
             service-side of the RPC.
-          credentials: An optional CallCredentials for the RPC.
+          credentials: An optional CallCredentials for the RPC. Only valid for
+            secure Channel.
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
             flag to enable wait for ready mechanism
             flag to enable wait for ready mechanism
           compression: An element of grpc.compression, e.g.
           compression: An element of grpc.compression, e.g.
@@ -714,7 +718,8 @@ class UnaryUnaryMultiCallable(six.with_metaclass(abc.ABCMeta)):
             the RPC.
             the RPC.
           metadata: Optional :term:`metadata` to be transmitted to the
           metadata: Optional :term:`metadata` to be transmitted to the
             service-side of the RPC.
             service-side of the RPC.
-          credentials: An optional CallCredentials for the RPC.
+          credentials: An optional CallCredentials for the RPC. Only valid for
+            secure Channel.
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
             flag to enable wait for ready mechanism
             flag to enable wait for ready mechanism
           compression: An element of grpc.compression, e.g.
           compression: An element of grpc.compression, e.g.
@@ -746,7 +751,8 @@ class UnaryUnaryMultiCallable(six.with_metaclass(abc.ABCMeta)):
             the RPC.
             the RPC.
           metadata: Optional :term:`metadata` to be transmitted to the
           metadata: Optional :term:`metadata` to be transmitted to the
             service-side of the RPC.
             service-side of the RPC.
-          credentials: An optional CallCredentials for the RPC.
+          credentials: An optional CallCredentials for the RPC. Only valid for
+            secure Channel.
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
             flag to enable wait for ready mechanism
             flag to enable wait for ready mechanism
           compression: An element of grpc.compression, e.g.
           compression: An element of grpc.compression, e.g.
@@ -781,7 +787,8 @@ class UnaryStreamMultiCallable(six.with_metaclass(abc.ABCMeta)):
             the RPC. If None, the timeout is considered infinite.
             the RPC. If None, the timeout is considered infinite.
           metadata: An optional :term:`metadata` to be transmitted to the
           metadata: An optional :term:`metadata` to be transmitted to the
             service-side of the RPC.
             service-side of the RPC.
-          credentials: An optional CallCredentials for the RPC.
+          credentials: An optional CallCredentials for the RPC. Only valid for
+            secure Channel.
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
             flag to enable wait for ready mechanism
             flag to enable wait for ready mechanism
           compression: An element of grpc.compression, e.g.
           compression: An element of grpc.compression, e.g.
@@ -816,7 +823,8 @@ class StreamUnaryMultiCallable(six.with_metaclass(abc.ABCMeta)):
             the RPC. If None, the timeout is considered infinite.
             the RPC. If None, the timeout is considered infinite.
           metadata: Optional :term:`metadata` to be transmitted to the
           metadata: Optional :term:`metadata` to be transmitted to the
             service-side of the RPC.
             service-side of the RPC.
-          credentials: An optional CallCredentials for the RPC.
+          credentials: An optional CallCredentials for the RPC. Only valid for
+            secure Channel.
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
             flag to enable wait for ready mechanism
             flag to enable wait for ready mechanism
           compression: An element of grpc.compression, e.g.
           compression: An element of grpc.compression, e.g.
@@ -849,7 +857,8 @@ class StreamUnaryMultiCallable(six.with_metaclass(abc.ABCMeta)):
             the RPC. If None, the timeout is considered infinite.
             the RPC. If None, the timeout is considered infinite.
           metadata: Optional :term:`metadata` to be transmitted to the
           metadata: Optional :term:`metadata` to be transmitted to the
             service-side of the RPC.
             service-side of the RPC.
-          credentials: An optional CallCredentials for the RPC.
+          credentials: An optional CallCredentials for the RPC. Only valid for
+            secure Channel.
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
             flag to enable wait for ready mechanism
             flag to enable wait for ready mechanism
           compression: An element of grpc.compression, e.g.
           compression: An element of grpc.compression, e.g.
@@ -881,7 +890,8 @@ class StreamUnaryMultiCallable(six.with_metaclass(abc.ABCMeta)):
             the RPC. If None, the timeout is considered infinite.
             the RPC. If None, the timeout is considered infinite.
           metadata: Optional :term:`metadata` to be transmitted to the
           metadata: Optional :term:`metadata` to be transmitted to the
             service-side of the RPC.
             service-side of the RPC.
-          credentials: An optional CallCredentials for the RPC.
+          credentials: An optional CallCredentials for the RPC. Only valid for
+            secure Channel.
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
             flag to enable wait for ready mechanism
             flag to enable wait for ready mechanism
           compression: An element of grpc.compression, e.g.
           compression: An element of grpc.compression, e.g.
@@ -916,7 +926,8 @@ class StreamStreamMultiCallable(six.with_metaclass(abc.ABCMeta)):
             the RPC. If not specified, the timeout is considered infinite.
             the RPC. If not specified, the timeout is considered infinite.
           metadata: Optional :term:`metadata` to be transmitted to the
           metadata: Optional :term:`metadata` to be transmitted to the
             service-side of the RPC.
             service-side of the RPC.
-          credentials: An optional CallCredentials for the RPC.
+          credentials: An optional CallCredentials for the RPC. Only valid for
+            secure Channel.
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
           wait_for_ready: This is an EXPERIMENTAL argument. An optional
             flag to enable wait for ready mechanism
             flag to enable wait for ready mechanism
           compression: An element of grpc.compression, e.g.
           compression: An element of grpc.compression, e.g.

+ 2 - 2
src/python/grpcio/grpc/_cython/_cygrpc/credentials.pxd.pxi

@@ -26,9 +26,9 @@ cdef int _get_metadata(
     grpc_credentials_plugin_metadata_cb cb, void *user_data,
     grpc_credentials_plugin_metadata_cb cb, void *user_data,
     grpc_metadata creds_md[GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX],
     grpc_metadata creds_md[GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX],
     size_t *num_creds_md, grpc_status_code *status,
     size_t *num_creds_md, grpc_status_code *status,
-    const char **error_details) with gil
+    const char **error_details) except * with gil
 
 
-cdef void _destroy(void *state) with gil
+cdef void _destroy(void *state) except * with gil
 
 
 
 
 cdef class MetadataPluginCallCredentials(CallCredentials):
 cdef class MetadataPluginCallCredentials(CallCredentials):

+ 2 - 2
src/python/grpcio/grpc/_cython/_cygrpc/credentials.pyx.pxi

@@ -42,7 +42,7 @@ cdef int _get_metadata(
     grpc_credentials_plugin_metadata_cb cb, void *user_data,
     grpc_credentials_plugin_metadata_cb cb, void *user_data,
     grpc_metadata creds_md[GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX],
     grpc_metadata creds_md[GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX],
     size_t *num_creds_md, grpc_status_code *status,
     size_t *num_creds_md, grpc_status_code *status,
-    const char **error_details) with gil:
+    const char **error_details) except * with gil:
   cdef size_t metadata_count
   cdef size_t metadata_count
   cdef grpc_metadata *c_metadata
   cdef grpc_metadata *c_metadata
   def callback(metadata, grpc_status_code status, bytes error_details):
   def callback(metadata, grpc_status_code status, bytes error_details):
@@ -57,7 +57,7 @@ cdef int _get_metadata(
   return 0  # Asynchronous return
   return 0  # Asynchronous return
 
 
 
 
-cdef void _destroy(void *state) with gil:
+cdef void _destroy(void *state) except * with gil:
   cpython.Py_DECREF(<object>state)
   cpython.Py_DECREF(<object>state)
   grpc_shutdown_blocking()
   grpc_shutdown_blocking()
 
 

+ 2 - 2
src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi

@@ -546,8 +546,8 @@ cdef extern from "grpc/grpc_security.h":
         grpc_credentials_plugin_metadata_cb cb, void *user_data,
         grpc_credentials_plugin_metadata_cb cb, void *user_data,
         grpc_metadata creds_md[GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX],
         grpc_metadata creds_md[GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX],
         size_t *num_creds_md, grpc_status_code *status,
         size_t *num_creds_md, grpc_status_code *status,
-        const char **error_details)
-    void (*destroy)(void *state)
+        const char **error_details) except *
+    void (*destroy)(void *state) except *
     void *state
     void *state
     const char *type
     const char *type
 
 

+ 2 - 0
src/ruby/ext/grpc/rb_grpc_imports.generated.c

@@ -124,6 +124,7 @@ grpc_channel_credentials_release_type grpc_channel_credentials_release_import;
 grpc_google_default_credentials_create_type grpc_google_default_credentials_create_import;
 grpc_google_default_credentials_create_type grpc_google_default_credentials_create_import;
 grpc_set_ssl_roots_override_callback_type grpc_set_ssl_roots_override_callback_import;
 grpc_set_ssl_roots_override_callback_type grpc_set_ssl_roots_override_callback_import;
 grpc_ssl_credentials_create_type grpc_ssl_credentials_create_import;
 grpc_ssl_credentials_create_type grpc_ssl_credentials_create_import;
+grpc_ssl_credentials_create_ex_type grpc_ssl_credentials_create_ex_import;
 grpc_call_credentials_release_type grpc_call_credentials_release_import;
 grpc_call_credentials_release_type grpc_call_credentials_release_import;
 grpc_composite_channel_credentials_create_type grpc_composite_channel_credentials_create_import;
 grpc_composite_channel_credentials_create_type grpc_composite_channel_credentials_create_import;
 grpc_composite_call_credentials_create_type grpc_composite_call_credentials_create_import;
 grpc_composite_call_credentials_create_type grpc_composite_call_credentials_create_import;
@@ -393,6 +394,7 @@ void grpc_rb_load_imports(HMODULE library) {
   grpc_google_default_credentials_create_import = (grpc_google_default_credentials_create_type) GetProcAddress(library, "grpc_google_default_credentials_create");
   grpc_google_default_credentials_create_import = (grpc_google_default_credentials_create_type) GetProcAddress(library, "grpc_google_default_credentials_create");
   grpc_set_ssl_roots_override_callback_import = (grpc_set_ssl_roots_override_callback_type) GetProcAddress(library, "grpc_set_ssl_roots_override_callback");
   grpc_set_ssl_roots_override_callback_import = (grpc_set_ssl_roots_override_callback_type) GetProcAddress(library, "grpc_set_ssl_roots_override_callback");
   grpc_ssl_credentials_create_import = (grpc_ssl_credentials_create_type) GetProcAddress(library, "grpc_ssl_credentials_create");
   grpc_ssl_credentials_create_import = (grpc_ssl_credentials_create_type) GetProcAddress(library, "grpc_ssl_credentials_create");
+  grpc_ssl_credentials_create_ex_import = (grpc_ssl_credentials_create_ex_type) GetProcAddress(library, "grpc_ssl_credentials_create_ex");
   grpc_call_credentials_release_import = (grpc_call_credentials_release_type) GetProcAddress(library, "grpc_call_credentials_release");
   grpc_call_credentials_release_import = (grpc_call_credentials_release_type) GetProcAddress(library, "grpc_call_credentials_release");
   grpc_composite_channel_credentials_create_import = (grpc_composite_channel_credentials_create_type) GetProcAddress(library, "grpc_composite_channel_credentials_create");
   grpc_composite_channel_credentials_create_import = (grpc_composite_channel_credentials_create_type) GetProcAddress(library, "grpc_composite_channel_credentials_create");
   grpc_composite_call_credentials_create_import = (grpc_composite_call_credentials_create_type) GetProcAddress(library, "grpc_composite_call_credentials_create");
   grpc_composite_call_credentials_create_import = (grpc_composite_call_credentials_create_type) GetProcAddress(library, "grpc_composite_call_credentials_create");

+ 3 - 0
src/ruby/ext/grpc/rb_grpc_imports.generated.h

@@ -347,6 +347,9 @@ extern grpc_set_ssl_roots_override_callback_type grpc_set_ssl_roots_override_cal
 typedef grpc_channel_credentials*(*grpc_ssl_credentials_create_type)(const char* pem_root_certs, grpc_ssl_pem_key_cert_pair* pem_key_cert_pair, const verify_peer_options* verify_options, void* reserved);
 typedef grpc_channel_credentials*(*grpc_ssl_credentials_create_type)(const char* pem_root_certs, grpc_ssl_pem_key_cert_pair* pem_key_cert_pair, const verify_peer_options* verify_options, void* reserved);
 extern grpc_ssl_credentials_create_type grpc_ssl_credentials_create_import;
 extern grpc_ssl_credentials_create_type grpc_ssl_credentials_create_import;
 #define grpc_ssl_credentials_create grpc_ssl_credentials_create_import
 #define grpc_ssl_credentials_create grpc_ssl_credentials_create_import
+typedef grpc_channel_credentials*(*grpc_ssl_credentials_create_ex_type)(const char* pem_root_certs, grpc_ssl_pem_key_cert_pair* pem_key_cert_pair, const grpc_ssl_verify_peer_options* verify_options, void* reserved);
+extern grpc_ssl_credentials_create_ex_type grpc_ssl_credentials_create_ex_import;
+#define grpc_ssl_credentials_create_ex grpc_ssl_credentials_create_ex_import
 typedef void(*grpc_call_credentials_release_type)(grpc_call_credentials* creds);
 typedef void(*grpc_call_credentials_release_type)(grpc_call_credentials* creds);
 extern grpc_call_credentials_release_type grpc_call_credentials_release_import;
 extern grpc_call_credentials_release_type grpc_call_credentials_release_import;
 #define grpc_call_credentials_release grpc_call_credentials_release_import
 #define grpc_call_credentials_release grpc_call_credentials_release_import

+ 41 - 35
test/core/channel/channelz_registry_test.cc

@@ -62,8 +62,8 @@ class ChannelzRegistryTest : public ::testing::Test {
 };
 };
 
 
 TEST_F(ChannelzRegistryTest, UuidStartsAboveZeroTest) {
 TEST_F(ChannelzRegistryTest, UuidStartsAboveZeroTest) {
-  UniquePtr<BaseNode> channelz_channel =
-      MakeUnique<BaseNode>(BaseNode::EntityType::kTopLevelChannel);
+  RefCountedPtr<BaseNode> channelz_channel =
+      MakeRefCounted<BaseNode>(BaseNode::EntityType::kTopLevelChannel);
   intptr_t uuid = channelz_channel->uuid();
   intptr_t uuid = channelz_channel->uuid();
   EXPECT_GT(uuid, 0) << "First uuid chose must be greater than zero. Zero if "
   EXPECT_GT(uuid, 0) << "First uuid chose must be greater than zero. Zero if "
                         "reserved according to "
                         "reserved according to "
@@ -72,11 +72,11 @@ TEST_F(ChannelzRegistryTest, UuidStartsAboveZeroTest) {
 }
 }
 
 
 TEST_F(ChannelzRegistryTest, UuidsAreIncreasing) {
 TEST_F(ChannelzRegistryTest, UuidsAreIncreasing) {
-  std::vector<UniquePtr<BaseNode>> channelz_channels;
+  std::vector<RefCountedPtr<BaseNode>> channelz_channels;
   channelz_channels.reserve(10);
   channelz_channels.reserve(10);
   for (int i = 0; i < 10; ++i) {
   for (int i = 0; i < 10; ++i) {
     channelz_channels.push_back(
     channelz_channels.push_back(
-        MakeUnique<BaseNode>(BaseNode::EntityType::kTopLevelChannel));
+        MakeRefCounted<BaseNode>(BaseNode::EntityType::kTopLevelChannel));
   }
   }
   for (size_t i = 1; i < channelz_channels.size(); ++i) {
   for (size_t i = 1; i < channelz_channels.size(); ++i) {
     EXPECT_LT(channelz_channels[i - 1]->uuid(), channelz_channels[i]->uuid())
     EXPECT_LT(channelz_channels[i - 1]->uuid(), channelz_channels[i]->uuid())
@@ -85,46 +85,50 @@ TEST_F(ChannelzRegistryTest, UuidsAreIncreasing) {
 }
 }
 
 
 TEST_F(ChannelzRegistryTest, RegisterGetTest) {
 TEST_F(ChannelzRegistryTest, RegisterGetTest) {
-  UniquePtr<BaseNode> channelz_channel =
-      MakeUnique<BaseNode>(BaseNode::EntityType::kTopLevelChannel);
-  BaseNode* retrieved = ChannelzRegistry::Get(channelz_channel->uuid());
-  EXPECT_EQ(channelz_channel.get(), retrieved);
+  RefCountedPtr<BaseNode> channelz_channel =
+      MakeRefCounted<BaseNode>(BaseNode::EntityType::kTopLevelChannel);
+  RefCountedPtr<BaseNode> retrieved =
+      ChannelzRegistry::Get(channelz_channel->uuid());
+  EXPECT_EQ(channelz_channel, retrieved);
 }
 }
 
 
 TEST_F(ChannelzRegistryTest, RegisterManyItems) {
 TEST_F(ChannelzRegistryTest, RegisterManyItems) {
-  std::vector<UniquePtr<BaseNode>> channelz_channels;
+  std::vector<RefCountedPtr<BaseNode>> channelz_channels;
   for (int i = 0; i < 100; i++) {
   for (int i = 0; i < 100; i++) {
     channelz_channels.push_back(
     channelz_channels.push_back(
-        MakeUnique<BaseNode>(BaseNode::EntityType::kTopLevelChannel));
-    BaseNode* retrieved = ChannelzRegistry::Get(channelz_channels[i]->uuid());
-    EXPECT_EQ(channelz_channels[i].get(), retrieved);
+        MakeRefCounted<BaseNode>(BaseNode::EntityType::kTopLevelChannel));
+    RefCountedPtr<BaseNode> retrieved =
+        ChannelzRegistry::Get(channelz_channels[i]->uuid());
+    EXPECT_EQ(channelz_channels[i], retrieved);
   }
   }
 }
 }
 
 
 TEST_F(ChannelzRegistryTest, NullIfNotPresentTest) {
 TEST_F(ChannelzRegistryTest, NullIfNotPresentTest) {
-  UniquePtr<BaseNode> channelz_channel =
-      MakeUnique<BaseNode>(BaseNode::EntityType::kTopLevelChannel);
+  RefCountedPtr<BaseNode> channelz_channel =
+      MakeRefCounted<BaseNode>(BaseNode::EntityType::kTopLevelChannel);
   // try to pull out a uuid that does not exist.
   // try to pull out a uuid that does not exist.
-  BaseNode* nonexistant = ChannelzRegistry::Get(channelz_channel->uuid() + 1);
+  RefCountedPtr<BaseNode> nonexistant =
+      ChannelzRegistry::Get(channelz_channel->uuid() + 1);
   EXPECT_EQ(nonexistant, nullptr);
   EXPECT_EQ(nonexistant, nullptr);
-  BaseNode* retrieved = ChannelzRegistry::Get(channelz_channel->uuid());
-  EXPECT_EQ(channelz_channel.get(), retrieved);
+  RefCountedPtr<BaseNode> retrieved =
+      ChannelzRegistry::Get(channelz_channel->uuid());
+  EXPECT_EQ(channelz_channel, retrieved);
 }
 }
 
 
 TEST_F(ChannelzRegistryTest, TestCompaction) {
 TEST_F(ChannelzRegistryTest, TestCompaction) {
   const int kLoopIterations = 300;
   const int kLoopIterations = 300;
   // These channels that will stay in the registry for the duration of the test.
   // These channels that will stay in the registry for the duration of the test.
-  std::vector<UniquePtr<BaseNode>> even_channels;
+  std::vector<RefCountedPtr<BaseNode>> even_channels;
   even_channels.reserve(kLoopIterations);
   even_channels.reserve(kLoopIterations);
   {
   {
     // The channels will unregister themselves at the end of the for block.
     // The channels will unregister themselves at the end of the for block.
-    std::vector<UniquePtr<BaseNode>> odd_channels;
+    std::vector<RefCountedPtr<BaseNode>> odd_channels;
     odd_channels.reserve(kLoopIterations);
     odd_channels.reserve(kLoopIterations);
     for (int i = 0; i < kLoopIterations; i++) {
     for (int i = 0; i < kLoopIterations; i++) {
       even_channels.push_back(
       even_channels.push_back(
-          MakeUnique<BaseNode>(BaseNode::EntityType::kTopLevelChannel));
+          MakeRefCounted<BaseNode>(BaseNode::EntityType::kTopLevelChannel));
       odd_channels.push_back(
       odd_channels.push_back(
-          MakeUnique<BaseNode>(BaseNode::EntityType::kTopLevelChannel));
+          MakeRefCounted<BaseNode>(BaseNode::EntityType::kTopLevelChannel));
     }
     }
   }
   }
   // without compaction, there would be exactly kLoopIterations empty slots at
   // without compaction, there would be exactly kLoopIterations empty slots at
@@ -137,25 +141,26 @@ TEST_F(ChannelzRegistryTest, TestCompaction) {
 TEST_F(ChannelzRegistryTest, TestGetAfterCompaction) {
 TEST_F(ChannelzRegistryTest, TestGetAfterCompaction) {
   const int kLoopIterations = 100;
   const int kLoopIterations = 100;
   // These channels that will stay in the registry for the duration of the test.
   // These channels that will stay in the registry for the duration of the test.
-  std::vector<UniquePtr<BaseNode>> even_channels;
+  std::vector<RefCountedPtr<BaseNode>> even_channels;
   even_channels.reserve(kLoopIterations);
   even_channels.reserve(kLoopIterations);
   std::vector<intptr_t> odd_uuids;
   std::vector<intptr_t> odd_uuids;
   odd_uuids.reserve(kLoopIterations);
   odd_uuids.reserve(kLoopIterations);
   {
   {
     // The channels will unregister themselves at the end of the for block.
     // The channels will unregister themselves at the end of the for block.
-    std::vector<UniquePtr<BaseNode>> odd_channels;
+    std::vector<RefCountedPtr<BaseNode>> odd_channels;
     odd_channels.reserve(kLoopIterations);
     odd_channels.reserve(kLoopIterations);
     for (int i = 0; i < kLoopIterations; i++) {
     for (int i = 0; i < kLoopIterations; i++) {
       even_channels.push_back(
       even_channels.push_back(
-          MakeUnique<BaseNode>(BaseNode::EntityType::kTopLevelChannel));
+          MakeRefCounted<BaseNode>(BaseNode::EntityType::kTopLevelChannel));
       odd_channels.push_back(
       odd_channels.push_back(
-          MakeUnique<BaseNode>(BaseNode::EntityType::kTopLevelChannel));
+          MakeRefCounted<BaseNode>(BaseNode::EntityType::kTopLevelChannel));
       odd_uuids.push_back(odd_channels[i]->uuid());
       odd_uuids.push_back(odd_channels[i]->uuid());
     }
     }
   }
   }
   for (int i = 0; i < kLoopIterations; i++) {
   for (int i = 0; i < kLoopIterations; i++) {
-    BaseNode* retrieved = ChannelzRegistry::Get(even_channels[i]->uuid());
-    EXPECT_EQ(even_channels[i].get(), retrieved);
+    RefCountedPtr<BaseNode> retrieved =
+        ChannelzRegistry::Get(even_channels[i]->uuid());
+    EXPECT_EQ(even_channels[i], retrieved);
     retrieved = ChannelzRegistry::Get(odd_uuids[i]);
     retrieved = ChannelzRegistry::Get(odd_uuids[i]);
     EXPECT_EQ(retrieved, nullptr);
     EXPECT_EQ(retrieved, nullptr);
   }
   }
@@ -164,29 +169,30 @@ TEST_F(ChannelzRegistryTest, TestGetAfterCompaction) {
 TEST_F(ChannelzRegistryTest, TestAddAfterCompaction) {
 TEST_F(ChannelzRegistryTest, TestAddAfterCompaction) {
   const int kLoopIterations = 100;
   const int kLoopIterations = 100;
   // These channels that will stay in the registry for the duration of the test.
   // These channels that will stay in the registry for the duration of the test.
-  std::vector<UniquePtr<BaseNode>> even_channels;
+  std::vector<RefCountedPtr<BaseNode>> even_channels;
   even_channels.reserve(kLoopIterations);
   even_channels.reserve(kLoopIterations);
   std::vector<intptr_t> odd_uuids;
   std::vector<intptr_t> odd_uuids;
   odd_uuids.reserve(kLoopIterations);
   odd_uuids.reserve(kLoopIterations);
   {
   {
     // The channels will unregister themselves at the end of the for block.
     // The channels will unregister themselves at the end of the for block.
-    std::vector<UniquePtr<BaseNode>> odd_channels;
+    std::vector<RefCountedPtr<BaseNode>> odd_channels;
     odd_channels.reserve(kLoopIterations);
     odd_channels.reserve(kLoopIterations);
     for (int i = 0; i < kLoopIterations; i++) {
     for (int i = 0; i < kLoopIterations; i++) {
       even_channels.push_back(
       even_channels.push_back(
-          MakeUnique<BaseNode>(BaseNode::EntityType::kTopLevelChannel));
+          MakeRefCounted<BaseNode>(BaseNode::EntityType::kTopLevelChannel));
       odd_channels.push_back(
       odd_channels.push_back(
-          MakeUnique<BaseNode>(BaseNode::EntityType::kTopLevelChannel));
+          MakeRefCounted<BaseNode>(BaseNode::EntityType::kTopLevelChannel));
       odd_uuids.push_back(odd_channels[i]->uuid());
       odd_uuids.push_back(odd_channels[i]->uuid());
     }
     }
   }
   }
-  std::vector<UniquePtr<BaseNode>> more_channels;
+  std::vector<RefCountedPtr<BaseNode>> more_channels;
   more_channels.reserve(kLoopIterations);
   more_channels.reserve(kLoopIterations);
   for (int i = 0; i < kLoopIterations; i++) {
   for (int i = 0; i < kLoopIterations; i++) {
     more_channels.push_back(
     more_channels.push_back(
-        MakeUnique<BaseNode>(BaseNode::EntityType::kTopLevelChannel));
-    BaseNode* retrieved = ChannelzRegistry::Get(more_channels[i]->uuid());
-    EXPECT_EQ(more_channels[i].get(), retrieved);
+        MakeRefCounted<BaseNode>(BaseNode::EntityType::kTopLevelChannel));
+    RefCountedPtr<BaseNode> retrieved =
+        ChannelzRegistry::Get(more_channels[i]->uuid());
+    EXPECT_EQ(more_channels[i], retrieved);
   }
   }
 }
 }
 
 

+ 41 - 21
test/core/gprpp/map_test.cc

@@ -17,7 +17,9 @@
  */
  */
 
 
 #include "src/core/lib/gprpp/map.h"
 #include "src/core/lib/gprpp/map.h"
+
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
+
 #include "include/grpc/support/string_util.h"
 #include "include/grpc/support/string_util.h"
 #include "src/core/lib/gprpp/inlined_vector.h"
 #include "src/core/lib/gprpp/inlined_vector.h"
 #include "src/core/lib/gprpp/memory.h"
 #include "src/core/lib/gprpp/memory.h"
@@ -319,43 +321,49 @@ TEST_F(MapTest, MapRandomInsertions) {
 // Test Map iterator
 // Test Map iterator
 TEST_F(MapTest, Iteration) {
 TEST_F(MapTest, Iteration) {
   Map<const char*, Payload, StringLess> test_map;
   Map<const char*, Payload, StringLess> test_map;
-  for (int i = 0; i < 5; i++) {
+  for (int i = 4; i >= 0; --i) {
     test_map.emplace(kKeys[i], Payload(i));
     test_map.emplace(kKeys[i], Payload(i));
   }
   }
-  int count = 0;
-  for (auto iter = test_map.begin(); iter != test_map.end(); iter++) {
-    EXPECT_EQ(iter->second.data(), count);
-    count++;
+  auto it = test_map.begin();
+  for (int i = 0; i < 5; ++i) {
+    ASSERT_NE(it, test_map.end());
+    EXPECT_STREQ(kKeys[i], it->first);
+    EXPECT_EQ(i, it->second.data());
+    ++it;
   }
   }
-  EXPECT_EQ(count, 5);
+  EXPECT_EQ(it, test_map.end());
 }
 }
 
 
 // Test Map iterator with unique ptr payload
 // Test Map iterator with unique ptr payload
 TEST_F(MapTest, IterationWithUniquePtrValue) {
 TEST_F(MapTest, IterationWithUniquePtrValue) {
   Map<const char*, UniquePtr<Payload>, StringLess> test_map;
   Map<const char*, UniquePtr<Payload>, StringLess> test_map;
-  for (int i = 0; i < 5; i++) {
+  for (int i = 4; i >= 0; --i) {
     test_map.emplace(kKeys[i], MakeUnique<Payload>(i));
     test_map.emplace(kKeys[i], MakeUnique<Payload>(i));
   }
   }
-  int count = 0;
-  for (auto iter = test_map.begin(); iter != test_map.end(); iter++) {
-    EXPECT_EQ(iter->second->data(), count);
-    count++;
+  auto it = test_map.begin();
+  for (int i = 0; i < 5; ++i) {
+    ASSERT_NE(it, test_map.end());
+    EXPECT_STREQ(kKeys[i], it->first);
+    EXPECT_EQ(i, it->second->data());
+    ++it;
   }
   }
-  EXPECT_EQ(count, 5);
+  EXPECT_EQ(it, test_map.end());
 }
 }
 
 
 // Test Map iterator with unique ptr to char key
 // Test Map iterator with unique ptr to char key
 TEST_F(MapTest, IterationWithUniquePtrKey) {
 TEST_F(MapTest, IterationWithUniquePtrKey) {
   Map<UniquePtr<char>, Payload, StringLess> test_map;
   Map<UniquePtr<char>, Payload, StringLess> test_map;
-  for (int i = 0; i < 5; i++) {
+  for (int i = 4; i >= 0; --i) {
     test_map.emplace(CopyString(kKeys[i]), Payload(i));
     test_map.emplace(CopyString(kKeys[i]), Payload(i));
   }
   }
-  int count = 0;
-  for (auto iter = test_map.begin(); iter != test_map.end(); iter++) {
-    EXPECT_EQ(iter->second.data(), count);
-    count++;
+  auto it = test_map.begin();
+  for (int i = 0; i < 5; ++i) {
+    ASSERT_NE(it, test_map.end());
+    EXPECT_STREQ(kKeys[i], it->first.get());
+    EXPECT_EQ(i, it->second.data());
+    ++it;
   }
   }
-  EXPECT_EQ(count, 5);
+  EXPECT_EQ(it, test_map.end());
 }
 }
 
 
 // Test removing entries while iterating the map
 // Test removing entries while iterating the map
@@ -367,11 +375,23 @@ TEST_F(MapTest, EraseUsingIterator) {
   int count = 0;
   int count = 0;
   for (auto iter = test_map.begin(); iter != test_map.end();) {
   for (auto iter = test_map.begin(); iter != test_map.end();) {
     EXPECT_EQ(iter->second.data(), count);
     EXPECT_EQ(iter->second.data(), count);
-    iter = test_map.erase(iter);
-    count++;
+    if (count % 2 == 1) {
+      iter = test_map.erase(iter);
+    } else {
+      ++iter;
+    }
+    ++count;
   }
   }
   EXPECT_EQ(count, 5);
   EXPECT_EQ(count, 5);
-  EXPECT_TRUE(test_map.empty());
+  auto it = test_map.begin();
+  for (int i = 0; i < 5; ++i) {
+    if (i % 2 == 0) {
+      EXPECT_STREQ(kKeys[i], it->first);
+      EXPECT_EQ(i, it->second.data());
+      ++it;
+    }
+  }
+  EXPECT_EQ(it, test_map.end());
 }
 }
 
 
 // Random ops on a Map with Integer key of Payload value,
 // Random ops on a Map with Integer key of Payload value,

+ 42 - 2
test/core/surface/completion_queue_test.cc

@@ -23,6 +23,7 @@
 #include <grpc/support/time.h>
 #include <grpc/support/time.h>
 #include "src/core/lib/gpr/useful.h"
 #include "src/core/lib/gpr/useful.h"
 #include "src/core/lib/gprpp/memory.h"
 #include "src/core/lib/gprpp/memory.h"
+#include "src/core/lib/gprpp/sync.h"
 #include "src/core/lib/iomgr/iomgr.h"
 #include "src/core/lib/iomgr/iomgr.h"
 #include "test/core/util/test_config.h"
 #include "test/core/util/test_config.h"
 
 
@@ -359,12 +360,19 @@ static void test_pluck_after_shutdown(void) {
 
 
 static void test_callback(void) {
 static void test_callback(void) {
   grpc_completion_queue* cc;
   grpc_completion_queue* cc;
-  void* tags[128];
+  static void* tags[128];
   grpc_cq_completion completions[GPR_ARRAY_SIZE(tags)];
   grpc_cq_completion completions[GPR_ARRAY_SIZE(tags)];
   grpc_cq_polling_type polling_types[] = {
   grpc_cq_polling_type polling_types[] = {
       GRPC_CQ_DEFAULT_POLLING, GRPC_CQ_NON_LISTENING, GRPC_CQ_NON_POLLING};
       GRPC_CQ_DEFAULT_POLLING, GRPC_CQ_NON_LISTENING, GRPC_CQ_NON_POLLING};
   grpc_completion_queue_attributes attr;
   grpc_completion_queue_attributes attr;
   unsigned i;
   unsigned i;
+  static gpr_mu mu, shutdown_mu;
+  static gpr_cv cv, shutdown_cv;
+  static int cb_counter;
+  gpr_mu_init(&mu);
+  gpr_mu_init(&shutdown_mu);
+  gpr_cv_init(&cv);
+  gpr_cv_init(&shutdown_cv);
 
 
   LOG_TEST("test_callback");
   LOG_TEST("test_callback");
 
 
@@ -376,7 +384,11 @@ static void test_callback(void) {
     }
     }
     ~ShutdownCallback() {}
     ~ShutdownCallback() {}
     static void Run(grpc_experimental_completion_queue_functor* cb, int ok) {
     static void Run(grpc_experimental_completion_queue_functor* cb, int ok) {
+      gpr_mu_lock(&shutdown_mu);
       *static_cast<ShutdownCallback*>(cb)->done_ = static_cast<bool>(ok);
       *static_cast<ShutdownCallback*>(cb)->done_ = static_cast<bool>(ok);
+      // Signal when the shutdown callback is completed.
+      gpr_cv_signal(&shutdown_cv);
+      gpr_mu_unlock(&shutdown_mu);
     }
     }
 
 
    private:
    private:
@@ -391,9 +403,9 @@ static void test_callback(void) {
   for (size_t pidx = 0; pidx < GPR_ARRAY_SIZE(polling_types); pidx++) {
   for (size_t pidx = 0; pidx < GPR_ARRAY_SIZE(polling_types); pidx++) {
     int sumtags = 0;
     int sumtags = 0;
     int counter = 0;
     int counter = 0;
+    cb_counter = 0;
     {
     {
       // reset exec_ctx types
       // reset exec_ctx types
-      grpc_core::ApplicationCallbackExecCtx callback_exec_ctx;
       grpc_core::ExecCtx exec_ctx;
       grpc_core::ExecCtx exec_ctx;
       attr.cq_polling_type = polling_types[pidx];
       attr.cq_polling_type = polling_types[pidx];
       cc = grpc_completion_queue_create(
       cc = grpc_completion_queue_create(
@@ -409,7 +421,13 @@ static void test_callback(void) {
                         int ok) {
                         int ok) {
           GPR_ASSERT(static_cast<bool>(ok));
           GPR_ASSERT(static_cast<bool>(ok));
           auto* callback = static_cast<TagCallback*>(cb);
           auto* callback = static_cast<TagCallback*>(cb);
+          gpr_mu_lock(&mu);
+          cb_counter++;
           *callback->counter_ += callback->tag_;
           *callback->counter_ += callback->tag_;
+          if (cb_counter == GPR_ARRAY_SIZE(tags)) {
+            gpr_cv_signal(&cv);
+          }
+          gpr_mu_unlock(&mu);
           grpc_core::Delete(callback);
           grpc_core::Delete(callback);
         };
         };
 
 
@@ -429,12 +447,34 @@ static void test_callback(void) {
                        nullptr, &completions[i]);
                        nullptr, &completions[i]);
       }
       }
 
 
+      gpr_mu_lock(&mu);
+      while (cb_counter != GPR_ARRAY_SIZE(tags)) {
+        // Wait for all the callbacks to complete.
+        gpr_cv_wait(&cv, &mu, gpr_inf_future(GPR_CLOCK_REALTIME));
+      }
+      gpr_mu_unlock(&mu);
+
       shutdown_and_destroy(cc);
       shutdown_and_destroy(cc);
+
+      gpr_mu_lock(&shutdown_mu);
+      while (!got_shutdown) {
+        // Wait for the shutdown callback to complete.
+        gpr_cv_wait(&shutdown_cv, &shutdown_mu,
+                    gpr_inf_future(GPR_CLOCK_REALTIME));
+      }
+      gpr_mu_unlock(&shutdown_mu);
     }
     }
+
+    // Run the assertions to check if the test ran successfully.
     GPR_ASSERT(sumtags == counter);
     GPR_ASSERT(sumtags == counter);
     GPR_ASSERT(got_shutdown);
     GPR_ASSERT(got_shutdown);
     got_shutdown = false;
     got_shutdown = false;
   }
   }
+
+  gpr_cv_destroy(&cv);
+  gpr_cv_destroy(&shutdown_cv);
+  gpr_mu_destroy(&mu);
+  gpr_mu_destroy(&shutdown_mu);
 }
 }
 
 
 struct thread_state {
 struct thread_state {

+ 1 - 0
test/core/surface/public_headers_must_be_c89.c

@@ -161,6 +161,7 @@ int main(int argc, char **argv) {
   printf("%lx", (unsigned long) grpc_google_default_credentials_create);
   printf("%lx", (unsigned long) grpc_google_default_credentials_create);
   printf("%lx", (unsigned long) grpc_set_ssl_roots_override_callback);
   printf("%lx", (unsigned long) grpc_set_ssl_roots_override_callback);
   printf("%lx", (unsigned long) grpc_ssl_credentials_create);
   printf("%lx", (unsigned long) grpc_ssl_credentials_create);
+  printf("%lx", (unsigned long) grpc_ssl_credentials_create_ex);
   printf("%lx", (unsigned long) grpc_call_credentials_release);
   printf("%lx", (unsigned long) grpc_call_credentials_release);
   printf("%lx", (unsigned long) grpc_composite_channel_credentials_create);
   printf("%lx", (unsigned long) grpc_composite_channel_credentials_create);
   printf("%lx", (unsigned long) grpc_composite_call_credentials_create);
   printf("%lx", (unsigned long) grpc_composite_call_credentials_create);

+ 28 - 0
test/cpp/end2end/client_callback_end2end_test.cc

@@ -374,6 +374,34 @@ TEST_P(ClientCallbackEnd2endTest, SimpleRpc) {
   SendRpcs(1, false);
   SendRpcs(1, false);
 }
 }
 
 
+TEST_P(ClientCallbackEnd2endTest, SimpleRpcUnderLock) {
+  MAYBE_SKIP_TEST;
+  ResetStub();
+  std::mutex mu;
+  std::condition_variable cv;
+  bool done = false;
+  EchoRequest request;
+  request.set_message("Hello locked world.");
+  EchoResponse response;
+  ClientContext cli_ctx;
+  {
+    std::lock_guard<std::mutex> l(mu);
+    stub_->experimental_async()->Echo(
+        &cli_ctx, &request, &response,
+        [&mu, &cv, &done, &request, &response](Status s) {
+          std::lock_guard<std::mutex> l(mu);
+          EXPECT_TRUE(s.ok());
+          EXPECT_EQ(request.message(), response.message());
+          done = true;
+          cv.notify_one();
+        });
+  }
+  std::unique_lock<std::mutex> l(mu);
+  while (!done) {
+    cv.wait(l);
+  }
+}
+
 TEST_P(ClientCallbackEnd2endTest, SequentialRpcs) {
 TEST_P(ClientCallbackEnd2endTest, SequentialRpcs) {
   MAYBE_SKIP_TEST;
   MAYBE_SKIP_TEST;
   ResetStub();
   ResetStub();

+ 74 - 5
test/cpp/end2end/client_interceptors_end2end_test.cc

@@ -499,9 +499,20 @@ class BidiStreamingRpcHijackingInterceptorFactory
   }
   }
 };
 };
 
 
+// The logging interceptor is for testing purposes only. It is used to verify
+// that all the appropriate hook points are invoked for an RPC. The counts are
+// reset each time a new object of LoggingInterceptor is created, so only a
+// single RPC should be made on the channel before calling the Verify methods.
 class LoggingInterceptor : public experimental::Interceptor {
 class LoggingInterceptor : public experimental::Interceptor {
  public:
  public:
-  LoggingInterceptor(experimental::ClientRpcInfo* info) { info_ = info; }
+  LoggingInterceptor(experimental::ClientRpcInfo* info) {
+    pre_send_initial_metadata_ = false;
+    pre_send_message_count_ = 0;
+    pre_send_close_ = false;
+    post_recv_initial_metadata_ = false;
+    post_recv_message_count_ = 0;
+    post_recv_status_ = false;
+  }
 
 
   virtual void Intercept(experimental::InterceptorBatchMethods* methods) {
   virtual void Intercept(experimental::InterceptorBatchMethods* methods) {
     if (methods->QueryInterceptionHookPoint(
     if (methods->QueryInterceptionHookPoint(
@@ -512,6 +523,8 @@ class LoggingInterceptor : public experimental::Interceptor {
       auto iterator = map->begin();
       auto iterator = map->begin();
       EXPECT_EQ("testkey", iterator->first);
       EXPECT_EQ("testkey", iterator->first);
       EXPECT_EQ("testvalue", iterator->second);
       EXPECT_EQ("testvalue", iterator->second);
+      ASSERT_FALSE(pre_send_initial_metadata_);
+      pre_send_initial_metadata_ = true;
     }
     }
     if (methods->QueryInterceptionHookPoint(
     if (methods->QueryInterceptionHookPoint(
             experimental::InterceptionHookPoints::PRE_SEND_MESSAGE)) {
             experimental::InterceptionHookPoints::PRE_SEND_MESSAGE)) {
@@ -526,22 +539,28 @@ class LoggingInterceptor : public experimental::Interceptor {
           SerializationTraits<EchoRequest>::Deserialize(&copied_buffer, &req)
           SerializationTraits<EchoRequest>::Deserialize(&copied_buffer, &req)
               .ok());
               .ok());
       EXPECT_TRUE(req.message().find("Hello") == 0u);
       EXPECT_TRUE(req.message().find("Hello") == 0u);
+      pre_send_message_count_++;
     }
     }
     if (methods->QueryInterceptionHookPoint(
     if (methods->QueryInterceptionHookPoint(
             experimental::InterceptionHookPoints::PRE_SEND_CLOSE)) {
             experimental::InterceptionHookPoints::PRE_SEND_CLOSE)) {
       // Got nothing to do here for now
       // Got nothing to do here for now
+      pre_send_close_ = true;
     }
     }
     if (methods->QueryInterceptionHookPoint(
     if (methods->QueryInterceptionHookPoint(
             experimental::InterceptionHookPoints::POST_RECV_INITIAL_METADATA)) {
             experimental::InterceptionHookPoints::POST_RECV_INITIAL_METADATA)) {
       auto* map = methods->GetRecvInitialMetadata();
       auto* map = methods->GetRecvInitialMetadata();
       // Got nothing better to do here for now
       // Got nothing better to do here for now
       EXPECT_EQ(map->size(), static_cast<unsigned>(0));
       EXPECT_EQ(map->size(), static_cast<unsigned>(0));
+      post_recv_initial_metadata_ = true;
     }
     }
     if (methods->QueryInterceptionHookPoint(
     if (methods->QueryInterceptionHookPoint(
             experimental::InterceptionHookPoints::POST_RECV_MESSAGE)) {
             experimental::InterceptionHookPoints::POST_RECV_MESSAGE)) {
       EchoResponse* resp =
       EchoResponse* resp =
           static_cast<EchoResponse*>(methods->GetRecvMessage());
           static_cast<EchoResponse*>(methods->GetRecvMessage());
-      EXPECT_TRUE(resp->message().find("Hello") == 0u);
+      if (resp != nullptr) {
+        EXPECT_TRUE(resp->message().find("Hello") == 0u);
+        post_recv_message_count_++;
+      }
     }
     }
     if (methods->QueryInterceptionHookPoint(
     if (methods->QueryInterceptionHookPoint(
             experimental::InterceptionHookPoints::POST_RECV_STATUS)) {
             experimental::InterceptionHookPoints::POST_RECV_STATUS)) {
@@ -556,14 +575,58 @@ class LoggingInterceptor : public experimental::Interceptor {
       EXPECT_EQ(found, true);
       EXPECT_EQ(found, true);
       auto* status = methods->GetRecvStatus();
       auto* status = methods->GetRecvStatus();
       EXPECT_EQ(status->ok(), true);
       EXPECT_EQ(status->ok(), true);
+      post_recv_status_ = true;
     }
     }
     methods->Proceed();
     methods->Proceed();
   }
   }
 
 
+  static void VerifyCallCommon() {
+    EXPECT_TRUE(pre_send_initial_metadata_);
+    EXPECT_TRUE(pre_send_close_);
+    EXPECT_TRUE(post_recv_initial_metadata_);
+    EXPECT_TRUE(post_recv_status_);
+  }
+
+  static void VerifyUnaryCall() {
+    VerifyCallCommon();
+    EXPECT_EQ(pre_send_message_count_, 1);
+    EXPECT_EQ(post_recv_message_count_, 1);
+  }
+
+  static void VerifyClientStreamingCall() {
+    VerifyCallCommon();
+    EXPECT_EQ(pre_send_message_count_, kNumStreamingMessages);
+    EXPECT_EQ(post_recv_message_count_, 1);
+  }
+
+  static void VerifyServerStreamingCall() {
+    VerifyCallCommon();
+    EXPECT_EQ(pre_send_message_count_, 1);
+    EXPECT_EQ(post_recv_message_count_, kNumStreamingMessages);
+  }
+
+  static void VerifyBidiStreamingCall() {
+    VerifyCallCommon();
+    EXPECT_EQ(pre_send_message_count_, kNumStreamingMessages);
+    EXPECT_EQ(post_recv_message_count_, kNumStreamingMessages);
+  }
+
  private:
  private:
-  experimental::ClientRpcInfo* info_;
+  static bool pre_send_initial_metadata_;
+  static int pre_send_message_count_;
+  static bool pre_send_close_;
+  static bool post_recv_initial_metadata_;
+  static int post_recv_message_count_;
+  static bool post_recv_status_;
 };
 };
 
 
+bool LoggingInterceptor::pre_send_initial_metadata_;
+int LoggingInterceptor::pre_send_message_count_;
+bool LoggingInterceptor::pre_send_close_;
+bool LoggingInterceptor::post_recv_initial_metadata_;
+int LoggingInterceptor::post_recv_message_count_;
+bool LoggingInterceptor::post_recv_status_;
+
 class LoggingInterceptorFactory
 class LoggingInterceptorFactory
     : public experimental::ClientInterceptorFactoryInterface {
     : public experimental::ClientInterceptorFactoryInterface {
  public:
  public:
@@ -607,6 +670,7 @@ TEST_F(ClientInterceptorsEnd2endTest, ClientInterceptorLoggingTest) {
   auto channel = experimental::CreateCustomChannelWithInterceptors(
   auto channel = experimental::CreateCustomChannelWithInterceptors(
       server_address_, InsecureChannelCredentials(), args, std::move(creators));
       server_address_, InsecureChannelCredentials(), args, std::move(creators));
   MakeCall(channel);
   MakeCall(channel);
+  LoggingInterceptor::VerifyUnaryCall();
   // Make sure all 20 dummy interceptors were run
   // Make sure all 20 dummy interceptors were run
   EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20);
   EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20);
 }
 }
@@ -643,7 +707,6 @@ TEST_F(ClientInterceptorsEnd2endTest, ClientInterceptorHijackingTest) {
   }
   }
   auto channel = experimental::CreateCustomChannelWithInterceptors(
   auto channel = experimental::CreateCustomChannelWithInterceptors(
       server_address_, InsecureChannelCredentials(), args, std::move(creators));
       server_address_, InsecureChannelCredentials(), args, std::move(creators));
-
   MakeCall(channel);
   MakeCall(channel);
   // Make sure only 20 dummy interceptors were run
   // Make sure only 20 dummy interceptors were run
   EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20);
   EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20);
@@ -659,8 +722,8 @@ TEST_F(ClientInterceptorsEnd2endTest, ClientInterceptorLogThenHijackTest) {
       new HijackingInterceptorFactory()));
       new HijackingInterceptorFactory()));
   auto channel = experimental::CreateCustomChannelWithInterceptors(
   auto channel = experimental::CreateCustomChannelWithInterceptors(
       server_address_, InsecureChannelCredentials(), args, std::move(creators));
       server_address_, InsecureChannelCredentials(), args, std::move(creators));
-
   MakeCall(channel);
   MakeCall(channel);
+  LoggingInterceptor::VerifyUnaryCall();
 }
 }
 
 
 TEST_F(ClientInterceptorsEnd2endTest,
 TEST_F(ClientInterceptorsEnd2endTest,
@@ -708,6 +771,7 @@ TEST_F(ClientInterceptorsEnd2endTest,
   auto channel = server_->experimental().InProcessChannelWithInterceptors(
   auto channel = server_->experimental().InProcessChannelWithInterceptors(
       args, std::move(creators));
       args, std::move(creators));
   MakeCallbackCall(channel);
   MakeCallbackCall(channel);
+  LoggingInterceptor::VerifyUnaryCall();
   // Make sure all 20 dummy interceptors were run
   // Make sure all 20 dummy interceptors were run
   EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20);
   EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20);
 }
 }
@@ -730,6 +794,7 @@ TEST_F(ClientInterceptorsEnd2endTest,
   auto channel = server_->experimental().InProcessChannelWithInterceptors(
   auto channel = server_->experimental().InProcessChannelWithInterceptors(
       args, std::move(creators));
       args, std::move(creators));
   MakeCallbackCall(channel);
   MakeCallbackCall(channel);
+  LoggingInterceptor::VerifyUnaryCall();
   // Make sure all 20 dummy interceptors were run
   // Make sure all 20 dummy interceptors were run
   EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20);
   EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20);
 }
 }
@@ -768,6 +833,7 @@ TEST_F(ClientInterceptorsStreamingEnd2endTest, ClientStreamingTest) {
   auto channel = experimental::CreateCustomChannelWithInterceptors(
   auto channel = experimental::CreateCustomChannelWithInterceptors(
       server_address_, InsecureChannelCredentials(), args, std::move(creators));
       server_address_, InsecureChannelCredentials(), args, std::move(creators));
   MakeClientStreamingCall(channel);
   MakeClientStreamingCall(channel);
+  LoggingInterceptor::VerifyClientStreamingCall();
   // Make sure all 20 dummy interceptors were run
   // Make sure all 20 dummy interceptors were run
   EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20);
   EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20);
 }
 }
@@ -787,6 +853,7 @@ TEST_F(ClientInterceptorsStreamingEnd2endTest, ServerStreamingTest) {
   auto channel = experimental::CreateCustomChannelWithInterceptors(
   auto channel = experimental::CreateCustomChannelWithInterceptors(
       server_address_, InsecureChannelCredentials(), args, std::move(creators));
       server_address_, InsecureChannelCredentials(), args, std::move(creators));
   MakeServerStreamingCall(channel);
   MakeServerStreamingCall(channel);
+  LoggingInterceptor::VerifyServerStreamingCall();
   // Make sure all 20 dummy interceptors were run
   // Make sure all 20 dummy interceptors were run
   EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20);
   EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20);
 }
 }
@@ -862,6 +929,7 @@ TEST_F(ClientInterceptorsStreamingEnd2endTest, BidiStreamingTest) {
   auto channel = experimental::CreateCustomChannelWithInterceptors(
   auto channel = experimental::CreateCustomChannelWithInterceptors(
       server_address_, InsecureChannelCredentials(), args, std::move(creators));
       server_address_, InsecureChannelCredentials(), args, std::move(creators));
   MakeBidiStreamingCall(channel);
   MakeBidiStreamingCall(channel);
+  LoggingInterceptor::VerifyBidiStreamingCall();
   // Make sure all 20 dummy interceptors were run
   // Make sure all 20 dummy interceptors were run
   EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20);
   EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20);
 }
 }
@@ -928,6 +996,7 @@ TEST_F(ClientGlobalInterceptorEnd2endTest, LoggingGlobalInterceptor) {
   auto channel = experimental::CreateCustomChannelWithInterceptors(
   auto channel = experimental::CreateCustomChannelWithInterceptors(
       server_address_, InsecureChannelCredentials(), args, std::move(creators));
       server_address_, InsecureChannelCredentials(), args, std::move(creators));
   MakeCall(channel);
   MakeCall(channel);
+  LoggingInterceptor::VerifyUnaryCall();
   // Make sure all 20 dummy interceptors were run
   // Make sure all 20 dummy interceptors were run
   EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20);
   EXPECT_EQ(DummyInterceptor::GetNumTimesRun(), 20);
   experimental::TestOnlyResetGlobalClientInterceptorFactory();
   experimental::TestOnlyResetGlobalClientInterceptorFactory();

+ 4 - 5
test/cpp/end2end/client_lb_end2end_test.cc

@@ -139,11 +139,7 @@ class ClientLbEnd2endTest : public ::testing::Test {
       : server_host_("localhost"),
       : server_host_("localhost"),
         kRequestMessage_("Live long and prosper."),
         kRequestMessage_("Live long and prosper."),
         creds_(new SecureChannelCredentials(
         creds_(new SecureChannelCredentials(
-            grpc_fake_transport_security_credentials_create())) {
-    // Make the backup poller poll very frequently in order to pick up
-    // updates from all the subchannels's FDs.
-    GPR_GLOBAL_CONFIG_SET(grpc_client_channel_backup_poll_interval_ms, 1);
-  }
+            grpc_fake_transport_security_credentials_create())) {}
 
 
   void SetUp() override {
   void SetUp() override {
     grpc_init();
     grpc_init();
@@ -1485,6 +1481,9 @@ TEST_F(ClientLbInterceptTrailingMetadataTest, InterceptsRetriesEnabled) {
 }  // namespace grpc
 }  // namespace grpc
 
 
 int main(int argc, char** argv) {
 int main(int argc, char** argv) {
+  // Make the backup poller poll very frequently in order to pick up
+  // updates from all the subchannels's FDs.
+  GPR_GLOBAL_CONFIG_SET(grpc_client_channel_backup_poll_interval_ms, 1);
   ::testing::InitGoogleTest(&argc, argv);
   ::testing::InitGoogleTest(&argc, argv);
   grpc::testing::TestEnvironment env(argc, argv);
   grpc::testing::TestEnvironment env(argc, argv);
   const auto result = RUN_ALL_TESTS();
   const auto result = RUN_ALL_TESTS();

+ 73 - 12
test/cpp/end2end/end2end_test.cc

@@ -26,6 +26,7 @@
 #include <grpcpp/channel.h>
 #include <grpcpp/channel.h>
 #include <grpcpp/client_context.h>
 #include <grpcpp/client_context.h>
 #include <grpcpp/create_channel.h>
 #include <grpcpp/create_channel.h>
+#include <grpcpp/impl/codegen/status_code_enum.h>
 #include <grpcpp/resource_quota.h>
 #include <grpcpp/resource_quota.h>
 #include <grpcpp/security/auth_metadata_processor.h>
 #include <grpcpp/security/auth_metadata_processor.h>
 #include <grpcpp/security/credentials.h>
 #include <grpcpp/security/credentials.h>
@@ -90,11 +91,13 @@ class TestMetadataCredentialsPlugin : public MetadataCredentialsPlugin {
 
 
   TestMetadataCredentialsPlugin(const grpc::string_ref& metadata_key,
   TestMetadataCredentialsPlugin(const grpc::string_ref& metadata_key,
                                 const grpc::string_ref& metadata_value,
                                 const grpc::string_ref& metadata_value,
-                                bool is_blocking, bool is_successful)
+                                bool is_blocking, bool is_successful,
+                                int delay_ms)
       : metadata_key_(metadata_key.data(), metadata_key.length()),
       : metadata_key_(metadata_key.data(), metadata_key.length()),
         metadata_value_(metadata_value.data(), metadata_value.length()),
         metadata_value_(metadata_value.data(), metadata_value.length()),
         is_blocking_(is_blocking),
         is_blocking_(is_blocking),
-        is_successful_(is_successful) {}
+        is_successful_(is_successful),
+        delay_ms_(delay_ms) {}
 
 
   bool IsBlocking() const override { return is_blocking_; }
   bool IsBlocking() const override { return is_blocking_; }
 
 
@@ -102,6 +105,11 @@ class TestMetadataCredentialsPlugin : public MetadataCredentialsPlugin {
       grpc::string_ref service_url, grpc::string_ref method_name,
       grpc::string_ref service_url, grpc::string_ref method_name,
       const grpc::AuthContext& channel_auth_context,
       const grpc::AuthContext& channel_auth_context,
       std::multimap<grpc::string, grpc::string>* metadata) override {
       std::multimap<grpc::string, grpc::string>* metadata) override {
+    if (delay_ms_ != 0) {
+      gpr_sleep_until(
+          gpr_time_add(gpr_now(GPR_CLOCK_REALTIME),
+                       gpr_time_from_millis(delay_ms_, GPR_TIMESPAN)));
+    }
     EXPECT_GT(service_url.length(), 0UL);
     EXPECT_GT(service_url.length(), 0UL);
     EXPECT_GT(method_name.length(), 0UL);
     EXPECT_GT(method_name.length(), 0UL);
     EXPECT_TRUE(channel_auth_context.IsPeerAuthenticated());
     EXPECT_TRUE(channel_auth_context.IsPeerAuthenticated());
@@ -119,6 +127,7 @@ class TestMetadataCredentialsPlugin : public MetadataCredentialsPlugin {
   grpc::string metadata_value_;
   grpc::string metadata_value_;
   bool is_blocking_;
   bool is_blocking_;
   bool is_successful_;
   bool is_successful_;
+  int delay_ms_;
 };
 };
 
 
 const char TestMetadataCredentialsPlugin::kBadMetadataKey[] =
 const char TestMetadataCredentialsPlugin::kBadMetadataKey[] =
@@ -137,7 +146,7 @@ class TestAuthMetadataProcessor : public AuthMetadataProcessor {
         std::unique_ptr<MetadataCredentialsPlugin>(
         std::unique_ptr<MetadataCredentialsPlugin>(
             new TestMetadataCredentialsPlugin(
             new TestMetadataCredentialsPlugin(
                 TestMetadataCredentialsPlugin::kGoodMetadataKey, kGoodGuy,
                 TestMetadataCredentialsPlugin::kGoodMetadataKey, kGoodGuy,
-                is_blocking_, true)));
+                is_blocking_, true, 0)));
   }
   }
 
 
   std::shared_ptr<CallCredentials> GetIncompatibleClientCreds() {
   std::shared_ptr<CallCredentials> GetIncompatibleClientCreds() {
@@ -145,7 +154,7 @@ class TestAuthMetadataProcessor : public AuthMetadataProcessor {
         std::unique_ptr<MetadataCredentialsPlugin>(
         std::unique_ptr<MetadataCredentialsPlugin>(
             new TestMetadataCredentialsPlugin(
             new TestMetadataCredentialsPlugin(
                 TestMetadataCredentialsPlugin::kGoodMetadataKey, "Mr Hyde",
                 TestMetadataCredentialsPlugin::kGoodMetadataKey, "Mr Hyde",
-                is_blocking_, true)));
+                is_blocking_, true, 0)));
   }
   }
 
 
   // Interface implementation
   // Interface implementation
@@ -1835,7 +1844,8 @@ TEST_P(SecureEnd2endTest, AuthMetadataPluginKeyFailure) {
       std::unique_ptr<MetadataCredentialsPlugin>(
       std::unique_ptr<MetadataCredentialsPlugin>(
           new TestMetadataCredentialsPlugin(
           new TestMetadataCredentialsPlugin(
               TestMetadataCredentialsPlugin::kBadMetadataKey,
               TestMetadataCredentialsPlugin::kBadMetadataKey,
-              "Does not matter, will fail the key is invalid.", false, true))));
+              "Does not matter, will fail the key is invalid.", false, true,
+              0))));
   request.set_message("Hello");
   request.set_message("Hello");
 
 
   Status s = stub_->Echo(&context, request, &response);
   Status s = stub_->Echo(&context, request, &response);
@@ -1853,7 +1863,7 @@ TEST_P(SecureEnd2endTest, AuthMetadataPluginValueFailure) {
       std::unique_ptr<MetadataCredentialsPlugin>(
       std::unique_ptr<MetadataCredentialsPlugin>(
           new TestMetadataCredentialsPlugin(
           new TestMetadataCredentialsPlugin(
               TestMetadataCredentialsPlugin::kGoodMetadataKey,
               TestMetadataCredentialsPlugin::kGoodMetadataKey,
-              "With illegal \n value.", false, true))));
+              "With illegal \n value.", false, true, 0))));
   request.set_message("Hello");
   request.set_message("Hello");
 
 
   Status s = stub_->Echo(&context, request, &response);
   Status s = stub_->Echo(&context, request, &response);
@@ -1861,6 +1871,57 @@ TEST_P(SecureEnd2endTest, AuthMetadataPluginValueFailure) {
   EXPECT_EQ(s.error_code(), StatusCode::UNAVAILABLE);
   EXPECT_EQ(s.error_code(), StatusCode::UNAVAILABLE);
 }
 }
 
 
+TEST_P(SecureEnd2endTest, AuthMetadataPluginWithDeadline) {
+  MAYBE_SKIP_TEST;
+  ResetStub();
+  EchoRequest request;
+  request.mutable_param()->set_skip_cancelled_check(true);
+  EchoResponse response;
+  ClientContext context;
+  const int delay = 100;
+  std::chrono::system_clock::time_point deadline =
+      std::chrono::system_clock::now() + std::chrono::milliseconds(delay);
+  context.set_deadline(deadline);
+  context.set_credentials(grpc::MetadataCredentialsFromPlugin(
+      std::unique_ptr<MetadataCredentialsPlugin>(
+          new TestMetadataCredentialsPlugin("meta_key", "Does not matter", true,
+                                            true, delay))));
+  request.set_message("Hello");
+
+  Status s = stub_->Echo(&context, request, &response);
+  if (!s.ok()) {
+    EXPECT_TRUE(s.error_code() == StatusCode::DEADLINE_EXCEEDED ||
+                s.error_code() == StatusCode::UNAVAILABLE);
+  }
+}
+
+TEST_P(SecureEnd2endTest, AuthMetadataPluginWithCancel) {
+  MAYBE_SKIP_TEST;
+  ResetStub();
+  EchoRequest request;
+  request.mutable_param()->set_skip_cancelled_check(true);
+  EchoResponse response;
+  ClientContext context;
+  const int delay = 100;
+  context.set_credentials(grpc::MetadataCredentialsFromPlugin(
+      std::unique_ptr<MetadataCredentialsPlugin>(
+          new TestMetadataCredentialsPlugin("meta_key", "Does not matter", true,
+                                            true, delay))));
+  request.set_message("Hello");
+
+  std::thread cancel_thread([&] {
+    gpr_sleep_until(gpr_time_add(gpr_now(GPR_CLOCK_REALTIME),
+                                 gpr_time_from_millis(delay, GPR_TIMESPAN)));
+    context.TryCancel();
+  });
+  Status s = stub_->Echo(&context, request, &response);
+  if (!s.ok()) {
+    EXPECT_TRUE(s.error_code() == StatusCode::CANCELLED ||
+                s.error_code() == StatusCode::UNAVAILABLE);
+  }
+  cancel_thread.join();
+}
+
 TEST_P(SecureEnd2endTest, NonBlockingAuthMetadataPluginFailure) {
 TEST_P(SecureEnd2endTest, NonBlockingAuthMetadataPluginFailure) {
   MAYBE_SKIP_TEST;
   MAYBE_SKIP_TEST;
   ResetStub();
   ResetStub();
@@ -1871,8 +1932,8 @@ TEST_P(SecureEnd2endTest, NonBlockingAuthMetadataPluginFailure) {
       std::unique_ptr<MetadataCredentialsPlugin>(
       std::unique_ptr<MetadataCredentialsPlugin>(
           new TestMetadataCredentialsPlugin(
           new TestMetadataCredentialsPlugin(
               TestMetadataCredentialsPlugin::kGoodMetadataKey,
               TestMetadataCredentialsPlugin::kGoodMetadataKey,
-              "Does not matter, will fail anyway (see 3rd param)", false,
-              false))));
+              "Does not matter, will fail anyway (see 3rd param)", false, false,
+              0))));
   request.set_message("Hello");
   request.set_message("Hello");
 
 
   Status s = stub_->Echo(&context, request, &response);
   Status s = stub_->Echo(&context, request, &response);
@@ -1935,8 +1996,8 @@ TEST_P(SecureEnd2endTest, BlockingAuthMetadataPluginFailure) {
       std::unique_ptr<MetadataCredentialsPlugin>(
       std::unique_ptr<MetadataCredentialsPlugin>(
           new TestMetadataCredentialsPlugin(
           new TestMetadataCredentialsPlugin(
               TestMetadataCredentialsPlugin::kGoodMetadataKey,
               TestMetadataCredentialsPlugin::kGoodMetadataKey,
-              "Does not matter, will fail anyway (see 3rd param)", true,
-              false))));
+              "Does not matter, will fail anyway (see 3rd param)", true, false,
+              0))));
   request.set_message("Hello");
   request.set_message("Hello");
 
 
   Status s = stub_->Echo(&context, request, &response);
   Status s = stub_->Echo(&context, request, &response);
@@ -1962,11 +2023,11 @@ TEST_P(SecureEnd2endTest, CompositeCallCreds) {
       grpc::MetadataCredentialsFromPlugin(
       grpc::MetadataCredentialsFromPlugin(
           std::unique_ptr<MetadataCredentialsPlugin>(
           std::unique_ptr<MetadataCredentialsPlugin>(
               new TestMetadataCredentialsPlugin(kMetadataKey1, kMetadataVal1,
               new TestMetadataCredentialsPlugin(kMetadataKey1, kMetadataVal1,
-                                                true, true))),
+                                                true, true, 0))),
       grpc::MetadataCredentialsFromPlugin(
       grpc::MetadataCredentialsFromPlugin(
           std::unique_ptr<MetadataCredentialsPlugin>(
           std::unique_ptr<MetadataCredentialsPlugin>(
               new TestMetadataCredentialsPlugin(kMetadataKey2, kMetadataVal2,
               new TestMetadataCredentialsPlugin(kMetadataKey2, kMetadataVal2,
-                                                true, true)))));
+                                                true, true, 0)))));
   request.set_message("Hello");
   request.set_message("Hello");
   request.mutable_param()->set_echo_metadata(true);
   request.mutable_param()->set_echo_metadata(true);
 
 

+ 4 - 5
test/cpp/end2end/grpclb_end2end_test.cc

@@ -372,11 +372,7 @@ class GrpclbEnd2endTest : public ::testing::Test {
         num_backends_(num_backends),
         num_backends_(num_backends),
         num_balancers_(num_balancers),
         num_balancers_(num_balancers),
         client_load_reporting_interval_seconds_(
         client_load_reporting_interval_seconds_(
-            client_load_reporting_interval_seconds) {
-    // Make the backup poller poll very frequently in order to pick up
-    // updates from all the subchannels's FDs.
-    GPR_GLOBAL_CONFIG_SET(grpc_client_channel_backup_poll_interval_ms, 1);
-  }
+            client_load_reporting_interval_seconds) {}
 
 
   void SetUp() override {
   void SetUp() override {
     response_generator_ =
     response_generator_ =
@@ -1994,6 +1990,9 @@ TEST_F(SingleBalancerWithClientLoadReportingTest, Drop) {
 }  // namespace grpc
 }  // namespace grpc
 
 
 int main(int argc, char** argv) {
 int main(int argc, char** argv) {
+  // Make the backup poller poll very frequently in order to pick up
+  // updates from all the subchannels's FDs.
+  GPR_GLOBAL_CONFIG_SET(grpc_client_channel_backup_poll_interval_ms, 1);
   grpc_init();
   grpc_init();
   grpc::testing::TestEnvironment env(argc, argv);
   grpc::testing::TestEnvironment env(argc, argv);
   ::testing::InitGoogleTest(&argc, argv);
   ::testing::InitGoogleTest(&argc, argv);

+ 3 - 3
test/cpp/end2end/interceptors_util.cc

@@ -48,7 +48,7 @@ void MakeClientStreamingCall(const std::shared_ptr<Channel>& channel) {
   EchoResponse resp;
   EchoResponse resp;
   string expected_resp = "";
   string expected_resp = "";
   auto writer = stub->RequestStream(&ctx, &resp);
   auto writer = stub->RequestStream(&ctx, &resp);
-  for (int i = 0; i < 10; i++) {
+  for (int i = 0; i < kNumStreamingMessages; i++) {
     writer->Write(req);
     writer->Write(req);
     expected_resp += "Hello";
     expected_resp += "Hello";
   }
   }
@@ -73,7 +73,7 @@ void MakeServerStreamingCall(const std::shared_ptr<Channel>& channel) {
     EXPECT_EQ(resp.message(), "Hello");
     EXPECT_EQ(resp.message(), "Hello");
     count++;
     count++;
   }
   }
-  ASSERT_EQ(count, 10);
+  ASSERT_EQ(count, kNumStreamingMessages);
   Status s = reader->Finish();
   Status s = reader->Finish();
   EXPECT_EQ(s.ok(), true);
   EXPECT_EQ(s.ok(), true);
 }
 }
@@ -85,7 +85,7 @@ void MakeBidiStreamingCall(const std::shared_ptr<Channel>& channel) {
   EchoResponse resp;
   EchoResponse resp;
   ctx.AddMetadata("testkey", "testvalue");
   ctx.AddMetadata("testkey", "testvalue");
   auto stream = stub->BidiStream(&ctx);
   auto stream = stub->BidiStream(&ctx);
-  for (auto i = 0; i < 10; i++) {
+  for (auto i = 0; i < kNumStreamingMessages; i++) {
     req.set_message("Hello" + std::to_string(i));
     req.set_message("Hello" + std::to_string(i));
     stream->Write(req);
     stream->Write(req);
     stream->Read(&resp);
     stream->Read(&resp);

+ 2 - 0
test/cpp/end2end/interceptors_util.h

@@ -152,6 +152,8 @@ class EchoTestServiceStreamingImpl : public EchoTestService::Service {
   }
   }
 };
 };
 
 
+constexpr int kNumStreamingMessages = 10;
+
 void MakeCall(const std::shared_ptr<Channel>& channel);
 void MakeCall(const std::shared_ptr<Channel>& channel);
 
 
 void MakeClientStreamingCall(const std::shared_ptr<Channel>& channel);
 void MakeClientStreamingCall(const std::shared_ptr<Channel>& channel);

+ 9 - 5
test/cpp/end2end/port_sharing_end2end_test.cc

@@ -159,6 +159,8 @@ class TestTcpServer {
     gpr_log(GPR_INFO, "Got incoming connection! from %s", peer);
     gpr_log(GPR_INFO, "Got incoming connection! from %s", peer);
     gpr_free(peer);
     gpr_free(peer);
     EXPECT_FALSE(acceptor->external_connection);
     EXPECT_FALSE(acceptor->external_connection);
+    listener_fd_ = grpc_tcp_server_port_fd(
+        acceptor->from_server, acceptor->port_index, acceptor->fd_index);
     gpr_free(acceptor);
     gpr_free(acceptor);
     grpc_tcp_destroy_and_release_fd(tcp, &fd_, &on_fd_released_);
     grpc_tcp_destroy_and_release_fd(tcp, &fd_, &on_fd_released_);
   }
   }
@@ -166,6 +168,7 @@ class TestTcpServer {
   void OnFdReleased(grpc_error* err) {
   void OnFdReleased(grpc_error* err) {
     EXPECT_EQ(GRPC_ERROR_NONE, err);
     EXPECT_EQ(GRPC_ERROR_NONE, err);
     experimental::ExternalConnectionAcceptor::NewConnectionParameters p;
     experimental::ExternalConnectionAcceptor::NewConnectionParameters p;
+    p.listener_fd = listener_fd_;
     p.fd = fd_;
     p.fd = fd_;
     if (queue_data_) {
     if (queue_data_) {
       char buf[1024];
       char buf[1024];
@@ -176,20 +179,21 @@ class TestTcpServer {
       Slice data(buf, read_bytes);
       Slice data(buf, read_bytes);
       p.read_buffer = ByteBuffer(&data, 1);
       p.read_buffer = ByteBuffer(&data, 1);
     }
     }
-    gpr_log(GPR_INFO, "Handing off fd %d with data size %d", fd_,
-            static_cast<int>(p.read_buffer.Length()));
+    gpr_log(GPR_INFO, "Handing off fd %d with data size %d from listener fd %d",
+            fd_, static_cast<int>(p.read_buffer.Length()), listener_fd_);
     connection_acceptor_->HandleNewConnection(&p);
     connection_acceptor_->HandleNewConnection(&p);
   }
   }
 
 
   std::mutex mu_;
   std::mutex mu_;
   bool shutdown_;
   bool shutdown_;
 
 
-  int fd_;
-  bool queue_data_;
+  int listener_fd_ = -1;
+  int fd_ = -1;
+  bool queue_data_ = false;
 
 
   grpc_closure on_fd_released_;
   grpc_closure on_fd_released_;
   std::thread running_thread_;
   std::thread running_thread_;
-  int port_;
+  int port_ = -1;
   grpc::string address_;
   grpc::string address_;
   std::unique_ptr<experimental::ExternalConnectionAcceptor>
   std::unique_ptr<experimental::ExternalConnectionAcceptor>
       connection_acceptor_;
       connection_acceptor_;

+ 3 - 1
test/cpp/end2end/server_interceptors_end2end_test.cc

@@ -120,7 +120,9 @@ class LoggingInterceptor : public experimental::Interceptor {
             experimental::InterceptionHookPoints::POST_RECV_MESSAGE)) {
             experimental::InterceptionHookPoints::POST_RECV_MESSAGE)) {
       EchoResponse* resp =
       EchoResponse* resp =
           static_cast<EchoResponse*>(methods->GetRecvMessage());
           static_cast<EchoResponse*>(methods->GetRecvMessage());
-      EXPECT_TRUE(resp->message().find("Hello") == 0);
+      if (resp != nullptr) {
+        EXPECT_TRUE(resp->message().find("Hello") == 0);
+      }
     }
     }
     if (methods->QueryInterceptionHookPoint(
     if (methods->QueryInterceptionHookPoint(
             experimental::InterceptionHookPoints::POST_RECV_CLOSE)) {
             experimental::InterceptionHookPoints::POST_RECV_CLOSE)) {

+ 4 - 5
test/cpp/end2end/service_config_end2end_test.cc

@@ -117,11 +117,7 @@ class ServiceConfigEnd2endTest : public ::testing::Test {
       : server_host_("localhost"),
       : server_host_("localhost"),
         kRequestMessage_("Live long and prosper."),
         kRequestMessage_("Live long and prosper."),
         creds_(new SecureChannelCredentials(
         creds_(new SecureChannelCredentials(
-            grpc_fake_transport_security_credentials_create())) {
-    // Make the backup poller poll very frequently in order to pick up
-    // updates from all the subchannels's FDs.
-    GPR_GLOBAL_CONFIG_SET(grpc_client_channel_backup_poll_interval_ms, 1);
-  }
+            grpc_fake_transport_security_credentials_create())) {}
 
 
   void SetUp() override {
   void SetUp() override {
     grpc_init();
     grpc_init();
@@ -615,6 +611,9 @@ TEST_F(ServiceConfigEnd2endTest,
 }  // namespace grpc
 }  // namespace grpc
 
 
 int main(int argc, char** argv) {
 int main(int argc, char** argv) {
+  // Make the backup poller poll very frequently in order to pick up
+  // updates from all the subchannels's FDs.
+  GPR_GLOBAL_CONFIG_SET(grpc_client_channel_backup_poll_interval_ms, 1);
   ::testing::InitGoogleTest(&argc, argv);
   ::testing::InitGoogleTest(&argc, argv);
   grpc::testing::TestEnvironment env(argc, argv);
   grpc::testing::TestEnvironment env(argc, argv);
   const auto result = RUN_ALL_TESTS();
   const auto result = RUN_ALL_TESTS();

+ 4 - 5
test/cpp/end2end/xds_end2end_test.cc

@@ -368,11 +368,7 @@ class XdsEnd2endTest : public ::testing::Test {
         num_backends_(num_backends),
         num_backends_(num_backends),
         num_balancers_(num_balancers),
         num_balancers_(num_balancers),
         client_load_reporting_interval_seconds_(
         client_load_reporting_interval_seconds_(
-            client_load_reporting_interval_seconds) {
-    // Make the backup poller poll very frequently in order to pick up
-    // updates from all the subchannels's FDs.
-    GPR_GLOBAL_CONFIG_SET(grpc_client_channel_backup_poll_interval_ms, 1);
-  }
+            client_load_reporting_interval_seconds) {}
 
 
   void SetUp() override {
   void SetUp() override {
     response_generator_ =
     response_generator_ =
@@ -1405,6 +1401,9 @@ class SingleBalancerWithClientLoadReportingTest : public XdsEnd2endTest {
 }  // namespace grpc
 }  // namespace grpc
 
 
 int main(int argc, char** argv) {
 int main(int argc, char** argv) {
+  // Make the backup poller poll very frequently in order to pick up
+  // updates from all the subchannels's FDs.
+  GPR_GLOBAL_CONFIG_SET(grpc_client_channel_backup_poll_interval_ms, 1);
   grpc_init();
   grpc_init();
   grpc::testing::TestEnvironment env(argc, argv);
   grpc::testing::TestEnvironment env(argc, argv);
   ::testing::InitGoogleTest(&argc, argv);
   ::testing::InitGoogleTest(&argc, argv);

+ 34 - 1
test/cpp/microbenchmarks/bm_cq.cc

@@ -150,6 +150,9 @@ static void shutdown_and_destroy(grpc_completion_queue* cc) {
   grpc_completion_queue_destroy(cc);
   grpc_completion_queue_destroy(cc);
 }
 }
 
 
+static gpr_mu shutdown_mu, mu;
+static gpr_cv shutdown_cv, cv;
+
 // Tag completion queue iterate times
 // Tag completion queue iterate times
 class TagCallback : public grpc_experimental_completion_queue_functor {
 class TagCallback : public grpc_experimental_completion_queue_functor {
  public:
  public:
@@ -158,8 +161,11 @@ class TagCallback : public grpc_experimental_completion_queue_functor {
   }
   }
   ~TagCallback() {}
   ~TagCallback() {}
   static void Run(grpc_experimental_completion_queue_functor* cb, int ok) {
   static void Run(grpc_experimental_completion_queue_functor* cb, int ok) {
+    gpr_mu_lock(&mu);
     GPR_ASSERT(static_cast<bool>(ok));
     GPR_ASSERT(static_cast<bool>(ok));
     *static_cast<TagCallback*>(cb)->iter_ += 1;
     *static_cast<TagCallback*>(cb)->iter_ += 1;
+    gpr_cv_signal(&cv);
+    gpr_mu_unlock(&mu);
   };
   };
 
 
  private:
  private:
@@ -174,7 +180,10 @@ class ShutdownCallback : public grpc_experimental_completion_queue_functor {
   }
   }
   ~ShutdownCallback() {}
   ~ShutdownCallback() {}
   static void Run(grpc_experimental_completion_queue_functor* cb, int ok) {
   static void Run(grpc_experimental_completion_queue_functor* cb, int ok) {
+    gpr_mu_lock(&shutdown_mu);
     *static_cast<ShutdownCallback*>(cb)->done_ = static_cast<bool>(ok);
     *static_cast<ShutdownCallback*>(cb)->done_ = static_cast<bool>(ok);
+    gpr_cv_signal(&shutdown_cv);
+    gpr_mu_unlock(&shutdown_mu);
   }
   }
 
 
  private:
  private:
@@ -183,8 +192,12 @@ class ShutdownCallback : public grpc_experimental_completion_queue_functor {
 
 
 static void BM_Callback_CQ_Pass1Core(benchmark::State& state) {
 static void BM_Callback_CQ_Pass1Core(benchmark::State& state) {
   TrackCounters track_counters;
   TrackCounters track_counters;
-  int iteration = 0;
+  int iteration = 0, current_iterations = 0;
   TagCallback tag_cb(&iteration);
   TagCallback tag_cb(&iteration);
+  gpr_mu_init(&mu);
+  gpr_cv_init(&cv);
+  gpr_mu_init(&shutdown_mu);
+  gpr_cv_init(&shutdown_cv);
   bool got_shutdown = false;
   bool got_shutdown = false;
   ShutdownCallback shutdown_cb(&got_shutdown);
   ShutdownCallback shutdown_cb(&got_shutdown);
   grpc_completion_queue* cc =
   grpc_completion_queue* cc =
@@ -198,9 +211,29 @@ static void BM_Callback_CQ_Pass1Core(benchmark::State& state) {
                    nullptr, &completion);
                    nullptr, &completion);
   }
   }
   shutdown_and_destroy(cc);
   shutdown_and_destroy(cc);
+
+  gpr_mu_lock(&mu);
+  current_iterations = static_cast<int>(state.iterations());
+  while (current_iterations != iteration) {
+    // Wait for all the callbacks to complete.
+    gpr_cv_wait(&cv, &mu, gpr_inf_future(GPR_CLOCK_REALTIME));
+  }
+  gpr_mu_unlock(&mu);
+
+  gpr_mu_lock(&shutdown_mu);
+  while (!got_shutdown) {
+    // Wait for the shutdown callback to complete.
+    gpr_cv_wait(&shutdown_cv, &shutdown_mu, gpr_inf_future(GPR_CLOCK_REALTIME));
+  }
+  gpr_mu_unlock(&shutdown_mu);
+
   GPR_ASSERT(got_shutdown);
   GPR_ASSERT(got_shutdown);
   GPR_ASSERT(iteration == static_cast<int>(state.iterations()));
   GPR_ASSERT(iteration == static_cast<int>(state.iterations()));
   track_counters.Finish(state);
   track_counters.Finish(state);
+  gpr_cv_destroy(&cv);
+  gpr_mu_destroy(&mu);
+  gpr_cv_destroy(&shutdown_cv);
+  gpr_mu_destroy(&shutdown_mu);
 }
 }
 BENCHMARK(BM_Callback_CQ_Pass1Core);
 BENCHMARK(BM_Callback_CQ_Pass1Core);
 
 

+ 1 - 1
test/cpp/microbenchmarks/callback_streaming_ping_pong.h

@@ -115,7 +115,7 @@ class BidiClient
   int msgs_size_;
   int msgs_size_;
   std::mutex mu;
   std::mutex mu;
   std::condition_variable cv;
   std::condition_variable cv;
-  bool done;
+  bool done = false;
 };
 };
 
 
 template <class Fixture, class ClientContextMutator, class ServerContextMutator>
 template <class Fixture, class ClientContextMutator, class ServerContextMutator>

+ 4 - 2
tools/dockerfile/grpc_artifact_linux_armv6/Dockerfile

@@ -14,11 +14,13 @@
 
 
 # Docker file for building gRPC Raspbian binaries
 # Docker file for building gRPC Raspbian binaries
 
 
+# TODO(https://github.com/grpc/grpc/issues/19199): Move off of this image.
 FROM quay.io/grpc/raspbian_armv6
 FROM quay.io/grpc/raspbian_armv6
 
 
 # Place any extra build instructions between these commands
 # Place any extra build instructions between these commands
 # Recommend modifying upstream docker image (quay.io/grpc/raspbian_armv6)
 # Recommend modifying upstream docker image (quay.io/grpc/raspbian_armv6)
 # for build steps because running them under QEMU is very slow
 # for build steps because running them under QEMU is very slow
 # (https://github.com/kpayson64/armv7hf-debian-qemu)
 # (https://github.com/kpayson64/armv7hf-debian-qemu)
-# RUN [ "cross-build-start" ]
-# RUN [ "cross-build-end" ]
+RUN [ "cross-build-start" ]
+RUN find /usr/local/bin -regex '.*python[0-9]+\.[0-9]+' | xargs -n1 -i{} bash -c "{} -m pip install --upgrade wheel setuptools"
+RUN [ "cross-build-end" ]

+ 4 - 2
tools/dockerfile/grpc_artifact_linux_armv7/Dockerfile

@@ -14,11 +14,13 @@
 
 
 # Docker file for building gRPC Raspbian binaries
 # Docker file for building gRPC Raspbian binaries
 
 
+# TODO(https://github.com/grpc/grpc/issues/19199): Move off of this base image.
 FROM quay.io/grpc/raspbian_armv7
 FROM quay.io/grpc/raspbian_armv7
 
 
 # Place any extra build instructions between these commands
 # Place any extra build instructions between these commands
 # Recommend modifying upstream docker image (quay.io/grpc/raspbian_armv7)
 # Recommend modifying upstream docker image (quay.io/grpc/raspbian_armv7)
 # for build steps because running them under QEMU is very slow
 # for build steps because running them under QEMU is very slow
 # (https://github.com/kpayson64/armv7hf-debian-qemu)
 # (https://github.com/kpayson64/armv7hf-debian-qemu)
-# RUN [ "cross-build-start" ]
-# RUN [ "cross-build-end" ]
+RUN [ "cross-build-start" ]
+RUN find /usr/local/bin -regex '.*python[0-9]+\.[0-9]+' | xargs -n1 -i{} bash -c "{} -m pip install --upgrade wheel setuptools"
+RUN [ "cross-build-end" ]

+ 2 - 3
tools/internal_ci/helper_scripts/prepare_build_interop_rc

@@ -30,7 +30,6 @@ git clone --recursive https://github.com/grpc/grpc-node ./../grpc-node
 git clone --recursive https://github.com/grpc/grpc-dart ./../grpc-dart
 git clone --recursive https://github.com/grpc/grpc-dart ./../grpc-dart
 git clone --recursive https://github.com/grpc/grpc-dotnet ./../grpc-dotnet
 git clone --recursive https://github.com/grpc/grpc-dotnet ./../grpc-dotnet
 
 
-# Download json file.
+# Grab the service account key to run interop tests against prod backends.
 mkdir ~/service_account
 mkdir ~/service_account
-gsutil cp gs://grpc-testing-secrets/interop/service_account/GrpcTesting-726eb1347f15.json ~/service_account
-export GOOGLE_APPLICATION_CREDENTIALS=~/service_account/GrpcTesting-726eb1347f15.json
+cp "${KOKORO_KEYSTORE_DIR}/73836_interop_to_prod_tests_service_account_key" ~/service_account/grpc-testing-ebe7c1ac7381.json || true

+ 9 - 0
tools/internal_ci/linux/grpc_interop_toprod.cfg

@@ -28,3 +28,12 @@ env_vars {
   key: "RUN_TESTS_FLAGS"
   key: "RUN_TESTS_FLAGS"
   value: "-l all --cloud_to_prod --cloud_to_prod_auth --prod_servers default gateway_v4 --use_docker --internal_ci -t -j 8 --bq_result_table interop_results"
   value: "-l all --cloud_to_prod --cloud_to_prod_auth --prod_servers default gateway_v4 --use_docker --internal_ci -t -j 8 --bq_result_table interop_results"
 }
 }
+
+before_action {
+  fetch_keystore {
+    keystore_resource {
+      keystore_config_id: 73836
+      keyname: "interop_to_prod_tests_service_account_key"
+    }
+  }
+}

+ 9 - 0
tools/internal_ci/linux/pull_request/grpc_interop_toprod.cfg

@@ -28,3 +28,12 @@ env_vars {
   key: "RUN_TESTS_FLAGS"
   key: "RUN_TESTS_FLAGS"
   value: "-l all --cloud_to_prod --cloud_to_prod_auth --prod_servers default gateway_v4 --use_docker --internal_ci -t -j 12"
   value: "-l all --cloud_to_prod --cloud_to_prod_auth --prod_servers default gateway_v4 --use_docker --internal_ci -t -j 12"
 }
 }
+
+before_action {
+  fetch_keystore {
+    keystore_resource {
+      keystore_config_id: 73836
+      keyname: "interop_to_prod_tests_service_account_key"
+    }
+  }
+}

+ 9 - 1
tools/internal_ci/macos/grpc_interop_toprod.cfg

@@ -17,7 +17,6 @@
 # Location of the continuous shell script in repository.
 # Location of the continuous shell script in repository.
 build_file: "grpc/tools/internal_ci/macos/grpc_interop_toprod.sh"
 build_file: "grpc/tools/internal_ci/macos/grpc_interop_toprod.sh"
 gfile_resources: "/bigstore/grpc-testing-secrets/gcp_credentials/GrpcTesting-d0eeee2db331.json"
 gfile_resources: "/bigstore/grpc-testing-secrets/gcp_credentials/GrpcTesting-d0eeee2db331.json"
-gfile_resources: "/bigstore/grpc-testing-secrets/interop/service_account/GrpcTesting-726eb1347f15.json"
 timeout_mins: 240
 timeout_mins: 240
 action {
 action {
   define_artifacts {
   define_artifacts {
@@ -25,3 +24,12 @@ action {
     regex: "github/grpc/reports/**"
     regex: "github/grpc/reports/**"
   }
   }
 }
 }
+
+before_action {
+  fetch_keystore {
+    keystore_resource {
+      keystore_config_id: 73836
+      keyname: "interop_to_prod_tests_service_account_key"
+    }
+  }
+}

+ 1 - 1
tools/internal_ci/macos/grpc_interop_toprod.sh

@@ -33,7 +33,7 @@ tools/run_tests/run_interop_tests.py -l c++ \
     --cloud_to_prod --cloud_to_prod_auth \
     --cloud_to_prod --cloud_to_prod_auth \
     --google_default_creds_use_key_file \
     --google_default_creds_use_key_file \
     --prod_servers default gateway_v4 \
     --prod_servers default gateway_v4 \
-    --service_account_key_file="${KOKORO_GFILE_DIR}/GrpcTesting-726eb1347f15.json" \
+    --service_account_key_file="${KOKORO_KEYSTORE_DIR}/73836_interop_to_prod_tests_service_account_key" \
     --skip_compute_engine_creds --internal_ci -t -j 4 || FAILED="true"
     --skip_compute_engine_creds --internal_ci -t -j 4 || FAILED="true"
 
 
 tools/internal_ci/helper_scripts/delete_nonartifacts.sh || true
 tools/internal_ci/helper_scripts/delete_nonartifacts.sh || true

+ 136 - 95
tools/interop_matrix/client_matrix.py

@@ -77,74 +77,115 @@ class ReleaseInfo:
 LANG_RELEASE_MATRIX = {
 LANG_RELEASE_MATRIX = {
     'cxx':
     'cxx':
     OrderedDict([
     OrderedDict([
-        ('v1.0.1', ReleaseInfo()),
-        ('v1.1.4', ReleaseInfo()),
-        ('v1.2.5', ReleaseInfo()),
-        ('v1.3.9', ReleaseInfo()),
-        ('v1.4.2', ReleaseInfo()),
-        ('v1.6.6', ReleaseInfo()),
-        ('v1.7.2', ReleaseInfo()),
-        ('v1.8.0', ReleaseInfo()),
-        ('v1.9.1', ReleaseInfo()),
-        ('v1.10.1', ReleaseInfo()),
-        ('v1.11.1', ReleaseInfo()),
-        ('v1.12.0', ReleaseInfo()),
-        ('v1.13.0', ReleaseInfo()),
-        ('v1.14.1', ReleaseInfo()),
-        ('v1.15.0', ReleaseInfo()),
-        ('v1.16.0', ReleaseInfo()),
-        ('v1.17.1', ReleaseInfo()),
-        ('v1.18.0', ReleaseInfo()),
-        ('v1.19.0', ReleaseInfo()),
+        ('v1.0.1', ReleaseInfo(testcases_file='cxx__v1.0.1')),
+        ('v1.1.4', ReleaseInfo(testcases_file='cxx__v1.0.1')),
+        ('v1.2.5', ReleaseInfo(testcases_file='cxx__v1.0.1')),
+        ('v1.3.9', ReleaseInfo(testcases_file='cxx__v1.0.1')),
+        ('v1.4.2', ReleaseInfo(testcases_file='cxx__v1.0.1')),
+        ('v1.6.6', ReleaseInfo(testcases_file='cxx__v1.0.1')),
+        ('v1.7.2', ReleaseInfo(testcases_file='cxx__v1.0.1')),
+        ('v1.8.0', ReleaseInfo(testcases_file='cxx__v1.0.1')),
+        ('v1.9.1', ReleaseInfo(testcases_file='cxx__v1.0.1')),
+        ('v1.10.1', ReleaseInfo(testcases_file='cxx__v1.0.1')),
+        ('v1.11.1', ReleaseInfo(testcases_file='cxx__v1.0.1')),
+        ('v1.12.0', ReleaseInfo(testcases_file='cxx__v1.0.1')),
+        ('v1.13.0', ReleaseInfo(testcases_file='cxx__v1.0.1')),
+        ('v1.14.1', ReleaseInfo(testcases_file='cxx__v1.0.1')),
+        ('v1.15.0', ReleaseInfo(testcases_file='cxx__v1.0.1')),
+        ('v1.16.0', ReleaseInfo(testcases_file='cxx__v1.0.1')),
+        ('v1.17.1', ReleaseInfo(testcases_file='cxx__v1.0.1')),
+        ('v1.18.0', ReleaseInfo(testcases_file='cxx__v1.0.1')),
+        ('v1.19.0', ReleaseInfo(testcases_file='cxx__v1.0.1')),
         ('v1.20.0', ReleaseInfo()),
         ('v1.20.0', ReleaseInfo()),
     ]),
     ]),
     'go':
     'go':
-    OrderedDict([
-        ('v1.0.5', ReleaseInfo(runtimes=['go1.8'])),
-        ('v1.2.1', ReleaseInfo(runtimes=['go1.8'])),
-        ('v1.3.0', ReleaseInfo(runtimes=['go1.8'])),
-        ('v1.4.2', ReleaseInfo(runtimes=['go1.8'])),
-        ('v1.5.2', ReleaseInfo(runtimes=['go1.8'])),
-        ('v1.6.0', ReleaseInfo(runtimes=['go1.8'])),
-        ('v1.7.4', ReleaseInfo(runtimes=['go1.8'])),
-        ('v1.8.2', ReleaseInfo(runtimes=['go1.8'])),
-        ('v1.9.2', ReleaseInfo(runtimes=['go1.8'])),
-        ('v1.10.1', ReleaseInfo(runtimes=['go1.8'])),
-        ('v1.11.3', ReleaseInfo(runtimes=['go1.8'])),
-        ('v1.12.2', ReleaseInfo(runtimes=['go1.8'])),
-        ('v1.13.0', ReleaseInfo(runtimes=['go1.8'])),
-        ('v1.14.0', ReleaseInfo(runtimes=['go1.8'])),
-        ('v1.15.0', ReleaseInfo(runtimes=['go1.8'])),
-        ('v1.16.0', ReleaseInfo(runtimes=['go1.8'])),
-        ('v1.17.0', ReleaseInfo(runtimes=['go1.11'])),
-        ('v1.18.0', ReleaseInfo(runtimes=['go1.11'])),
-        ('v1.19.0', ReleaseInfo(runtimes=['go1.11'])),
-        ('v1.20.0', ReleaseInfo(runtimes=['go1.11'])),
-        ('v1.21.0', ReleaseInfo(runtimes=['go1.11'])),
-    ]),
+    OrderedDict(
+        [
+            ('v1.0.5',
+             ReleaseInfo(runtimes=['go1.8'], testcases_file='go__v1.0.5')),
+            ('v1.2.1',
+             ReleaseInfo(runtimes=['go1.8'], testcases_file='go__v1.0.5')),
+            ('v1.3.0',
+             ReleaseInfo(runtimes=['go1.8'], testcases_file='go__v1.0.5')),
+            ('v1.4.2',
+             ReleaseInfo(runtimes=['go1.8'], testcases_file='go__v1.0.5')),
+            ('v1.5.2',
+             ReleaseInfo(runtimes=['go1.8'], testcases_file='go__v1.0.5')),
+            ('v1.6.0',
+             ReleaseInfo(runtimes=['go1.8'], testcases_file='go__v1.0.5')),
+            ('v1.7.4',
+             ReleaseInfo(runtimes=['go1.8'], testcases_file='go__v1.0.5')),
+            ('v1.8.2',
+             ReleaseInfo(runtimes=['go1.8'], testcases_file='go__v1.0.5')),
+            ('v1.9.2',
+             ReleaseInfo(runtimes=['go1.8'], testcases_file='go__v1.0.5')),
+            ('v1.10.1',
+             ReleaseInfo(runtimes=['go1.8'], testcases_file='go__v1.0.5')),
+            ('v1.11.3',
+             ReleaseInfo(runtimes=['go1.8'], testcases_file='go__v1.0.5')),
+            ('v1.12.2',
+             ReleaseInfo(runtimes=['go1.8'], testcases_file='go__v1.0.5')),
+            ('v1.13.0',
+             ReleaseInfo(runtimes=['go1.8'], testcases_file='go__v1.0.5')),
+            ('v1.14.0',
+             ReleaseInfo(runtimes=['go1.8'], testcases_file='go__v1.0.5')),
+            ('v1.15.0',
+             ReleaseInfo(runtimes=['go1.8'], testcases_file='go__v1.0.5')),
+            ('v1.16.0',
+             ReleaseInfo(runtimes=['go1.8'], testcases_file='go__v1.0.5')),
+            ('v1.17.0',
+             ReleaseInfo(runtimes=['go1.11'], testcases_file='go__v1.0.5')),
+            ('v1.18.0',
+             ReleaseInfo(runtimes=['go1.11'], testcases_file='go__v1.0.5')),
+            ('v1.19.0',
+             ReleaseInfo(runtimes=['go1.11'], testcases_file='go__v1.0.5')),
+            ('v1.20.0', ReleaseInfo(runtimes=['go1.11'])),
+            ('v1.21.0', ReleaseInfo(runtimes=['go1.11'])),
+        ]),
     'java':
     'java':
     OrderedDict([
     OrderedDict([
-        ('v1.0.3', ReleaseInfo(runtimes=['java_oracle8'])),
-        ('v1.1.2', ReleaseInfo(runtimes=['java_oracle8'])),
-        ('v1.2.0', ReleaseInfo(runtimes=['java_oracle8'])),
-        ('v1.3.1', ReleaseInfo(runtimes=['java_oracle8'])),
-        ('v1.4.0', ReleaseInfo(runtimes=['java_oracle8'])),
-        ('v1.5.0', ReleaseInfo(runtimes=['java_oracle8'])),
-        ('v1.6.1', ReleaseInfo(runtimes=['java_oracle8'])),
-        ('v1.7.0', ReleaseInfo(runtimes=['java_oracle8'])),
-        ('v1.8.0', ReleaseInfo(runtimes=['java_oracle8'])),
-        ('v1.9.1', ReleaseInfo(runtimes=['java_oracle8'])),
-        ('v1.10.1', ReleaseInfo(runtimes=['java_oracle8'])),
-        ('v1.11.0', ReleaseInfo(runtimes=['java_oracle8'])),
-        ('v1.12.0', ReleaseInfo(runtimes=['java_oracle8'])),
-        ('v1.13.1', ReleaseInfo(runtimes=['java_oracle8'])),
-        ('v1.14.0', ReleaseInfo(runtimes=['java_oracle8'])),
-        ('v1.15.0', ReleaseInfo(runtimes=['java_oracle8'])),
-        ('v1.16.1', ReleaseInfo(runtimes=['java_oracle8'])),
-        ('v1.17.1', ReleaseInfo(runtimes=['java_oracle8'])),
-        ('v1.18.0', ReleaseInfo(runtimes=['java_oracle8'])),
-        ('v1.19.0', ReleaseInfo(runtimes=['java_oracle8'])),
+        ('v1.0.3',
+         ReleaseInfo(runtimes=['java_oracle8'], testcases_file='java__v1.0.3')),
+        ('v1.1.2',
+         ReleaseInfo(runtimes=['java_oracle8'], testcases_file='java__v1.0.3')),
+        ('v1.2.0',
+         ReleaseInfo(runtimes=['java_oracle8'], testcases_file='java__v1.0.3')),
+        ('v1.3.1',
+         ReleaseInfo(runtimes=['java_oracle8'], testcases_file='java__v1.0.3')),
+        ('v1.4.0',
+         ReleaseInfo(runtimes=['java_oracle8'], testcases_file='java__v1.0.3')),
+        ('v1.5.0',
+         ReleaseInfo(runtimes=['java_oracle8'], testcases_file='java__v1.0.3')),
+        ('v1.6.1',
+         ReleaseInfo(runtimes=['java_oracle8'], testcases_file='java__v1.0.3')),
+        ('v1.7.0',
+         ReleaseInfo(runtimes=['java_oracle8'], testcases_file='java__v1.0.3')),
+        ('v1.8.0',
+         ReleaseInfo(runtimes=['java_oracle8'], testcases_file='java__v1.0.3')),
+        ('v1.9.1',
+         ReleaseInfo(runtimes=['java_oracle8'], testcases_file='java__v1.0.3')),
+        ('v1.10.1',
+         ReleaseInfo(runtimes=['java_oracle8'], testcases_file='java__v1.0.3')),
+        ('v1.11.0',
+         ReleaseInfo(runtimes=['java_oracle8'], testcases_file='java__v1.0.3')),
+        ('v1.12.0',
+         ReleaseInfo(runtimes=['java_oracle8'], testcases_file='java__v1.0.3')),
+        ('v1.13.1',
+         ReleaseInfo(runtimes=['java_oracle8'], testcases_file='java__v1.0.3')),
+        ('v1.14.0',
+         ReleaseInfo(runtimes=['java_oracle8'], testcases_file='java__v1.0.3')),
+        ('v1.15.0',
+         ReleaseInfo(runtimes=['java_oracle8'], testcases_file='java__v1.0.3')),
+        ('v1.16.1',
+         ReleaseInfo(runtimes=['java_oracle8'], testcases_file='java__v1.0.3')),
+        ('v1.17.1',
+         ReleaseInfo(runtimes=['java_oracle8'], testcases_file='java__v1.0.3')),
+        ('v1.18.0',
+         ReleaseInfo(runtimes=['java_oracle8'], testcases_file='java__v1.0.3')),
+        ('v1.19.0',
+         ReleaseInfo(runtimes=['java_oracle8'], testcases_file='java__v1.0.3')),
         ('v1.20.0', ReleaseInfo(runtimes=['java_oracle8'])),
         ('v1.20.0', ReleaseInfo(runtimes=['java_oracle8'])),
+        ('v1.21.0', ReleaseInfo(runtimes=['java_oracle8'])),
     ]),
     ]),
     'python':
     'python':
     OrderedDict([
     OrderedDict([
@@ -194,22 +235,22 @@ LANG_RELEASE_MATRIX = {
                  'tools/dockerfile/interoptest/grpc_interop_ruby/build_interop.sh',
                  'tools/dockerfile/interoptest/grpc_interop_ruby/build_interop.sh',
              ],
              ],
              testcases_file='ruby__v1.0.1')),
              testcases_file='ruby__v1.0.1')),
-        ('v1.1.4', ReleaseInfo()),
-        ('v1.2.5', ReleaseInfo()),
-        ('v1.3.9', ReleaseInfo()),
-        ('v1.4.2', ReleaseInfo()),
-        ('v1.6.6', ReleaseInfo()),
-        ('v1.7.2', ReleaseInfo()),
-        ('v1.8.0', ReleaseInfo()),
-        ('v1.9.1', ReleaseInfo()),
-        ('v1.10.1', ReleaseInfo()),
-        ('v1.11.1', ReleaseInfo()),
-        ('v1.12.0', ReleaseInfo()),
-        ('v1.13.0', ReleaseInfo()),
-        ('v1.14.1', ReleaseInfo()),
-        ('v1.15.0', ReleaseInfo()),
-        ('v1.16.0', ReleaseInfo()),
-        ('v1.17.1', ReleaseInfo()),
+        ('v1.1.4', ReleaseInfo(testcases_file='ruby__v1.1.4')),
+        ('v1.2.5', ReleaseInfo(testcases_file='ruby__v1.1.4')),
+        ('v1.3.9', ReleaseInfo(testcases_file='ruby__v1.1.4')),
+        ('v1.4.2', ReleaseInfo(testcases_file='ruby__v1.1.4')),
+        ('v1.6.6', ReleaseInfo(testcases_file='ruby__v1.1.4')),
+        ('v1.7.2', ReleaseInfo(testcases_file='ruby__v1.1.4')),
+        ('v1.8.0', ReleaseInfo(testcases_file='ruby__v1.1.4')),
+        ('v1.9.1', ReleaseInfo(testcases_file='ruby__v1.1.4')),
+        ('v1.10.1', ReleaseInfo(testcases_file='ruby__v1.1.4')),
+        ('v1.11.1', ReleaseInfo(testcases_file='ruby__v1.1.4')),
+        ('v1.12.0', ReleaseInfo(testcases_file='ruby__v1.1.4')),
+        ('v1.13.0', ReleaseInfo(testcases_file='ruby__v1.1.4')),
+        ('v1.14.1', ReleaseInfo(testcases_file='ruby__v1.1.4')),
+        ('v1.15.0', ReleaseInfo(testcases_file='ruby__v1.1.4')),
+        ('v1.16.0', ReleaseInfo(testcases_file='ruby__v1.1.4')),
+        ('v1.17.1', ReleaseInfo(testcases_file='ruby__v1.1.4')),
         ('v1.18.0',
         ('v1.18.0',
          ReleaseInfo(patch=[
          ReleaseInfo(patch=[
              'tools/dockerfile/interoptest/grpc_interop_ruby/build_interop.sh',
              'tools/dockerfile/interoptest/grpc_interop_ruby/build_interop.sh',
@@ -222,23 +263,23 @@ LANG_RELEASE_MATRIX = {
     ]),
     ]),
     'php':
     'php':
     OrderedDict([
     OrderedDict([
-        ('v1.0.1', ReleaseInfo()),
-        ('v1.1.4', ReleaseInfo()),
-        ('v1.2.5', ReleaseInfo()),
-        ('v1.3.9', ReleaseInfo()),
-        ('v1.4.2', ReleaseInfo()),
-        ('v1.6.6', ReleaseInfo()),
-        ('v1.7.2', ReleaseInfo()),
-        ('v1.8.0', ReleaseInfo()),
-        ('v1.9.1', ReleaseInfo()),
-        ('v1.10.1', ReleaseInfo()),
-        ('v1.11.1', ReleaseInfo()),
-        ('v1.12.0', ReleaseInfo()),
-        ('v1.13.0', ReleaseInfo()),
-        ('v1.14.1', ReleaseInfo()),
-        ('v1.15.0', ReleaseInfo()),
-        ('v1.16.0', ReleaseInfo()),
-        ('v1.17.1', ReleaseInfo()),
+        ('v1.0.1', ReleaseInfo(testcases_file='php__v1.0.1')),
+        ('v1.1.4', ReleaseInfo(testcases_file='php__v1.0.1')),
+        ('v1.2.5', ReleaseInfo(testcases_file='php__v1.0.1')),
+        ('v1.3.9', ReleaseInfo(testcases_file='php__v1.0.1')),
+        ('v1.4.2', ReleaseInfo(testcases_file='php__v1.0.1')),
+        ('v1.6.6', ReleaseInfo(testcases_file='php__v1.0.1')),
+        ('v1.7.2', ReleaseInfo(testcases_file='php__v1.0.1')),
+        ('v1.8.0', ReleaseInfo(testcases_file='php__v1.0.1')),
+        ('v1.9.1', ReleaseInfo(testcases_file='php__v1.0.1')),
+        ('v1.10.1', ReleaseInfo(testcases_file='php__v1.0.1')),
+        ('v1.11.1', ReleaseInfo(testcases_file='php__v1.0.1')),
+        ('v1.12.0', ReleaseInfo(testcases_file='php__v1.0.1')),
+        ('v1.13.0', ReleaseInfo(testcases_file='php__v1.0.1')),
+        ('v1.14.1', ReleaseInfo(testcases_file='php__v1.0.1')),
+        ('v1.15.0', ReleaseInfo(testcases_file='php__v1.0.1')),
+        ('v1.16.0', ReleaseInfo(testcases_file='php__v1.0.1')),
+        ('v1.17.1', ReleaseInfo(testcases_file='php__v1.0.1')),
         ('v1.18.0', ReleaseInfo()),
         ('v1.18.0', ReleaseInfo()),
         # TODO:https://github.com/grpc/grpc/issues/18264
         # TODO:https://github.com/grpc/grpc/issues/18264
         # Error in above issues needs to be resolved.
         # Error in above issues needs to be resolved.

+ 1 - 1
tools/interop_matrix/create_testcases.sh

@@ -60,7 +60,7 @@ fi
 echo $client_lang
 echo $client_lang
 
 
 ${GRPC_ROOT}/tools/run_tests/run_interop_tests.py -l $client_lang --use_docker \
 ${GRPC_ROOT}/tools/run_tests/run_interop_tests.py -l $client_lang --use_docker \
-  --cloud_to_prod --prod_servers default gateway_v4 --manual_run
+  --cloud_to_prod --prod_servers default gateway_v4 --manual_run --custom_credentials_type tls
 
 
 trap cleanup EXIT
 trap cleanup EXIT
 # TODO(adelez): add test auth tests but do not run if not testing on GCE.
 # TODO(adelez): add test auth tests but do not run if not testing on GCE.

+ 2 - 0
tools/interop_matrix/run_interop_matrix_tests.py

@@ -86,6 +86,8 @@ argp.add_argument(
     type=str,
     type=str,
     nargs='?',
     nargs='?',
     help='Upload test results to a specified BQ table.')
     help='Upload test results to a specified BQ table.')
+# Requests will be routed through specified VIP by default.
+# See go/grpc-interop-tests (internal-only) for details.
 argp.add_argument(
 argp.add_argument(
     '--server_host',
     '--server_host',
     default='74.125.206.210',
     default='74.125.206.210',

Some files were not shown because too many files changed in this diff