diff --git a/BUILD b/BUILD index 7f7add436fb..a03d2b61be4 100644 --- a/BUILD +++ b/BUILD @@ -433,6 +433,7 @@ grpc_cc_library( "src/compiler/config.h", "src/compiler/cpp_generator.h", "src/compiler/cpp_generator_helpers.h", + "src/compiler/cpp_plugin.h", "src/compiler/csharp_generator.h", "src/compiler/csharp_generator_helpers.h", "src/compiler/generator_helpers.h", diff --git a/bazel/BUILD b/bazel/BUILD index 32402892cc3..c3c82c9c0c7 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -17,15 +17,3 @@ licenses(["notice"]) # Apache v2 package(default_visibility = ["//:__subpackages__"]) load(":cc_grpc_library.bzl", "cc_grpc_library") - -proto_library( - name = "well_known_protos_list", - srcs = ["@com_google_protobuf//:well_known_protos"], -) - -cc_grpc_library( - name = "well_known_protos", - srcs = "well_known_protos_list", - proto_only = True, - deps = [], -) diff --git a/bazel/cc_grpc_library.bzl b/bazel/cc_grpc_library.bzl index 6bfcd653f51..572af756176 100644 --- a/bazel/cc_grpc_library.bzl +++ b/bazel/cc_grpc_library.bzl @@ -1,71 +1,105 @@ """Generates and compiles C++ grpc stubs from proto_library rules.""" load("//bazel:generate_cc.bzl", "generate_cc") +load("//bazel:protobuf.bzl", "well_known_proto_libs") -def cc_grpc_library(name, srcs, deps, proto_only, well_known_protos, generate_mocks = False, use_external = False, **kwargs): - """Generates C++ grpc classes from a .proto file. +def cc_grpc_library( + name, + srcs, + deps, + proto_only = False, + well_known_protos = False, + generate_mocks = False, + use_external = False, + grpc_only = False, + **kwargs): + """Generates C++ grpc classes for services defined in a proto file. - Assumes the generated classes will be used in cc_api_version = 2. + If grpc_only is True, this rule is compatible with proto_library and + cc_proto_library native rules such that it expects proto_library target + as srcs argument and generates only grpc library classes, expecting + protobuf messages classes library (cc_proto_library target) to be passed in + deps argument. By default grpc_only is False which makes this rule to behave + in a backwards-compatible mode (trying to generate both proto and grpc + classes). - Arguments: - name: name of rule. - srcs: a single proto_library, which wraps the .proto files with services. - deps: a list of C++ proto_library (or cc_proto_library) which provides - the compiled code of any message that the services depend on. - well_known_protos: Should this library additionally depend on well known - protos - use_external: When True the grpc deps are prefixed with //external. This - allows grpc to be used as a dependency in other bazel projects. - generate_mocks: When True, Google Mock code for client stub is generated. - **kwargs: rest of arguments, e.g., compatible_with and visibility. - """ - if len(srcs) > 1: - fail("Only one srcs value supported", "srcs") + Assumes the generated classes will be used in cc_api_version = 2. - proto_target = "_" + name + "_only" - codegen_target = "_" + name + "_codegen" - codegen_grpc_target = "_" + name + "_grpc_codegen" - proto_deps = ["_" + dep + "_only" for dep in deps if dep.find(':') == -1] - proto_deps += [dep.split(':')[0] + ':' + "_" + dep.split(':')[1] + "_only" for dep in deps if dep.find(':') != -1] + Args: + name (str): Name of rule. + srcs (list): A single .proto file which contains services definitions, + or if grpc_only parameter is True, a single proto_library which + contains services descriptors. + deps (list): A list of C++ proto_library (or cc_proto_library) which + provides the compiled code of any message that the services depend on. + proto_only (bool): If True, create only C++ proto classes library, + avoid creating C++ grpc classes library (expect it in deps). + Deprecated, use native cc_proto_library instead. False by default. + well_known_protos (bool): Should this library additionally depend on + well known protos. Deprecated, the well known protos should be + specified as explicit dependencies of the proto_library target + (passed in srcs parameter) instead. False by default. + generate_mocks (bool): when True, Google Mock code for client stub is + generated. False by default. + use_external (bool): Not used. + grpc_only (bool): if True, generate only grpc library, expecting + protobuf messages library (cc_proto_library target) to be passed as + deps. False by default (will become True by default eventually). + **kwargs: rest of arguments, e.g., compatible_with and visibility + """ + if len(srcs) > 1: + fail("Only one srcs value supported", "srcs") + if grpc_only and proto_only: + fail("A mutualy exclusive configuration is specified: grpc_only = True and proto_only = True") - native.proto_library( - name = proto_target, - srcs = srcs, - deps = proto_deps, - **kwargs - ) + extra_deps = [] + proto_targets = [] - generate_cc( - name = codegen_target, - srcs = [proto_target], - well_known_protos = well_known_protos, - **kwargs - ) + if not grpc_only: + proto_target = "_" + name + "_only" + cc_proto_target = name if proto_only else "_" + name + "_cc_proto" - if not proto_only: - plugin = "@com_github_grpc_grpc//:grpc_cpp_plugin" - generate_cc( - name = codegen_grpc_target, - srcs = [proto_target], - plugin = plugin, - well_known_protos = well_known_protos, - generate_mocks = generate_mocks, - **kwargs - ) - grpc_deps = ["@com_github_grpc_grpc//:grpc++_codegen_proto", - "//external:protobuf"] - native.cc_library( - name = name, - srcs = [":" + codegen_grpc_target, ":" + codegen_target], - hdrs = [":" + codegen_grpc_target, ":" + codegen_target], - deps = deps + grpc_deps, - **kwargs - ) - else: - native.cc_library( - name = name, - srcs = [":" + codegen_target], - hdrs = [":" + codegen_target], - deps = deps + ["//external:protobuf"], - **kwargs - ) + proto_deps = ["_" + dep + "_only" for dep in deps if dep.find(":") == -1] + proto_deps += [dep.split(":")[0] + ":" + "_" + dep.split(":")[1] + "_only" for dep in deps if dep.find(":") != -1] + if well_known_protos: + proto_deps += well_known_proto_libs() + + native.proto_library( + name = proto_target, + srcs = srcs, + deps = proto_deps, + **kwargs + ) + + native.cc_proto_library( + name = cc_proto_target, + deps = [":" + proto_target], + **kwargs + ) + extra_deps.append(":" + cc_proto_target) + proto_targets.append(proto_target) + else: + if not srcs: + fail("srcs cannot be empty", "srcs") + proto_targets += srcs + + if not proto_only: + codegen_grpc_target = "_" + name + "_grpc_codegen" + generate_cc( + name = codegen_grpc_target, + srcs = proto_targets, + plugin = "@com_github_grpc_grpc//:grpc_cpp_plugin", + well_known_protos = well_known_protos, + generate_mocks = generate_mocks, + **kwargs + ) + + native.cc_library( + name = name, + srcs = [":" + codegen_grpc_target], + hdrs = [":" + codegen_grpc_target], + deps = deps + + extra_deps + + ["@com_github_grpc_grpc//:grpc++_codegen_proto"], + **kwargs + ) diff --git a/bazel/generate_cc.bzl b/bazel/generate_cc.bzl index 82f5cbad310..29a888f608f 100644 --- a/bazel/generate_cc.bzl +++ b/bazel/generate_cc.bzl @@ -18,12 +18,22 @@ _GRPC_PROTO_MOCK_HEADER_FMT = "{}_mock.grpc.pb.h" _PROTO_HEADER_FMT = "{}.pb.h" _PROTO_SRC_FMT = "{}.pb.cc" -def _strip_package_from_path(label_package, path): +def _strip_package_from_path(label_package, file): + prefix_len = 0 + if not file.is_source and file.path.startswith(file.root.path): + prefix_len = len(file.root.path) + 1 + + path = file.path if len(label_package) == 0: return path - if not path.startswith(label_package + "/"): + if not path.startswith(label_package + "/", prefix_len): fail("'{}' does not lie within '{}'.".format(path, label_package)) - return path[len(label_package + "/"):] + return path[prefix_len + len(label_package + "/"):] + +def _get_srcs_file_path(file): + if not file.is_source and file.path.startswith(file.root.path): + return file.path[len(file.root.path) + 1:] + return file.path def _join_directories(directories): massaged_directories = [directory for directory in directories if len(directory) != 0] @@ -31,7 +41,7 @@ def _join_directories(directories): def generate_cc_impl(ctx): """Implementation of the generate_cc rule.""" - protos = [f for src in ctx.attr.srcs for f in src.proto.direct_sources] + protos = [f for src in ctx.attr.srcs for f in src.proto.check_deps_sources] includes = [ f for src in ctx.attr.srcs @@ -46,14 +56,14 @@ def generate_cc_impl(ctx): if ctx.executable.plugin: outs += [ proto_path_to_generated_filename( - _strip_package_from_path(label_package, proto.path), + _strip_package_from_path(label_package, proto), _GRPC_PROTO_HEADER_FMT, ) for proto in protos ] outs += [ proto_path_to_generated_filename( - _strip_package_from_path(label_package, proto.path), + _strip_package_from_path(label_package, proto), _GRPC_PROTO_SRC_FMT, ) for proto in protos @@ -61,7 +71,7 @@ def generate_cc_impl(ctx): if ctx.attr.generate_mocks: outs += [ proto_path_to_generated_filename( - _strip_package_from_path(label_package, proto.path), + _strip_package_from_path(label_package, proto), _GRPC_PROTO_MOCK_HEADER_FMT, ) for proto in protos @@ -69,14 +79,14 @@ def generate_cc_impl(ctx): else: outs += [ proto_path_to_generated_filename( - _strip_package_from_path(label_package, proto.path), + _strip_package_from_path(label_package, proto), _PROTO_HEADER_FMT, ) for proto in protos ] outs += [ proto_path_to_generated_filename( - _strip_package_from_path(label_package, proto.path), + _strip_package_from_path(label_package, proto), _PROTO_SRC_FMT, ) for proto in protos @@ -102,7 +112,7 @@ def generate_cc_impl(ctx): # Include the output directory so that protoc puts the generated code in the # right directory. arguments += ["--proto_path={0}{1}".format(dir_out, proto_root)] - arguments += [proto.path for proto in protos] + arguments += [_get_srcs_file_path(proto) for proto in protos] # create a list of well known proto files if the argument is non-None well_known_proto_files = [] diff --git a/bazel/protobuf.bzl b/bazel/protobuf.bzl index bddd0d70c7f..f2df7bd87b2 100644 --- a/bazel/protobuf.bzl +++ b/bazel/protobuf.bzl @@ -2,6 +2,22 @@ _PROTO_EXTENSION = ".proto" +def well_known_proto_libs(): + return [ + "@com_google_protobuf//:any_proto", + "@com_google_protobuf//:api_proto", + "@com_google_protobuf//:compiler_plugin_proto", + "@com_google_protobuf//:descriptor_proto", + "@com_google_protobuf//:duration_proto", + "@com_google_protobuf//:empty_proto", + "@com_google_protobuf//:field_mask_proto", + "@com_google_protobuf//:source_context_proto", + "@com_google_protobuf//:struct_proto", + "@com_google_protobuf//:timestamp_proto", + "@com_google_protobuf//:type_proto", + "@com_google_protobuf//:wrappers_proto", + ] + def get_proto_root(workspace_root): """Gets the root protobuf directory. @@ -42,12 +58,16 @@ def proto_path_to_generated_filename(proto_path, fmt_str): def _get_include_directory(include): directory = include.path - if directory.startswith("external"): - external_separator = directory.find("/") + prefix_len = 0 + if not include.is_source and directory.startswith(include.root.path): + prefix_len = len(include.root.path) + 1 + + if directory.startswith("external", prefix_len): + external_separator = directory.find("/", prefix_len) repository_separator = directory.find("/", external_separator + 1) return directory[:repository_separator] else: - return "." + return include.root.path if include.root.path else "." def get_include_protoc_args(includes): """Returns protoc args that imports protos relative to their import root. diff --git a/bazel/python_rules.bzl b/bazel/python_rules.bzl index bf6b4bec8d2..2f3b38af002 100644 --- a/bazel/python_rules.bzl +++ b/bazel/python_rules.bzl @@ -130,21 +130,6 @@ def _generate_py(well_known_protos, **kwargs): else: __generate_py(**kwargs) -_WELL_KNOWN_PROTO_LIBS = [ - "@com_google_protobuf//:any_proto", - "@com_google_protobuf//:api_proto", - "@com_google_protobuf//:compiler_plugin_proto", - "@com_google_protobuf//:descriptor_proto", - "@com_google_protobuf//:duration_proto", - "@com_google_protobuf//:empty_proto", - "@com_google_protobuf//:field_mask_proto", - "@com_google_protobuf//:source_context_proto", - "@com_google_protobuf//:struct_proto", - "@com_google_protobuf//:timestamp_proto", - "@com_google_protobuf//:type_proto", - "@com_google_protobuf//:wrappers_proto", -] - def py_proto_library( name, deps, @@ -167,8 +152,6 @@ def py_proto_library( codegen_target = "_{}_codegen".format(name) codegen_grpc_target = "_{}_grpc_codegen".format(name) - well_known_proto_rules = _WELL_KNOWN_PROTO_LIBS if well_known_protos else [] - _generate_py( name = codegen_target, deps = deps, diff --git a/doc/statuscodes.md b/doc/statuscodes.md index 3d4d87e931a..61e0d820b48 100644 --- a/doc/statuscodes.md +++ b/doc/statuscodes.md @@ -20,7 +20,7 @@ statuses are defined as such: | OUT_OF_RANGE | 11 | The operation was attempted past the valid range. E.g., seeking or reading past end-of-file. Unlike `INVALID_ARGUMENT`, this error indicates a problem that may be fixed if the system state changes. For example, a 32-bit file system will generate `INVALID_ARGUMENT` if asked to read at an offset that is not in the range [0,2^32-1], but it will generate `OUT_OF_RANGE` if asked to read from an offset past the current file size. There is a fair bit of overlap between `FAILED_PRECONDITION` and `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific error) when it applies so that callers who are iterating through a space can easily look for an `OUT_OF_RANGE` error to detect when they are done. | 400 Bad Request | | UNIMPLEMENTED | 12 | The operation is not implemented or is not supported/enabled in this service. | 501 Not Implemented | | INTERNAL | 13 | Internal errors. This means that some invariants expected by the underlying system have been broken. This error code is reserved for serious errors. | 500 Internal Server Error | -| UNAVAILABLE | 14 | The service is currently unavailable. This is most likely a transient condition, which can be corrected by retrying with a backoff. | 503 Service Unavailable | +| UNAVAILABLE | 14 | The service is currently unavailable. This is most likely a transient condition, which can be corrected by retrying with a backoff. Note that it is not always safe to retry non-idempotent operations. | 503 Service Unavailable | | DATA_LOSS | 15 | Unrecoverable data loss or corruption. | 500 Internal Server Error | All RPCs started at a client return a `status` object composed of an integer diff --git a/examples/BUILD b/examples/BUILD index d5fb6903d7c..a9dd94902a4 100644 --- a/examples/BUILD +++ b/examples/BUILD @@ -17,6 +17,7 @@ licenses(["notice"]) # 3-clause BSD package(default_visibility = ["//visibility:public"]) load("//bazel:grpc_build_system.bzl", "grpc_proto_library") +load("//bazel:cc_grpc_library.bzl", "cc_grpc_library") load("//bazel:python_rules.bzl", "py_proto_library") grpc_proto_library( @@ -29,11 +30,25 @@ grpc_proto_library( srcs = ["protos/hellostreamingworld.proto"], ) -grpc_proto_library( - name = "helloworld", +# The following three rules demonstrate the usage of the cc_grpc_library rule in +# in a mode compatible with the native proto_library and cc_proto_library rules. +proto_library( + name = "helloworld_proto", srcs = ["protos/helloworld.proto"], ) +cc_proto_library( + name = "helloworld_cc_proto", + deps = [":helloworld_proto"], +) + +cc_grpc_library( + name = "helloworld_cc_grpc", + srcs = [":helloworld_proto"], + grpc_only = True, + deps = [":helloworld_cc_proto"], +) + grpc_proto_library( name = "route_guide", srcs = ["protos/route_guide.proto"], @@ -59,7 +74,7 @@ cc_binary( srcs = ["cpp/helloworld/greeter_client.cc"], defines = ["BAZEL_BUILD"], deps = [ - ":helloworld", + ":helloworld_cc_grpc", "//:grpc++", ], ) @@ -69,7 +84,7 @@ cc_binary( srcs = ["cpp/helloworld/greeter_async_client.cc"], defines = ["BAZEL_BUILD"], deps = [ - ":helloworld", + ":helloworld_cc_grpc", "//:grpc++", ], ) @@ -79,7 +94,7 @@ cc_binary( srcs = ["cpp/helloworld/greeter_async_client2.cc"], defines = ["BAZEL_BUILD"], deps = [ - ":helloworld", + ":helloworld_cc_grpc", "//:grpc++", ], ) @@ -89,7 +104,7 @@ cc_binary( srcs = ["cpp/helloworld/greeter_server.cc"], defines = ["BAZEL_BUILD"], deps = [ - ":helloworld", + ":helloworld_cc_grpc", "//:grpc++", ], ) @@ -99,7 +114,7 @@ cc_binary( srcs = ["cpp/helloworld/greeter_async_server.cc"], defines = ["BAZEL_BUILD"], deps = [ - ":helloworld", + ":helloworld_cc_grpc", "//:grpc++", ], ) @@ -109,7 +124,7 @@ cc_binary( srcs = ["cpp/metadata/greeter_client.cc"], defines = ["BAZEL_BUILD"], deps = [ - ":helloworld", + ":helloworld_cc_grpc", "//:grpc++", ], ) @@ -119,7 +134,7 @@ cc_binary( srcs = ["cpp/metadata/greeter_server.cc"], defines = ["BAZEL_BUILD"], deps = [ - ":helloworld", + ":helloworld_cc_grpc", "//:grpc++", ], ) @@ -129,7 +144,7 @@ cc_binary( srcs = ["cpp/load_balancing/greeter_client.cc"], defines = ["BAZEL_BUILD"], deps = [ - ":helloworld", + ":helloworld_cc_grpc", "//:grpc++", ], ) @@ -139,7 +154,7 @@ cc_binary( srcs = ["cpp/load_balancing/greeter_server.cc"], defines = ["BAZEL_BUILD"], deps = [ - ":helloworld", + ":helloworld_cc_grpc", "//:grpc++", ], ) @@ -149,7 +164,7 @@ cc_binary( srcs = ["cpp/compression/greeter_client.cc"], defines = ["BAZEL_BUILD"], deps = [ - ":helloworld", + ":helloworld_cc_grpc", "//:grpc++", ], ) @@ -159,15 +174,17 @@ cc_binary( srcs = ["cpp/compression/greeter_server.cc"], defines = ["BAZEL_BUILD"], deps = [ - ":helloworld", + ":helloworld_cc_grpc", "//:grpc++", ], ) cc_binary( name = "keyvaluestore_client", - srcs = ["cpp/keyvaluestore/caching_interceptor.h", - "cpp/keyvaluestore/client.cc"], + srcs = [ + "cpp/keyvaluestore/caching_interceptor.h", + "cpp/keyvaluestore/client.cc", + ], defines = ["BAZEL_BUILD"], deps = [ ":keyvaluestore", diff --git a/include/grpc/impl/codegen/port_platform.h b/include/grpc/impl/codegen/port_platform.h index ff759d2baed..d7294d59d41 100644 --- a/include/grpc/impl/codegen/port_platform.h +++ b/include/grpc/impl/codegen/port_platform.h @@ -77,7 +77,6 @@ #define GPR_WINDOWS 1 #define GPR_WINDOWS_SUBPROCESS 1 #define GPR_WINDOWS_ENV -#define GPR_HAS_ALIGNED_MALLOC 1 #ifdef __MSYS__ #define GPR_GETPID_IN_UNISTD_H 1 #define GPR_MSYS_TMPFILE @@ -174,7 +173,6 @@ #define GPR_POSIX_SYNC 1 #define GPR_POSIX_TIME 1 #define GPR_HAS_PTHREAD_H 1 -#define GPR_HAS_ALIGNED_ALLOC 1 #define GPR_GETPID_IN_UNISTD_H 1 #ifdef _LP64 #define GPR_ARCH_64 1 @@ -240,7 +238,6 @@ #define GPR_POSIX_SUBPROCESS 1 #define GPR_POSIX_SYNC 1 #define GPR_POSIX_TIME 1 -#define GPR_HAS_POSIX_MEMALIGN 1 #define GPR_HAS_PTHREAD_H 1 #define GPR_GETPID_IN_UNISTD_H 1 #ifndef GRPC_CFSTREAM diff --git a/include/grpc/impl/codegen/status.h b/include/grpc/impl/codegen/status.h index 9bc3dc95609..dec3b8f340e 100644 --- a/include/grpc/impl/codegen/status.h +++ b/include/grpc/impl/codegen/status.h @@ -128,7 +128,8 @@ typedef enum { /** The service is currently unavailable. This is a most likely a transient condition and may be corrected by retrying with - a backoff. + a backoff. Note that it is not always safe to retry non-idempotent + operations. WARNING: Although data MIGHT not have been transmitted when this status occurs, there is NOT A GUARANTEE that the server has not seen diff --git a/include/grpc/support/alloc.h b/include/grpc/support/alloc.h index 4d8e007a577..8bd940bec47 100644 --- a/include/grpc/support/alloc.h +++ b/include/grpc/support/alloc.h @@ -32,8 +32,6 @@ typedef struct gpr_allocation_functions { void* (*zalloc_fn)(size_t size); /** if NULL, uses malloc_fn then memset */ void* (*realloc_fn)(void* ptr, size_t size); void (*free_fn)(void* ptr); - void* (*aligned_alloc_fn)(size_t size, size_t alignment); - void (*aligned_free_fn)(void* ptr); } gpr_allocation_functions; /** malloc. diff --git a/include/grpcpp/impl/codegen/status_code_enum.h b/include/grpcpp/impl/codegen/status_code_enum.h index 09943f10de8..bdd7ead6add 100644 --- a/include/grpcpp/impl/codegen/status_code_enum.h +++ b/include/grpcpp/impl/codegen/status_code_enum.h @@ -119,7 +119,8 @@ enum StatusCode { INTERNAL = 13, /// The service is currently unavailable. This is a most likely a transient - /// condition and may be corrected by retrying with a backoff. + /// condition and may be corrected by retrying with a backoff. Note that it is + /// not always safe to retry non-idempotent operations. /// /// \warning Although data MIGHT not have been transmitted when this /// status occurs, there is NOT A GUARANTEE that the server has not seen diff --git a/src/compiler/cpp_plugin.cc b/src/compiler/cpp_plugin.cc index 3c09b6feb24..2de2745445f 100644 --- a/src/compiler/cpp_plugin.cc +++ b/src/compiler/cpp_plugin.cc @@ -18,137 +18,7 @@ // Generates cpp gRPC service interface out of Protobuf IDL. // - -#include -#include - -#include "src/compiler/config.h" - -#include "src/compiler/cpp_generator.h" -#include "src/compiler/generator_helpers.h" -#include "src/compiler/protobuf_plugin.h" - -class CppGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator { - public: - CppGrpcGenerator() {} - virtual ~CppGrpcGenerator() {} - - virtual bool Generate(const grpc::protobuf::FileDescriptor* file, - const grpc::string& parameter, - grpc::protobuf::compiler::GeneratorContext* context, - grpc::string* error) const { - if (file->options().cc_generic_services()) { - *error = - "cpp grpc proto compiler plugin does not work with generic " - "services. To generate cpp grpc APIs, please set \"" - "cc_generic_service = false\"."; - return false; - } - - grpc_cpp_generator::Parameters generator_parameters; - generator_parameters.use_system_headers = true; - generator_parameters.generate_mock_code = false; - generator_parameters.include_import_headers = false; - - ProtoBufFile pbfile(file); - - if (!parameter.empty()) { - std::vector parameters_list = - grpc_generator::tokenize(parameter, ","); - for (auto parameter_string = parameters_list.begin(); - parameter_string != parameters_list.end(); parameter_string++) { - std::vector param = - grpc_generator::tokenize(*parameter_string, "="); - if (param[0] == "services_namespace") { - generator_parameters.services_namespace = param[1]; - } else if (param[0] == "use_system_headers") { - if (param[1] == "true") { - generator_parameters.use_system_headers = true; - } else if (param[1] == "false") { - generator_parameters.use_system_headers = false; - } else { - *error = grpc::string("Invalid parameter: ") + *parameter_string; - return false; - } - } else if (param[0] == "grpc_search_path") { - generator_parameters.grpc_search_path = param[1]; - } else if (param[0] == "generate_mock_code") { - if (param[1] == "true") { - generator_parameters.generate_mock_code = true; - } else if (param[1] != "false") { - *error = grpc::string("Invalid parameter: ") + *parameter_string; - return false; - } - } else if (param[0] == "gmock_search_path") { - generator_parameters.gmock_search_path = param[1]; - } else if (param[0] == "additional_header_includes") { - generator_parameters.additional_header_includes = - grpc_generator::tokenize(param[1], ":"); - } else if (param[0] == "message_header_extension") { - generator_parameters.message_header_extension = param[1]; - } else if (param[0] == "include_import_headers") { - if (param[1] == "true") { - generator_parameters.include_import_headers = true; - } else if (param[1] != "false") { - *error = grpc::string("Invalid parameter: ") + *parameter_string; - return false; - } - } else { - *error = grpc::string("Unknown parameter: ") + *parameter_string; - return false; - } - } - } - - grpc::string file_name = grpc_generator::StripProto(file->name()); - - grpc::string header_code = - grpc_cpp_generator::GetHeaderPrologue(&pbfile, generator_parameters) + - grpc_cpp_generator::GetHeaderIncludes(&pbfile, generator_parameters) + - grpc_cpp_generator::GetHeaderServices(&pbfile, generator_parameters) + - grpc_cpp_generator::GetHeaderEpilogue(&pbfile, generator_parameters); - std::unique_ptr header_output( - context->Open(file_name + ".grpc.pb.h")); - grpc::protobuf::io::CodedOutputStream header_coded_out(header_output.get()); - header_coded_out.WriteRaw(header_code.data(), header_code.size()); - - grpc::string source_code = - grpc_cpp_generator::GetSourcePrologue(&pbfile, generator_parameters) + - grpc_cpp_generator::GetSourceIncludes(&pbfile, generator_parameters) + - grpc_cpp_generator::GetSourceServices(&pbfile, generator_parameters) + - grpc_cpp_generator::GetSourceEpilogue(&pbfile, generator_parameters); - std::unique_ptr source_output( - context->Open(file_name + ".grpc.pb.cc")); - grpc::protobuf::io::CodedOutputStream source_coded_out(source_output.get()); - source_coded_out.WriteRaw(source_code.data(), source_code.size()); - - if (!generator_parameters.generate_mock_code) { - return true; - } - grpc::string mock_code = - grpc_cpp_generator::GetMockPrologue(&pbfile, generator_parameters) + - grpc_cpp_generator::GetMockIncludes(&pbfile, generator_parameters) + - grpc_cpp_generator::GetMockServices(&pbfile, generator_parameters) + - grpc_cpp_generator::GetMockEpilogue(&pbfile, generator_parameters); - std::unique_ptr mock_output( - context->Open(file_name + "_mock.grpc.pb.h")); - grpc::protobuf::io::CodedOutputStream mock_coded_out(mock_output.get()); - mock_coded_out.WriteRaw(mock_code.data(), mock_code.size()); - - return true; - } - - private: - // Insert the given code into the given file at the given insertion point. - void Insert(grpc::protobuf::compiler::GeneratorContext* context, - const grpc::string& filename, const grpc::string& insertion_point, - const grpc::string& code) const { - std::unique_ptr output( - context->OpenForInsert(filename, insertion_point)); - grpc::protobuf::io::CodedOutputStream coded_out(output.get()); - coded_out.WriteRaw(code.data(), code.size()); - } -}; +#include "src/compiler/cpp_plugin.h" int main(int argc, char* argv[]) { CppGrpcGenerator generator; diff --git a/src/compiler/cpp_plugin.h b/src/compiler/cpp_plugin.h new file mode 100644 index 00000000000..1cdf0b3c196 --- /dev/null +++ b/src/compiler/cpp_plugin.h @@ -0,0 +1,154 @@ +/* + * + * 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. + * + */ + +#ifndef GRPC_INTERNAL_COMPILER_CPP_PLUGIN_H +#define GRPC_INTERNAL_COMPILER_CPP_PLUGIN_H + +#include +#include + +#include "src/compiler/config.h" + +#include "src/compiler/cpp_generator.h" +#include "src/compiler/generator_helpers.h" +#include "src/compiler/protobuf_plugin.h" + +// Cpp Generator for Protobug IDL +class CppGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator { + public: + CppGrpcGenerator() {} + virtual ~CppGrpcGenerator() {} + + virtual bool Generate(const grpc::protobuf::FileDescriptor* file, + const grpc::string& parameter, + grpc::protobuf::compiler::GeneratorContext* context, + grpc::string* error) const { + if (file->options().cc_generic_services()) { + *error = + "cpp grpc proto compiler plugin does not work with generic " + "services. To generate cpp grpc APIs, please set \"" + "cc_generic_service = false\"."; + return false; + } + + grpc_cpp_generator::Parameters generator_parameters; + generator_parameters.use_system_headers = true; + generator_parameters.generate_mock_code = false; + generator_parameters.include_import_headers = false; + + ProtoBufFile pbfile(file); + + if (!parameter.empty()) { + std::vector parameters_list = + grpc_generator::tokenize(parameter, ","); + for (auto parameter_string = parameters_list.begin(); + parameter_string != parameters_list.end(); parameter_string++) { + std::vector param = + grpc_generator::tokenize(*parameter_string, "="); + if (param[0] == "services_namespace") { + generator_parameters.services_namespace = param[1]; + } else if (param[0] == "use_system_headers") { + if (param[1] == "true") { + generator_parameters.use_system_headers = true; + } else if (param[1] == "false") { + generator_parameters.use_system_headers = false; + } else { + *error = grpc::string("Invalid parameter: ") + *parameter_string; + return false; + } + } else if (param[0] == "grpc_search_path") { + generator_parameters.grpc_search_path = param[1]; + } else if (param[0] == "generate_mock_code") { + if (param[1] == "true") { + generator_parameters.generate_mock_code = true; + } else if (param[1] != "false") { + *error = grpc::string("Invalid parameter: ") + *parameter_string; + return false; + } + } else if (param[0] == "gmock_search_path") { + generator_parameters.gmock_search_path = param[1]; + } else if (param[0] == "additional_header_includes") { + generator_parameters.additional_header_includes = + grpc_generator::tokenize(param[1], ":"); + } else if (param[0] == "message_header_extension") { + generator_parameters.message_header_extension = param[1]; + } else if (param[0] == "include_import_headers") { + if (param[1] == "true") { + generator_parameters.include_import_headers = true; + } else if (param[1] != "false") { + *error = grpc::string("Invalid parameter: ") + *parameter_string; + return false; + } + } else { + *error = grpc::string("Unknown parameter: ") + *parameter_string; + return false; + } + } + } + + grpc::string file_name = grpc_generator::StripProto(file->name()); + + grpc::string header_code = + grpc_cpp_generator::GetHeaderPrologue(&pbfile, generator_parameters) + + grpc_cpp_generator::GetHeaderIncludes(&pbfile, generator_parameters) + + grpc_cpp_generator::GetHeaderServices(&pbfile, generator_parameters) + + grpc_cpp_generator::GetHeaderEpilogue(&pbfile, generator_parameters); + std::unique_ptr header_output( + context->Open(file_name + ".grpc.pb.h")); + grpc::protobuf::io::CodedOutputStream header_coded_out(header_output.get()); + header_coded_out.WriteRaw(header_code.data(), header_code.size()); + + grpc::string source_code = + grpc_cpp_generator::GetSourcePrologue(&pbfile, generator_parameters) + + grpc_cpp_generator::GetSourceIncludes(&pbfile, generator_parameters) + + grpc_cpp_generator::GetSourceServices(&pbfile, generator_parameters) + + grpc_cpp_generator::GetSourceEpilogue(&pbfile, generator_parameters); + std::unique_ptr source_output( + context->Open(file_name + ".grpc.pb.cc")); + grpc::protobuf::io::CodedOutputStream source_coded_out(source_output.get()); + source_coded_out.WriteRaw(source_code.data(), source_code.size()); + + if (!generator_parameters.generate_mock_code) { + return true; + } + grpc::string mock_code = + grpc_cpp_generator::GetMockPrologue(&pbfile, generator_parameters) + + grpc_cpp_generator::GetMockIncludes(&pbfile, generator_parameters) + + grpc_cpp_generator::GetMockServices(&pbfile, generator_parameters) + + grpc_cpp_generator::GetMockEpilogue(&pbfile, generator_parameters); + std::unique_ptr mock_output( + context->Open(file_name + "_mock.grpc.pb.h")); + grpc::protobuf::io::CodedOutputStream mock_coded_out(mock_output.get()); + mock_coded_out.WriteRaw(mock_code.data(), mock_code.size()); + + return true; + } + + private: + // Insert the given code into the given file at the given insertion point. + void Insert(grpc::protobuf::compiler::GeneratorContext* context, + const grpc::string& filename, const grpc::string& insertion_point, + const grpc::string& code) const { + std::unique_ptr output( + context->OpenForInsert(filename, insertion_point)); + grpc::protobuf::io::CodedOutputStream coded_out(output.get()); + coded_out.WriteRaw(code.data(), code.size()); + } +}; + +#endif // GRPC_INTERNAL_COMPILER_CPP_PLUGIN_H diff --git a/src/core/ext/filters/client_channel/subchannel.cc b/src/core/ext/filters/client_channel/subchannel.cc index 873db8ef57c..a284e692b09 100644 --- a/src/core/ext/filters/client_channel/subchannel.cc +++ b/src/core/ext/filters/client_channel/subchannel.cc @@ -66,13 +66,12 @@ #define GRPC_SUBCHANNEL_RECONNECT_JITTER 0.2 // Conversion between subchannel call and call stack. -#define SUBCHANNEL_CALL_TO_CALL_STACK(call) \ - (grpc_call_stack*)((char*)(call) + GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE( \ - sizeof(SubchannelCall))) -#define CALL_STACK_TO_SUBCHANNEL_CALL(callstack) \ - (SubchannelCall*)(((char*)(call_stack)) - \ - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE( \ - sizeof(SubchannelCall))) +#define SUBCHANNEL_CALL_TO_CALL_STACK(call) \ + (grpc_call_stack*)((char*)(call) + \ + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(SubchannelCall))) +#define CALL_STACK_TO_SUBCHANNEL_CALL(callstack) \ + (SubchannelCall*)(((char*)(call_stack)) - \ + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(SubchannelCall))) namespace grpc_core { @@ -152,10 +151,10 @@ RefCountedPtr ConnectedSubchannel::CreateCall( size_t ConnectedSubchannel::GetInitialCallSizeEstimate( size_t parent_data_size) const { size_t allocation_size = - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(sizeof(SubchannelCall)); + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(SubchannelCall)); if (parent_data_size > 0) { allocation_size += - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(channel_stack_->call_stack_size) + + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(channel_stack_->call_stack_size) + parent_data_size; } else { allocation_size += channel_stack_->call_stack_size; @@ -179,9 +178,8 @@ void SubchannelCall::StartTransportStreamOpBatch( void* SubchannelCall::GetParentData() { grpc_channel_stack* chanstk = connected_subchannel_->channel_stack(); - return (char*)this + - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(sizeof(SubchannelCall)) + - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(chanstk->call_stack_size); + return (char*)this + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(SubchannelCall)) + + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(chanstk->call_stack_size); } grpc_call_stack* SubchannelCall::GetCallStack() { diff --git a/src/core/ext/transport/chttp2/transport/chttp2_transport.cc b/src/core/ext/transport/chttp2/transport/chttp2_transport.cc index d4188775722..5ced5829cc1 100644 --- a/src/core/ext/transport/chttp2/transport/chttp2_transport.cc +++ b/src/core/ext/transport/chttp2/transport/chttp2_transport.cc @@ -679,8 +679,6 @@ grpc_chttp2_stream::grpc_chttp2_stream(grpc_chttp2_transport* t, grpc_slice_buffer_init(&frame_storage); grpc_slice_buffer_init(&unprocessed_incoming_frames_buffer); grpc_slice_buffer_init(&flow_controlled_buffer); - grpc_slice_buffer_init(&compressed_data_buffer); - grpc_slice_buffer_init(&decompressed_data_buffer); GRPC_CLOSURE_INIT(&complete_fetch_locked, ::complete_fetch_locked, this, grpc_combiner_scheduler(t->combiner)); @@ -704,8 +702,13 @@ grpc_chttp2_stream::~grpc_chttp2_stream() { grpc_slice_buffer_destroy_internal(&unprocessed_incoming_frames_buffer); grpc_slice_buffer_destroy_internal(&frame_storage); - grpc_slice_buffer_destroy_internal(&compressed_data_buffer); - grpc_slice_buffer_destroy_internal(&decompressed_data_buffer); + if (stream_compression_method != GRPC_STREAM_COMPRESSION_IDENTITY_COMPRESS) { + grpc_slice_buffer_destroy_internal(&compressed_data_buffer); + } + if (stream_decompression_method != + GRPC_STREAM_COMPRESSION_IDENTITY_DECOMPRESS) { + grpc_slice_buffer_destroy_internal(&decompressed_data_buffer); + } grpc_chttp2_list_remove_stalled_by_transport(t, this); grpc_chttp2_list_remove_stalled_by_stream(t, this); @@ -759,12 +762,15 @@ static void destroy_stream(grpc_transport* gt, grpc_stream* gs, GPR_TIMER_SCOPE("destroy_stream", 0); grpc_chttp2_transport* t = reinterpret_cast(gt); grpc_chttp2_stream* s = reinterpret_cast(gs); - - if (s->stream_compression_ctx != nullptr) { + if (s->stream_compression_method != + GRPC_STREAM_COMPRESSION_IDENTITY_COMPRESS && + s->stream_compression_ctx != nullptr) { grpc_stream_compression_context_destroy(s->stream_compression_ctx); s->stream_compression_ctx = nullptr; } - if (s->stream_decompression_ctx != nullptr) { + if (s->stream_decompression_method != + GRPC_STREAM_COMPRESSION_IDENTITY_DECOMPRESS && + s->stream_decompression_ctx != nullptr) { grpc_stream_compression_context_destroy(s->stream_decompression_ctx); s->stream_decompression_ctx = nullptr; } @@ -1442,7 +1448,12 @@ static void perform_stream_op_locked(void* stream_op, true, &s->stream_compression_method) == 0) { s->stream_compression_method = GRPC_STREAM_COMPRESSION_IDENTITY_COMPRESS; } - + if (s->stream_compression_method != + GRPC_STREAM_COMPRESSION_IDENTITY_COMPRESS) { + s->uncompressed_data_size = 0; + s->stream_compression_ctx = nullptr; + grpc_slice_buffer_init(&s->compressed_data_buffer); + } s->send_initial_metadata_finished = add_closure_barrier(on_complete); s->send_initial_metadata = op_payload->send_initial_metadata.send_initial_metadata; @@ -1998,27 +2009,39 @@ void grpc_chttp2_maybe_complete_recv_trailing_metadata(grpc_chttp2_transport* t, !s->seen_error && s->recv_trailing_metadata_finished != nullptr) { /* Maybe some SYNC_FLUSH data is left in frame_storage. Consume them and * maybe decompress the next 5 bytes in the stream. */ - bool end_of_context; - if (!s->stream_decompression_ctx) { - s->stream_decompression_ctx = grpc_stream_compression_context_create( - s->stream_decompression_method); - } - if (!grpc_stream_decompress( - s->stream_decompression_ctx, &s->frame_storage, - &s->unprocessed_incoming_frames_buffer, nullptr, - GRPC_HEADER_SIZE_IN_BYTES, &end_of_context)) { - grpc_slice_buffer_reset_and_unref_internal(&s->frame_storage); - grpc_slice_buffer_reset_and_unref_internal( - &s->unprocessed_incoming_frames_buffer); - s->seen_error = true; - } else { + if (s->stream_decompression_method == + GRPC_STREAM_COMPRESSION_IDENTITY_DECOMPRESS) { + grpc_slice_buffer_move_first(&s->frame_storage, + GRPC_HEADER_SIZE_IN_BYTES, + &s->unprocessed_incoming_frames_buffer); if (s->unprocessed_incoming_frames_buffer.length > 0) { s->unprocessed_incoming_frames_decompressed = true; pending_data = true; } - if (end_of_context) { - grpc_stream_compression_context_destroy(s->stream_decompression_ctx); - s->stream_decompression_ctx = nullptr; + } else { + bool end_of_context; + if (!s->stream_decompression_ctx) { + s->stream_decompression_ctx = grpc_stream_compression_context_create( + s->stream_decompression_method); + } + if (!grpc_stream_decompress( + s->stream_decompression_ctx, &s->frame_storage, + &s->unprocessed_incoming_frames_buffer, nullptr, + GRPC_HEADER_SIZE_IN_BYTES, &end_of_context)) { + grpc_slice_buffer_reset_and_unref_internal(&s->frame_storage); + grpc_slice_buffer_reset_and_unref_internal( + &s->unprocessed_incoming_frames_buffer); + s->seen_error = true; + } else { + if (s->unprocessed_incoming_frames_buffer.length > 0) { + s->unprocessed_incoming_frames_decompressed = true; + pending_data = true; + } + if (end_of_context) { + grpc_stream_compression_context_destroy( + s->stream_decompression_ctx); + s->stream_decompression_ctx = nullptr; + } } } } @@ -2941,6 +2964,8 @@ bool Chttp2IncomingByteStream::Next(size_t max_size_hint, } void Chttp2IncomingByteStream::MaybeCreateStreamDecompressionCtx() { + GPR_DEBUG_ASSERT(stream_->stream_decompression_method != + GRPC_STREAM_COMPRESSION_IDENTITY_DECOMPRESS); if (!stream_->stream_decompression_ctx) { stream_->stream_decompression_ctx = grpc_stream_compression_context_create( stream_->stream_decompression_method); @@ -2951,7 +2976,9 @@ grpc_error* Chttp2IncomingByteStream::Pull(grpc_slice* slice) { GPR_TIMER_SCOPE("incoming_byte_stream_pull", 0); grpc_error* error; if (stream_->unprocessed_incoming_frames_buffer.length > 0) { - if (!stream_->unprocessed_incoming_frames_decompressed) { + if (!stream_->unprocessed_incoming_frames_decompressed && + stream_->stream_decompression_method != + GRPC_STREAM_COMPRESSION_IDENTITY_DECOMPRESS) { bool end_of_context; MaybeCreateStreamDecompressionCtx(); if (!grpc_stream_decompress(stream_->stream_decompression_ctx, diff --git a/src/core/ext/transport/chttp2/transport/hpack_parser.cc b/src/core/ext/transport/chttp2/transport/hpack_parser.cc index 5bcdb4e2326..7a37d37fd10 100644 --- a/src/core/ext/transport/chttp2/transport/hpack_parser.cc +++ b/src/core/ext/transport/chttp2/transport/hpack_parser.cc @@ -1616,6 +1616,12 @@ static void parse_stream_compression_md(grpc_chttp2_transport* t, s->stream_decompression_method = GRPC_STREAM_COMPRESSION_IDENTITY_DECOMPRESS; } + + if (s->stream_decompression_method != + GRPC_STREAM_COMPRESSION_IDENTITY_DECOMPRESS) { + s->stream_decompression_ctx = nullptr; + grpc_slice_buffer_init(&s->decompressed_data_buffer); + } } grpc_error* grpc_chttp2_header_parser_parse(void* hpack_parser, diff --git a/src/core/ext/transport/chttp2/transport/internal.h b/src/core/ext/transport/chttp2/transport/internal.h index 00b1fe18b28..4cbe02ac463 100644 --- a/src/core/ext/transport/chttp2/transport/internal.h +++ b/src/core/ext/transport/chttp2/transport/internal.h @@ -238,7 +238,7 @@ class Chttp2IncomingByteStream : public ByteStream { // switch to std::shared_ptr<>. void Ref() { refs_.Ref(); } void Unref() { - if (refs_.Unref()) { + if (GPR_UNLIKELY(refs_.Unref())) { grpc_core::Delete(this); } } @@ -583,10 +583,6 @@ struct grpc_chttp2_stream { grpc_slice_buffer frame_storage; /* protected by t combiner */ - /* Accessed only by transport thread when stream->pending_byte_stream == false - * Accessed only by application thread when stream->pending_byte_stream == - * true */ - grpc_slice_buffer unprocessed_incoming_frames_buffer; grpc_closure* on_next = nullptr; /* protected by t combiner */ bool pending_byte_stream = false; /* protected by t combiner */ // cached length of buffer to be used by the transport thread in cases where @@ -594,6 +590,10 @@ struct grpc_chttp2_stream { // application threads are allowed to modify // unprocessed_incoming_frames_buffer size_t unprocessed_incoming_frames_buffer_cached_length = 0; + /* Accessed only by transport thread when stream->pending_byte_stream == false + * Accessed only by application thread when stream->pending_byte_stream == + * true */ + grpc_slice_buffer unprocessed_incoming_frames_buffer; grpc_closure reset_byte_stream; grpc_error* byte_stream_error = GRPC_ERROR_NONE; /* protected by t combiner */ bool received_last_frame = false; /* protected by t combiner */ @@ -634,18 +634,7 @@ struct grpc_chttp2_stream { /* Stream decompression method to be used. */ grpc_stream_compression_method stream_decompression_method = GRPC_STREAM_COMPRESSION_IDENTITY_DECOMPRESS; - /** Stream compression decompress context */ - grpc_stream_compression_context* stream_decompression_ctx = nullptr; - /** Stream compression compress context */ - grpc_stream_compression_context* stream_compression_ctx = nullptr; - /** Buffer storing data that is compressed but not sent */ - grpc_slice_buffer compressed_data_buffer; - /** Amount of uncompressed bytes sent out when compressed_data_buffer is - * emptied */ - size_t uncompressed_data_size = 0; - /** Temporary buffer storing decompressed data */ - grpc_slice_buffer decompressed_data_buffer; /** Whether bytes stored in unprocessed_incoming_byte_stream is decompressed */ bool unprocessed_incoming_frames_decompressed = false; @@ -655,6 +644,22 @@ struct grpc_chttp2_stream { size_t decompressed_header_bytes = 0; /** Byte counter for number of bytes written */ size_t byte_counter = 0; + + /** Amount of uncompressed bytes sent out when compressed_data_buffer is + * emptied */ + size_t uncompressed_data_size; + /** Stream compression compress context */ + grpc_stream_compression_context* stream_compression_ctx; + /** Buffer storing data that is compressed but not sent */ + grpc_slice_buffer compressed_data_buffer; + + /** Stream compression decompress context */ + grpc_stream_compression_context* stream_decompression_ctx; + /** Temporary buffer storing decompressed data. + * Initialized, used, and destroyed only when stream uses (non-identity) + * compression. + */ + grpc_slice_buffer decompressed_data_buffer; }; /** Transport writing call flow: diff --git a/src/core/ext/transport/chttp2/transport/writing.cc b/src/core/ext/transport/chttp2/transport/writing.cc index 3d1db0aa144..90015bd97ec 100644 --- a/src/core/ext/transport/chttp2/transport/writing.cc +++ b/src/core/ext/transport/chttp2/transport/writing.cc @@ -25,6 +25,7 @@ #include +#include "src/core/lib/compression/stream_compression.h" #include "src/core/lib/debug/stats.h" #include "src/core/lib/profiling/timers.h" #include "src/core/lib/slice/slice_internal.h" @@ -150,7 +151,11 @@ static void report_stall(grpc_chttp2_transport* t, grpc_chttp2_stream* s, ":flowed=%" PRId64 ":peer_initwin=%d:t_win=%" PRId64 ":s_win=%d:s_delta=%" PRId64 "]", t->peer_string, t, s->id, staller, s->flow_controlled_buffer.length, - s->compressed_data_buffer.length, s->flow_controlled_bytes_flowed, + s->stream_compression_method == + GRPC_STREAM_COMPRESSION_IDENTITY_COMPRESS + ? 0 + : s->compressed_data_buffer.length, + s->flow_controlled_bytes_flowed, t->settings[GRPC_ACKED_SETTINGS] [GRPC_CHTTP2_SETTINGS_INITIAL_WINDOW_SIZE], t->flow_control->remote_window(), @@ -325,7 +330,23 @@ class DataSendContext { bool AnyOutgoing() const { return max_outgoing() > 0; } + void FlushUncompressedBytes() { + uint32_t send_bytes = static_cast GPR_MIN( + max_outgoing(), s_->flow_controlled_buffer.length); + is_last_frame_ = send_bytes == s_->flow_controlled_buffer.length && + s_->fetching_send_message == nullptr && + s_->send_trailing_metadata != nullptr && + grpc_metadata_batch_is_empty(s_->send_trailing_metadata); + grpc_chttp2_encode_data(s_->id, &s_->flow_controlled_buffer, send_bytes, + is_last_frame_, &s_->stats.outgoing, &t_->outbuf); + s_->flow_control->SentData(send_bytes); + s_->sending_bytes += send_bytes; + } + void FlushCompressedBytes() { + GPR_DEBUG_ASSERT(s_->stream_compression_method != + GRPC_STREAM_COMPRESSION_IDENTITY_COMPRESS); + uint32_t send_bytes = static_cast GPR_MIN( max_outgoing(), s_->compressed_data_buffer.length); bool is_last_data_frame = @@ -360,6 +381,9 @@ class DataSendContext { } void CompressMoreBytes() { + GPR_DEBUG_ASSERT(s_->stream_compression_method != + GRPC_STREAM_COMPRESSION_IDENTITY_COMPRESS); + if (s_->stream_compression_ctx == nullptr) { s_->stream_compression_ctx = grpc_stream_compression_context_create(s_->stream_compression_method); @@ -417,7 +441,7 @@ class StreamWriteContext { // https://github.com/grpc/proposal/blob/master/A6-client-retries.md#when-retries-are-valid if (!t_->is_client && s_->fetching_send_message == nullptr && s_->flow_controlled_buffer.length == 0 && - s_->compressed_data_buffer.length == 0 && + compressed_data_buffer_len() == 0 && s_->send_trailing_metadata != nullptr && is_default_initial_metadata(s_->send_initial_metadata)) { ConvertInitialMetadataToTrailingMetadata(); @@ -446,6 +470,13 @@ class StreamWriteContext { "send_initial_metadata_finished"); } + bool compressed_data_buffer_len() { + return s_->stream_compression_method == + GRPC_STREAM_COMPRESSION_IDENTITY_COMPRESS + ? 0 + : s_->compressed_data_buffer.length; + } + void FlushWindowUpdates() { /* send any window updates */ const uint32_t stream_announce = s_->flow_control->MaybeSendUpdate(); @@ -462,7 +493,7 @@ class StreamWriteContext { if (!s_->sent_initial_metadata) return; if (s_->flow_controlled_buffer.length == 0 && - s_->compressed_data_buffer.length == 0) { + compressed_data_buffer_len() == 0) { return; // early out: nothing to do } @@ -479,13 +510,21 @@ class StreamWriteContext { return; // early out: nothing to do } - while ((s_->flow_controlled_buffer.length > 0 || - s_->compressed_data_buffer.length > 0) && - data_send_context.max_outgoing() > 0) { - if (s_->compressed_data_buffer.length > 0) { - data_send_context.FlushCompressedBytes(); - } else { - data_send_context.CompressMoreBytes(); + if (s_->stream_compression_method == + GRPC_STREAM_COMPRESSION_IDENTITY_COMPRESS) { + while (s_->flow_controlled_buffer.length > 0 && + data_send_context.max_outgoing() > 0) { + data_send_context.FlushUncompressedBytes(); + } + } else { + while ((s_->flow_controlled_buffer.length > 0 || + s_->compressed_data_buffer.length > 0) && + data_send_context.max_outgoing() > 0) { + if (s_->compressed_data_buffer.length > 0) { + data_send_context.FlushCompressedBytes(); + } else { + data_send_context.CompressMoreBytes(); + } } } write_context_->ResetPingClock(); @@ -495,7 +534,7 @@ class StreamWriteContext { data_send_context.CallCallbacks(); stream_became_writable_ = true; if (s_->flow_controlled_buffer.length > 0 || - s_->compressed_data_buffer.length > 0) { + compressed_data_buffer_len() > 0) { GRPC_CHTTP2_STREAM_REF(s_, "chttp2_writing:fork"); grpc_chttp2_list_add_writable_stream(t_, s_); } @@ -508,7 +547,7 @@ class StreamWriteContext { if (s_->send_trailing_metadata == nullptr) return; if (s_->fetching_send_message != nullptr) return; if (s_->flow_controlled_buffer.length != 0) return; - if (s_->compressed_data_buffer.length != 0) return; + if (compressed_data_buffer_len() != 0) return; GRPC_CHTTP2_IF_TRACING(gpr_log(GPR_INFO, "sending trailing_metadata")); if (grpc_metadata_batch_is_empty(s_->send_trailing_metadata)) { diff --git a/src/core/lib/channel/channel_stack.cc b/src/core/lib/channel/channel_stack.cc index 7dfabbb0a1c..df956c7176c 100644 --- a/src/core/lib/channel/channel_stack.cc +++ b/src/core/lib/channel/channel_stack.cc @@ -47,9 +47,9 @@ grpc_core::TraceFlag grpc_trace_channel(false, "channel"); size_t grpc_channel_stack_size(const grpc_channel_filter** filters, size_t filter_count) { /* always need the header, and size for the channel elements */ - size_t size = GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(sizeof(grpc_channel_stack)) + - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE( - filter_count * sizeof(grpc_channel_element)); + size_t size = GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_channel_stack)) + + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(filter_count * + sizeof(grpc_channel_element)); size_t i; GPR_ASSERT((GPR_MAX_ALIGNMENT & (GPR_MAX_ALIGNMENT - 1)) == 0 && @@ -57,18 +57,18 @@ size_t grpc_channel_stack_size(const grpc_channel_filter** filters, /* add the size for each filter */ for (i = 0; i < filter_count; i++) { - size += GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(filters[i]->sizeof_channel_data); + size += GPR_ROUND_UP_TO_ALIGNMENT_SIZE(filters[i]->sizeof_channel_data); } return size; } -#define CHANNEL_ELEMS_FROM_STACK(stk) \ - ((grpc_channel_element*)((char*)(stk) + GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE( \ +#define CHANNEL_ELEMS_FROM_STACK(stk) \ + ((grpc_channel_element*)((char*)(stk) + GPR_ROUND_UP_TO_ALIGNMENT_SIZE( \ sizeof(grpc_channel_stack)))) -#define CALL_ELEMS_FROM_STACK(stk) \ - ((grpc_call_element*)((char*)(stk) + GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE( \ +#define CALL_ELEMS_FROM_STACK(stk) \ + ((grpc_call_element*)((char*)(stk) + GPR_ROUND_UP_TO_ALIGNMENT_SIZE( \ sizeof(grpc_call_stack)))) grpc_channel_element* grpc_channel_stack_element( @@ -92,9 +92,8 @@ grpc_error* grpc_channel_stack_init( const grpc_channel_args* channel_args, grpc_transport* optional_transport, const char* name, grpc_channel_stack* stack) { size_t call_size = - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(sizeof(grpc_call_stack)) + - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(filter_count * - sizeof(grpc_call_element)); + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_call_stack)) + + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(filter_count * sizeof(grpc_call_element)); grpc_channel_element* elems; grpc_channel_element_args args; char* user_data; @@ -105,8 +104,8 @@ grpc_error* grpc_channel_stack_init( name); elems = CHANNEL_ELEMS_FROM_STACK(stack); user_data = (reinterpret_cast(elems)) + - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(filter_count * - sizeof(grpc_channel_element)); + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(filter_count * + sizeof(grpc_channel_element)); /* init per-filter data */ grpc_error* first_error = GRPC_ERROR_NONE; @@ -127,9 +126,8 @@ grpc_error* grpc_channel_stack_init( } } user_data += - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(filters[i]->sizeof_channel_data); - call_size += - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(filters[i]->sizeof_call_data); + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(filters[i]->sizeof_channel_data); + call_size += GPR_ROUND_UP_TO_ALIGNMENT_SIZE(filters[i]->sizeof_call_data); } GPR_ASSERT(user_data > (char*)stack); @@ -164,9 +162,8 @@ grpc_error* grpc_call_stack_init(grpc_channel_stack* channel_stack, GRPC_STREAM_REF_INIT(&elem_args->call_stack->refcount, initial_refs, destroy, destroy_arg, "CALL_STACK"); call_elems = CALL_ELEMS_FROM_STACK(elem_args->call_stack); - user_data = - (reinterpret_cast(call_elems)) + - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(count * sizeof(grpc_call_element)); + user_data = (reinterpret_cast(call_elems)) + + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(count * sizeof(grpc_call_element)); /* init per-filter data */ grpc_error* first_error = GRPC_ERROR_NONE; @@ -174,8 +171,8 @@ grpc_error* grpc_call_stack_init(grpc_channel_stack* channel_stack, call_elems[i].filter = channel_elems[i].filter; call_elems[i].channel_data = channel_elems[i].channel_data; call_elems[i].call_data = user_data; - user_data += GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE( - call_elems[i].filter->sizeof_call_data); + user_data += + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(call_elems[i].filter->sizeof_call_data); } for (size_t i = 0; i < count; i++) { grpc_error* error = @@ -245,11 +242,11 @@ grpc_channel_stack* grpc_channel_stack_from_top_element( grpc_channel_element* elem) { return reinterpret_cast( reinterpret_cast(elem) - - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(sizeof(grpc_channel_stack))); + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_channel_stack))); } grpc_call_stack* grpc_call_stack_from_top_element(grpc_call_element* elem) { return reinterpret_cast( reinterpret_cast(elem) - - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(sizeof(grpc_call_stack))); + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_call_stack))); } diff --git a/src/core/lib/gpr/alloc.cc b/src/core/lib/gpr/alloc.cc index f79e645971a..611e4cceee4 100644 --- a/src/core/lib/gpr/alloc.cc +++ b/src/core/lib/gpr/alloc.cc @@ -23,7 +23,6 @@ #include #include #include -#include "src/core/lib/gpr/alloc.h" #include "src/core/lib/profiling/timers.h" static void* zalloc_with_calloc(size_t sz) { return calloc(sz, 1); } @@ -34,56 +33,8 @@ static void* zalloc_with_gpr_malloc(size_t sz) { return p; } -#ifndef NDEBUG -static constexpr bool is_power_of_two(size_t value) { - // 2^N = 100000...000 - // 2^N - 1 = 011111...111 - // (2^N) && ((2^N)-1)) = 0 - return (value & (value - 1)) == 0; -} -#endif - -static void* platform_malloc_aligned(size_t size, size_t alignment) { -#if defined(GPR_HAS_ALIGNED_ALLOC) - GPR_DEBUG_ASSERT(is_power_of_two(alignment)); - size = GPR_ROUND_UP_TO_ALIGNMENT_SIZE(size, alignment); - void* ret = aligned_alloc(alignment, size); - GPR_ASSERT(ret != nullptr); - return ret; -#elif defined(GPR_HAS_ALIGNED_MALLOC) - GPR_DEBUG_ASSERT(is_power_of_two(alignment)); - void* ret = _aligned_malloc(size, alignment); - GPR_ASSERT(ret != nullptr); - return ret; -#elif defined(GPR_HAS_POSIX_MEMALIGN) - GPR_DEBUG_ASSERT(is_power_of_two(alignment)); - GPR_DEBUG_ASSERT(alignment % sizeof(void*) == 0); - void* ret = nullptr; - GPR_ASSERT(posix_memalign(&ret, alignment, size) == 0); - return ret; -#else - GPR_DEBUG_ASSERT(is_power_of_two(alignment)); - size_t extra = alignment - 1 + sizeof(void*); - void* p = gpr_malloc(size + extra); - void** ret = (void**)(((uintptr_t)p + extra) & ~(alignment - 1)); - ret[-1] = p; - return (void*)ret; -#endif -} - -static void platform_free_aligned(void* ptr) { -#if defined(GPR_HAS_ALIGNED_ALLOC) || defined(GPR_HAS_POSIX_MEMALIGN) - free(ptr); -#elif defined(GPR_HAS_ALIGNED_MALLOC) - _aligned_free(ptr); -#else - gpr_free((static_cast(ptr))[-1]); -#endif -} - -static gpr_allocation_functions g_alloc_functions = { - malloc, zalloc_with_calloc, realloc, - free, platform_malloc_aligned, platform_free_aligned}; +static gpr_allocation_functions g_alloc_functions = {malloc, zalloc_with_calloc, + realloc, free}; gpr_allocation_functions gpr_get_allocation_functions() { return g_alloc_functions; @@ -96,12 +47,6 @@ void gpr_set_allocation_functions(gpr_allocation_functions functions) { if (functions.zalloc_fn == nullptr) { functions.zalloc_fn = zalloc_with_gpr_malloc; } - GPR_ASSERT((functions.aligned_alloc_fn == nullptr) == - (functions.aligned_free_fn == nullptr)); - if (functions.aligned_alloc_fn == nullptr) { - functions.aligned_alloc_fn = platform_malloc_aligned; - functions.aligned_free_fn = platform_free_aligned; - } g_alloc_functions = functions; } @@ -143,12 +88,12 @@ void* gpr_realloc(void* p, size_t size) { } void* gpr_malloc_aligned(size_t size, size_t alignment) { - GPR_TIMER_SCOPE("gpr_malloc_aligned", 0); - if (size == 0) return nullptr; - return g_alloc_functions.aligned_alloc_fn(size, alignment); + GPR_ASSERT(((alignment - 1) & alignment) == 0); // Must be power of 2. + size_t extra = alignment - 1 + sizeof(void*); + void* p = gpr_malloc(size + extra); + void** ret = (void**)(((uintptr_t)p + extra) & ~(alignment - 1)); + ret[-1] = p; + return (void*)ret; } -void gpr_free_aligned(void* ptr) { - GPR_TIMER_SCOPE("gpr_free_aligned", 0); - g_alloc_functions.aligned_free_fn(ptr); -} +void gpr_free_aligned(void* ptr) { gpr_free((static_cast(ptr))[-1]); } diff --git a/src/core/lib/gpr/alloc.h b/src/core/lib/gpr/alloc.h index c77fbeaca49..762b51bf663 100644 --- a/src/core/lib/gpr/alloc.h +++ b/src/core/lib/gpr/alloc.h @@ -22,13 +22,7 @@ #include /// Given a size, round up to the next multiple of sizeof(void*). -#define GPR_ROUND_UP_TO_ALIGNMENT_SIZE(x, align) \ - (((x) + (align)-1u) & ~((align)-1u)) - -#define GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(x) \ - GPR_ROUND_UP_TO_ALIGNMENT_SIZE((x), GPR_MAX_ALIGNMENT) - -#define GPR_ROUND_UP_TO_CACHELINE_SIZE(x) \ - GPR_ROUND_UP_TO_ALIGNMENT_SIZE((x), GPR_CACHELINE_SIZE) +#define GPR_ROUND_UP_TO_ALIGNMENT_SIZE(x) \ + (((x) + GPR_MAX_ALIGNMENT - 1u) & ~(GPR_MAX_ALIGNMENT - 1u)) #endif /* GRPC_CORE_LIB_GPR_ALLOC_H */ diff --git a/src/core/lib/gprpp/arena.cc b/src/core/lib/gprpp/arena.cc index 2623ae870a1..5c344db4e35 100644 --- a/src/core/lib/gprpp/arena.cc +++ b/src/core/lib/gprpp/arena.cc @@ -35,8 +35,8 @@ namespace { void* ArenaStorage(size_t initial_size) { static constexpr size_t base_size = - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(sizeof(grpc_core::Arena)); - initial_size = GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(initial_size); + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_core::Arena)); + initial_size = GPR_ROUND_UP_TO_ALIGNMENT_SIZE(initial_size); size_t alloc_size = base_size + initial_size; static constexpr size_t alignment = (GPR_CACHELINE_SIZE > GPR_MAX_ALIGNMENT && @@ -67,7 +67,7 @@ Arena* Arena::Create(size_t initial_size) { Pair Arena::CreateWithAlloc(size_t initial_size, size_t alloc_size) { static constexpr size_t base_size = - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(sizeof(Arena)); + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(Arena)); auto* new_arena = new (ArenaStorage(initial_size)) Arena(initial_size, alloc_size); void* first_alloc = reinterpret_cast(new_arena) + base_size; @@ -88,7 +88,7 @@ void* Arena::AllocZone(size_t size) { // sizing hysteresis (that is, most calls should have a large enough initial // zone and will not need to grow the arena). static constexpr size_t zone_base_size = - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(sizeof(Zone)); + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(Zone)); size_t alloc_size = zone_base_size + size; Zone* z = new (gpr_malloc_aligned(alloc_size, GPR_MAX_ALIGNMENT)) Zone(); { diff --git a/src/core/lib/gprpp/arena.h b/src/core/lib/gprpp/arena.h index 6c646c5871d..b1b0c4a85cb 100644 --- a/src/core/lib/gprpp/arena.h +++ b/src/core/lib/gprpp/arena.h @@ -58,10 +58,10 @@ class Arena { // Allocate \a size bytes from the arena. void* Alloc(size_t size) { static constexpr size_t base_size = - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(sizeof(Arena)); - size = GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(size); + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(Arena)); + size = GPR_ROUND_UP_TO_ALIGNMENT_SIZE(size); size_t begin = total_used_.FetchAdd(size, MemoryOrder::RELAXED); - if (GPR_LIKELY(begin + size <= initial_zone_size_)) { + if (begin + size <= initial_zone_size_) { return reinterpret_cast(this) + base_size + begin; } else { return AllocZone(size); diff --git a/src/core/lib/gprpp/orphanable.h b/src/core/lib/gprpp/orphanable.h index dda5026cbca..2e467e49060 100644 --- a/src/core/lib/gprpp/orphanable.h +++ b/src/core/lib/gprpp/orphanable.h @@ -110,12 +110,12 @@ class InternallyRefCounted : public Orphanable { } void Unref() { - if (refs_.Unref()) { + if (GPR_UNLIKELY(refs_.Unref())) { Delete(static_cast(this)); } } void Unref(const DebugLocation& location, const char* reason) { - if (refs_.Unref(location, reason)) { + if (GPR_UNLIKELY(refs_.Unref(location, reason))) { Delete(static_cast(this)); } } diff --git a/src/core/lib/gprpp/ref_counted.h b/src/core/lib/gprpp/ref_counted.h index 87b342e0093..83e9429ba37 100644 --- a/src/core/lib/gprpp/ref_counted.h +++ b/src/core/lib/gprpp/ref_counted.h @@ -211,12 +211,12 @@ class RefCounted : public Impl { // private, since it will only be used by RefCountedPtr<>, which is a // friend of this class. void Unref() { - if (refs_.Unref()) { + if (GPR_UNLIKELY(refs_.Unref())) { Delete(static_cast(this)); } } void Unref(const DebugLocation& location, const char* reason) { - if (refs_.Unref(location, reason)) { + if (GPR_UNLIKELY(refs_.Unref(location, reason))) { Delete(static_cast(this)); } } diff --git a/src/core/lib/iomgr/ev_epoll1_linux.cc b/src/core/lib/iomgr/ev_epoll1_linux.cc index c2165341964..e1ab2afef15 100644 --- a/src/core/lib/iomgr/ev_epoll1_linux.cc +++ b/src/core/lib/iomgr/ev_epoll1_linux.cc @@ -383,6 +383,13 @@ static void fd_shutdown_internal(grpc_fd* fd, grpc_error* why, if (fd->read_closure->SetShutdown(GRPC_ERROR_REF(why))) { if (!releasing_fd) { shutdown(fd->fd, SHUT_RDWR); + } else { + /* we need a dummy event for earlier linux versions. */ + epoll_event dummy_event; + if (epoll_ctl(g_epoll_set.epfd, EPOLL_CTL_DEL, fd->fd, &dummy_event) != + 0) { + gpr_log(GPR_ERROR, "epoll_ctl failed: %s", strerror(errno)); + } } fd->write_closure->SetShutdown(GRPC_ERROR_REF(why)); fd->error_closure->SetShutdown(GRPC_ERROR_REF(why)); diff --git a/src/core/lib/slice/slice_buffer.cc b/src/core/lib/slice/slice_buffer.cc index ce3b5512d1d..111d3c9578e 100644 --- a/src/core/lib/slice/slice_buffer.cc +++ b/src/core/lib/slice/slice_buffer.cc @@ -33,7 +33,7 @@ #define GROW(x) (3 * (x) / 2) static void maybe_embiggen(grpc_slice_buffer* sb) { - if (sb->length == 0) { + if (sb->count == 0) { sb->slices = sb->base_slices; } diff --git a/src/core/lib/slice/slice_internal.h b/src/core/lib/slice/slice_internal.h index db8c8e5c7cc..a9f6087e11f 100644 --- a/src/core/lib/slice/slice_internal.h +++ b/src/core/lib/slice/slice_internal.h @@ -222,7 +222,7 @@ inline uint32_t grpc_slice_refcount::Hash(const grpc_slice& slice) { g_hash_seed); } -inline grpc_slice grpc_slice_ref_internal(const grpc_slice& slice) { +inline const grpc_slice& grpc_slice_ref_internal(const grpc_slice& slice) { if (slice.refcount) { slice.refcount->Ref(); } diff --git a/src/core/lib/surface/call.cc b/src/core/lib/surface/call.cc index 0a26872a9ea..bd140021c96 100644 --- a/src/core/lib/surface/call.cc +++ b/src/core/lib/surface/call.cc @@ -260,10 +260,10 @@ grpc_core::TraceFlag grpc_compression_trace(false, "compression"); #define CALL_STACK_FROM_CALL(call) \ (grpc_call_stack*)((char*)(call) + \ - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(sizeof(grpc_call))) + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_call))) #define CALL_FROM_CALL_STACK(call_stack) \ (grpc_call*)(((char*)(call_stack)) - \ - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(sizeof(grpc_call))) + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_call))) #define CALL_ELEM_FROM_CALL(call, idx) \ grpc_call_stack_element(CALL_STACK_FROM_CALL(call), idx) @@ -329,7 +329,7 @@ grpc_error* grpc_call_create(const grpc_call_create_args* args, size_t initial_size = grpc_channel_get_call_size_estimate(args->channel); GRPC_STATS_INC_CALL_INITIAL_SIZE(initial_size); size_t call_and_stack_size = - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(sizeof(grpc_call)) + + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(grpc_call)) + channel_stack->call_stack_size; size_t call_alloc_size = call_and_stack_size + (args->parent ? sizeof(child_call) : 0); diff --git a/src/core/lib/transport/metadata.cc b/src/core/lib/transport/metadata.cc index 7e0b2163246..4609eb42cdc 100644 --- a/src/core/lib/transport/metadata.cc +++ b/src/core/lib/transport/metadata.cc @@ -466,7 +466,7 @@ void grpc_mdelem_do_unref(grpc_mdelem gmd DEBUG_ARGS) { case GRPC_MDELEM_STORAGE_INTERNED: { auto* md = reinterpret_cast GRPC_MDELEM_DATA(gmd); uint32_t hash = md->hash(); - if (md->Unref(FWD_DEBUG_ARGS)) { + if (GPR_UNLIKELY(md->Unref(FWD_DEBUG_ARGS))) { /* once the refcount hits zero, some other thread can come along and free md at any time: it's unsafe from this point on to access it */ note_disposed_interned_metadata(hash); @@ -475,7 +475,7 @@ void grpc_mdelem_do_unref(grpc_mdelem gmd DEBUG_ARGS) { } case GRPC_MDELEM_STORAGE_ALLOCATED: { auto* md = reinterpret_cast GRPC_MDELEM_DATA(gmd); - if (md->Unref(FWD_DEBUG_ARGS)) { + if (GPR_UNLIKELY(md->Unref(FWD_DEBUG_ARGS))) { grpc_core::Delete(md); } break; diff --git a/src/core/lib/transport/transport.cc b/src/core/lib/transport/transport.cc index 40870657b84..29c1e561891 100644 --- a/src/core/lib/transport/transport.cc +++ b/src/core/lib/transport/transport.cc @@ -115,7 +115,7 @@ void grpc_transport_move_stats(grpc_transport_stream_stats* from, } size_t grpc_transport_stream_size(grpc_transport* transport) { - return GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(transport->vtable->sizeof_stream); + return GPR_ROUND_UP_TO_ALIGNMENT_SIZE(transport->vtable->sizeof_stream); } void grpc_transport_destroy(grpc_transport* transport) { diff --git a/src/core/lib/transport/transport.h b/src/core/lib/transport/transport.h index aac136b92dd..a6a6e904f08 100644 --- a/src/core/lib/transport/transport.h +++ b/src/core/lib/transport/transport.h @@ -98,7 +98,7 @@ inline void grpc_stream_unref(grpc_stream_refcount* refcount, #else inline void grpc_stream_unref(grpc_stream_refcount* refcount) { #endif - if (refcount->refs.Unref()) { + if (GPR_UNLIKELY(refcount->refs.Unref())) { grpc_stream_destroy(refcount); } } diff --git a/src/csharp/Grpc.Core.Api/DeserializationContext.cs b/src/csharp/Grpc.Core.Api/DeserializationContext.cs index d69e0db5bdf..966bcfa8c8e 100644 --- a/src/csharp/Grpc.Core.Api/DeserializationContext.cs +++ b/src/csharp/Grpc.Core.Api/DeserializationContext.cs @@ -39,7 +39,7 @@ namespace Grpc.Core /// Also, allocating a new buffer each time can put excessive pressure on GC, especially if /// the payload is more than 86700 bytes large (which means the newly allocated buffer will be placed in LOH, /// and LOH object can only be garbage collected via a full ("stop the world") GC run). - /// NOTE: Deserializers are expected not to call this method more than once per received message + /// NOTE: Deserializers are expected not to call this method (or other payload accessor methods) more than once per received message /// (as there is no practical reason for doing so) and DeserializationContext implementations are free to assume so. /// /// byte array containing the entire payload. @@ -47,5 +47,22 @@ namespace Grpc.Core { throw new NotImplementedException(); } + +#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY + /// + /// Gets the entire payload as a ReadOnlySequence. + /// The ReadOnlySequence is only valid for the duration of the deserializer routine and the caller must not access it after the deserializer returns. + /// Using the read only sequence is the most efficient way to access the message payload. Where possible it allows directly + /// accessing the received payload without needing to perform any buffer copying or buffer allocations. + /// NOTE: This method is only available in the netstandard2.0 build of the library. + /// NOTE: Deserializers are expected not to call this method (or other payload accessor methods) more than once per received message + /// (as there is no practical reason for doing so) and DeserializationContext implementations are free to assume so. + /// + /// read only sequence containing the entire payload. + public virtual System.Buffers.ReadOnlySequence PayloadAsReadOnlySequence() + { + throw new NotImplementedException(); + } +#endif } } diff --git a/src/csharp/Grpc.Core.Api/Grpc.Core.Api.csproj b/src/csharp/Grpc.Core.Api/Grpc.Core.Api.csproj index 6c29530402c..8a32bc757df 100755 --- a/src/csharp/Grpc.Core.Api/Grpc.Core.Api.csproj +++ b/src/csharp/Grpc.Core.Api/Grpc.Core.Api.csproj @@ -19,12 +19,20 @@ true + + $(DefineConstants);GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY + + + + + + diff --git a/src/csharp/Grpc.Core.Api/StatusCode.cs b/src/csharp/Grpc.Core.Api/StatusCode.cs index 57fe538e815..8493f375adb 100644 --- a/src/csharp/Grpc.Core.Api/StatusCode.cs +++ b/src/csharp/Grpc.Core.Api/StatusCode.cs @@ -114,7 +114,8 @@ namespace Grpc.Core /// /// The service is currently unavailable. This is a most likely a /// transient condition and may be corrected by retrying with - /// a backoff. + /// a backoff. Note that it is not always safe to retry + /// non-idempotent operations. /// Unavailable = 14, diff --git a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj index 23e5d7f65ef..7fef2c77091 100755 --- a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj +++ b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj @@ -8,6 +8,10 @@ true + + $(DefineConstants);GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY + + diff --git a/src/csharp/Grpc.Core.Tests/Internal/AsyncCallServerTest.cs b/src/csharp/Grpc.Core.Tests/Internal/AsyncCallServerTest.cs index 5c7d48f786c..fd221613c04 100644 --- a/src/csharp/Grpc.Core.Tests/Internal/AsyncCallServerTest.cs +++ b/src/csharp/Grpc.Core.Tests/Internal/AsyncCallServerTest.cs @@ -35,6 +35,7 @@ namespace Grpc.Core.Internal.Tests Server server; FakeNativeCall fakeCall; AsyncCallServer asyncCallServer; + FakeBufferReaderManager fakeBufferReaderManager; [SetUp] public void Init() @@ -52,11 +53,13 @@ namespace Grpc.Core.Internal.Tests Marshallers.StringMarshaller.ContextualSerializer, Marshallers.StringMarshaller.ContextualDeserializer, server); asyncCallServer.InitializeForTesting(fakeCall); + fakeBufferReaderManager = new FakeBufferReaderManager(); } [TearDown] public void Cleanup() { + fakeBufferReaderManager.Dispose(); server.ShutdownAsync().Wait(); } @@ -77,7 +80,7 @@ namespace Grpc.Core.Internal.Tests var moveNextTask = requestStream.MoveNext(); fakeCall.ReceivedCloseOnServerCallback.OnReceivedCloseOnServer(true, cancelled: true); - fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null); + fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse()); Assert.IsFalse(moveNextTask.Result); AssertFinished(asyncCallServer, fakeCall, finishedTask); @@ -107,7 +110,7 @@ namespace Grpc.Core.Internal.Tests // if a read completion's success==false, the request stream will silently finish // and we rely on C core cancelling the call. var moveNextTask = requestStream.MoveNext(); - fakeCall.ReceivedMessageCallback.OnReceivedMessage(false, null); + fakeCall.ReceivedMessageCallback.OnReceivedMessage(false, CreateNullResponse()); Assert.IsFalse(moveNextTask.Result); fakeCall.ReceivedCloseOnServerCallback.OnReceivedCloseOnServer(true, cancelled: true); @@ -182,5 +185,10 @@ namespace Grpc.Core.Internal.Tests Assert.IsTrue(finishedTask.IsCompleted); Assert.DoesNotThrow(() => finishedTask.Wait()); } + + IBufferReader CreateNullResponse() + { + return fakeBufferReaderManager.CreateNullPayloadBufferReader(); + } } } diff --git a/src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs b/src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs index 775849d89b6..78c7f3ad5bb 100644 --- a/src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs +++ b/src/csharp/Grpc.Core.Tests/Internal/AsyncCallTest.cs @@ -21,6 +21,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Grpc.Core.Internal; +using Grpc.Core.Utils; using NUnit.Framework; namespace Grpc.Core.Internal.Tests @@ -33,6 +34,7 @@ namespace Grpc.Core.Internal.Tests Channel channel; FakeNativeCall fakeCall; AsyncCall asyncCall; + FakeBufferReaderManager fakeBufferReaderManager; [SetUp] public void Init() @@ -43,12 +45,14 @@ namespace Grpc.Core.Internal.Tests var callDetails = new CallInvocationDetails(channel, "someMethod", null, Marshallers.StringMarshaller, Marshallers.StringMarshaller, new CallOptions()); asyncCall = new AsyncCall(callDetails, fakeCall); + fakeBufferReaderManager = new FakeBufferReaderManager(); } [TearDown] public void Cleanup() { channel.ShutdownAsync().Wait(); + fakeBufferReaderManager.Dispose(); } [Test] @@ -87,7 +91,7 @@ namespace Grpc.Core.Internal.Tests var resultTask = asyncCall.UnaryCallAsync("request1"); fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true, CreateClientSideStatus(StatusCode.InvalidArgument), - null, + CreateNullResponse(), new Metadata()); AssertUnaryResponseError(asyncCall, fakeCall, resultTask, StatusCode.InvalidArgument); @@ -168,7 +172,7 @@ namespace Grpc.Core.Internal.Tests var resultTask = asyncCall.ClientStreamingCallAsync(); fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true, CreateClientSideStatus(StatusCode.InvalidArgument), - null, + CreateNullResponse(), new Metadata()); AssertUnaryResponseError(asyncCall, fakeCall, resultTask, StatusCode.InvalidArgument); @@ -214,7 +218,7 @@ namespace Grpc.Core.Internal.Tests fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true, CreateClientSideStatus(StatusCode.Internal), - null, + CreateNullResponse(), new Metadata()); var ex = Assert.ThrowsAsync(async () => await writeTask); @@ -233,7 +237,7 @@ namespace Grpc.Core.Internal.Tests fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true, CreateClientSideStatus(StatusCode.Internal), - null, + CreateNullResponse(), new Metadata()); fakeCall.SendCompletionCallback.OnSendCompletion(false); @@ -259,7 +263,7 @@ namespace Grpc.Core.Internal.Tests fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true, CreateClientSideStatus(StatusCode.Internal), - null, + CreateNullResponse(), new Metadata()); var ex = Assert.ThrowsAsync(async () => await writeTask); @@ -357,7 +361,7 @@ namespace Grpc.Core.Internal.Tests fakeCall.UnaryResponseClientCallback.OnUnaryResponseClient(true, CreateClientSideStatus(StatusCode.Cancelled), - null, + CreateNullResponse(), new Metadata()); AssertUnaryResponseError(asyncCall, fakeCall, resultTask, StatusCode.Cancelled); @@ -390,7 +394,7 @@ namespace Grpc.Core.Internal.Tests fakeCall.ReceivedResponseHeadersCallback.OnReceivedResponseHeaders(true, new Metadata()); Assert.AreEqual(0, asyncCall.ResponseHeadersAsync.Result.Count); - fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null); + fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse()); fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata())); AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask); @@ -405,7 +409,7 @@ namespace Grpc.Core.Internal.Tests // try alternative order of completions fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata())); - fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null); + fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse()); AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask); } @@ -417,7 +421,7 @@ namespace Grpc.Core.Internal.Tests var responseStream = new ClientResponseStream(asyncCall); var readTask = responseStream.MoveNext(); - fakeCall.ReceivedMessageCallback.OnReceivedMessage(false, null); // after a failed read, we rely on C core to deliver appropriate status code. + fakeCall.ReceivedMessageCallback.OnReceivedMessage(false, CreateNullResponse()); // after a failed read, we rely on C core to deliver appropriate status code. fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, CreateClientSideStatus(StatusCode.Internal)); AssertStreamingResponseError(asyncCall, fakeCall, readTask, StatusCode.Internal); @@ -441,7 +445,7 @@ namespace Grpc.Core.Internal.Tests var readTask3 = responseStream.MoveNext(); fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata())); - fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null); + fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse()); AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask3); } @@ -479,7 +483,7 @@ namespace Grpc.Core.Internal.Tests Assert.DoesNotThrowAsync(async () => await writeTask1); var readTask = responseStream.MoveNext(); - fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null); + fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse()); fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata())); AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask); @@ -493,7 +497,7 @@ namespace Grpc.Core.Internal.Tests var responseStream = new ClientResponseStream(asyncCall); var readTask = responseStream.MoveNext(); - fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null); + fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse()); fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata())); AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask); @@ -511,7 +515,7 @@ namespace Grpc.Core.Internal.Tests var responseStream = new ClientResponseStream(asyncCall); var readTask = responseStream.MoveNext(); - fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null); + fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse()); fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, new ClientSideStatus(Status.DefaultSuccess, new Metadata())); AssertStreamingResponseSuccess(asyncCall, fakeCall, readTask); @@ -533,7 +537,7 @@ namespace Grpc.Core.Internal.Tests Assert.IsFalse(writeTask.IsCompleted); var readTask = responseStream.MoveNext(); - fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null); + fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse()); fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, CreateClientSideStatus(StatusCode.PermissionDenied)); var ex = Assert.ThrowsAsync(async () => await writeTask); @@ -552,7 +556,7 @@ namespace Grpc.Core.Internal.Tests var writeTask = requestStream.WriteAsync("request1"); var readTask = responseStream.MoveNext(); - fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null); + fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse()); fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, CreateClientSideStatus(StatusCode.PermissionDenied)); fakeCall.SendCompletionCallback.OnSendCompletion(false); @@ -576,7 +580,7 @@ namespace Grpc.Core.Internal.Tests Assert.ThrowsAsync(typeof(TaskCanceledException), async () => await writeTask); var readTask = responseStream.MoveNext(); - fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null); + fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse()); fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, CreateClientSideStatus(StatusCode.Cancelled)); AssertStreamingResponseError(asyncCall, fakeCall, readTask, StatusCode.Cancelled); @@ -597,7 +601,7 @@ namespace Grpc.Core.Internal.Tests Assert.AreEqual("response1", responseStream.Current); var readTask2 = responseStream.MoveNext(); - fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null); + fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse()); fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, CreateClientSideStatus(StatusCode.Cancelled)); AssertStreamingResponseError(asyncCall, fakeCall, readTask2, StatusCode.Cancelled); @@ -618,7 +622,7 @@ namespace Grpc.Core.Internal.Tests Assert.AreEqual("response1", responseStream.Current); var readTask2 = responseStream.MoveNext(); - fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, null); + fakeCall.ReceivedMessageCallback.OnReceivedMessage(true, CreateNullResponse()); fakeCall.ReceivedStatusOnClientCallback.OnReceivedStatusOnClient(true, CreateClientSideStatus(StatusCode.Cancelled)); AssertStreamingResponseError(asyncCall, fakeCall, readTask2, StatusCode.Cancelled); @@ -638,9 +642,14 @@ namespace Grpc.Core.Internal.Tests return new ClientSideStatus(new Status(statusCode, ""), new Metadata()); } - byte[] CreateResponsePayload() + IBufferReader CreateResponsePayload() + { + return fakeBufferReaderManager.CreateSingleSegmentBufferReader(Marshallers.StringMarshaller.Serializer("response1")); + } + + IBufferReader CreateNullResponse() { - return Marshallers.StringMarshaller.Serializer("response1"); + return fakeBufferReaderManager.CreateNullPayloadBufferReader(); } static void AssertUnaryResponseSuccess(AsyncCall asyncCall, FakeNativeCall fakeCall, Task resultTask) diff --git a/src/csharp/Grpc.Core.Tests/Internal/DefaultDeserializationContextTest.cs b/src/csharp/Grpc.Core.Tests/Internal/DefaultDeserializationContextTest.cs new file mode 100644 index 00000000000..63baab31624 --- /dev/null +++ b/src/csharp/Grpc.Core.Tests/Internal/DefaultDeserializationContextTest.cs @@ -0,0 +1,240 @@ +#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 System.Collections.Generic; +using Grpc.Core; +using Grpc.Core.Internal; +using Grpc.Core.Utils; +using NUnit.Framework; + +using System.Runtime.InteropServices; + +#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY +using System.Buffers; +#endif + +namespace Grpc.Core.Internal.Tests +{ + public class DefaultDeserializationContextTest + { + FakeBufferReaderManager fakeBufferReaderManager; + + [SetUp] + public void Init() + { + fakeBufferReaderManager = new FakeBufferReaderManager(); + } + + [TearDown] + public void Cleanup() + { + fakeBufferReaderManager.Dispose(); + } + +#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY + [TestCase] + public void PayloadAsReadOnlySequence_ZeroSegmentPayload() + { + var context = new DefaultDeserializationContext(); + context.Initialize(fakeBufferReaderManager.CreateMultiSegmentBufferReader(new List {})); + + Assert.AreEqual(0, context.PayloadLength); + + var sequence = context.PayloadAsReadOnlySequence(); + + Assert.AreEqual(ReadOnlySequence.Empty, sequence); + Assert.IsTrue(sequence.IsEmpty); + Assert.IsTrue(sequence.IsSingleSegment); + } + + [TestCase(0)] + [TestCase(1)] + [TestCase(10)] + [TestCase(100)] + [TestCase(1000)] + public void PayloadAsReadOnlySequence_SingleSegmentPayload(int segmentLength) + { + var origBuffer = GetTestBuffer(segmentLength); + var context = new DefaultDeserializationContext(); + context.Initialize(fakeBufferReaderManager.CreateSingleSegmentBufferReader(origBuffer)); + + Assert.AreEqual(origBuffer.Length, context.PayloadLength); + + var sequence = context.PayloadAsReadOnlySequence(); + + Assert.AreEqual(origBuffer.Length, sequence.Length); + Assert.AreEqual(origBuffer.Length, sequence.First.Length); + Assert.IsTrue(sequence.IsSingleSegment); + CollectionAssert.AreEqual(origBuffer, sequence.First.ToArray()); + } + + [TestCase(0, 5, 10)] + [TestCase(1, 1, 1)] + [TestCase(10, 100, 1000)] + [TestCase(100, 100, 10)] + [TestCase(1000, 1000, 1000)] + public void PayloadAsReadOnlySequence_MultiSegmentPayload(int segmentLen1, int segmentLen2, int segmentLen3) + { + var origBuffer1 = GetTestBuffer(segmentLen1); + var origBuffer2 = GetTestBuffer(segmentLen2); + var origBuffer3 = GetTestBuffer(segmentLen3); + int totalLen = origBuffer1.Length + origBuffer2.Length + origBuffer3.Length; + + var context = new DefaultDeserializationContext(); + context.Initialize(fakeBufferReaderManager.CreateMultiSegmentBufferReader(new List { origBuffer1, origBuffer2, origBuffer3 })); + + Assert.AreEqual(totalLen, context.PayloadLength); + + var sequence = context.PayloadAsReadOnlySequence(); + + Assert.AreEqual(totalLen, sequence.Length); + + var segmentEnumerator = sequence.GetEnumerator(); + + Assert.IsTrue(segmentEnumerator.MoveNext()); + CollectionAssert.AreEqual(origBuffer1, segmentEnumerator.Current.ToArray()); + + Assert.IsTrue(segmentEnumerator.MoveNext()); + CollectionAssert.AreEqual(origBuffer2, segmentEnumerator.Current.ToArray()); + + Assert.IsTrue(segmentEnumerator.MoveNext()); + CollectionAssert.AreEqual(origBuffer3, segmentEnumerator.Current.ToArray()); + + Assert.IsFalse(segmentEnumerator.MoveNext()); + } +#endif + + [TestCase] + public void NullPayloadNotAllowed() + { + var context = new DefaultDeserializationContext(); + Assert.Throws(typeof(InvalidOperationException), () => context.Initialize(fakeBufferReaderManager.CreateNullPayloadBufferReader())); + } + + [TestCase] + public void PayloadAsNewByteBuffer_ZeroSegmentPayload() + { + var context = new DefaultDeserializationContext(); + context.Initialize(fakeBufferReaderManager.CreateMultiSegmentBufferReader(new List {})); + + Assert.AreEqual(0, context.PayloadLength); + + var payload = context.PayloadAsNewBuffer(); + Assert.AreEqual(0, payload.Length); + } + + [TestCase(0)] + [TestCase(1)] + [TestCase(10)] + [TestCase(100)] + [TestCase(1000)] + public void PayloadAsNewByteBuffer_SingleSegmentPayload(int segmentLength) + { + var origBuffer = GetTestBuffer(segmentLength); + var context = new DefaultDeserializationContext(); + context.Initialize(fakeBufferReaderManager.CreateSingleSegmentBufferReader(origBuffer)); + + Assert.AreEqual(origBuffer.Length, context.PayloadLength); + + var payload = context.PayloadAsNewBuffer(); + CollectionAssert.AreEqual(origBuffer, payload); + } + + [TestCase(0, 5, 10)] + [TestCase(1, 1, 1)] + [TestCase(10, 100, 1000)] + [TestCase(100, 100, 10)] + [TestCase(1000, 1000, 1000)] + public void PayloadAsNewByteBuffer_MultiSegmentPayload(int segmentLen1, int segmentLen2, int segmentLen3) + { + var origBuffer1 = GetTestBuffer(segmentLen1); + var origBuffer2 = GetTestBuffer(segmentLen2); + var origBuffer3 = GetTestBuffer(segmentLen3); + + var context = new DefaultDeserializationContext(); + context.Initialize(fakeBufferReaderManager.CreateMultiSegmentBufferReader(new List { origBuffer1, origBuffer2, origBuffer3 })); + + var payload = context.PayloadAsNewBuffer(); + + var concatenatedOrigBuffers = new List(); + concatenatedOrigBuffers.AddRange(origBuffer1); + concatenatedOrigBuffers.AddRange(origBuffer2); + concatenatedOrigBuffers.AddRange(origBuffer3); + + Assert.AreEqual(concatenatedOrigBuffers.Count, context.PayloadLength); + Assert.AreEqual(concatenatedOrigBuffers.Count, payload.Length); + CollectionAssert.AreEqual(concatenatedOrigBuffers, payload); + } + + [TestCase] + public void GetPayloadMultipleTimesIsIllegal() + { + var origBuffer = GetTestBuffer(100); + var context = new DefaultDeserializationContext(); + context.Initialize(fakeBufferReaderManager.CreateSingleSegmentBufferReader(origBuffer)); + + Assert.AreEqual(origBuffer.Length, context.PayloadLength); + + var payload = context.PayloadAsNewBuffer(); + CollectionAssert.AreEqual(origBuffer, payload); + + // Getting payload multiple times is illegal + Assert.Throws(typeof(InvalidOperationException), () => context.PayloadAsNewBuffer()); +#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY + Assert.Throws(typeof(InvalidOperationException), () => context.PayloadAsReadOnlySequence()); +#endif + } + + [TestCase] + public void ResetContextAndReinitialize() + { + var origBuffer = GetTestBuffer(100); + var context = new DefaultDeserializationContext(); + context.Initialize(fakeBufferReaderManager.CreateSingleSegmentBufferReader(origBuffer)); + + Assert.AreEqual(origBuffer.Length, context.PayloadLength); + + // Reset invalidates context + context.Reset(); + + Assert.AreEqual(0, context.PayloadLength); + Assert.Throws(typeof(NullReferenceException), () => context.PayloadAsNewBuffer()); +#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY + Assert.Throws(typeof(NullReferenceException), () => context.PayloadAsReadOnlySequence()); +#endif + + // Previously reset context can be initialized again + var origBuffer2 = GetTestBuffer(50); + context.Initialize(fakeBufferReaderManager.CreateSingleSegmentBufferReader(origBuffer2)); + + Assert.AreEqual(origBuffer2.Length, context.PayloadLength); + CollectionAssert.AreEqual(origBuffer2, context.PayloadAsNewBuffer()); + } + + private byte[] GetTestBuffer(int length) + { + var testBuffer = new byte[length]; + for (int i = 0; i < testBuffer.Length; i++) + { + testBuffer[i] = (byte) i; + } + return testBuffer; + } + } +} diff --git a/src/csharp/Grpc.Core.Tests/Internal/FakeBufferReaderManager.cs b/src/csharp/Grpc.Core.Tests/Internal/FakeBufferReaderManager.cs new file mode 100644 index 00000000000..d8d0c0a6354 --- /dev/null +++ b/src/csharp/Grpc.Core.Tests/Internal/FakeBufferReaderManager.cs @@ -0,0 +1,118 @@ +#region Copyright notice and license + +// Copyright 2018 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 System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +using Grpc.Core.Internal; +using Grpc.Core.Utils; + +namespace Grpc.Core.Internal.Tests +{ + // Creates instances of fake IBufferReader. All created instances will become invalid once Dispose is called. + internal class FakeBufferReaderManager : IDisposable + { + List pinnedHandles = new List(); + bool disposed = false; + public IBufferReader CreateSingleSegmentBufferReader(byte[] data) + { + return CreateMultiSegmentBufferReader(new List { data }); + } + + public IBufferReader CreateMultiSegmentBufferReader(IEnumerable dataSegments) + { + GrpcPreconditions.CheckState(!disposed); + GrpcPreconditions.CheckNotNull(dataSegments); + var segments = new List(); + foreach (var data in dataSegments) + { + GrpcPreconditions.CheckNotNull(data); + segments.Add(GCHandle.Alloc(data, GCHandleType.Pinned)); + } + pinnedHandles.AddRange(segments); // all the allocated GCHandles will be freed on Dispose() + return new FakeBufferReader(segments); + } + + public IBufferReader CreateNullPayloadBufferReader() + { + GrpcPreconditions.CheckState(!disposed); + return new FakeBufferReader(null); + } + + public void Dispose() + { + if (!disposed) + { + disposed = true; + for (int i = 0; i < pinnedHandles.Count; i++) + { + pinnedHandles[i].Free(); + } + } + } + + private class FakeBufferReader : IBufferReader + { + readonly List bufferSegments; + readonly int? totalLength; + readonly IEnumerator segmentEnumerator; + + public FakeBufferReader(List bufferSegments) + { + this.bufferSegments = bufferSegments; + this.totalLength = ComputeTotalLength(bufferSegments); + this.segmentEnumerator = bufferSegments?.GetEnumerator(); + } + + public int? TotalLength => totalLength; + + public bool TryGetNextSlice(out Slice slice) + { + GrpcPreconditions.CheckNotNull(bufferSegments); + if (!segmentEnumerator.MoveNext()) + { + slice = default(Slice); + return false; + } + + var segment = segmentEnumerator.Current; + int sliceLen = ((byte[]) segment.Target).Length; + slice = new Slice(segment.AddrOfPinnedObject(), sliceLen); + return true; + } + + static int? ComputeTotalLength(List bufferSegments) + { + if (bufferSegments == null) + { + return null; + } + + int sum = 0; + foreach (var segment in bufferSegments) + { + var data = (byte[]) segment.Target; + sum += data.Length; + } + return sum; + } + } + } +} diff --git a/src/csharp/Grpc.Core.Tests/Internal/FakeBufferReaderManagerTest.cs b/src/csharp/Grpc.Core.Tests/Internal/FakeBufferReaderManagerTest.cs new file mode 100644 index 00000000000..7c4ff652bd3 --- /dev/null +++ b/src/csharp/Grpc.Core.Tests/Internal/FakeBufferReaderManagerTest.cs @@ -0,0 +1,121 @@ +#region Copyright notice and license + +// Copyright 2018 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 System.Collections.Generic; +using Grpc.Core; +using Grpc.Core.Internal; +using Grpc.Core.Utils; +using NUnit.Framework; + +namespace Grpc.Core.Internal.Tests +{ + public class FakeBufferReaderManagerTest + { + FakeBufferReaderManager fakeBufferReaderManager; + + [SetUp] + public void Init() + { + fakeBufferReaderManager = new FakeBufferReaderManager(); + } + + [TearDown] + public void Cleanup() + { + fakeBufferReaderManager.Dispose(); + } + + [TestCase] + public void NullPayload() + { + var fakeBufferReader = fakeBufferReaderManager.CreateNullPayloadBufferReader(); + Assert.IsFalse(fakeBufferReader.TotalLength.HasValue); + Assert.Throws(typeof(ArgumentNullException), () => fakeBufferReader.TryGetNextSlice(out Slice slice)); + } + [TestCase] + public void ZeroSegmentPayload() + { + var fakeBufferReader = fakeBufferReaderManager.CreateMultiSegmentBufferReader(new List {}); + Assert.AreEqual(0, fakeBufferReader.TotalLength.Value); + Assert.IsFalse(fakeBufferReader.TryGetNextSlice(out Slice slice)); + } + + [TestCase(0)] + [TestCase(1)] + [TestCase(10)] + [TestCase(30)] + [TestCase(100)] + [TestCase(1000)] + public void SingleSegmentPayload(int bufferLen) + { + var origBuffer = GetTestBuffer(bufferLen); + var fakeBufferReader = fakeBufferReaderManager.CreateSingleSegmentBufferReader(origBuffer); + Assert.AreEqual(origBuffer.Length, fakeBufferReader.TotalLength.Value); + + Assert.IsTrue(fakeBufferReader.TryGetNextSlice(out Slice slice)); + AssertSliceDataEqual(origBuffer, slice); + + Assert.IsFalse(fakeBufferReader.TryGetNextSlice(out Slice slice2)); + } + + [TestCase(0, 5, 10)] + [TestCase(1, 1, 1)] + [TestCase(10, 100, 1000)] + [TestCase(100, 100, 10)] + [TestCase(1000, 1000, 1000)] + public void MultiSegmentPayload(int segmentLen1, int segmentLen2, int segmentLen3) + { + var origBuffer1 = GetTestBuffer(segmentLen1); + var origBuffer2 = GetTestBuffer(segmentLen2); + var origBuffer3 = GetTestBuffer(segmentLen3); + var fakeBufferReader = fakeBufferReaderManager.CreateMultiSegmentBufferReader(new List { origBuffer1, origBuffer2, origBuffer3 }); + + Assert.AreEqual(origBuffer1.Length + origBuffer2.Length + origBuffer3.Length, fakeBufferReader.TotalLength.Value); + + Assert.IsTrue(fakeBufferReader.TryGetNextSlice(out Slice slice1)); + AssertSliceDataEqual(origBuffer1, slice1); + + Assert.IsTrue(fakeBufferReader.TryGetNextSlice(out Slice slice2)); + AssertSliceDataEqual(origBuffer2, slice2); + + Assert.IsTrue(fakeBufferReader.TryGetNextSlice(out Slice slice3)); + AssertSliceDataEqual(origBuffer3, slice3); + + Assert.IsFalse(fakeBufferReader.TryGetNextSlice(out Slice slice4)); + } + + private void AssertSliceDataEqual(byte[] expected, Slice actual) + { + var actualSliceData = new byte[actual.Length]; + actual.CopyTo(new ArraySegment(actualSliceData)); + CollectionAssert.AreEqual(expected, actualSliceData); + } + + // create a buffer of given size and fill it with some data + private byte[] GetTestBuffer(int length) + { + var testBuffer = new byte[length]; + for (int i = 0; i < testBuffer.Length; i++) + { + testBuffer[i] = (byte) i; + } + return testBuffer; + } + } +} diff --git a/src/csharp/Grpc.Core.Tests/Internal/ReusableSliceBufferTest.cs b/src/csharp/Grpc.Core.Tests/Internal/ReusableSliceBufferTest.cs new file mode 100644 index 00000000000..7630785aef4 --- /dev/null +++ b/src/csharp/Grpc.Core.Tests/Internal/ReusableSliceBufferTest.cs @@ -0,0 +1,151 @@ +#region Copyright notice and license + +// Copyright 2018 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 System.Collections.Generic; +using System.Linq; +using Grpc.Core; +using Grpc.Core.Internal; +using Grpc.Core.Utils; +using NUnit.Framework; + +using System.Runtime.InteropServices; + +#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY +using System.Buffers; +#endif + +namespace Grpc.Core.Internal.Tests +{ + // Converts IBufferReader into instances of ReadOnlySequence + // Objects representing the sequence segments are cached to decrease GC load. + public class ReusableSliceBufferTest + { + FakeBufferReaderManager fakeBufferReaderManager; + + [SetUp] + public void Init() + { + fakeBufferReaderManager = new FakeBufferReaderManager(); + } + + [TearDown] + public void Cleanup() + { + fakeBufferReaderManager.Dispose(); + } + +#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY + [TestCase] + public void NullPayload() + { + var sliceBuffer = new ReusableSliceBuffer(); + Assert.Throws(typeof(ArgumentNullException), () => sliceBuffer.PopulateFrom(fakeBufferReaderManager.CreateNullPayloadBufferReader())); + } + + [TestCase] + public void ZeroSegmentPayload() + { + var sliceBuffer = new ReusableSliceBuffer(); + var sequence = sliceBuffer.PopulateFrom(fakeBufferReaderManager.CreateMultiSegmentBufferReader(new List {})); + + Assert.AreEqual(ReadOnlySequence.Empty, sequence); + Assert.IsTrue(sequence.IsEmpty); + Assert.IsTrue(sequence.IsSingleSegment); + } + + [TestCase] + public void SegmentsAreCached() + { + var bufferSegments1 = Enumerable.Range(0, 100).Select((_) => GetTestBuffer(50)).ToList(); + var bufferSegments2 = Enumerable.Range(0, 100).Select((_) => GetTestBuffer(50)).ToList(); + + var sliceBuffer = new ReusableSliceBuffer(); + + var sequence1 = sliceBuffer.PopulateFrom(fakeBufferReaderManager.CreateMultiSegmentBufferReader(bufferSegments1)); + var memoryManagers1 = GetMemoryManagersForSequenceSegments(sequence1); + + sliceBuffer.Invalidate(); + + var sequence2 = sliceBuffer.PopulateFrom(fakeBufferReaderManager.CreateMultiSegmentBufferReader(bufferSegments2)); + var memoryManagers2 = GetMemoryManagersForSequenceSegments(sequence2); + + // check memory managers are identical objects (i.e. they've been reused) + CollectionAssert.AreEquivalent(memoryManagers1, memoryManagers2); + } + + [TestCase] + public void MultiSegmentPayload_LotsOfSegments() + { + var bufferSegments = Enumerable.Range(0, ReusableSliceBuffer.MaxCachedSegments + 100).Select((_) => GetTestBuffer(10)).ToList(); + + var sliceBuffer = new ReusableSliceBuffer(); + var sequence = sliceBuffer.PopulateFrom(fakeBufferReaderManager.CreateMultiSegmentBufferReader(bufferSegments)); + + int index = 0; + foreach (var memory in sequence) + { + CollectionAssert.AreEqual(bufferSegments[index], memory.ToArray()); + index ++; + } + } + + [TestCase] + public void InvalidateMakesSequenceUnusable() + { + var origBuffer = GetTestBuffer(100); + + var sliceBuffer = new ReusableSliceBuffer(); + var sequence = sliceBuffer.PopulateFrom(fakeBufferReaderManager.CreateMultiSegmentBufferReader(new List { origBuffer })); + + Assert.AreEqual(origBuffer.Length, sequence.Length); + + sliceBuffer.Invalidate(); + + // Invalidate with make the returned sequence completely unusable and broken, users must not use it beyond the deserializer functions. + Assert.Throws(typeof(ArgumentOutOfRangeException), () => { var first = sequence.First; }); + } + + private List> GetMemoryManagersForSequenceSegments(ReadOnlySequence sequence) + { + var result = new List>(); + foreach (var memory in sequence) + { + Assert.IsTrue(MemoryMarshal.TryGetMemoryManager(memory, out MemoryManager memoryManager)); + result.Add(memoryManager); + } + return result; + } +#else + [TestCase] + public void OnlySupportedOnNetCore() + { + // Test case needs to exist to make C# sanity test happy. + } +#endif + private byte[] GetTestBuffer(int length) + { + var testBuffer = new byte[length]; + for (int i = 0; i < testBuffer.Length; i++) + { + testBuffer[i] = (byte) i; + } + return testBuffer; + } + } +} diff --git a/src/csharp/Grpc.Core.Tests/Internal/SliceTest.cs b/src/csharp/Grpc.Core.Tests/Internal/SliceTest.cs new file mode 100644 index 00000000000..eb090bbfa50 --- /dev/null +++ b/src/csharp/Grpc.Core.Tests/Internal/SliceTest.cs @@ -0,0 +1,83 @@ +#region Copyright notice and license + +// Copyright 2018 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; +using Grpc.Core.Internal; +using Grpc.Core.Utils; +using NUnit.Framework; + +using System.Runtime.InteropServices; + +namespace Grpc.Core.Internal.Tests +{ + public class SliceTest + { + [TestCase(0)] + [TestCase(1)] + [TestCase(10)] + [TestCase(100)] + [TestCase(1000)] + public void SliceFromNativePtr_CopyToArraySegment(int bufferLength) + { + var origBuffer = GetTestBuffer(bufferLength); + var gcHandle = GCHandle.Alloc(origBuffer, GCHandleType.Pinned); + try + { + var slice = new Slice(gcHandle.AddrOfPinnedObject(), origBuffer.Length); + Assert.AreEqual(bufferLength, slice.Length); + + var newBuffer = new byte[bufferLength]; + slice.CopyTo(new ArraySegment(newBuffer)); + CollectionAssert.AreEqual(origBuffer, newBuffer); + } + finally + { + gcHandle.Free(); + } + } + + [TestCase] + public void SliceFromNativePtr_CopyToArraySegmentTooSmall() + { + var origBuffer = GetTestBuffer(100); + var gcHandle = GCHandle.Alloc(origBuffer, GCHandleType.Pinned); + try + { + var slice = new Slice(gcHandle.AddrOfPinnedObject(), origBuffer.Length); + var tooSmall = new byte[origBuffer.Length - 1]; + Assert.Catch(typeof(ArgumentException), () => slice.CopyTo(new ArraySegment(tooSmall))); + } + finally + { + gcHandle.Free(); + } + } + + // create a buffer of given size and fill it with some data + private byte[] GetTestBuffer(int length) + { + var testBuffer = new byte[length]; + for (int i = 0; i < testBuffer.Length; i++) + { + testBuffer[i] = (byte) i; + } + return testBuffer; + } + } +} diff --git a/src/csharp/Grpc.Core/Grpc.Core.csproj b/src/csharp/Grpc.Core/Grpc.Core.csproj index b7c191ea6a9..afd60e73a21 100755 --- a/src/csharp/Grpc.Core/Grpc.Core.csproj +++ b/src/csharp/Grpc.Core/Grpc.Core.csproj @@ -19,6 +19,15 @@ true + + true + + + + 7.2 + $(DefineConstants);GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY + + diff --git a/src/csharp/Grpc.Core/Internal/AsyncCall.cs b/src/csharp/Grpc.Core/Internal/AsyncCall.cs index 785081c341a..a1c688140d1 100644 --- a/src/csharp/Grpc.Core/Internal/AsyncCall.cs +++ b/src/csharp/Grpc.Core/Internal/AsyncCall.cs @@ -111,7 +111,7 @@ namespace Grpc.Core.Internal { using (profiler.NewScope("AsyncCall.UnaryCall.HandleBatch")) { - HandleUnaryResponse(success, ctx.GetReceivedStatusOnClient(), ctx.GetReceivedMessage(), ctx.GetReceivedInitialMetadata()); + HandleUnaryResponse(success, ctx.GetReceivedStatusOnClient(), ctx.GetReceivedMessageReader(), ctx.GetReceivedInitialMetadata()); } } catch (Exception e) @@ -537,14 +537,14 @@ namespace Grpc.Core.Internal /// /// Handler for unary response completion. /// - private void HandleUnaryResponse(bool success, ClientSideStatus receivedStatus, byte[] receivedMessage, Metadata responseHeaders) + private void HandleUnaryResponse(bool success, ClientSideStatus receivedStatus, IBufferReader receivedMessageReader, Metadata responseHeaders) { // NOTE: because this event is a result of batch containing GRPC_OP_RECV_STATUS_ON_CLIENT, // success will be always set to true. TaskCompletionSource delayedStreamingWriteTcs = null; TResponse msg = default(TResponse); - var deserializeException = TryDeserialize(receivedMessage, out msg); + var deserializeException = TryDeserialize(receivedMessageReader, out msg); bool releasedResources; lock (myLock) @@ -634,9 +634,9 @@ namespace Grpc.Core.Internal IUnaryResponseClientCallback UnaryResponseClientCallback => this; - void IUnaryResponseClientCallback.OnUnaryResponseClient(bool success, ClientSideStatus receivedStatus, byte[] receivedMessage, Metadata responseHeaders) + void IUnaryResponseClientCallback.OnUnaryResponseClient(bool success, ClientSideStatus receivedStatus, IBufferReader receivedMessageReader, Metadata responseHeaders) { - HandleUnaryResponse(success, receivedStatus, receivedMessage, responseHeaders); + HandleUnaryResponse(success, receivedStatus, receivedMessageReader, responseHeaders); } IReceivedStatusOnClientCallback ReceivedStatusOnClientCallback => this; diff --git a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs index 39c9f7c6160..0b99aaf3e21 100644 --- a/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs +++ b/src/csharp/Grpc.Core/Internal/AsyncCallBase.cs @@ -228,12 +228,12 @@ namespace Grpc.Core.Internal } } - protected Exception TryDeserialize(byte[] payload, out TRead msg) + protected Exception TryDeserialize(IBufferReader reader, out TRead msg) { DefaultDeserializationContext context = null; try { - context = DefaultDeserializationContext.GetInitializedThreadLocal(payload); + context = DefaultDeserializationContext.GetInitializedThreadLocal(reader); msg = deserializer(context); return null; } @@ -245,7 +245,6 @@ namespace Grpc.Core.Internal finally { context?.Reset(); - } } @@ -333,21 +332,21 @@ namespace Grpc.Core.Internal /// /// Handles streaming read completion. /// - protected void HandleReadFinished(bool success, byte[] receivedMessage) + protected void HandleReadFinished(bool success, IBufferReader receivedMessageReader) { - // if success == false, received message will be null. It that case we will + // if success == false, the message reader will report null payload. It that case we will // treat this completion as the last read an rely on C core to handle the failed // read (e.g. deliver approriate statusCode on the clientside). TRead msg = default(TRead); - var deserializeException = (success && receivedMessage != null) ? TryDeserialize(receivedMessage, out msg) : null; + var deserializeException = (success && receivedMessageReader.TotalLength.HasValue) ? TryDeserialize(receivedMessageReader, out msg) : null; TaskCompletionSource origTcs = null; bool releasedResources; lock (myLock) { origTcs = streamingReadTcs; - if (receivedMessage == null) + if (!receivedMessageReader.TotalLength.HasValue) { // This was the last read. readingDone = true; @@ -391,9 +390,9 @@ namespace Grpc.Core.Internal IReceivedMessageCallback ReceivedMessageCallback => this; - void IReceivedMessageCallback.OnReceivedMessage(bool success, byte[] receivedMessage) + void IReceivedMessageCallback.OnReceivedMessage(bool success, IBufferReader receivedMessageReader) { - HandleReadFinished(success, receivedMessage); + HandleReadFinished(success, receivedMessageReader); } } } diff --git a/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs b/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs index 085e7faf595..50a626842dd 100644 --- a/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs +++ b/src/csharp/Grpc.Core/Internal/BatchContextSafeHandle.cs @@ -30,10 +30,17 @@ namespace Grpc.Core.Internal void OnComplete(bool success); } + internal interface IBufferReader + { + int? TotalLength { get; } + + bool TryGetNextSlice(out Slice slice); + } + /// /// grpcsharp_batch_context /// - internal class BatchContextSafeHandle : SafeHandleZeroIsInvalid, IOpCompletionCallback, IPooledObject + internal class BatchContextSafeHandle : SafeHandleZeroIsInvalid, IOpCompletionCallback, IPooledObject, IBufferReader { static readonly NativeMethods Native = NativeMethods.Get(); static readonly ILogger Logger = GrpcEnvironment.Logger.ForType(); @@ -93,17 +100,9 @@ namespace Grpc.Core.Internal return new ClientSideStatus(status, metadata); } - // Gets data of recv_message completion. - public byte[] GetReceivedMessage() + public IBufferReader GetReceivedMessageReader() { - IntPtr len = Native.grpcsharp_batch_context_recv_message_length(this); - if (len == new IntPtr(-1)) - { - return null; - } - byte[] data = new byte[(int)len]; - Native.grpcsharp_batch_context_recv_message_to_buffer(this, data, new UIntPtr((ulong)data.Length)); - return data; + return this; } // Gets data of receive_close_on_server completion. @@ -153,6 +152,29 @@ namespace Grpc.Core.Internal } } + int? IBufferReader.TotalLength + { + get + { + var len = Native.grpcsharp_batch_context_recv_message_length(this); + return len != new IntPtr(-1) ? (int?) len : null; + } + } + + bool IBufferReader.TryGetNextSlice(out Slice slice) + { + UIntPtr sliceLen; + IntPtr sliceDataPtr; + + if (0 == Native.grpcsharp_batch_context_recv_message_next_slice_peek(this, out sliceLen, out sliceDataPtr)) + { + slice = default(Slice); + return false; + } + slice = new Slice(sliceDataPtr, (int) sliceLen); + return true; + } + struct CompletionCallbackData { public CompletionCallbackData(BatchCompletionDelegate callback, object state) diff --git a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs index a3ef3e61ee1..858d2a69605 100644 --- a/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs +++ b/src/csharp/Grpc.Core/Internal/CallSafeHandle.cs @@ -35,11 +35,11 @@ namespace Grpc.Core.Internal // Completion handlers are pre-allocated to avoid unneccessary delegate allocations. // The "state" field is used to store the actual callback to invoke. static readonly BatchCompletionDelegate CompletionHandler_IUnaryResponseClientCallback = - (success, context, state) => ((IUnaryResponseClientCallback)state).OnUnaryResponseClient(success, context.GetReceivedStatusOnClient(), context.GetReceivedMessage(), context.GetReceivedInitialMetadata()); + (success, context, state) => ((IUnaryResponseClientCallback)state).OnUnaryResponseClient(success, context.GetReceivedStatusOnClient(), context.GetReceivedMessageReader(), context.GetReceivedInitialMetadata()); static readonly BatchCompletionDelegate CompletionHandler_IReceivedStatusOnClientCallback = (success, context, state) => ((IReceivedStatusOnClientCallback)state).OnReceivedStatusOnClient(success, context.GetReceivedStatusOnClient()); static readonly BatchCompletionDelegate CompletionHandler_IReceivedMessageCallback = - (success, context, state) => ((IReceivedMessageCallback)state).OnReceivedMessage(success, context.GetReceivedMessage()); + (success, context, state) => ((IReceivedMessageCallback)state).OnReceivedMessage(success, context.GetReceivedMessageReader()); static readonly BatchCompletionDelegate CompletionHandler_IReceivedResponseHeadersCallback = (success, context, state) => ((IReceivedResponseHeadersCallback)state).OnReceivedResponseHeaders(success, context.GetReceivedInitialMetadata()); static readonly BatchCompletionDelegate CompletionHandler_ISendCompletionCallback = diff --git a/src/csharp/Grpc.Core/Internal/DefaultDeserializationContext.cs b/src/csharp/Grpc.Core/Internal/DefaultDeserializationContext.cs index 7ace80e8d53..946c37de190 100644 --- a/src/csharp/Grpc.Core/Internal/DefaultDeserializationContext.cs +++ b/src/csharp/Grpc.Core/Internal/DefaultDeserializationContext.cs @@ -20,6 +20,10 @@ using Grpc.Core.Utils; using System; using System.Threading; +#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY +using System.Buffers; +#endif + namespace Grpc.Core.Internal { internal class DefaultDeserializationContext : DeserializationContext @@ -27,40 +31,71 @@ namespace Grpc.Core.Internal static readonly ThreadLocal threadLocalInstance = new ThreadLocal(() => new DefaultDeserializationContext(), false); - byte[] payload; - bool alreadyCalledPayloadAsNewBuffer; + IBufferReader bufferReader; + int payloadLength; +#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY + ReusableSliceBuffer cachedSliceBuffer = new ReusableSliceBuffer(); +#endif public DefaultDeserializationContext() { Reset(); } - public override int PayloadLength => payload.Length; + public override int PayloadLength => payloadLength; public override byte[] PayloadAsNewBuffer() { - GrpcPreconditions.CheckState(!alreadyCalledPayloadAsNewBuffer); - alreadyCalledPayloadAsNewBuffer = true; - return payload; + var buffer = new byte[payloadLength]; + FillContinguousBuffer(bufferReader, buffer); + return buffer; + } + +#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY + public override ReadOnlySequence PayloadAsReadOnlySequence() + { + var sequence = cachedSliceBuffer.PopulateFrom(bufferReader); + GrpcPreconditions.CheckState(sequence.Length == payloadLength); + return sequence; } +#endif - public void Initialize(byte[] payload) + public void Initialize(IBufferReader bufferReader) { - this.payload = GrpcPreconditions.CheckNotNull(payload); - this.alreadyCalledPayloadAsNewBuffer = false; + this.bufferReader = GrpcPreconditions.CheckNotNull(bufferReader); + this.payloadLength = bufferReader.TotalLength.Value; // payload must not be null } public void Reset() { - this.payload = null; - this.alreadyCalledPayloadAsNewBuffer = true; // mark payload as read + this.bufferReader = null; + this.payloadLength = 0; +#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY + this.cachedSliceBuffer.Invalidate(); +#endif } - public static DefaultDeserializationContext GetInitializedThreadLocal(byte[] payload) + public static DefaultDeserializationContext GetInitializedThreadLocal(IBufferReader bufferReader) { var instance = threadLocalInstance.Value; - instance.Initialize(payload); + instance.Initialize(bufferReader); return instance; } + + private void FillContinguousBuffer(IBufferReader reader, byte[] destination) + { +#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY + PayloadAsReadOnlySequence().CopyTo(new Span(destination)); +#else + int offset = 0; + while (reader.TryGetNextSlice(out Slice slice)) + { + slice.CopyTo(new ArraySegment(destination, offset, (int)slice.Length)); + offset += (int)slice.Length; + } + // check that we filled the entire destination + GrpcPreconditions.CheckState(offset == payloadLength); +#endif + } } } diff --git a/src/csharp/Grpc.Core/Internal/INativeCall.cs b/src/csharp/Grpc.Core/Internal/INativeCall.cs index 5c35b2ba461..98117c6988a 100644 --- a/src/csharp/Grpc.Core/Internal/INativeCall.cs +++ b/src/csharp/Grpc.Core/Internal/INativeCall.cs @@ -22,7 +22,7 @@ namespace Grpc.Core.Internal { internal interface IUnaryResponseClientCallback { - void OnUnaryResponseClient(bool success, ClientSideStatus receivedStatus, byte[] receivedMessage, Metadata responseHeaders); + void OnUnaryResponseClient(bool success, ClientSideStatus receivedStatus, IBufferReader receivedMessageReader, Metadata responseHeaders); } // Received status for streaming response calls. @@ -33,7 +33,7 @@ namespace Grpc.Core.Internal internal interface IReceivedMessageCallback { - void OnReceivedMessage(bool success, byte[] receivedMessage); + void OnReceivedMessage(bool success, IBufferReader receivedMessageReader); } internal interface IReceivedResponseHeadersCallback diff --git a/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs b/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs index a1387aff562..b8a60b31c40 100644 --- a/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs +++ b/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs @@ -40,7 +40,7 @@ namespace Grpc.Core.Internal public readonly Delegates.grpcsharp_batch_context_create_delegate grpcsharp_batch_context_create; public readonly Delegates.grpcsharp_batch_context_recv_initial_metadata_delegate grpcsharp_batch_context_recv_initial_metadata; public readonly Delegates.grpcsharp_batch_context_recv_message_length_delegate grpcsharp_batch_context_recv_message_length; - public readonly Delegates.grpcsharp_batch_context_recv_message_to_buffer_delegate grpcsharp_batch_context_recv_message_to_buffer; + public readonly Delegates.grpcsharp_batch_context_recv_message_next_slice_peek_delegate grpcsharp_batch_context_recv_message_next_slice_peek; public readonly Delegates.grpcsharp_batch_context_recv_status_on_client_status_delegate grpcsharp_batch_context_recv_status_on_client_status; public readonly Delegates.grpcsharp_batch_context_recv_status_on_client_details_delegate grpcsharp_batch_context_recv_status_on_client_details; public readonly Delegates.grpcsharp_batch_context_recv_status_on_client_trailing_metadata_delegate grpcsharp_batch_context_recv_status_on_client_trailing_metadata; @@ -141,7 +141,7 @@ namespace Grpc.Core.Internal this.grpcsharp_batch_context_create = GetMethodDelegate(library); this.grpcsharp_batch_context_recv_initial_metadata = GetMethodDelegate(library); this.grpcsharp_batch_context_recv_message_length = GetMethodDelegate(library); - this.grpcsharp_batch_context_recv_message_to_buffer = GetMethodDelegate(library); + this.grpcsharp_batch_context_recv_message_next_slice_peek = GetMethodDelegate(library); this.grpcsharp_batch_context_recv_status_on_client_status = GetMethodDelegate(library); this.grpcsharp_batch_context_recv_status_on_client_details = GetMethodDelegate(library); this.grpcsharp_batch_context_recv_status_on_client_trailing_metadata = GetMethodDelegate(library); @@ -241,7 +241,7 @@ namespace Grpc.Core.Internal this.grpcsharp_batch_context_create = DllImportsFromStaticLib.grpcsharp_batch_context_create; this.grpcsharp_batch_context_recv_initial_metadata = DllImportsFromStaticLib.grpcsharp_batch_context_recv_initial_metadata; this.grpcsharp_batch_context_recv_message_length = DllImportsFromStaticLib.grpcsharp_batch_context_recv_message_length; - this.grpcsharp_batch_context_recv_message_to_buffer = DllImportsFromStaticLib.grpcsharp_batch_context_recv_message_to_buffer; + this.grpcsharp_batch_context_recv_message_next_slice_peek = DllImportsFromStaticLib.grpcsharp_batch_context_recv_message_next_slice_peek; this.grpcsharp_batch_context_recv_status_on_client_status = DllImportsFromStaticLib.grpcsharp_batch_context_recv_status_on_client_status; this.grpcsharp_batch_context_recv_status_on_client_details = DllImportsFromStaticLib.grpcsharp_batch_context_recv_status_on_client_details; this.grpcsharp_batch_context_recv_status_on_client_trailing_metadata = DllImportsFromStaticLib.grpcsharp_batch_context_recv_status_on_client_trailing_metadata; @@ -341,7 +341,7 @@ namespace Grpc.Core.Internal this.grpcsharp_batch_context_create = DllImportsFromSharedLib.grpcsharp_batch_context_create; this.grpcsharp_batch_context_recv_initial_metadata = DllImportsFromSharedLib.grpcsharp_batch_context_recv_initial_metadata; this.grpcsharp_batch_context_recv_message_length = DllImportsFromSharedLib.grpcsharp_batch_context_recv_message_length; - this.grpcsharp_batch_context_recv_message_to_buffer = DllImportsFromSharedLib.grpcsharp_batch_context_recv_message_to_buffer; + this.grpcsharp_batch_context_recv_message_next_slice_peek = DllImportsFromSharedLib.grpcsharp_batch_context_recv_message_next_slice_peek; this.grpcsharp_batch_context_recv_status_on_client_status = DllImportsFromSharedLib.grpcsharp_batch_context_recv_status_on_client_status; this.grpcsharp_batch_context_recv_status_on_client_details = DllImportsFromSharedLib.grpcsharp_batch_context_recv_status_on_client_details; this.grpcsharp_batch_context_recv_status_on_client_trailing_metadata = DllImportsFromSharedLib.grpcsharp_batch_context_recv_status_on_client_trailing_metadata; @@ -444,7 +444,7 @@ namespace Grpc.Core.Internal public delegate BatchContextSafeHandle grpcsharp_batch_context_create_delegate(); public delegate IntPtr grpcsharp_batch_context_recv_initial_metadata_delegate(BatchContextSafeHandle ctx); public delegate IntPtr grpcsharp_batch_context_recv_message_length_delegate(BatchContextSafeHandle ctx); - public delegate void grpcsharp_batch_context_recv_message_to_buffer_delegate(BatchContextSafeHandle ctx, byte[] buffer, UIntPtr bufferLen); + public delegate int grpcsharp_batch_context_recv_message_next_slice_peek_delegate(BatchContextSafeHandle ctx, out UIntPtr sliceLen, out IntPtr sliceDataPtr); public delegate StatusCode grpcsharp_batch_context_recv_status_on_client_status_delegate(BatchContextSafeHandle ctx); public delegate IntPtr grpcsharp_batch_context_recv_status_on_client_details_delegate(BatchContextSafeHandle ctx, out UIntPtr detailsLength); public delegate IntPtr grpcsharp_batch_context_recv_status_on_client_trailing_metadata_delegate(BatchContextSafeHandle ctx); @@ -562,7 +562,7 @@ namespace Grpc.Core.Internal public static extern IntPtr grpcsharp_batch_context_recv_message_length(BatchContextSafeHandle ctx); [DllImport(ImportName)] - public static extern void grpcsharp_batch_context_recv_message_to_buffer(BatchContextSafeHandle ctx, byte[] buffer, UIntPtr bufferLen); + public static extern int grpcsharp_batch_context_recv_message_next_slice_peek(BatchContextSafeHandle ctx, out UIntPtr sliceLen, out IntPtr sliceDataPtr); [DllImport(ImportName)] public static extern StatusCode grpcsharp_batch_context_recv_status_on_client_status(BatchContextSafeHandle ctx); @@ -858,7 +858,7 @@ namespace Grpc.Core.Internal public static extern IntPtr grpcsharp_batch_context_recv_message_length(BatchContextSafeHandle ctx); [DllImport(ImportName)] - public static extern void grpcsharp_batch_context_recv_message_to_buffer(BatchContextSafeHandle ctx, byte[] buffer, UIntPtr bufferLen); + public static extern int grpcsharp_batch_context_recv_message_next_slice_peek(BatchContextSafeHandle ctx, out UIntPtr sliceLen, out IntPtr sliceDataPtr); [DllImport(ImportName)] public static extern StatusCode grpcsharp_batch_context_recv_status_on_client_status(BatchContextSafeHandle ctx); diff --git a/src/csharp/Grpc.Core/Internal/ReusableSliceBuffer.cs b/src/csharp/Grpc.Core/Internal/ReusableSliceBuffer.cs new file mode 100644 index 00000000000..2d38509e511 --- /dev/null +++ b/src/csharp/Grpc.Core/Internal/ReusableSliceBuffer.cs @@ -0,0 +1,148 @@ +#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 + +#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY + +using Grpc.Core.Utils; +using System; +using System.Threading; + +using System.Buffers; + +namespace Grpc.Core.Internal +{ + internal class ReusableSliceBuffer + { + public const int MaxCachedSegments = 1024; // ~4MB payload for 4K slices + + readonly SliceSegment[] cachedSegments = new SliceSegment[MaxCachedSegments]; + int populatedSegmentCount; + + public ReadOnlySequence PopulateFrom(IBufferReader bufferReader) + { + populatedSegmentCount = 0; + long offset = 0; + SliceSegment prevSegment = null; + while (bufferReader.TryGetNextSlice(out Slice slice)) + { + // Initialize cached segment if still null or just allocate a new segment if we already reached MaxCachedSegments + var current = populatedSegmentCount < cachedSegments.Length ? cachedSegments[populatedSegmentCount] : new SliceSegment(); + if (current == null) + { + current = cachedSegments[populatedSegmentCount] = new SliceSegment(); + } + + current.Reset(slice, offset); + prevSegment?.SetNext(current); + + populatedSegmentCount ++; + offset += slice.Length; + prevSegment = current; + } + + // Not necessary for ending the ReadOnlySequence, but for making sure we + // don't keep more than MaxCachedSegments alive. + prevSegment?.SetNext(null); + + if (populatedSegmentCount == 0) + { + return ReadOnlySequence.Empty; + } + + var firstSegment = cachedSegments[0]; + var lastSegment = prevSegment; + return new ReadOnlySequence(firstSegment, 0, lastSegment, lastSegment.Memory.Length); + } + + public void Invalidate() + { + if (populatedSegmentCount == 0) + { + return; + } + var segment = cachedSegments[0]; + while (segment != null) + { + segment.Reset(new Slice(IntPtr.Zero, 0), 0); + var nextSegment = (SliceSegment) segment.Next; + segment.SetNext(null); + segment = nextSegment; + } + populatedSegmentCount = 0; + } + + // Represents a segment in ReadOnlySequence + // Segment is backed by Slice and the instances are reusable. + private class SliceSegment : ReadOnlySequenceSegment + { + readonly SliceMemoryManager pointerMemoryManager = new SliceMemoryManager(); + + public void Reset(Slice slice, long runningIndex) + { + pointerMemoryManager.Reset(slice); + Memory = pointerMemoryManager.Memory; // maybe not always necessary + RunningIndex = runningIndex; + } + + public void SetNext(ReadOnlySequenceSegment next) + { + Next = next; + } + } + + // Allow creating instances of Memory from Slice. + // Represents a chunk of native memory, but doesn't manage its lifetime. + // Instances of this class are reuseable - they can be reset to point to a different memory chunk. + // That is important to make the instances cacheable (rather then creating new instances + // the old ones will be reused to reduce GC pressure). + private class SliceMemoryManager : MemoryManager + { + private Slice slice; + + public void Reset(Slice slice) + { + this.slice = slice; + } + + public void Reset() + { + Reset(new Slice(IntPtr.Zero, 0)); + } + + public override Span GetSpan() + { + return slice.ToSpanUnsafe(); + } + + public override MemoryHandle Pin(int elementIndex = 0) + { + throw new NotSupportedException(); + } + + public override void Unpin() + { + } + + protected override void Dispose(bool disposing) + { + // NOP + } + } + } +} +#endif diff --git a/src/csharp/Grpc.Core/Internal/Slice.cs b/src/csharp/Grpc.Core/Internal/Slice.cs new file mode 100644 index 00000000000..22eb9537951 --- /dev/null +++ b/src/csharp/Grpc.Core/Internal/Slice.cs @@ -0,0 +1,68 @@ +#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 System.Runtime.InteropServices; +using System.Threading; +using Grpc.Core.Utils; + +namespace Grpc.Core.Internal +{ + /// + /// Slice of native memory. + /// Rough equivalent of grpc_slice (but doesn't support inlined slices, just a pointer to data and length) + /// + internal struct Slice + { + private readonly IntPtr dataPtr; + private readonly int length; + + public Slice(IntPtr dataPtr, int length) + { + this.dataPtr = dataPtr; + this.length = length; + } + + public int Length => length; + + // copies data of the slice to given span. + // there needs to be enough space in the destination buffer + public void CopyTo(ArraySegment destination) + { + Marshal.Copy(dataPtr, destination.Array, destination.Offset, length); + } + +#if GRPC_CSHARP_SUPPORT_SYSTEM_MEMORY + public Span ToSpanUnsafe() + { + unsafe + { + return new Span((byte*) dataPtr, length); + } + } +#endif + + /// + /// Returns a that represents the current . + /// + public override string ToString() + { + return string.Format("[Slice: dataPtr={0}, length={1}]", dataPtr, length); + } + } +} diff --git a/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets b/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets index 1a862337c58..b1030ba1f8b 100644 --- a/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets +++ b/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets @@ -28,6 +28,7 @@ True $(Protobuf_OutputPath) + MSBuild:Compile diff --git a/src/csharp/ext/grpc_csharp_ext.c b/src/csharp/ext/grpc_csharp_ext.c index 91d3957dbf0..51e498bcfb8 100644 --- a/src/csharp/ext/grpc_csharp_ext.c +++ b/src/csharp/ext/grpc_csharp_ext.c @@ -59,12 +59,16 @@ typedef struct grpcsharp_batch_context { } send_status_from_server; grpc_metadata_array recv_initial_metadata; grpc_byte_buffer* recv_message; + grpc_byte_buffer_reader* recv_message_reader; struct { grpc_metadata_array trailing_metadata; grpc_status_code status; grpc_slice status_details; } recv_status_on_client; int recv_close_on_server_cancelled; + + /* reserve space for byte_buffer_reader */ + grpc_byte_buffer_reader reserved_recv_message_reader; } grpcsharp_batch_context; GPR_EXPORT grpcsharp_batch_context* GPR_CALLTYPE @@ -206,6 +210,9 @@ grpcsharp_batch_context_reset(grpcsharp_batch_context* ctx) { grpcsharp_metadata_array_destroy_metadata_only(&(ctx->recv_initial_metadata)); + if (ctx->recv_message_reader) { + grpc_byte_buffer_reader_destroy(ctx->recv_message_reader); + } grpc_byte_buffer_destroy(ctx->recv_message); grpcsharp_metadata_array_destroy_metadata_only( @@ -264,27 +271,42 @@ GPR_EXPORT intptr_t GPR_CALLTYPE grpcsharp_batch_context_recv_message_length( } /* - * Copies data from recv_message to a buffer. Fatal error occurs if - * buffer is too small. + * Gets the next slice from recv_message byte buffer. + * Returns 1 if a slice was get successfully, 0 if there are no more slices to + * read. Set slice_len to the length of the slice and the slice_data_ptr to + * point to slice's data. Caller must ensure that the byte buffer being read + * from stays alive as long as the data of the slice are being accessed + * (grpc_byte_buffer_reader_peek method is used internally) + * + * Remarks: + * Slices can only be iterated once. + * Initializes recv_message_buffer_reader if it was not initialized yet. */ -GPR_EXPORT void GPR_CALLTYPE grpcsharp_batch_context_recv_message_to_buffer( - const grpcsharp_batch_context* ctx, char* buffer, size_t buffer_len) { - grpc_byte_buffer_reader reader; - grpc_slice slice; - size_t offset = 0; +GPR_EXPORT int GPR_CALLTYPE +grpcsharp_batch_context_recv_message_next_slice_peek( + grpcsharp_batch_context* ctx, size_t* slice_len, uint8_t** slice_data_ptr) { + *slice_len = 0; + *slice_data_ptr = NULL; - GPR_ASSERT(grpc_byte_buffer_reader_init(&reader, ctx->recv_message)); + if (!ctx->recv_message) { + return 0; + } - while (grpc_byte_buffer_reader_next(&reader, &slice)) { - size_t len = GRPC_SLICE_LENGTH(slice); - GPR_ASSERT(offset + len <= buffer_len); - memcpy(buffer + offset, GRPC_SLICE_START_PTR(slice), - GRPC_SLICE_LENGTH(slice)); - offset += len; - grpc_slice_unref(slice); + if (!ctx->recv_message_reader) { + ctx->recv_message_reader = &ctx->reserved_recv_message_reader; + GPR_ASSERT(grpc_byte_buffer_reader_init(ctx->recv_message_reader, + ctx->recv_message)); } - grpc_byte_buffer_reader_destroy(&reader); + grpc_slice* slice_ptr; + if (!grpc_byte_buffer_reader_peek(ctx->recv_message_reader, &slice_ptr)) { + return 0; + } + + /* recv_message buffer must not be deleted before all the data is read */ + *slice_len = GRPC_SLICE_LENGTH(*slice_ptr); + *slice_data_ptr = GRPC_SLICE_START_PTR(*slice_ptr); + return 1; } GPR_EXPORT grpc_status_code GPR_CALLTYPE diff --git a/src/csharp/tests.json b/src/csharp/tests.json index c1e7fc1a6bf..cacdb305d2e 100644 --- a/src/csharp/tests.json +++ b/src/csharp/tests.json @@ -7,8 +7,12 @@ "Grpc.Core.Internal.Tests.ChannelArgsSafeHandleTest", "Grpc.Core.Internal.Tests.CompletionQueueEventTest", "Grpc.Core.Internal.Tests.CompletionQueueSafeHandleTest", + "Grpc.Core.Internal.Tests.DefaultDeserializationContextTest", "Grpc.Core.Internal.Tests.DefaultObjectPoolTest", + "Grpc.Core.Internal.Tests.FakeBufferReaderManagerTest", "Grpc.Core.Internal.Tests.MetadataArraySafeHandleTest", + "Grpc.Core.Internal.Tests.ReusableSliceBufferTest", + "Grpc.Core.Internal.Tests.SliceTest", "Grpc.Core.Internal.Tests.TimespecTest", "Grpc.Core.Tests.AppDomainUnloadTest", "Grpc.Core.Tests.AuthContextTest", diff --git a/src/csharp/unitypackage/unitypackage_skeleton/Plugins/Grpc.Core/runtimes/grpc_csharp_ext_dummy_stubs.c b/src/csharp/unitypackage/unitypackage_skeleton/Plugins/Grpc.Core/runtimes/grpc_csharp_ext_dummy_stubs.c index 0e9d56f5bdf..c2f9d20333d 100644 --- a/src/csharp/unitypackage/unitypackage_skeleton/Plugins/Grpc.Core/runtimes/grpc_csharp_ext_dummy_stubs.c +++ b/src/csharp/unitypackage/unitypackage_skeleton/Plugins/Grpc.Core/runtimes/grpc_csharp_ext_dummy_stubs.c @@ -46,7 +46,7 @@ void grpcsharp_batch_context_recv_message_length() { fprintf(stderr, "Should never reach here"); abort(); } -void grpcsharp_batch_context_recv_message_to_buffer() { +void grpcsharp_batch_context_recv_message_next_slice_peek() { fprintf(stderr, "Should never reach here"); abort(); } diff --git a/src/objective-c/GRPCClient/GRPCCall.h b/src/objective-c/GRPCClient/GRPCCall.h index 97ece7e0c9b..a430d5ca158 100644 --- a/src/objective-c/GRPCClient/GRPCCall.h +++ b/src/objective-c/GRPCClient/GRPCCall.h @@ -135,7 +135,8 @@ typedef NS_ENUM(NSUInteger, GRPCErrorCode) { /** * The server is currently unavailable. This is most likely a transient condition and may be - * corrected by retrying with a backoff. + * corrected by retrying with a backoff. Note that it is not always safe to retry + * non-idempotent operations. */ GRPCErrorCodeUnavailable = 14, diff --git a/src/proto/grpc/testing/BUILD b/src/proto/grpc/testing/BUILD index 16e47d81061..727c99cf99c 100644 --- a/src/proto/grpc/testing/BUILD +++ b/src/proto/grpc/testing/BUILD @@ -146,6 +146,37 @@ grpc_proto_library( ], ) +# Test that grpc_proto_library/cc_grpc_library can consume generated files +genrule( + name = "messages_gen_proto_file", + srcs = ["messages.proto"], + outs = ["messages_gen.proto"], + cmd = "cp $< $@", +) + +grpc_proto_library( + name = "messages_gen_proto", + srcs = ["messages_gen_proto_file"], + has_services = False, +) + +genrule( + name = "test_gen_proto_file", + srcs = ["test.proto"], + outs = ["test_gen.proto"], + cmd = "sed 's/messages.proto/messages_gen.proto/' $< > $@", +) + +# Consume generated files in srcs and in deps +grpc_proto_library( + name = "test_gen_proto", + srcs = ["test_gen_proto_file"], + deps = [ + "empty_proto", + "messages_gen_proto", + ], +) + proto_library( name = "test_proto_descriptor", srcs = ["test.proto"], diff --git a/src/ruby/lib/grpc/errors.rb b/src/ruby/lib/grpc/errors.rb index 83beb6a906a..0b27bee1b9c 100644 --- a/src/ruby/lib/grpc/errors.rb +++ b/src/ruby/lib/grpc/errors.rb @@ -13,7 +13,6 @@ # limitations under the License. require_relative './grpc' -require_relative './google_rpc_status_utils' # GRPC contains the General RPC module. module GRPC @@ -58,10 +57,11 @@ module GRPC # # @return [Google::Rpc::Status, nil] def to_rpc_status + # Lazily require google_rpc_status_utils to scope + # loading protobuf_c.so to the users of this method. + require_relative './google_rpc_status_utils' status = to_status - return if status.nil? - GoogleRpcStatusUtils.extract_google_rpc_status(status) rescue Google::Protobuf::ParseError => parse_error GRPC.logger.warn('parse error: to_rpc_status failed') diff --git a/templates/src/csharp/Grpc.Core/Internal/native_methods.include b/templates/src/csharp/Grpc.Core/Internal/native_methods.include index e8ec4c87b06..4a31171aa27 100644 --- a/templates/src/csharp/Grpc.Core/Internal/native_methods.include +++ b/templates/src/csharp/Grpc.Core/Internal/native_methods.include @@ -6,7 +6,7 @@ native_method_signatures = [ 'BatchContextSafeHandle grpcsharp_batch_context_create()', 'IntPtr grpcsharp_batch_context_recv_initial_metadata(BatchContextSafeHandle ctx)', 'IntPtr grpcsharp_batch_context_recv_message_length(BatchContextSafeHandle ctx)', - 'void grpcsharp_batch_context_recv_message_to_buffer(BatchContextSafeHandle ctx, byte[] buffer, UIntPtr bufferLen)', + 'int grpcsharp_batch_context_recv_message_next_slice_peek(BatchContextSafeHandle ctx, out UIntPtr sliceLen, out IntPtr sliceDataPtr)', 'StatusCode grpcsharp_batch_context_recv_status_on_client_status(BatchContextSafeHandle ctx)', 'IntPtr grpcsharp_batch_context_recv_status_on_client_details(BatchContextSafeHandle ctx, out UIntPtr detailsLength)', 'IntPtr grpcsharp_batch_context_recv_status_on_client_trailing_metadata(BatchContextSafeHandle ctx)', diff --git a/test/core/gpr/alloc_test.cc b/test/core/gpr/alloc_test.cc index fb6bc9a0bdd..0b110e2b77d 100644 --- a/test/core/gpr/alloc_test.cc +++ b/test/core/gpr/alloc_test.cc @@ -31,21 +31,12 @@ static void fake_free(void* addr) { *(static_cast(addr)) = static_cast(0xdeadd00d); } -static void* fake_aligned_malloc(size_t size, size_t alignment) { - return (void*)(size + alignment); -} - -static void fake_aligned_free(void* addr) { - *(static_cast(addr)) = static_cast(0xcafef00d); -} - static void test_custom_allocs() { const gpr_allocation_functions default_fns = gpr_get_allocation_functions(); intptr_t addr_to_free = 0; char* i; - gpr_allocation_functions fns = {fake_malloc, nullptr, - fake_realloc, fake_free, - fake_aligned_malloc, fake_aligned_free}; + gpr_allocation_functions fns = {fake_malloc, nullptr, fake_realloc, + fake_free}; gpr_set_allocation_functions(fns); GPR_ASSERT((void*)(size_t)0xdeadbeef == gpr_malloc(0xdeadbeef)); @@ -54,11 +45,6 @@ static void test_custom_allocs() { gpr_free(&addr_to_free); GPR_ASSERT(addr_to_free == (intptr_t)0xdeadd00d); - GPR_ASSERT((void*)(size_t)(0xdeadbeef + 64) == - gpr_malloc_aligned(0xdeadbeef, 64)); - gpr_free_aligned(&addr_to_free); - GPR_ASSERT(addr_to_free == (intptr_t)0xcafef00d); - /* Restore and check we don't get funky values and that we don't leak */ gpr_set_allocation_functions(default_fns); GPR_ASSERT((void*)sizeof(*i) != diff --git a/test/core/util/memory_counters.cc b/test/core/util/memory_counters.cc index 60d22b18309..787fb76e48b 100644 --- a/test/core/util/memory_counters.cc +++ b/test/core/util/memory_counters.cc @@ -54,10 +54,9 @@ static void* guard_malloc(size_t size) { NO_BARRIER_FETCH_ADD(&g_memory_counters.total_allocs_absolute, (gpr_atm)1); NO_BARRIER_FETCH_ADD(&g_memory_counters.total_allocs_relative, (gpr_atm)1); void* ptr = g_old_allocs.malloc_fn( - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(sizeof(size)) + size); + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(size)) + size); *static_cast(ptr) = size; - return static_cast(ptr) + - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(sizeof(size)); + return static_cast(ptr) + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(size)); } static void* guard_realloc(void* vptr, size_t size) { @@ -68,36 +67,31 @@ static void* guard_realloc(void* vptr, size_t size) { guard_free(vptr); return nullptr; } - void* ptr = static_cast(vptr) - - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(sizeof(size)); + void* ptr = + static_cast(vptr) - GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(size)); NO_BARRIER_FETCH_ADD(&g_memory_counters.total_size_absolute, (gpr_atm)size); NO_BARRIER_FETCH_ADD(&g_memory_counters.total_size_relative, -*static_cast(ptr)); NO_BARRIER_FETCH_ADD(&g_memory_counters.total_size_relative, (gpr_atm)size); NO_BARRIER_FETCH_ADD(&g_memory_counters.total_allocs_absolute, (gpr_atm)1); ptr = g_old_allocs.realloc_fn( - ptr, GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(sizeof(size)) + size); + ptr, GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(size)) + size); *static_cast(ptr) = size; - return static_cast(ptr) + - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(sizeof(size)); + return static_cast(ptr) + GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(size)); } static void guard_free(void* vptr) { if (vptr == nullptr) return; - void* ptr = static_cast(vptr) - - GPR_ROUND_UP_TO_MAX_ALIGNMENT_SIZE(sizeof(size_t)); + void* ptr = + static_cast(vptr) - GPR_ROUND_UP_TO_ALIGNMENT_SIZE(sizeof(size_t)); NO_BARRIER_FETCH_ADD(&g_memory_counters.total_size_relative, -*static_cast(ptr)); NO_BARRIER_FETCH_ADD(&g_memory_counters.total_allocs_relative, -(gpr_atm)1); g_old_allocs.free_fn(ptr); } -// NB: We do not specify guard_malloc_aligned/guard_free_aligned methods. Since -// they are null, calls to gpr_malloc_aligned/gpr_free_aligned are executed as a -// wrapper over gpr_malloc/gpr_free, which do use guard_malloc/guard_free, and -// thus their allocations are tracked as well. -struct gpr_allocation_functions g_guard_allocs = { - guard_malloc, nullptr, guard_realloc, guard_free, nullptr, nullptr}; +struct gpr_allocation_functions g_guard_allocs = {guard_malloc, nullptr, + guard_realloc, guard_free}; void grpc_memory_counters_init() { memset(&g_memory_counters, 0, sizeof(g_memory_counters)); diff --git a/tools/internal_ci/linux/pull_request/grpc_basictests_c_cpp_dbg.cfg b/tools/internal_ci/linux/pull_request/grpc_basictests_csharp.cfg similarity index 85% rename from tools/internal_ci/linux/pull_request/grpc_basictests_c_cpp_dbg.cfg rename to tools/internal_ci/linux/pull_request/grpc_basictests_csharp.cfg index 8a67d28ce4c..6a7029d9f83 100644 --- a/tools/internal_ci/linux/pull_request/grpc_basictests_c_cpp_dbg.cfg +++ b/tools/internal_ci/linux/pull_request/grpc_basictests_csharp.cfg @@ -1,4 +1,4 @@ -# Copyright 2017 gRPC authors. +# 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. @@ -16,7 +16,7 @@ # Location of the continuous shell script in repository. build_file: "grpc/tools/internal_ci/linux/grpc_run_tests_matrix.sh" -timeout_mins: 240 +timeout_mins: 60 action { define_artifacts { regex: "**/*sponge_log.*" @@ -26,5 +26,5 @@ action { env_vars { key: "RUN_TESTS_FLAGS" - value: "-f basictests linux corelang dbg --inner_jobs 16 -j 1 --internal_ci --max_time=3600" + value: "-f basictests linux csharp --inner_jobs 16 -j 2 --internal_ci --max_time=3600" } diff --git a/tools/internal_ci/linux/pull_request/grpc_basictests_c_cpp_opt.cfg b/tools/internal_ci/linux/pull_request/grpc_basictests_node.cfg similarity index 85% rename from tools/internal_ci/linux/pull_request/grpc_basictests_c_cpp_opt.cfg rename to tools/internal_ci/linux/pull_request/grpc_basictests_node.cfg index a681978b6e2..a0c67cc136c 100644 --- a/tools/internal_ci/linux/pull_request/grpc_basictests_c_cpp_opt.cfg +++ b/tools/internal_ci/linux/pull_request/grpc_basictests_node.cfg @@ -1,4 +1,4 @@ -# Copyright 2017 gRPC authors. +# 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. @@ -16,7 +16,7 @@ # Location of the continuous shell script in repository. build_file: "grpc/tools/internal_ci/linux/grpc_run_tests_matrix.sh" -timeout_mins: 240 +timeout_mins: 60 action { define_artifacts { regex: "**/*sponge_log.*" @@ -26,5 +26,5 @@ action { env_vars { key: "RUN_TESTS_FLAGS" - value: "-f basictests linux corelang opt --inner_jobs 16 -j 1 --internal_ci --max_time=3600" + value: "-f basictests linux grpc-node --inner_jobs 16 -j 2 --internal_ci --max_time=3600" } diff --git a/tools/internal_ci/linux/pull_request/grpc_basictests_php.cfg b/tools/internal_ci/linux/pull_request/grpc_basictests_php.cfg new file mode 100644 index 00000000000..c844fcfae00 --- /dev/null +++ b/tools/internal_ci/linux/pull_request/grpc_basictests_php.cfg @@ -0,0 +1,30 @@ +# 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. + +# Config file for the internal CI (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc/tools/internal_ci/linux/grpc_run_tests_matrix.sh" +timeout_mins: 60 +action { + define_artifacts { + regex: "**/*sponge_log.*" + regex: "github/grpc/reports/**" + } +} + +env_vars { + key: "RUN_TESTS_FLAGS" + value: "-f basictests linux php --inner_jobs 16 -j 2 --internal_ci --max_time=3600" +} diff --git a/tools/internal_ci/linux/pull_request/grpc_basictests_python.cfg b/tools/internal_ci/linux/pull_request/grpc_basictests_python.cfg new file mode 100644 index 00000000000..b4e91c2a7e2 --- /dev/null +++ b/tools/internal_ci/linux/pull_request/grpc_basictests_python.cfg @@ -0,0 +1,30 @@ +# 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. + +# Config file for the internal CI (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc/tools/internal_ci/linux/grpc_run_tests_matrix.sh" +timeout_mins: 60 +action { + define_artifacts { + regex: "**/*sponge_log.*" + regex: "github/grpc/reports/**" + } +} + +env_vars { + key: "RUN_TESTS_FLAGS" + value: "-f basictests linux python --inner_jobs 16 -j 2 --internal_ci --max_time=3600" +} diff --git a/tools/internal_ci/linux/pull_request/grpc_basictests_ruby.cfg b/tools/internal_ci/linux/pull_request/grpc_basictests_ruby.cfg new file mode 100644 index 00000000000..49d3dad592e --- /dev/null +++ b/tools/internal_ci/linux/pull_request/grpc_basictests_ruby.cfg @@ -0,0 +1,30 @@ +# 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. + +# Config file for the internal CI (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc/tools/internal_ci/linux/grpc_run_tests_matrix.sh" +timeout_mins: 60 +action { + define_artifacts { + regex: "**/*sponge_log.*" + regex: "github/grpc/reports/**" + } +} + +env_vars { + key: "RUN_TESTS_FLAGS" + value: "-f basictests linux ruby --inner_jobs 16 -j 2 --internal_ci --max_time=3600" +} diff --git a/tools/internal_ci/macos/grpc_basictests_c_cpp.cfg b/tools/internal_ci/macos/grpc_basictests_c_cpp.cfg index 9783dd64673..f16e3e8ee68 100644 --- a/tools/internal_ci/macos/grpc_basictests_c_cpp.cfg +++ b/tools/internal_ci/macos/grpc_basictests_c_cpp.cfg @@ -17,7 +17,7 @@ # Location of the continuous shell script in repository. build_file: "grpc/tools/internal_ci/macos/grpc_run_tests_matrix.sh" gfile_resources: "/bigstore/grpc-testing-secrets/gcp_credentials/GrpcTesting-d0eeee2db331.json" -timeout_mins: 60 +timeout_mins: 120 action { define_artifacts { regex: "**/*sponge_log.*" diff --git a/tools/internal_ci/macos/pull_request/grpc_basictests_c_cpp.cfg b/tools/internal_ci/macos/pull_request/grpc_basictests_c_cpp.cfg new file mode 100644 index 00000000000..00f402d389b --- /dev/null +++ b/tools/internal_ci/macos/pull_request/grpc_basictests_c_cpp.cfg @@ -0,0 +1,31 @@ +# 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. + +# Config file for the internal CI (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc/tools/internal_ci/macos/grpc_run_tests_matrix.sh" +gfile_resources: "/bigstore/grpc-testing-secrets/gcp_credentials/GrpcTesting-d0eeee2db331.json" +timeout_mins: 120 +action { + define_artifacts { + regex: "**/*sponge_log.*" + regex: "github/grpc/reports/**" + } +} + +env_vars { + key: "RUN_TESTS_FLAGS" + value: "-f basictests macos corelang --internal_ci -j 1 --inner_jobs 4 --max_time=3600" +} diff --git a/tools/internal_ci/macos/pull_request/grpc_basictests_csharp.cfg b/tools/internal_ci/macos/pull_request/grpc_basictests_csharp.cfg new file mode 100644 index 00000000000..c9c1403693c --- /dev/null +++ b/tools/internal_ci/macos/pull_request/grpc_basictests_csharp.cfg @@ -0,0 +1,31 @@ +# 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. + +# Config file for the internal CI (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc/tools/internal_ci/macos/grpc_run_tests_matrix.sh" +gfile_resources: "/bigstore/grpc-testing-secrets/gcp_credentials/GrpcTesting-d0eeee2db331.json" +timeout_mins: 60 +action { + define_artifacts { + regex: "**/*sponge_log.*" + regex: "github/grpc/reports/**" + } +} + +env_vars { + key: "RUN_TESTS_FLAGS" + value: "-f basictests macos csharp --internal_ci -j 1 --inner_jobs 4 --max_time=3600" +} diff --git a/tools/internal_ci/macos/pull_request/grpc_basictests_node.cfg b/tools/internal_ci/macos/pull_request/grpc_basictests_node.cfg new file mode 100644 index 00000000000..ed729ef5a91 --- /dev/null +++ b/tools/internal_ci/macos/pull_request/grpc_basictests_node.cfg @@ -0,0 +1,31 @@ +# 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. + +# Config file for the internal CI (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc/tools/internal_ci/macos/grpc_run_tests_matrix.sh" +gfile_resources: "/bigstore/grpc-testing-secrets/gcp_credentials/GrpcTesting-d0eeee2db331.json" +timeout_mins: 60 +action { + define_artifacts { + regex: "**/*sponge_log.*" + regex: "github/grpc/reports/**" + } +} + +env_vars { + key: "RUN_TESTS_FLAGS" + value: "-f basictests macos grpc-node --internal_ci -j 1 --inner_jobs 4 --max_time=3600" +} diff --git a/tools/internal_ci/macos/pull_request/grpc_basictests_php.cfg b/tools/internal_ci/macos/pull_request/grpc_basictests_php.cfg new file mode 100644 index 00000000000..11b17874c28 --- /dev/null +++ b/tools/internal_ci/macos/pull_request/grpc_basictests_php.cfg @@ -0,0 +1,31 @@ +# 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. + +# Config file for the internal CI (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc/tools/internal_ci/macos/grpc_run_tests_matrix.sh" +gfile_resources: "/bigstore/grpc-testing-secrets/gcp_credentials/GrpcTesting-d0eeee2db331.json" +timeout_mins: 60 +action { + define_artifacts { + regex: "**/*sponge_log.*" + regex: "github/grpc/reports/**" + } +} + +env_vars { + key: "RUN_TESTS_FLAGS" + value: "-f basictests macos php --internal_ci -j 1 --inner_jobs 4 --max_time=3600" +} diff --git a/tools/internal_ci/macos/pull_request/grpc_basictests_python.cfg b/tools/internal_ci/macos/pull_request/grpc_basictests_python.cfg new file mode 100644 index 00000000000..c86871f80cb --- /dev/null +++ b/tools/internal_ci/macos/pull_request/grpc_basictests_python.cfg @@ -0,0 +1,31 @@ +# 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. + +# Config file for the internal CI (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc/tools/internal_ci/macos/grpc_run_tests_matrix.sh" +gfile_resources: "/bigstore/grpc-testing-secrets/gcp_credentials/GrpcTesting-d0eeee2db331.json" +timeout_mins: 60 +action { + define_artifacts { + regex: "**/*sponge_log.*" + regex: "github/grpc/reports/**" + } +} + +env_vars { + key: "RUN_TESTS_FLAGS" + value: "-f basictests macos python --internal_ci -j 1 --inner_jobs 4 --max_time=3600" +} diff --git a/tools/internal_ci/macos/pull_request/grpc_basictests_ruby.cfg b/tools/internal_ci/macos/pull_request/grpc_basictests_ruby.cfg new file mode 100644 index 00000000000..5729bf8f751 --- /dev/null +++ b/tools/internal_ci/macos/pull_request/grpc_basictests_ruby.cfg @@ -0,0 +1,31 @@ +# 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. + +# Config file for the internal CI (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc/tools/internal_ci/macos/grpc_run_tests_matrix.sh" +gfile_resources: "/bigstore/grpc-testing-secrets/gcp_credentials/GrpcTesting-d0eeee2db331.json" +timeout_mins: 60 +action { + define_artifacts { + regex: "**/*sponge_log.*" + regex: "github/grpc/reports/**" + } +} + +env_vars { + key: "RUN_TESTS_FLAGS" + value: "-f basictests macos ruby --internal_ci -j 1 --inner_jobs 4 --max_time=3600" +} diff --git a/tools/internal_ci/windows/grpc_basictests_c.cfg b/tools/internal_ci/windows/grpc_basictests_c.cfg index 150a28e3f89..223cf389d0e 100644 --- a/tools/internal_ci/windows/grpc_basictests_c.cfg +++ b/tools/internal_ci/windows/grpc_basictests_c.cfg @@ -16,7 +16,7 @@ # Location of the continuous shell script in repository. build_file: "grpc/tools/internal_ci/windows/grpc_run_tests_matrix.bat" -timeout_mins: 60 +timeout_mins: 120 action { define_artifacts { regex: "**/*sponge_log.*" diff --git a/tools/internal_ci/windows/pull_request/grpc_basictests_c.cfg b/tools/internal_ci/windows/pull_request/grpc_basictests_c.cfg new file mode 100644 index 00000000000..4cedcae9a87 --- /dev/null +++ b/tools/internal_ci/windows/pull_request/grpc_basictests_c.cfg @@ -0,0 +1,30 @@ +# 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. + +# Config file for the internal CI (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc/tools/internal_ci/windows/grpc_run_tests_matrix.bat" +timeout_mins: 120 +action { + define_artifacts { + regex: "**/*sponge_log.*" + regex: "github/grpc/reports/**" + } +} + +env_vars { + key: "RUN_TESTS_FLAGS" + value: "-f basictests windows c -j 1 --inner_jobs 8 --internal_ci --max_time=3600" +} diff --git a/tools/internal_ci/windows/pull_request/grpc_basictests_csharp.cfg b/tools/internal_ci/windows/pull_request/grpc_basictests_csharp.cfg new file mode 100644 index 00000000000..e1c5af108ec --- /dev/null +++ b/tools/internal_ci/windows/pull_request/grpc_basictests_csharp.cfg @@ -0,0 +1,30 @@ +# 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. + +# Config file for the internal CI (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc/tools/internal_ci/windows/grpc_run_tests_matrix.bat" +timeout_mins: 60 +action { + define_artifacts { + regex: "**/*sponge_log.*" + regex: "github/grpc/reports/**" + } +} + +env_vars { + key: "RUN_TESTS_FLAGS" + value: "-f basictests windows csharp -j 1 --inner_jobs 8 --internal_ci --max_time=3600" +} diff --git a/tools/internal_ci/windows/pull_request/grpc_basictests_python.cfg b/tools/internal_ci/windows/pull_request/grpc_basictests_python.cfg new file mode 100644 index 00000000000..6947e8e1b4e --- /dev/null +++ b/tools/internal_ci/windows/pull_request/grpc_basictests_python.cfg @@ -0,0 +1,30 @@ +# 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. + +# Config file for the internal CI (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc/tools/internal_ci/windows/grpc_run_tests_matrix.bat" +timeout_mins: 60 +action { + define_artifacts { + regex: "**/*sponge_log.*" + regex: "github/grpc/reports/**" + } +} + +env_vars { + key: "RUN_TESTS_FLAGS" + value: "-f basictests windows python -j 1 --inner_jobs 8 --internal_ci --max_time=3600" +} diff --git a/tools/internal_ci/windows/pull_request/grpc_bazel_rbe_dbg.cfg b/tools/internal_ci/windows/pull_request/grpc_bazel_rbe_dbg.cfg new file mode 100644 index 00000000000..f958fed33dd --- /dev/null +++ b/tools/internal_ci/windows/pull_request/grpc_bazel_rbe_dbg.cfg @@ -0,0 +1,32 @@ +# 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. + +# Config file for the internal CI (in protobuf text format) + +# Location of the continuous shell script in repository. +build_file: "grpc/tools/internal_ci/windows/bazel_rbe.bat" + +timeout_mins: 60 + +gfile_resources: "/bigstore/grpc-testing-secrets/gcp_credentials/GrpcTesting-d0eeee2db331.json" +gfile_resources: "/bigstore/grpc-testing-secrets/gcp_credentials/resultstore_api_key" +gfile_resources: "/bigstore/grpc-testing-secrets/gcp_credentials/rbe-windows-credentials.json" + +bazel_setting { + # In order for Kokoro to recognize this as a bazel build and publish the bazel resultstore link, + # the bazel_setting section needs to be present and "upsalite_frontend_address" needs to be + # set. The rest of configuration from bazel_setting is unused (we configure everything when bazel + # command is invoked). + upsalite_frontend_address: "https://source.cloud.google.com" +}