Merge branch 'master' into fix-b-148110727

pull/38096/head
AJ Heller 2 weeks ago
commit 4f5fb321bd
  1. 2
      .pylintrc
  2. 2
      .pylintrc-examples
  3. 2
      .pylintrc-tests
  4. 13
      BUILD
  5. 554
      CMakeLists.txt
  6. 16
      bazel/experiments.bzl
  7. 460
      build_autogenerated.yaml
  8. 2
      examples/python/cancellation/search.py
  9. 4
      examples/python/debug/README.md
  10. 4
      examples/python/interceptors/async/README.md
  11. 6
      examples/python/keep_alive/greeter_client.py
  12. 2
      examples/python/keep_alive/greeter_server.py
  13. 2
      examples/python/route_guide/asyncio_route_guide_client.py
  14. 2
      examples/python/wait_for_ready/wait_for_ready_with_client_timeout_example_server.py
  15. 1
      gRPC-C++.podspec
  16. 12
      include/grpcpp/impl/codegen/config_protobuf.h
  17. 6
      include/grpcpp/impl/generic_serialize.h
  18. 2
      setup.cfg
  19. 2
      setup.py
  20. 66
      src/core/BUILD
  21. 51
      src/core/ext/transport/chaotic_good/chaotic_good_frame.proto
  22. 109
      src/core/ext/transport/chaotic_good/chaotic_good_transport.h
  23. 87
      src/core/ext/transport/chaotic_good/client/chaotic_good_connector.cc
  24. 5
      src/core/ext/transport/chaotic_good/client/chaotic_good_connector.h
  25. 202
      src/core/ext/transport/chaotic_good/client_transport.cc
  26. 16
      src/core/ext/transport/chaotic_good/client_transport.h
  27. 554
      src/core/ext/transport/chaotic_good/frame.cc
  28. 172
      src/core/ext/transport/chaotic_good/frame.h
  29. 40
      src/core/ext/transport/chaotic_good/frame_header.cc
  30. 60
      src/core/ext/transport/chaotic_good/frame_header.h
  31. 75
      src/core/ext/transport/chaotic_good/server/chaotic_good_server.cc
  32. 4
      src/core/ext/transport/chaotic_good/server/chaotic_good_server.h
  33. 302
      src/core/ext/transport/chaotic_good/server_transport.cc
  34. 30
      src/core/ext/transport/chaotic_good/server_transport.h
  35. 79
      src/core/ext/transport/chaotic_good/settings_metadata.cc
  36. 45
      src/core/ext/transport/chaotic_good/settings_metadata.h
  37. 70
      src/core/ext/transport/chaotic_good_legacy/server_transport.cc
  38. 64
      src/core/lib/experiments/experiments.cc
  39. 22
      src/core/lib/experiments/experiments.h
  40. 18
      src/core/lib/experiments/experiments.yaml
  41. 11
      src/core/lib/experiments/rollouts.yaml
  42. 50
      src/core/lib/promise/detail/promise_variant.h
  43. 23
      src/core/lib/promise/match_promise.h
  44. 7
      src/core/lib/promise/status_flag.h
  45. 55
      src/core/lib/promise/switch.h
  46. 1
      src/core/lib/slice/slice_buffer.cc
  47. 19
      src/core/util/http_client/httpcli.cc
  48. 14
      src/core/util/uri.cc
  49. 6
      src/core/util/uri.h
  50. 2
      src/python/grpcio/_parallel_compile_patch.py
  51. 2
      src/python/grpcio/commands.py
  52. 4
      src/python/grpcio/grpc/__init__.py
  53. 10
      src/python/grpcio/grpc/_cython/_cygrpc/aio/call.pyx.pxi
  54. 4
      src/python/grpcio/grpc/_cython/_cygrpc/aio/callback_common.pyx.pxi
  55. 4
      src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi
  56. 2
      src/python/grpcio/grpc/_cython/_cygrpc/aio/rpc_status.pyx.pxi
  57. 10
      src/python/grpcio/grpc/_cython/_cygrpc/aio/server.pyx.pxi
  58. 2
      src/python/grpcio/grpc/_cython/_cygrpc/credentials.pxd.pxi
  59. 4
      src/python/grpcio/grpc/_observability.py
  60. 10
      src/python/grpcio/grpc/_runtime_protos.py
  61. 2
      src/python/grpcio/grpc/_server.py
  62. 2
      src/python/grpcio/grpc/aio/_base_call.py
  63. 4
      src/python/grpcio/grpc/aio/_base_channel.py
  64. 2
      src/python/grpcio/grpc/aio/_base_server.py
  65. 16
      src/python/grpcio/grpc/aio/_interceptor.py
  66. 2
      src/python/grpcio/grpc/framework/interfaces/base/base.py
  67. 2
      src/python/grpcio/grpc/framework/interfaces/base/utilities.py
  68. 2
      src/python/grpcio/grpc/framework/interfaces/face/utilities.py
  69. 4
      src/python/grpcio_csm_observability/grpc_csm_observability/_csm_observability_plugin.py
  70. 2
      src/python/grpcio_observability/_parallel_compile_patch.py
  71. 4
      src/python/grpcio_observability/grpc_observability/_cyobservability.pyx
  72. 2
      src/python/grpcio_observability/grpc_observability/_measures.py
  73. 2
      src/python/grpcio_observability/grpc_observability/_open_census_exporter.py
  74. 2
      src/python/grpcio_observability/grpc_observability/_open_telemetry_observability.py
  75. 8
      src/python/grpcio_observability/grpc_observability/python_observability_context.h
  76. 2
      src/python/grpcio_tests/tests/_loader.py
  77. 2
      src/python/grpcio_tests/tests/qps/client_runner.py
  78. 2
      src/python/grpcio_tests/tests/status/_grpc_status_test.py
  79. 2
      src/python/grpcio_tests/tests/unit/_compression_test.py
  80. 2
      src/python/grpcio_tests/tests/unit/_contextvars_propagation_test.py
  81. 2
      src/python/grpcio_tests/tests/unit/_cython/_read_some_but_not_all_responses_test.py
  82. 2
      src/python/grpcio_tests/tests/unit/_exit_test.py
  83. 2
      src/python/grpcio_tests/tests/unit/_grpc_shutdown_test.py
  84. 2
      src/python/grpcio_tests/tests/unit/_metadata_flags_test.py
  85. 36
      src/python/grpcio_tests/tests/unit/test_all_modules_installed.py
  86. 2
      src/python/grpcio_tests/tests_aio/status/grpc_status_test.py
  87. 2
      src/python/grpcio_tests/tests_aio/unit/_common.py
  88. 2
      src/python/grpcio_tests/tests_aio/unit/_metadata_test.py
  89. 4
      src/python/grpcio_tests/tests_aio/unit/_test_server.py
  90. 2
      src/python/grpcio_tests/tests_aio/unit/call_test.py
  91. 2
      src/python/grpcio_tests/tests_aio/unit/client_stream_unary_interceptor_test.py
  92. 2
      src/python/grpcio_tests/tests_aio/unit/client_unary_unary_interceptor_test.py
  93. 2
      src/python/grpcio_tests/tests_aio/unit/connectivity_test.py
  94. 2
      src/python/grpcio_tests/tests_gevent/unit/_test_server.py
  95. 2
      templates/src/python/_parallel_compile_patch.py.include
  96. 1
      test/core/end2end/fuzzers/BUILD
  97. 41
      test/core/end2end/fuzzers/fuzzer_input.proto
  98. 114
      test/core/end2end/fuzzers/network_input.cc
  99. 2
      test/core/end2end/tests/no_logging.cc
  100. 12
      test/core/http/httpcli_test.cc
  101. Some files were not shown because too many files have changed in this diff Show More

@ -73,7 +73,7 @@ disable=
protected-access,
# NOTE(nathaniel): Pylint and I will probably never agree on this.
too-few-public-methods,
# NOTE(nathaniel): Pylint and I wil probably never agree on this for
# NOTE(nathaniel): Pylint and I will probably never agree on this for
# private classes. For public classes maybe?
too-many-instance-attributes,
# NOTE(nathaniel): Some of our modules have a lot of lines... of

@ -76,7 +76,7 @@ disable=
protected-access,
# NOTE(nathaniel): Pylint and I will probably never agree on this.
too-few-public-methods,
# NOTE(nathaniel): Pylint and I wil probably never agree on this for
# NOTE(nathaniel): Pylint and I will probably never agree on this for
# private classes. For public classes maybe?
too-many-instance-attributes,
# NOTE(nathaniel): Some of our modules have a lot of lines... of

@ -102,7 +102,7 @@ disable=
protected-access,
# NOTE(nathaniel): Pylint and I will probably never agree on this.
too-few-public-methods,
# NOTE(nathaniel): Pylint and I wil probably never agree on this for
# NOTE(nathaniel): Pylint and I will probably never agree on this for
# private classes. For public classes maybe?
too-many-instance-attributes,
# NOTE(nathaniel): Some of our modules have a lot of lines... of

13
BUILD

@ -904,6 +904,19 @@ grpc_cc_library(
hdrs = ["include/grpc/impl/channel_arg_names.h"],
)
grpc_cc_library(
name = "grpc_slice",
hdrs = [
"include/grpc/slice.h",
"include/grpc/slice_buffer.h",
],
visibility = ["@grpc:public"],
deps = [
"//src/core:slice",
"//src/core:slice_buffer",
],
)
grpc_cc_library(
name = "grpc++",
hdrs = [

554
CMakeLists.txt generated

File diff suppressed because it is too large Load Diff

@ -34,6 +34,8 @@ EXPERIMENT_ENABLES = {
"monitoring_experiment": "monitoring_experiment",
"multiping": "multiping",
"pick_first_new": "pick_first_new",
"promise_based_http2_client_transport": "promise_based_http2_client_transport",
"promise_based_http2_server_transport": "promise_based_http2_server_transport",
"promise_based_inproc_transport": "promise_based_inproc_transport",
"rq_fast_reject": "rq_fast_reject",
"schedule_cancellation_over_write": "schedule_cancellation_over_write",
@ -84,6 +86,10 @@ EXPERIMENTS = {
"core_end2end_test": [
"event_engine_client",
"event_engine_listener",
"work_serializer_dispatch",
],
"cpp_end2end_test": [
"work_serializer_dispatch",
],
"cpp_lb_end2end_test": [
"pick_first_new",
@ -96,12 +102,14 @@ EXPERIMENTS = {
],
"lb_unit_test": [
"pick_first_new",
"work_serializer_dispatch",
],
"resolver_component_tests_runner_invoker": [
"event_engine_dns",
],
"xds_end2end_test": [
"pick_first_new",
"work_serializer_dispatch",
],
},
},
@ -129,14 +137,22 @@ EXPERIMENTS = {
],
},
"on": {
"core_end2end_test": [
"work_serializer_dispatch",
],
"cpp_end2end_test": [
"work_serializer_dispatch",
],
"cpp_lb_end2end_test": [
"pick_first_new",
],
"lb_unit_test": [
"pick_first_new",
"work_serializer_dispatch",
],
"xds_end2end_test": [
"pick_first_new",
"work_serializer_dispatch",
],
},
},

File diff suppressed because it is too large Load Diff

@ -39,7 +39,7 @@ def _get_hamming_distance(a, b):
def _get_substring_hamming_distance(candidate, target):
"""Calculates the minimum hamming distance between between the target
"""Calculates the minimum hamming distance between the target
and any substring of the candidate.
Args:

@ -46,11 +46,11 @@ GRPC_TRACE=call_error,connectivity_state,pick_first,round_robin,glb
## How to debug your application?
`pdb` is a debugging tool that is available for Python interpreters natively.
You can set breakpoint, and execute commands while the application is stopped.
You can set breakpoints, and execute commands while the application is stopped.
The simplest usage is add a single line in the place you want to inspect:
`import pdb; pdb.set_trace()`. When interpreter see this line, it would pop out
a interactive command line interface for you to inspect the application state.
an interactive command line interface for you to inspect the application state.
For more detailed usage, see https://docs.python.org/3/library/pdb.html.

@ -15,8 +15,8 @@ This example demonstrate the usage of Async interceptors and context propagation
This example have the following steps:
1. Generate RPC ID on client side and propagate to server using `metadata`.
* `contextvars` can be used here if client and server is running in a same coroutine (or same thead for Sync).
2. Server interceptor1 intercept the request, it checks `rpc_id_var` and decorate it with it's tag `Interceptor1`.
3. Server interceptor2 intercept the request, it checks `rpc_id_var` and decorate it with it's tag `Interceptor2`.
2. Server interceptor1 intercept the request, it checks `rpc_id_var` and decorate it with its tag `Interceptor1`.
3. Server interceptor2 intercept the request, it checks `rpc_id_var` and decorate it with its tag `Interceptor2`.
4. Server handler receives the request with `rpc_id_var` decorated by both interceptor1 and interceptor2.
## How to run this example

@ -37,7 +37,7 @@ def run():
grpc.keepalive_time_ms: The period (in milliseconds) after which a keepalive ping is
sent on the transport.
grpc.keepalive_timeout_ms: The amount of time (in milliseconds) the sender of the keepalive
ping waits for an acknowledgement. If it does not receive an acknowledgment within this
ping waits for an acknowledgement. If it does not receive an acknowledgement within this
time, it will close the connection.
grpc.keepalive_permit_without_calls: If set to 1 (0 : false; 1 : true), allows keepalive
pings to be sent even if there are no calls in flight.
@ -60,9 +60,9 @@ def run():
unary_call(stub, 1, "you")
# Run 30s, run this with GRPC_VERBOSITY=DEBUG GRPC_TRACE=http_keepalive to observe logs.
# Client will be closed after receveing GOAWAY from server.
# Client will be closed after receiving GOAWAY from server.
for i in range(30):
print(f"{i} seconds paased.")
print(f"{i} seconds passed.")
sleep(1)

@ -35,7 +35,7 @@ def serve():
grpc.keepalive_time_ms: The period (in milliseconds) after which a keepalive ping is
sent on the transport.
grpc.keepalive_timeout_ms: The amount of time (in milliseconds) the sender of the keepalive
ping waits for an acknowledgement. If it does not receive an acknowledgment within
ping waits for an acknowledgement. If it does not receive an acknowledgement within
this time, it will close the connection.
grpc.http2.min_ping_interval_without_data_ms: Minimum allowed time (in milliseconds)
between a server receiving successive ping frames without sending any data/header frame.

@ -33,7 +33,7 @@ def make_route_note(
)
# Performs an unary call
# Performs a unary call
async def guide_get_one_feature(
stub: route_guide_pb2_grpc.RouteGuideStub, point: route_guide_pb2.Point
) -> None:

@ -51,7 +51,7 @@ class Greeter(helloworld_pb2_grpc.GreeterServicer):
# for server to up and running.
starting_up_server()
# Initial metadata will be send back immediately after calling send_initial_metadata.
# Initial metadata will be sent back immediately after calling send_initial_metadata.
print("sending initial metadata back")
servicer_context.send_initial_metadata(_INITIAL_METADATA)

1
gRPC-C++.podspec generated

@ -161,6 +161,7 @@ Pod::Spec.new do |s|
'include/grpcpp/impl/completion_queue_tag.h',
'include/grpcpp/impl/create_auth_context.h',
'include/grpcpp/impl/delegating_channel.h',
'include/grpcpp/impl/generic_serialize.h',
'include/grpcpp/impl/generic_stub_internal.h',
'include/grpcpp/impl/grpc_library.h',
'include/grpcpp/impl/intercepted_channel.h',

@ -59,22 +59,26 @@
#ifndef GRPC_CUSTOM_DESCRIPTORDATABASE
#include <google/protobuf/descriptor_database.h>
#define GRPC_CUSTOM_DESCRIPTORDATABASE ::google::protobuf::DescriptorDatabase
#define GRPC_CUSTOM_SIMPLEDESCRIPTORDATABASE ::google::protobuf::SimpleDescriptorDatabase
#define GRPC_CUSTOM_SIMPLEDESCRIPTORDATABASE \
::google::protobuf::SimpleDescriptorDatabase
#endif
#ifndef GRPC_CUSTOM_ZEROCOPYOUTPUTSTREAM
#include <google/protobuf/io/coded_stream.h>
#include <google/protobuf/io/zero_copy_stream.h>
#define GRPC_CUSTOM_ZEROCOPYOUTPUTSTREAM ::google::protobuf::io::ZeroCopyOutputStream
#define GRPC_CUSTOM_ZEROCOPYINPUTSTREAM ::google::protobuf::io::ZeroCopyInputStream
#define GRPC_CUSTOM_ZEROCOPYOUTPUTSTREAM \
::google::protobuf::io::ZeroCopyOutputStream
#define GRPC_CUSTOM_ZEROCOPYINPUTSTREAM \
::google::protobuf::io::ZeroCopyInputStream
#define GRPC_CUSTOM_CODEDINPUTSTREAM ::google::protobuf::io::CodedInputStream
#define GRPC_CUSTOM_CODEDOUTPUTSTREAM ::google::protobuf::io::CodedOutputStream
#endif
#ifndef GRPC_CUSTOM_JSONUTIL
#include "absl/status/status.h"
#include <google/protobuf/util/json_util.h>
#include <google/protobuf/util/type_resolver_util.h>
#include "absl/status/status.h"
#define GRPC_CUSTOM_JSONUTIL ::google::protobuf::util
#define GRPC_CUSTOM_UTIL_STATUS ::absl::Status
#endif

@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef GRPCPP_IMPL_PROTO_UTILS_H
#define GRPCPP_IMPL_PROTO_UTILS_H
#ifndef GRPCPP_IMPL_GENERIC_SERIALIZE_H
#define GRPCPP_IMPL_GENERIC_SERIALIZE_H
#include <grpc/byte_buffer_reader.h>
#include <grpc/impl/grpc_types.h>
@ -90,4 +90,4 @@ Status GenericDeserialize(ByteBuffer* buffer,
} // namespace grpc
#endif // GRPCPP_IMPL_PROTO_UTILS_H
#endif // GRPCPP_IMPL_GENERIC_SERIALIZE_H

@ -15,7 +15,7 @@ exclude=.*protoc_plugin/protoc_plugin_test\.proto$
[metadata]
license_files = LICENSE
# NOTE(lidiz) Adding examples one by one due to pytype aggressive errer:
# NOTE(lidiz) Adding examples one by one due to pytype aggressive error:
# ninja: error: build.ninja:178: multiple rules generate helloworld_pb2.pyi [-w dupbuild=err]
# TODO(xuanwn): include all files in src/python/grpcio/grpc
[pytype]

@ -125,7 +125,7 @@ BUILD_WITH_BORING_SSL_ASM = _env_bool_value(
# Export this environment variable to override the platform variant that will
# be chosen for boringssl assembly optimizations. This option is useful when
# crosscompiling and the host platform as obtained by sysconfig.get_platform()
# doesn't match the platform we are targetting.
# doesn't match the platform we are targeting.
# Example value: "linux-aarch64"
BUILD_OVERRIDE_BORING_SSL_ASM_PLATFORM = os.environ.get(
"GRPC_BUILD_OVERRIDE_BORING_SSL_ASM_PLATFORM", ""

@ -15,7 +15,9 @@
load(
"//bazel:grpc_build_system.bzl",
"grpc_cc_library",
"grpc_cc_proto_library",
"grpc_generate_one_off_internal_targets",
"grpc_internal_proto_library",
"grpc_upb_proto_library",
"grpc_upb_proto_reflection_library",
)
@ -696,6 +698,17 @@ grpc_cc_library(
],
)
grpc_cc_library(
name = "promise_variant",
external_deps = [
"absl/types:variant",
],
language = "c++",
public_hdrs = ["lib/promise/detail/promise_variant.h"],
deps = [
],
)
grpc_cc_library(
name = "match_promise",
external_deps = [
@ -709,6 +722,7 @@ grpc_cc_library(
"poll",
"promise_factory",
"promise_like",
"promise_variant",
"//:gpr_platform",
],
)
@ -830,6 +844,8 @@ grpc_cc_library(
deps = [
"if",
"promise_factory",
"promise_variant",
"//:gpr",
"//:gpr_platform",
],
)
@ -7797,6 +7813,17 @@ grpc_cc_library(
],
)
grpc_internal_proto_library(
name = "chaotic_good_frame_proto",
srcs = ["ext/transport/chaotic_good/chaotic_good_frame.proto"],
has_services = False,
)
grpc_cc_proto_library(
name = "chaotic_good_frame_cc_proto",
deps = ["chaotic_good_frame_proto"],
)
grpc_cc_library(
name = "chaotic_good_frame",
srcs = [
@ -7815,38 +7842,19 @@ grpc_cc_library(
deps = [
"arena",
"bitset",
"chaotic_good_frame_cc_proto",
"chaotic_good_frame_header",
"context",
"match",
"message",
"metadata",
"metadata_batch",
"no_destruct",
"slice",
"slice_buffer",
"status_helper",
"//:gpr",
"//:gpr_platform",
"//:grpc_base",
"//:hpack_encoder",
"//:hpack_parser",
],
)
grpc_cc_library(
name = "chaotic_good_settings_metadata",
srcs = [
"ext/transport/chaotic_good/settings_metadata.cc",
],
hdrs = [
"ext/transport/chaotic_good/settings_metadata.h",
],
external_deps = [
"absl/status",
"absl/types:optional",
],
deps = [
"arena",
"metadata_batch",
"//:gpr",
],
)
@ -8087,6 +8095,7 @@ grpc_cc_library(
external_deps = [
"absl/log:log",
"absl/random",
"absl/strings",
],
language = "c++",
deps = [
@ -8099,7 +8108,6 @@ grpc_cc_library(
"try_seq",
"//:gpr_platform",
"//:grpc_trace",
"//:hpack_encoder",
"//:promise",
],
)
@ -8150,14 +8158,13 @@ grpc_cc_library(
"resource_quota",
"slice",
"slice_buffer",
"switch",
"try_join",
"try_seq",
"//:exec_ctx",
"//:gpr",
"//:gpr_platform",
"//:grpc_base",
"//:hpack_encoder",
"//:hpack_parser",
"//:ref_counted_ptr",
],
)
@ -8217,8 +8224,6 @@ grpc_cc_library(
"//:gpr",
"//:gpr_platform",
"//:grpc_base",
"//:hpack_encoder",
"//:hpack_parser",
"//:ref_counted_ptr",
],
)
@ -8714,7 +8719,6 @@ grpc_cc_library(
"chaotic_good_frame_header",
"chaotic_good_legacy_server",
"chaotic_good_server_transport",
"chaotic_good_settings_metadata",
"closure",
"context",
"error",
@ -8747,8 +8751,6 @@ grpc_cc_library(
"//:gpr_platform",
"//:grpc_base",
"//:handshaker",
"//:hpack_encoder",
"//:hpack_parser",
"//:iomgr",
"//:orphanable",
"//:ref_counted_ptr",
@ -8847,9 +8849,9 @@ grpc_cc_library(
"channel_args_endpoint_config",
"chaotic_good_client_transport",
"chaotic_good_frame",
"chaotic_good_frame_cc_proto",
"chaotic_good_frame_header",
"chaotic_good_legacy_connector",
"chaotic_good_settings_metadata",
"closure",
"context",
"error",
@ -8884,8 +8886,6 @@ grpc_cc_library(
"//:grpc_base",
"//:grpc_client_channel",
"//:handshaker",
"//:hpack_encoder",
"//:hpack_parser",
"//:iomgr",
"//:ref_counted_ptr",
],

@ -0,0 +1,51 @@
// Copyright 2024 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.
syntax = "proto3";
package chaotic_good_frame;
message Settings {
// Connection id
// - sent server->client on the control channel to specify the
// data channel connection id
// - sent client->server on the data channel to complete the
// connection
bytes connection_id = 1;
// Flag true if this is a data channel (and not a control channel)
bool data_channel = 2;
// Requested alignment for the data channel
// Client and server each send this with their preferences
uint32 alignment = 3;
}
message UnknownMetadata {
string key = 1;
bytes value = 2;
}
message ClientMetadata {
optional string path = 1;
optional string authority = 2;
optional uint64 timeout_ms = 3;
repeated UnknownMetadata unknown_metadata = 100;
}
message ServerMetadata {
optional uint32 status = 1;
optional bytes message = 2;
repeated UnknownMetadata unknown_metadata = 100;
}

@ -22,9 +22,9 @@
#include "absl/log/log.h"
#include "absl/random/random.h"
#include "absl/strings/escaping.h"
#include "src/core/ext/transport/chaotic_good/frame.h"
#include "src/core/ext/transport/chaotic_good/frame_header.h"
#include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
#include "src/core/lib/debug/trace.h"
#include "src/core/lib/event_engine/tcp_socket_utils.h"
#include "src/core/lib/promise/if.h"
@ -38,13 +38,17 @@ namespace chaotic_good {
class ChaoticGoodTransport : public RefCounted<ChaoticGoodTransport> {
public:
struct Options {
uint32_t encode_alignment = 64;
uint32_t decode_alignment = 64;
uint32_t inlined_payload_size_threshold = 8 * 1024;
};
ChaoticGoodTransport(PromiseEndpoint control_endpoint,
PromiseEndpoint data_endpoint, HPackParser hpack_parser,
HPackCompressor hpack_encoder)
PromiseEndpoint data_endpoint, Options options)
: control_endpoint_(std::move(control_endpoint)),
data_endpoint_(std::move(data_endpoint)),
encoder_(std::move(hpack_encoder)),
parser_(std::move(hpack_parser)) {
options_(options) {
// Enable RxMemoryAlignment and RPC receive coalescing after the transport
// setup is complete. At this point all the settings frames should have
// been read.
@ -52,17 +56,31 @@ class ChaoticGoodTransport : public RefCounted<ChaoticGoodTransport> {
}
auto WriteFrame(const FrameInterface& frame) {
bool saw_encoding_errors = false;
auto buffers = frame.Serialize(&encoder_, saw_encoding_errors);
SliceBuffer control;
SliceBuffer data;
FrameHeader header = frame.MakeHeader();
if (header.payload_length > options_.inlined_payload_size_threshold) {
header.payload_connection_id = 1;
header.Serialize(control.AddTiny(FrameHeader::kFrameHeaderSize));
frame.SerializePayload(data);
const size_t padding = header.Padding(options_.encode_alignment);
if (padding != 0) {
auto slice = MutableSlice::CreateUninitialized(padding);
memset(slice.data(), 0, padding);
data.AppendIndexed(Slice(std::move(slice)));
}
} else {
header.Serialize(control.AddTiny(FrameHeader::kFrameHeaderSize));
frame.SerializePayload(control);
}
// ignore encoding errors: they will be logged separately already
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "CHAOTIC_GOOD: WriteFrame to:"
<< ResolvedAddressToString(control_endpoint_.GetPeerAddress())
.value_or("<<unknown peer address>>")
<< " " << frame.ToString();
return TryJoin<absl::StatusOr>(
control_endpoint_.Write(std::move(buffers.control)),
data_endpoint_.Write(std::move(buffers.data)));
return TryJoin<absl::StatusOr>(control_endpoint_.Write(std::move(control)),
data_endpoint_.Write(std::move(data)));
}
// Read frame header and payloads for control and data portions of one frame.
@ -81,59 +99,46 @@ class ChaoticGoodTransport : public RefCounted<ChaoticGoodTransport> {
<< " "
<< (frame_header.ok() ? frame_header->ToString()
: frame_header.status().ToString());
// Read header and trailers from control endpoint.
// Read message padding and message from data endpoint.
return If(
frame_header.ok(),
[this, &frame_header] {
const uint32_t message_padding = frame_header->message_padding;
const uint32_t message_length = frame_header->message_length;
return Map(
TryJoin<absl::StatusOr>(
control_endpoint_.Read(frame_header->GetFrameLength()),
data_endpoint_.Read(message_length + message_padding)),
[frame_header = *frame_header, message_padding](
absl::StatusOr<std::tuple<SliceBuffer, SliceBuffer>>
buffers)
-> absl::StatusOr<std::tuple<FrameHeader, BufferPair>> {
if (!buffers.ok()) return buffers.status();
SliceBuffer data_read = std::move(std::get<1>(*buffers));
if (message_padding > 0) {
data_read.RemoveLastNBytesNoInline(message_padding);
}
return std::tuple<FrameHeader, BufferPair>(
frame_header,
BufferPair{std::move(std::get<0>(*buffers)),
std::move(data_read)});
});
},
[&frame_header]() {
return
[status = frame_header.status()]() mutable
-> absl::StatusOr<std::tuple<FrameHeader, BufferPair>> {
return std::move(status);
};
});
return frame_header;
},
[this](FrameHeader frame_header) {
current_frame_header_ = frame_header;
auto con = frame_header.payload_connection_id == 0
? &control_endpoint_
: &data_endpoint_;
return con->Read(frame_header.payload_length +
frame_header.Padding(options_.decode_alignment));
},
[this](SliceBuffer payload)
-> absl::StatusOr<std::tuple<FrameHeader, SliceBuffer>> {
payload.RemoveLastNBytesNoInline(
current_frame_header_.Padding(options_.decode_alignment));
return std::tuple<FrameHeader, SliceBuffer>(current_frame_header_,
std::move(payload));
});
}
absl::Status DeserializeFrame(FrameHeader header, BufferPair buffers,
Arena* arena, FrameInterface& frame,
FrameLimits limits) {
auto s = frame.Deserialize(&parser_, header, bitgen_, arena,
std::move(buffers), limits);
template <typename T>
absl::StatusOr<T> DeserializeFrame(const FrameHeader& header,
SliceBuffer payload) {
T frame;
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "CHAOTIC_GOOD: Deserialize " << header << " with payload "
<< absl::CEscape(payload.JoinIntoString());
CHECK_EQ(header.payload_length, payload.Length());
auto s = frame.Deserialize(header, std::move(payload));
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "CHAOTIC_GOOD: DeserializeFrame "
<< (s.ok() ? frame.ToString() : s.ToString());
return s;
if (s.ok()) return std::move(frame);
return std::move(s);
}
private:
PromiseEndpoint control_endpoint_;
PromiseEndpoint data_endpoint_;
HPackCompressor encoder_;
HPackParser parser_;
absl::BitGen bitgen_;
FrameHeader current_frame_header_;
Options options_;
};
} // namespace chaotic_good

@ -28,10 +28,10 @@
#include "absl/status/statusor.h"
#include "src/core/client_channel/client_channel_factory.h"
#include "src/core/client_channel/client_channel_filter.h"
#include "src/core/ext/transport/chaotic_good/chaotic_good_frame.pb.h"
#include "src/core/ext/transport/chaotic_good/client_transport.h"
#include "src/core/ext/transport/chaotic_good/frame.h"
#include "src/core/ext/transport/chaotic_good/frame_header.h"
#include "src/core/ext/transport/chaotic_good/settings_metadata.h"
#include "src/core/ext/transport/chaotic_good_legacy/client/chaotic_good_connector.h"
#include "src/core/handshaker/handshaker.h"
#include "src/core/lib/channel/channel_args.h"
@ -91,38 +91,38 @@ ChaoticGoodConnector::~ChaoticGoodConnector() {
auto ChaoticGoodConnector::DataEndpointReadSettingsFrame(
RefCountedPtr<ChaoticGoodConnector> self) {
return TrySeq(
self->data_endpoint_.ReadSlice(FrameHeader::kFrameHeaderSize),
[self](Slice slice) mutable {
// Read setting frame;
// Parse frame header
auto frame_header_ =
FrameHeader::Parse(reinterpret_cast<const uint8_t*>(
GRPC_SLICE_START_PTR(slice.c_slice())));
return If(
frame_header_.ok(),
[frame_header_ = *frame_header_, self]() {
auto frame_header_length = frame_header_.GetFrameLength();
return TrySeq(self->data_endpoint_.Read(frame_header_length),
return TrySeq(self->data_endpoint_.ReadSlice(FrameHeader::kFrameHeaderSize),
[self](Slice slice) mutable {
// Read setting frame;
// Parse frame header
auto frame_header_ =
FrameHeader::Parse(reinterpret_cast<const uint8_t*>(
GRPC_SLICE_START_PTR(slice.c_slice())));
return If(
frame_header_.ok(),
[frame_header_ = *frame_header_, self]() {
auto frame_header_length = frame_header_.payload_length;
return TrySeq(
self->data_endpoint_.Read(frame_header_length),
[]() { return absl::OkStatus(); });
},
[status = frame_header_.status()]() { return status; });
});
},
[status = frame_header_.status()]() { return status; });
});
}
auto ChaoticGoodConnector::DataEndpointWriteSettingsFrame(
RefCountedPtr<ChaoticGoodConnector> self) {
// Serialize setting frame.
SettingsFrame frame;
// frame.header set connectiion_type: control
frame.headers = SettingsMetadata{SettingsMetadata::ConnectionType::kData,
self->connection_id_, kDataAlignmentBytes}
.ToMetadataBatch();
bool saw_encoding_errors = false;
auto write_buffer =
frame.Serialize(&self->hpack_compressor_, saw_encoding_errors);
frame.settings.set_data_channel(true);
frame.settings.set_connection_id(self->connection_id_);
frame.settings.set_alignment(kDataAlignmentBytes);
SliceBuffer write_buffer;
frame.MakeHeader().Serialize(
write_buffer.AddTiny(FrameHeader::kFrameHeaderSize));
frame.SerializePayload(write_buffer);
// ignore encoding errors: they will be logged separately already
return self->data_endpoint_.Write(std::move(write_buffer.control));
return self->data_endpoint_.Write(std::move(write_buffer));
}
auto ChaoticGoodConnector::WaitForDataEndpointSetup(
@ -187,29 +187,18 @@ auto ChaoticGoodConnector::ControlEndpointReadSettingsFrame(
return If(
frame_header.ok(),
TrySeq(
self->control_endpoint_.Read(frame_header->GetFrameLength()),
self->control_endpoint_.Read(frame_header->payload_length),
[frame_header = *frame_header, self](SliceBuffer buffer) {
// Deserialize setting frame.
SettingsFrame frame;
BufferPair buffer_pair{std::move(buffer), SliceBuffer()};
auto status = frame.Deserialize(
&self->hpack_parser_, frame_header,
absl::BitGenRef(self->bitgen_), GetContext<Arena>(),
std::move(buffer_pair), FrameLimits{});
auto status =
frame.Deserialize(frame_header, std::move(buffer));
if (!status.ok()) return status;
if (frame.headers == nullptr) {
return absl::UnavailableError("no settings headers");
}
auto settings_metadata =
SettingsMetadata::FromMetadataBatch(*frame.headers);
if (!settings_metadata.ok()) {
return settings_metadata.status();
}
if (!settings_metadata->connection_id.has_value()) {
if (frame.settings.connection_id().empty()) {
return absl::UnavailableError(
"no connection id in settings frame");
}
self->connection_id_ = *settings_metadata->connection_id;
self->connection_id_ = frame.settings.connection_id();
return absl::OkStatus();
},
WaitForDataEndpointSetup(self)),
@ -222,14 +211,13 @@ auto ChaoticGoodConnector::ControlEndpointWriteSettingsFrame(
// Serialize setting frame.
SettingsFrame frame;
// frame.header set connectiion_type: control
frame.headers = SettingsMetadata{SettingsMetadata::ConnectionType::kControl,
absl::nullopt, absl::nullopt}
.ToMetadataBatch();
bool saw_encoding_errors = false;
auto write_buffer =
frame.Serialize(&self->hpack_compressor_, saw_encoding_errors);
frame.settings.set_data_channel(false);
SliceBuffer write_buffer;
frame.MakeHeader().Serialize(
write_buffer.AddTiny(FrameHeader::kFrameHeaderSize));
frame.SerializePayload(write_buffer);
// ignore encoding errors: they will be logged separately already
return self->control_endpoint_.Write(std::move(write_buffer.control));
return self->control_endpoint_.Write(std::move(write_buffer));
}
void ChaoticGoodConnector::Connect(const Args& args, Result* result,
@ -331,8 +319,7 @@ void ChaoticGoodConnector::OnHandshakeDone(
self->result_->transport = new ChaoticGoodClientTransport(
std::move(self->control_endpoint_),
std::move(self->data_endpoint_), self->args_.channel_args,
self->event_engine_, std::move(self->hpack_parser_),
std::move(self->hpack_compressor_));
self->event_engine_);
self->result_->channel_args = self->args_.channel_args;
ExecCtx::Run(DEBUG_LOCATION, std::exchange(self->notify_, nullptr),
status);

@ -25,8 +25,6 @@
#include "absl/random/random.h"
#include "absl/status/statusor.h"
#include "src/core/client_channel/connector.h"
#include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
#include "src/core/ext/transport/chttp2/transport/hpack_parser.h"
#include "src/core/handshaker/handshaker.h"
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/event_engine/channel_args_endpoint_config.h"
@ -93,9 +91,6 @@ class ChaoticGoodConnector : public SubchannelConnector {
const std::shared_ptr<grpc_event_engine::experimental::EventEngine>
event_engine_;
RefCountedPtr<HandshakeManager> handshake_mgr_;
HPackCompressor hpack_compressor_;
HPackParser hpack_parser_;
absl::BitGen bitgen_;
InterActivityLatch<void> data_endpoint_ready_;
std::string connection_id_;
};

@ -34,13 +34,13 @@
#include "src/core/ext/transport/chaotic_good/chaotic_good_transport.h"
#include "src/core/ext/transport/chaotic_good/frame.h"
#include "src/core/ext/transport/chaotic_good/frame_header.h"
#include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
#include "src/core/lib/event_engine/event_engine_context.h"
#include "src/core/lib/event_engine/extensions/tcp_trace.h"
#include "src/core/lib/event_engine/query_extensions.h"
#include "src/core/lib/iomgr/exec_ctx.h"
#include "src/core/lib/promise/loop.h"
#include "src/core/lib/promise/map.h"
#include "src/core/lib/promise/switch.h"
#include "src/core/lib/promise/try_seq.h"
#include "src/core/lib/resource_quota/arena.h"
#include "src/core/lib/resource_quota/resource_quota.h"
@ -91,36 +91,59 @@ absl::optional<CallHandler> ChaoticGoodClientTransport::LookupStream(
return it->second;
}
auto ChaoticGoodClientTransport::PushFrameIntoCall(ServerFragmentFrame frame,
auto ChaoticGoodClientTransport::PushFrameIntoCall(
ServerInitialMetadataFrame frame, CallHandler call_handler) {
auto headers = ServerMetadataGrpcFromProto(frame.headers);
if (!headers.ok()) {
LOG_EVERY_N_SEC(INFO, 10) << "Encode headers failed: " << headers.status();
return Immediate(StatusFlag(Failure{}));
}
return Immediate(call_handler.PushServerInitialMetadata(std::move(*headers)));
}
auto ChaoticGoodClientTransport::PushFrameIntoCall(MessageFrame frame,
CallHandler call_handler) {
const bool has_headers = frame.headers != nullptr;
auto push = TrySeq(
If(
has_headers,
[call_handler, headers = std::move(frame.headers)]() mutable {
return call_handler.PushServerInitialMetadata(std::move(headers));
},
[]() -> StatusFlag { return Success{}; }),
[call_handler, message = std::move(frame.message)]() mutable {
return If(
message.has_value(),
[&call_handler, &message]() mutable {
return call_handler.PushMessage(std::move(message->message));
},
[]() -> StatusFlag { return Success{}; });
},
[call_handler,
trailers = std::move(frame.trailers)]() mutable -> StatusFlag {
if (trailers != nullptr) {
call_handler.PushServerTrailingMetadata(std::move(trailers));
}
return Success{};
});
// Wrap the actual sequence with something that owns the call handler so that
// its lifetime extends until the push completes.
return call_handler.PushMessage(std::move(frame.message));
}
auto ChaoticGoodClientTransport::PushFrameIntoCall(
ServerTrailingMetadataFrame frame, CallHandler call_handler) {
auto trailers = ServerMetadataGrpcFromProto(frame.trailers);
if (!trailers.ok()) {
call_handler.PushServerTrailingMetadata(
CancelledServerMetadataFromStatus(trailers.status()));
} else {
call_handler.PushServerTrailingMetadata(std::move(*trailers));
}
return Immediate(Success{});
}
template <typename T>
auto ChaoticGoodClientTransport::DispatchFrame(ChaoticGoodTransport* transport,
const FrameHeader& header,
SliceBuffer payload) {
return GRPC_LATENT_SEE_PROMISE(
"PushFrameIntoCall",
([call_handler, push = std::move(push)]() mutable { return push(); }));
"ChaoticGoodClientTransport::DispatchFrame",
TrySeq(
[transport, header, payload = std::move(payload)]() mutable {
return transport->DeserializeFrame<T>(header, std::move(payload));
},
[this](T frame) {
absl::optional<CallHandler> call_handler =
LookupStream(frame.stream_id);
return If(
call_handler.has_value(),
[this, &call_handler, &frame]() {
return call_handler->SpawnWaitable(
"push-frame", [this, call_handler = *call_handler,
frame = std::move(frame)]() mutable {
return Map(call_handler.CancelIfFails(PushFrameIntoCall(
std::move(frame), call_handler)),
[](StatusFlag) { return absl::OkStatus(); });
});
},
[]() { return absl::OkStatus(); });
}));
}
auto ChaoticGoodClientTransport::TransportReadLoop(
@ -128,54 +151,29 @@ auto ChaoticGoodClientTransport::TransportReadLoop(
return Loop([this, transport = std::move(transport)] {
return TrySeq(
transport->ReadFrameBytes(),
[](std::tuple<FrameHeader, BufferPair> frame_bytes)
-> absl::StatusOr<std::tuple<FrameHeader, BufferPair>> {
const auto& frame_header = std::get<0>(frame_bytes);
if (frame_header.type != FrameType::kFragment) {
return absl::InternalError(
absl::StrCat("Expected fragment frame, got ",
static_cast<int>(frame_header.type)));
}
return frame_bytes;
},
[this, transport = transport.get()](
std::tuple<FrameHeader, BufferPair> frame_bytes) {
const auto& frame_header = std::get<0>(frame_bytes);
auto& buffers = std::get<1>(frame_bytes);
absl::optional<CallHandler> call_handler =
LookupStream(frame_header.stream_id);
ServerFragmentFrame frame;
absl::Status deserialize_status;
const FrameLimits frame_limits{1024 * 1024 * 1024,
aligned_bytes_ - 1};
if (call_handler.has_value()) {
deserialize_status = transport->DeserializeFrame(
frame_header, std::move(buffers), call_handler->arena(), frame,
frame_limits);
} else {
// Stream not found, skip the frame.
deserialize_status = transport->DeserializeFrame(
frame_header, std::move(buffers),
SimpleArenaAllocator()->MakeArena().get(), frame, frame_limits);
}
return If(
deserialize_status.ok() && call_handler.has_value(),
[this, &frame, &call_handler]() {
return call_handler->SpawnWaitable(
"push-frame", [this, call_handler = *call_handler,
frame = std::move(frame)]() mutable {
return Map(call_handler.CancelIfFails(PushFrameIntoCall(
std::move(frame), call_handler)),
[](StatusFlag) { return absl::OkStatus(); });
});
},
[&deserialize_status]() {
// Stream not found, nothing to do.
return [deserialize_status =
std::move(deserialize_status)]() mutable {
return std::move(deserialize_status);
};
});
std::tuple<FrameHeader, SliceBuffer> frame_bytes) {
const auto& header = std::get<0>(frame_bytes);
SliceBuffer& payload = std::get<1>(frame_bytes);
return Switch(
header.type,
Case<FrameType, FrameType::kServerInitialMetadata>([&, this]() {
return DispatchFrame<ServerInitialMetadataFrame>(
transport, header, std::move(payload));
}),
Case<FrameType, FrameType::kServerTrailingMetadata>([&, this]() {
return DispatchFrame<ServerTrailingMetadataFrame>(
transport, header, std::move(payload));
}),
Case<FrameType, FrameType::kMessage>([&, this]() {
return DispatchFrame<MessageFrame>(transport, header,
std::move(payload));
}),
Default([&]() {
LOG_EVERY_N_SEC(INFO, 10)
<< "Bad frame type: " << header.ToString();
return absl::OkStatus();
}));
},
[]() -> LoopCtl<absl::Status> { return Continue{}; });
});
@ -195,8 +193,7 @@ auto ChaoticGoodClientTransport::OnTransportActivityDone(
ChaoticGoodClientTransport::ChaoticGoodClientTransport(
PromiseEndpoint control_endpoint, PromiseEndpoint data_endpoint,
const ChannelArgs& args,
std::shared_ptr<grpc_event_engine::experimental::EventEngine> event_engine,
HPackParser hpack_parser, HPackCompressor hpack_encoder)
std::shared_ptr<grpc_event_engine::experimental::EventEngine> event_engine)
: allocator_(args.GetObject<ResourceQuota>()
->memory_quota()
->CreateMemoryAllocator("chaotic-good")),
@ -210,9 +207,12 @@ ChaoticGoodClientTransport::ChaoticGoodClientTransport(
epte->InitializeAndReturnTcpTracer();
}
}
ChaoticGoodTransport::Options options;
options.inlined_payload_size_threshold =
args.GetInt("grpc.chaotic_good.inlined_payload_size_threshold")
.value_or(options.inlined_payload_size_threshold);
auto transport = MakeRefCounted<ChaoticGoodTransport>(
std::move(control_endpoint), std::move(data_endpoint),
std::move(hpack_parser), std::move(hpack_encoder));
std::move(control_endpoint), std::move(data_endpoint), options);
auto party_arena = SimpleArenaAllocator(0)->MakeArena();
party_arena->SetContext<grpc_event_engine::experimental::EventEngine>(
event_engine.get());
@ -276,19 +276,19 @@ absl::Status BooleanSuccessToTransportError(bool success) {
auto ChaoticGoodClientTransport::CallOutboundLoop(uint32_t stream_id,
CallHandler call_handler) {
auto send_fragment = [stream_id,
outgoing_frames = outgoing_frames_.MakeSender()](
ClientFragmentFrame frame) mutable {
outgoing_frames =
outgoing_frames_.MakeSender()](auto frame) mutable {
frame.stream_id = stream_id;
return Map(outgoing_frames.Send(std::move(frame)),
BooleanSuccessToTransportError);
};
auto send_fragment_acked = [stream_id,
outgoing_frames = outgoing_frames_.MakeSender()](
ClientFragmentFrame frame) mutable {
frame.stream_id = stream_id;
return Map(outgoing_frames.SendAcked(std::move(frame)),
BooleanSuccessToTransportError);
};
auto send_fragment_acked =
[stream_id,
outgoing_frames = outgoing_frames_.MakeSender()](auto frame) mutable {
frame.stream_id = stream_id;
return Map(outgoing_frames.SendAcked(std::move(frame)),
BooleanSuccessToTransportError);
};
return GRPC_LATENT_SEE_PROMISE(
"CallOutboundLoop",
TrySeq(
@ -298,31 +298,19 @@ auto ChaoticGoodClientTransport::CallOutboundLoop(uint32_t stream_id,
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "CHAOTIC_GOOD: Sending initial metadata: "
<< md->DebugString();
ClientFragmentFrame frame;
frame.headers = std::move(md);
ClientInitialMetadataFrame frame;
frame.headers = ClientMetadataProtoFromGrpc(*md);
return send_fragment(std::move(frame));
},
// Continuously send client frame with client to server messages.
ForEach(OutgoingMessages(call_handler),
[send_fragment_acked, aligned_bytes = aligned_bytes_](
MessageHandle message) mutable {
ClientFragmentFrame frame;
// Construct frame header (flags, header_length and
// trailer_length will be added in serialization).
const uint32_t message_length =
message->payload()->Length();
const uint32_t padding =
message_length % aligned_bytes == 0
? 0
: aligned_bytes - message_length % aligned_bytes;
CHECK_EQ((message_length + padding) % aligned_bytes, 0u);
frame.message = FragmentMessage(std::move(message), padding,
message_length);
[send_fragment_acked](MessageHandle message) mutable {
MessageFrame frame;
frame.message = std::move(message);
return send_fragment_acked(std::move(frame));
}),
[send_fragment]() mutable {
ClientFragmentFrame frame;
frame.end_of_stream = true;
ClientEndOfStream frame;
return send_fragment(std::move(frame));
}));
}

@ -39,8 +39,6 @@
#include "src/core/ext/transport/chaotic_good/chaotic_good_transport.h"
#include "src/core/ext/transport/chaotic_good/frame.h"
#include "src/core/ext/transport/chaotic_good/frame_header.h"
#include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
#include "src/core/ext/transport/chttp2/transport/hpack_parser.h"
#include "src/core/lib/promise/activity.h"
#include "src/core/lib/promise/context.h"
#include "src/core/lib/promise/for_each.h"
@ -69,8 +67,7 @@ class ChaoticGoodClientTransport final : public ClientTransport {
PromiseEndpoint control_endpoint, PromiseEndpoint data_endpoint,
const ChannelArgs& channel_args,
std::shared_ptr<grpc_event_engine::experimental::EventEngine>
event_engine,
HPackParser hpack_parser, HPackCompressor hpack_encoder);
event_engine);
~ChaoticGoodClientTransport() override;
FilterStackTransport* filter_stack_transport() override { return nullptr; }
@ -93,16 +90,21 @@ class ChaoticGoodClientTransport final : public ClientTransport {
auto CallOutboundLoop(uint32_t stream_id, CallHandler call_handler);
auto OnTransportActivityDone(absl::string_view what);
auto TransportWriteLoop(RefCountedPtr<ChaoticGoodTransport> transport);
template <typename T>
auto DispatchFrame(ChaoticGoodTransport* transport, const FrameHeader& header,
SliceBuffer payload);
auto TransportReadLoop(RefCountedPtr<ChaoticGoodTransport> transport);
// Push one frame into a call
auto PushFrameIntoCall(ServerFragmentFrame frame, CallHandler call_handler);
auto PushFrameIntoCall(ServerInitialMetadataFrame frame,
CallHandler call_handler);
auto PushFrameIntoCall(MessageFrame frame, CallHandler call_handler);
auto PushFrameIntoCall(ServerTrailingMetadataFrame frame,
CallHandler call_handler);
grpc_event_engine::experimental::MemoryAllocator allocator_;
// Max buffer is set to 4, so that for stream writes each time it will queue
// at most 2 frames.
MpscReceiver<ClientFrame> outgoing_frames_;
// Assigned aligned bytes from setting frame.
size_t aligned_bytes_ = 64;
Mutex mu_;
uint32_t next_stream_id_ ABSL_GUARDED_BY(mu_) = 1;
// Map of stream incoming server frames, key is stream_id.

@ -20,11 +20,13 @@
#include <cstdint>
#include <limits>
#include <type_traits>
#include <utility>
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "src/core/ext/transport/chaotic_good/chaotic_good_frame.pb.h"
#include "src/core/ext/transport/chaotic_good/frame_header.h"
#include "src/core/lib/promise/context.h"
#include "src/core/lib/resource_quota/arena.h"
@ -38,262 +40,255 @@ namespace grpc_core {
namespace chaotic_good {
namespace {
const uint8_t kZeros[64] = {};
absl::Status ReadProto(SliceBuffer payload,
google::protobuf::MessageLite& msg) {
auto payload_slice = payload.JoinIntoSlice();
const bool ok =
msg.ParseFromArray(payload_slice.data(), payload_slice.length());
return ok ? absl::OkStatus() : absl::InternalError("Protobuf parse error");
}
namespace {
const NoDestruct<Slice> kZeroSlice{[] {
// Frame header size is fixed to 24 bytes.
auto slice = GRPC_SLICE_MALLOC(FrameHeader::kFrameHeaderSize);
memset(GRPC_SLICE_START_PTR(slice), 0, FrameHeader::kFrameHeaderSize);
return slice;
}()};
class FrameSerializer {
public:
explicit FrameSerializer(FrameType frame_type, uint32_t stream_id) {
output_.control.AppendIndexed(kZeroSlice->Copy());
header_.type = frame_type;
header_.stream_id = stream_id;
header_.flags.SetAll(false);
void WriteProto(const google::protobuf::MessageLite& msg, SliceBuffer& output) {
auto length = msg.ByteSizeLong();
auto slice = MutableSlice::CreateUninitialized(length);
CHECK(msg.SerializeToArray(slice.data(), length));
output.AppendIndexed(Slice(std::move(slice)));
}
uint32_t ProtoPayloadSize(const google::protobuf::MessageLite& msg) {
auto length = msg.ByteSizeLong();
CHECK_LE(length, std::numeric_limits<uint32_t>::max());
return static_cast<uint32_t>(length);
}
struct ClientMetadataEncoder {
void Encode(HttpPathMetadata,
const typename HttpPathMetadata::ValueType& value) {
out.set_path(value.as_string_view());
}
// If called, must be called before AddTrailers, Finish.
SliceBuffer& AddHeaders() {
header_.flags.set(0);
return output_.control;
void Encode(HttpAuthorityMetadata,
const typename HttpAuthorityMetadata::ValueType& value) {
out.set_authority(value.as_string_view());
}
void AddMessage(const FragmentMessage& msg) {
header_.flags.set(1);
header_.message_length = msg.length;
header_.message_padding = msg.padding;
output_.data = msg.message->payload()->Copy();
if (msg.padding != 0) {
output_.data.Append(Slice::FromStaticBuffer(kZeros, msg.padding));
void Encode(GrpcTimeoutMetadata,
const typename GrpcTimeoutMetadata::ValueType& value) {
auto now = Timestamp::Now();
if (now > value) {
out.set_timeout_ms(0);
} else {
out.set_timeout_ms((value - now).millis());
}
}
// If called, must be called before Finish.
SliceBuffer& AddTrailers() {
header_.flags.set(2);
header_.header_length =
output_.control.Length() - FrameHeader::kFrameHeaderSize;
return output_.control;
template <typename Which>
void Encode(Which, const typename Which::ValueType& value) {
EncodeWithWarning(Slice::FromExternalString(Which::key()),
Slice(Which::Encode(value)));
}
BufferPair Finish() {
// Calculate frame header_length or trailer_length if available.
if (header_.flags.is_set(2)) {
// Header length is already known in AddTrailers().
header_.trailer_length = output_.control.Length() -
header_.header_length -
FrameHeader::kFrameHeaderSize;
} else {
if (header_.flags.is_set(0)) {
// Calculate frame header length in Finish() since AddTrailers() isn't
// called.
header_.header_length =
output_.control.Length() - FrameHeader::kFrameHeaderSize;
}
}
header_.Serialize(
GRPC_SLICE_START_PTR(output_.control.c_slice_buffer()->slices[0]));
return std::move(output_);
void EncodeWithWarning(const Slice& key, const Slice& value) {
LOG_EVERY_N_SEC(INFO, 10) << "encoding known key " << key.as_string_view()
<< " with unknown encoding";
Encode(key, value);
}
private:
FrameHeader header_;
BufferPair output_;
void Encode(const Slice& key, const Slice& value) {
auto* unk = out.add_unknown_metadata();
unk->set_key(key.as_string_view());
unk->set_value(value.as_string_view());
}
chaotic_good_frame::ClientMetadata out;
};
class FrameDeserializer {
public:
FrameDeserializer(const FrameHeader& header, BufferPair& input)
: header_(header), input_(input) {}
const FrameHeader& header() const { return header_; }
// If called, must be called before ReceiveTrailers, Finish.
absl::StatusOr<SliceBuffer> ReceiveHeaders() {
return Take(header_.header_length);
struct ServerMetadataEncoder {
void Encode(GrpcStatusMetadata, grpc_status_code code) {
out.set_status(code);
}
// If called, must be called before Finish.
absl::StatusOr<SliceBuffer> ReceiveTrailers() {
return Take(header_.trailer_length);
void Encode(GrpcMessageMetadata, const Slice& value) {
out.set_message(value.as_string_view());
}
// Return message length to get payload size in data plane.
uint32_t GetMessageLength() const { return header_.message_length; }
// Return message padding to get padding size in data plane.
uint32_t GetMessagePadding() const { return header_.message_padding; }
template <typename Which>
void Encode(Which, const typename Which::ValueType& value) {
EncodeWithWarning(Slice::FromExternalString(Which::key()),
Slice(Which::Encode(value)));
}
absl::Status Finish() { return absl::OkStatus(); }
void EncodeWithWarning(const Slice& key, const Slice& value) {
LOG_EVERY_N_SEC(INFO, 10) << "encoding known key " << key.as_string_view()
<< " with unknown encoding";
Encode(key, value);
}
private:
absl::StatusOr<SliceBuffer> Take(uint32_t length) {
if (length == 0) return SliceBuffer{};
if (input_.control.Length() < length) {
return absl::InvalidArgumentError(
"Frame too short (insufficient payload)");
}
SliceBuffer out;
input_.control.MoveFirstNBytesIntoSliceBuffer(length, out);
return std::move(out);
void Encode(const Slice& key, const Slice& value) {
auto* unk = out.add_unknown_metadata();
unk->set_key(key.as_string_view());
unk->set_value(value.as_string_view());
}
FrameHeader header_;
BufferPair& input_;
chaotic_good_frame::ServerMetadata out;
};
template <typename Metadata>
absl::StatusOr<Arena::PoolPtr<Metadata>> ReadMetadata(
HPackParser* parser, absl::StatusOr<SliceBuffer> maybe_slices,
uint32_t stream_id, bool is_header, bool is_client, absl::BitGenRef bitsrc,
Arena* arena) {
if (!maybe_slices.ok()) return maybe_slices.status();
auto& slices = *maybe_slices;
CHECK_NE(arena, nullptr);
Arena::PoolPtr<Metadata> metadata = Arena::MakePooledForOverwrite<Metadata>();
parser->BeginFrame(
metadata.get(), std::numeric_limits<uint32_t>::max(),
std::numeric_limits<uint32_t>::max(),
is_header ? HPackParser::Boundary::EndOfHeaders
: HPackParser::Boundary::EndOfStream,
HPackParser::Priority::None,
HPackParser::LogInfo{stream_id,
is_header ? HPackParser::LogInfo::Type::kHeaders
: HPackParser::LogInfo::Type::kTrailers,
is_client});
for (size_t i = 0; i < slices.Count(); i++) {
GRPC_RETURN_IF_ERROR(parser->Parse(slices.c_slice_at(i),
i == slices.Count() - 1, bitsrc,
/*call_tracer=*/nullptr));
}
parser->FinishFrame();
return std::move(metadata);
template <typename T, typename M>
absl::StatusOr<T> ReadUnknownFields(const M& msg, T md) {
absl::Status error = absl::OkStatus();
for (const auto& unk : msg.unknown_metadata()) {
md->Append(unk.key(), Slice::FromCopiedString(unk.value()),
[&error](absl::string_view error_msg, const Slice&) {
if (!error.ok()) return;
error = absl::InternalError(error_msg);
});
}
if (!error.ok()) return error;
return std::move(md);
}
} // namespace
absl::Status FrameLimits::ValidateMessage(const FrameHeader& header) {
if (header.message_length > max_message_size) {
return absl::InvalidArgumentError(
absl::StrCat("Message length ", header.message_length,
" exceeds maximum allowed ", max_message_size));
}
if (header.message_padding > max_padding) {
return absl::InvalidArgumentError(
absl::StrCat("Message padding ", header.message_padding,
" exceeds maximum allowed ", max_padding));
}
return absl::OkStatus();
chaotic_good_frame::ClientMetadata ClientMetadataProtoFromGrpc(
const ClientMetadata& md) {
ClientMetadataEncoder e;
md.Encode(&e);
return std::move(e.out);
}
absl::Status SettingsFrame::Deserialize(HPackParser* parser,
const FrameHeader& header,
absl::BitGenRef bitsrc, Arena* arena,
BufferPair buffers, FrameLimits) {
if (header.type != FrameType::kSettings) {
return absl::InvalidArgumentError("Expected settings frame");
absl::StatusOr<ClientMetadataHandle> ClientMetadataGrpcFromProto(
chaotic_good_frame::ClientMetadata& metadata) {
auto md = Arena::MakePooled<ClientMetadata>();
md->Set(GrpcStatusFromWire(), true);
if (metadata.has_path()) {
md->Set(HttpPathMetadata(), Slice::FromCopiedString(metadata.path()));
}
if (header.flags.is_set(1) || header.flags.is_set(2)) {
return absl::InvalidArgumentError("Unexpected flags");
if (metadata.has_authority()) {
md->Set(HttpAuthorityMetadata(),
Slice::FromCopiedString(metadata.authority()));
}
if (buffers.data.Length() != 0) {
return absl::InvalidArgumentError("Unexpected data");
if (metadata.has_timeout_ms()) {
md->Set(GrpcTimeoutMetadata(),
Timestamp::Now() + Duration::Milliseconds(metadata.timeout_ms()));
}
FrameDeserializer deserializer(header, buffers);
if (header.flags.is_set(0)) {
auto r = ReadMetadata<ClientMetadata>(parser, deserializer.ReceiveHeaders(),
header.stream_id, true, true, bitsrc,
arena);
if (!r.ok()) return r.status();
if (r.value() != nullptr) {
headers = std::move(r.value());
}
} else if (header.header_length != 0) {
return absl::InvalidArgumentError(absl::StrCat(
"Unexpected non-zero header length", header.header_length));
return ReadUnknownFields(metadata, std::move(md));
}
chaotic_good_frame::ServerMetadata ServerMetadataProtoFromGrpc(
const ServerMetadata& md) {
ServerMetadataEncoder e;
md.Encode(&e);
return std::move(e.out);
}
absl::StatusOr<ServerMetadataHandle> ServerMetadataGrpcFromProto(
chaotic_good_frame::ServerMetadata& metadata) {
auto md = Arena::MakePooled<ServerMetadata>();
md->Set(GrpcStatusFromWire(), true);
if (metadata.has_status()) {
md->Set(GrpcStatusMetadata(),
static_cast<grpc_status_code>(metadata.status()));
}
return deserializer.Finish();
if (metadata.has_message()) {
md->Set(GrpcMessageMetadata(), Slice::FromCopiedString(metadata.message()));
}
return ReadUnknownFields(metadata, std::move(md));
}
BufferPair SettingsFrame::Serialize(HPackCompressor* encoder,
bool& saw_encoding_errors) const {
FrameSerializer serializer(FrameType::kSettings, 0);
if (headers.get() != nullptr) {
saw_encoding_errors |=
!encoder->EncodeRawHeaders(*headers.get(), serializer.AddHeaders());
absl::Status SettingsFrame::Deserialize(const FrameHeader& header,
SliceBuffer payload) {
CHECK_EQ(header.type, FrameType::kSettings);
if (header.stream_id != 0) {
return absl::InternalError("Expected stream id 0");
}
return serializer.Finish();
return ReadProto(std::move(payload), settings);
}
std::string SettingsFrame::ToString() const { return "SettingsFrame{}"; }
FrameHeader SettingsFrame::MakeHeader() const {
return FrameHeader{FrameType::kSettings, 0, 0, ProtoPayloadSize(settings)};
}
absl::Status ClientFragmentFrame::Deserialize(HPackParser* parser,
const FrameHeader& header,
absl::BitGenRef bitsrc,
Arena* arena, BufferPair buffers,
FrameLimits limits) {
void SettingsFrame::SerializePayload(SliceBuffer& payload) const {
WriteProto(settings, payload);
}
std::string SettingsFrame::ToString() const {
return settings.ShortDebugString();
}
absl::Status ClientInitialMetadataFrame::Deserialize(const FrameHeader& header,
SliceBuffer payload) {
CHECK_EQ(header.type, FrameType::kClientInitialMetadata);
if (header.stream_id == 0) {
return absl::InvalidArgumentError("Expected non-zero stream id");
return absl::InternalError("Expected non-zero stream id");
}
stream_id = header.stream_id;
if (header.type != FrameType::kFragment) {
return absl::InvalidArgumentError("Expected fragment frame");
}
FrameDeserializer deserializer(header, buffers);
if (header.flags.is_set(0)) {
auto r = ReadMetadata<ClientMetadata>(parser, deserializer.ReceiveHeaders(),
header.stream_id, true, true, bitsrc,
arena);
if (!r.ok()) return r.status();
if (r.value() != nullptr) {
headers = std::move(r.value());
}
} else if (header.header_length != 0) {
return absl::InvalidArgumentError(absl::StrCat(
"Unexpected non-zero header length", header.header_length));
}
if (header.flags.is_set(1)) {
auto r = limits.ValidateMessage(header);
if (!r.ok()) return r;
message =
FragmentMessage{Arena::MakePooled<Message>(std::move(buffers.data), 0),
header.message_padding, header.message_length};
} else if (buffers.data.Length() != 0) {
return absl::InvalidArgumentError(absl::StrCat(
"Unexpected non-zero message length ", buffers.data.Length()));
}
if (header.flags.is_set(2)) {
if (header.trailer_length != 0) {
return absl::InvalidArgumentError(
absl::StrCat("Unexpected trailer length ", header.trailer_length));
}
end_of_stream = true;
} else {
end_of_stream = false;
}
return deserializer.Finish();
return ReadProto(std::move(payload), headers);
}
FrameHeader ClientInitialMetadataFrame::MakeHeader() const {
return FrameHeader{FrameType::kClientInitialMetadata, 0, stream_id,
ProtoPayloadSize(headers)};
}
BufferPair ClientFragmentFrame::Serialize(HPackCompressor* encoder,
bool& saw_encoding_errors) const {
void ClientInitialMetadataFrame::SerializePayload(SliceBuffer& payload) const {
CHECK_NE(stream_id, 0u);
FrameSerializer serializer(FrameType::kFragment, stream_id);
if (headers.get() != nullptr) {
saw_encoding_errors |=
!encoder->EncodeRawHeaders(*headers.get(), serializer.AddHeaders());
WriteProto(headers, payload);
}
std::string ClientInitialMetadataFrame::ToString() const {
return absl::StrCat("ClientInitialMetadataFrame{stream_id=", stream_id,
", headers=", headers.ShortDebugString(), "}");
}
absl::Status ClientEndOfStream::Deserialize(const FrameHeader& header,
SliceBuffer) {
CHECK_EQ(header.type, FrameType::kClientEndOfStream);
if (header.stream_id == 0) {
return absl::InternalError("Expected non-zero stream id");
}
if (message.has_value()) {
serializer.AddMessage(message.value());
if (header.payload_length != 0) {
return absl::InternalError(
"Expected zero payload length on ClientEndOfStream");
}
if (end_of_stream) {
serializer.AddTrailers();
stream_id = header.stream_id;
return absl::OkStatus();
}
FrameHeader ClientEndOfStream::MakeHeader() const {
return FrameHeader{FrameType::kClientEndOfStream, 0, stream_id, 0};
}
void ClientEndOfStream::SerializePayload(SliceBuffer&) const {}
std::string ClientEndOfStream::ToString() const { return "ClientEndOfStream"; }
absl::Status MessageFrame::Deserialize(const FrameHeader& header,
SliceBuffer payload) {
CHECK_EQ(header.type, FrameType::kMessage);
if (header.stream_id == 0) {
return absl::InternalError("Expected non-zero stream id");
}
return serializer.Finish();
stream_id = header.stream_id;
message = Arena::MakePooled<Message>(std::move(payload), 0);
return absl::OkStatus();
}
std::string FragmentMessage::ToString() const {
std::string out =
absl::StrCat("FragmentMessage{length=", length, ", padding=", padding);
FrameHeader MessageFrame::MakeHeader() const {
auto length = message->payload()->Length();
CHECK_LE(length, std::numeric_limits<uint32_t>::max());
return FrameHeader{FrameType::kMessage, 0, stream_id,
static_cast<uint32_t>(length)};
}
void MessageFrame::SerializePayload(SliceBuffer& payload) const {
CHECK_NE(stream_id, 0u);
payload.Append(*message->payload());
}
std::string MessageFrame::ToString() const {
std::string out = absl::StrCat("MessageFrame{stream_id=", stream_id);
if (message.get() != nullptr) {
absl::StrAppend(&out, ", message=", message->DebugString().c_str());
}
@ -301,114 +296,83 @@ std::string FragmentMessage::ToString() const {
return out;
}
std::string ClientFragmentFrame::ToString() const {
return absl::StrCat(
"ClientFragmentFrame{stream_id=", stream_id, ", headers=",
headers.get() != nullptr ? headers->DebugString().c_str() : "nullptr",
", message=", message.has_value() ? message->ToString().c_str() : "none",
", end_of_stream=", end_of_stream, "}");
absl::Status ServerInitialMetadataFrame::Deserialize(const FrameHeader& header,
SliceBuffer payload) {
CHECK_EQ(header.type, FrameType::kServerInitialMetadata);
if (header.stream_id == 0) {
return absl::InternalError("Expected non-zero stream id");
}
stream_id = header.stream_id;
return ReadProto(std::move(payload), headers);
}
FrameHeader ServerInitialMetadataFrame::MakeHeader() const {
return FrameHeader{FrameType::kServerInitialMetadata, 0, stream_id,
ProtoPayloadSize(headers)};
}
absl::Status ServerFragmentFrame::Deserialize(HPackParser* parser,
const FrameHeader& header,
absl::BitGenRef bitsrc,
Arena* arena, BufferPair buffers,
FrameLimits limits) {
void ServerInitialMetadataFrame::SerializePayload(SliceBuffer& payload) const {
CHECK_NE(stream_id, 0u);
WriteProto(headers, payload);
}
std::string ServerInitialMetadataFrame::ToString() const {
return absl::StrCat("ServerInitialMetadataFrame{stream_id=", stream_id,
", headers=", headers.ShortDebugString(), "}");
}
absl::Status ServerTrailingMetadataFrame::Deserialize(const FrameHeader& header,
SliceBuffer payload) {
CHECK_EQ(header.type, FrameType::kServerTrailingMetadata);
if (header.stream_id == 0) {
return absl::InvalidArgumentError("Expected non-zero stream id");
return absl::InternalError("Expected non-zero stream id");
}
stream_id = header.stream_id;
FrameDeserializer deserializer(header, buffers);
if (header.flags.is_set(0)) {
auto r = ReadMetadata<ServerMetadata>(parser, deserializer.ReceiveHeaders(),
header.stream_id, true, false, bitsrc,
arena);
if (!r.ok()) return r.status();
if (r.value() != nullptr) {
headers = std::move(r.value());
}
} else if (header.header_length != 0) {
return absl::InvalidArgumentError(absl::StrCat(
"Unexpected non-zero header length", header.header_length));
}
if (header.flags.is_set(1)) {
auto r = limits.ValidateMessage(header);
if (!r.ok()) return r;
message.emplace(Arena::MakePooled<Message>(std::move(buffers.data), 0),
header.message_padding, header.message_length);
} else if (buffers.data.Length() != 0) {
return absl::InvalidArgumentError(absl::StrCat(
"Unexpected non-zero message length", buffers.data.Length()));
}
if (header.flags.is_set(2)) {
auto r = ReadMetadata<ServerMetadata>(
parser, deserializer.ReceiveTrailers(), header.stream_id, false, false,
bitsrc, arena);
if (!r.ok()) return r.status();
if (r.value() != nullptr) {
trailers = std::move(r.value());
}
} else if (header.trailer_length != 0) {
return absl::InvalidArgumentError(absl::StrCat(
"Unexpected non-zero trailer length", header.trailer_length));
}
return deserializer.Finish();
return ReadProto(std::move(payload), trailers);
}
BufferPair ServerFragmentFrame::Serialize(HPackCompressor* encoder,
bool& saw_encoding_errors) const {
FrameHeader ServerTrailingMetadataFrame::MakeHeader() const {
return FrameHeader{FrameType::kServerTrailingMetadata, 0, stream_id,
ProtoPayloadSize(trailers)};
}
void ServerTrailingMetadataFrame::SerializePayload(SliceBuffer& payload) const {
CHECK_NE(stream_id, 0u);
FrameSerializer serializer(FrameType::kFragment, stream_id);
if (headers.get() != nullptr) {
saw_encoding_errors |=
!encoder->EncodeRawHeaders(*headers.get(), serializer.AddHeaders());
}
if (message.has_value()) {
serializer.AddMessage(message.value());
}
if (trailers.get() != nullptr) {
saw_encoding_errors |=
!encoder->EncodeRawHeaders(*trailers.get(), serializer.AddTrailers());
}
return serializer.Finish();
WriteProto(trailers, payload);
}
std::string ServerFragmentFrame::ToString() const {
return absl::StrCat(
"ServerFragmentFrame{stream_id=", stream_id, ", headers=",
headers.get() != nullptr ? headers->DebugString().c_str() : "nullptr",
", message=", message.has_value() ? message->ToString().c_str() : "none",
", trailers=",
trailers.get() != nullptr ? trailers->DebugString().c_str() : "nullptr",
"}");
std::string ServerTrailingMetadataFrame::ToString() const {
return absl::StrCat("ServerTrailingMetadataFrame{stream_id=", stream_id,
", trailers=", trailers.ShortDebugString(), "}");
}
absl::Status CancelFrame::Deserialize(HPackParser*, const FrameHeader& header,
absl::BitGenRef, Arena*,
BufferPair buffers, FrameLimits) {
if (header.type != FrameType::kCancel) {
return absl::InvalidArgumentError("Expected cancel frame");
}
if (header.flags.any()) {
return absl::InvalidArgumentError("Unexpected flags");
}
absl::Status CancelFrame::Deserialize(const FrameHeader& header,
SliceBuffer payload) {
// Ensure the frame type is Cancel
CHECK_EQ(header.type, FrameType::kCancel);
// Ensure the stream_id is non-zero
if (header.stream_id == 0) {
return absl::InvalidArgumentError("Expected non-zero stream id");
return absl::InternalError("Expected non-zero stream id");
}
if (buffers.data.Length() != 0) {
return absl::InvalidArgumentError("Unexpected data");
// Ensure there is no payload
if (payload.Length() != 0) {
return absl::InternalError("Unexpected payload for Cancel frame");
}
FrameDeserializer deserializer(header, buffers);
// Set the stream_id
stream_id = header.stream_id;
return deserializer.Finish();
return absl::OkStatus();
}
BufferPair CancelFrame::Serialize(HPackCompressor*, bool&) const {
CHECK_NE(stream_id, 0u);
FrameSerializer serializer(FrameType::kCancel, stream_id);
return serializer.Finish();
FrameHeader CancelFrame::MakeHeader() const {
return FrameHeader{FrameType::kCancel, 0, stream_id, 0};
}
void CancelFrame::SerializePayload(SliceBuffer&) const {}
std::string CancelFrame::ToString() const {
return absl::StrCat("CancelFrame{stream_id=", stream_id, "}");
}

@ -18,19 +18,17 @@
#include <grpc/support/port_platform.h>
#include <cstdint>
#include <memory>
#include <string>
#include "absl/random/bit_gen_ref.h"
#include "absl/status/status.h"
#include "absl/types/variant.h"
#include "src/core/ext/transport/chaotic_good/chaotic_good_frame.pb.h"
#include "src/core/ext/transport/chaotic_good/frame_header.h"
#include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
#include "src/core/ext/transport/chttp2/transport/hpack_parser.h"
#include "src/core/lib/resource_quota/arena.h"
#include "src/core/lib/slice/slice_buffer.h"
#include "src/core/lib/transport/metadata_batch.h"
#include "src/core/lib/transport/transport.h"
#include "src/core/lib/transport/message.h"
#include "src/core/lib/transport/metadata.h"
#include "src/core/util/match.h"
namespace grpc_core {
@ -41,21 +39,12 @@ struct BufferPair {
SliceBuffer data;
};
struct FrameLimits {
size_t max_message_size = 1024 * 1024 * 1024;
size_t max_padding = 63;
absl::Status ValidateMessage(const FrameHeader& header);
};
class FrameInterface {
public:
virtual absl::Status Deserialize(HPackParser* parser,
const FrameHeader& header,
absl::BitGenRef bitsrc, Arena* arena,
BufferPair buffers, FrameLimits limits) = 0;
virtual BufferPair Serialize(HPackCompressor* encoder,
bool& saw_encoding_errors) const = 0;
virtual absl::Status Deserialize(const FrameHeader& header,
SliceBuffer payload) = 0;
virtual FrameHeader MakeHeader() const = 0;
virtual void SerializePayload(SliceBuffer& payload) const = 0;
virtual std::string ToString() const = 0;
template <typename Sink>
@ -64,16 +53,6 @@ class FrameInterface {
}
protected:
static bool EqVal(const grpc_metadata_batch& a,
const grpc_metadata_batch& b) {
return a.DebugString() == b.DebugString();
}
template <typename T>
static bool EqHdl(const Arena::PoolPtr<T>& a, const Arena::PoolPtr<T>& b) {
if (a == nullptr && b == nullptr) return true;
if (a == nullptr || b == nullptr) return false;
return EqVal(*a, *b);
}
~FrameInterface() = default;
};
@ -81,111 +60,118 @@ inline std::ostream& operator<<(std::ostream& os, const FrameInterface& frame) {
return os << frame.ToString();
}
chaotic_good_frame::ClientMetadata ClientMetadataProtoFromGrpc(
const ClientMetadata& md);
absl::StatusOr<ClientMetadataHandle> ClientMetadataGrpcFromProto(
chaotic_good_frame::ClientMetadata& metadata);
chaotic_good_frame::ServerMetadata ServerMetadataProtoFromGrpc(
const ServerMetadata& md);
absl::StatusOr<ServerMetadataHandle> ServerMetadataGrpcFromProto(
chaotic_good_frame::ServerMetadata& metadata);
struct SettingsFrame final : public FrameInterface {
absl::Status Deserialize(HPackParser* parser, const FrameHeader& header,
absl::BitGenRef bitsrc, Arena* arena,
BufferPair buffers, FrameLimits limits) override;
BufferPair Serialize(HPackCompressor* encoder,
bool& saw_encoding_errors) const override;
ClientMetadataHandle headers;
absl::Status Deserialize(const FrameHeader& header,
SliceBuffer payload) override;
FrameHeader MakeHeader() const override;
void SerializePayload(SliceBuffer& payload) const override;
std::string ToString() const override;
bool operator==(const SettingsFrame&) const { return true; }
chaotic_good_frame::Settings settings;
};
struct FragmentMessage {
FragmentMessage(MessageHandle message, uint32_t padding, uint32_t length)
: message(std::move(message)), padding(padding), length(length) {}
MessageHandle message;
uint32_t padding;
uint32_t length;
struct ClientInitialMetadataFrame final : public FrameInterface {
absl::Status Deserialize(const FrameHeader& header,
SliceBuffer payload) override;
FrameHeader MakeHeader() const override;
void SerializePayload(SliceBuffer& payload) const override;
std::string ToString() const override;
std::string ToString() const;
uint32_t stream_id;
chaotic_good_frame::ClientMetadata headers;
};
static bool EqVal(const Message& a, const Message& b) {
return a.payload()->JoinIntoString() == b.payload()->JoinIntoString() &&
a.flags() == b.flags();
}
struct MessageFrame final : public FrameInterface {
absl::Status Deserialize(const FrameHeader& header,
SliceBuffer payload) override;
FrameHeader MakeHeader() const override;
void SerializePayload(SliceBuffer& payload) const override;
std::string ToString() const override;
bool operator==(const FragmentMessage& other) const {
if (length != other.length) return false;
if (message == nullptr && other.message == nullptr) return true;
if (message == nullptr || other.message == nullptr) return false;
return EqVal(*message, *other.message);
}
uint32_t stream_id;
MessageHandle message;
};
struct ClientFragmentFrame final : public FrameInterface {
absl::Status Deserialize(HPackParser* parser, const FrameHeader& header,
absl::BitGenRef bitsrc, Arena* arena,
BufferPair buffers, FrameLimits limits) override;
BufferPair Serialize(HPackCompressor* encoder,
bool& saw_encoding_errors) const override;
struct ClientEndOfStream final : public FrameInterface {
absl::Status Deserialize(const FrameHeader& header,
SliceBuffer payload) override;
FrameHeader MakeHeader() const override;
void SerializePayload(SliceBuffer& payload) const override;
std::string ToString() const override;
uint32_t stream_id;
ClientMetadataHandle headers;
absl::optional<FragmentMessage> message;
bool end_of_stream = false;
bool operator==(const ClientFragmentFrame& other) const {
return stream_id == other.stream_id && EqHdl(headers, other.headers) &&
message == other.message && end_of_stream == other.end_of_stream;
}
};
struct ServerFragmentFrame final : public FrameInterface {
absl::Status Deserialize(HPackParser* parser, const FrameHeader& header,
absl::BitGenRef bitsrc, Arena* arena,
BufferPair buffers, FrameLimits limits) override;
BufferPair Serialize(HPackCompressor* encoder,
bool& saw_encoding_errors) const override;
struct ServerInitialMetadataFrame final : public FrameInterface {
absl::Status Deserialize(const FrameHeader& header,
SliceBuffer payload) override;
FrameHeader MakeHeader() const override;
void SerializePayload(SliceBuffer& payload) const override;
std::string ToString() const override;
uint32_t stream_id;
ServerMetadataHandle headers;
absl::optional<FragmentMessage> message;
ServerMetadataHandle trailers;
chaotic_good_frame::ServerMetadata headers;
};
bool operator==(const ServerFragmentFrame& other) const {
return stream_id == other.stream_id && EqHdl(headers, other.headers) &&
message == other.message && EqHdl(trailers, other.trailers);
}
struct ServerTrailingMetadataFrame final : public FrameInterface {
absl::Status Deserialize(const FrameHeader& header,
SliceBuffer payload) override;
FrameHeader MakeHeader() const override;
void SerializePayload(SliceBuffer& payload) const override;
std::string ToString() const override;
uint32_t stream_id;
chaotic_good_frame::ServerMetadata trailers;
};
struct CancelFrame final : public FrameInterface {
CancelFrame() = default;
explicit CancelFrame(uint32_t stream_id) : stream_id(stream_id) {}
absl::Status Deserialize(HPackParser* parser, const FrameHeader& header,
absl::BitGenRef bitsrc, Arena* arena,
BufferPair buffers, FrameLimits limits) override;
BufferPair Serialize(HPackCompressor* encoder,
bool& saw_encoding_errors) const override;
absl::Status Deserialize(const FrameHeader& header,
SliceBuffer payload) override;
FrameHeader MakeHeader() const override;
void SerializePayload(SliceBuffer& payload) const override;
std::string ToString() const override;
uint32_t stream_id;
bool operator==(const CancelFrame& other) const {
return stream_id == other.stream_id;
}
};
using ClientFrame = absl::variant<ClientFragmentFrame, CancelFrame>;
using ServerFrame = absl::variant<ServerFragmentFrame>;
using ClientFrame = absl::variant<ClientInitialMetadataFrame, MessageFrame,
ClientEndOfStream, CancelFrame>;
using ServerFrame = absl::variant<ServerInitialMetadataFrame, MessageFrame,
ServerTrailingMetadataFrame>;
inline FrameInterface& GetFrameInterface(ClientFrame& frame) {
return MatchMutable(
&frame,
[](ClientFragmentFrame* frame) -> FrameInterface& { return *frame; },
[](ClientInitialMetadataFrame* frame) -> FrameInterface& {
return *frame;
},
[](MessageFrame* frame) -> FrameInterface& { return *frame; },
[](ClientEndOfStream* frame) -> FrameInterface& { return *frame; },
[](CancelFrame* frame) -> FrameInterface& { return *frame; });
}
inline FrameInterface& GetFrameInterface(ServerFrame& frame) {
return MatchMutable(
&frame,
[](ServerFragmentFrame* frame) -> FrameInterface& { return *frame; });
[](ServerInitialMetadataFrame* frame) -> FrameInterface& {
return *frame;
},
[](MessageFrame* frame) -> FrameInterface& { return *frame; },
[](ServerTrailingMetadataFrame* frame) -> FrameInterface& {
return *frame;
});
}
} // namespace chaotic_good

@ -42,44 +42,32 @@ uint32_t ReadLittleEndianUint32(const uint8_t* data) {
// Serializes a frame header into a buffer of 24 bytes.
void FrameHeader::Serialize(uint8_t* data) const {
WriteLittleEndianUint32(
static_cast<uint32_t>(type) | (flags.ToInt<uint32_t>() << 8), data);
WriteLittleEndianUint32((static_cast<uint32_t>(type) << 16) |
static_cast<uint32_t>(payload_connection_id),
data);
WriteLittleEndianUint32(stream_id, data + 4);
WriteLittleEndianUint32(header_length, data + 8);
WriteLittleEndianUint32(message_length, data + 12);
WriteLittleEndianUint32(message_padding, data + 16);
WriteLittleEndianUint32(trailer_length, data + 20);
WriteLittleEndianUint32(payload_length, data + 8);
}
// Parses a frame header from a buffer of 24 bytes. All 24 bytes are consumed.
absl::StatusOr<FrameHeader> FrameHeader::Parse(const uint8_t* data) {
FrameHeader header;
const uint32_t type_and_flags = ReadLittleEndianUint32(data);
header.type = static_cast<FrameType>(type_and_flags & 0xff);
const uint32_t flags = type_and_flags >> 8;
if (flags > 7) return absl::InvalidArgumentError("Invalid flags");
header.flags = BitSet<3>::FromInt(flags);
const uint32_t type_and_conn_id = ReadLittleEndianUint32(data);
if (type_and_conn_id & 0xff000000u) {
return absl::InternalError("Non-zero reserved byte received");
}
header.type = static_cast<FrameType>(type_and_conn_id >> 16);
header.payload_connection_id = type_and_conn_id & 0xffff;
header.stream_id = ReadLittleEndianUint32(data + 4);
header.header_length = ReadLittleEndianUint32(data + 8);
header.message_length = ReadLittleEndianUint32(data + 12);
header.message_padding = ReadLittleEndianUint32(data + 16);
header.trailer_length = ReadLittleEndianUint32(data + 20);
header.payload_length = ReadLittleEndianUint32(data + 8);
return header;
}
uint32_t FrameHeader::GetFrameLength() const {
// In chaotic-good transport design, message and message padding are sent
// through different channel. So not included in the frame length calculation.
uint32_t frame_length = header_length + trailer_length;
return frame_length;
}
std::string FrameHeader::ToString() const {
return absl::StrFormat(
"[type=0x%02x, flags=0x%02x, stream_id=%d, header_length=%d, "
"message_length=%d, message_padding=%d, trailer_length=%d]",
static_cast<uint8_t>(type), flags.ToInt<uint8_t>(), stream_id,
header_length, message_length, message_padding, trailer_length);
"[type=0x%02x, conn=0x%04x, stream_id=%d, payload_length=%d]",
static_cast<uint8_t>(type), payload_connection_id, stream_id,
payload_length);
}
} // namespace chaotic_good

@ -26,18 +26,31 @@
namespace grpc_core {
namespace chaotic_good {
// Remember to add new frame types to frame_fuzzer.cc
enum class FrameType : uint8_t {
kSettings = 0x00,
kFragment = 0x80,
kCancel = 0x81,
kClientInitialMetadata = 0x80,
kClientEndOfStream = 0x81,
kServerInitialMetadata = 0x91,
kServerTrailingMetadata = 0x92,
kMessage = 0xa0,
kCancel = 0xff,
};
inline std::ostream& operator<<(std::ostream& out, FrameType type) {
switch (type) {
case FrameType::kSettings:
return out << "Settings";
case FrameType::kFragment:
return out << "Fragment";
case FrameType::kClientInitialMetadata:
return out << "ClientInitialMetadata";
case FrameType::kClientEndOfStream:
return out << "ClientEndOfStream";
case FrameType::kMessage:
return out << "Message";
case FrameType::kServerInitialMetadata:
return out << "ServerInitialMetadata";
case FrameType::kServerTrailingMetadata:
return out << "ServerTrailingMetadata";
case FrameType::kCancel:
return out << "Cancel";
default:
@ -47,33 +60,40 @@ inline std::ostream& operator<<(std::ostream& out, FrameType type) {
struct FrameHeader {
FrameType type = FrameType::kCancel;
BitSet<3> flags;
uint16_t payload_connection_id = 0;
uint32_t stream_id = 0;
uint32_t header_length = 0;
uint32_t message_length = 0;
uint32_t message_padding = 0;
uint32_t trailer_length = 0;
uint32_t payload_length = 0;
// Parses a frame header from a buffer of 24 bytes. All 24 bytes are consumed.
// Parses a frame header from a buffer of 12 bytes. All 12 bytes are consumed.
static absl::StatusOr<FrameHeader> Parse(const uint8_t* data);
// Serializes a frame header into a buffer of 24 bytes.
// Serializes a frame header into a buffer of 12 bytes.
void Serialize(uint8_t* data) const;
// Compute frame sizes from the header.
uint32_t GetFrameLength() const;
// Report contents as a string
std::string ToString() const;
// Required padding to maintain alignment.
uint32_t Padding(uint32_t alignment) const {
if (payload_connection_id == 0) {
return 0;
}
if (payload_length % alignment == 0) {
return 0;
}
return alignment - (payload_length % alignment);
}
bool operator==(const FrameHeader& h) const {
return type == h.type && flags == h.flags && stream_id == h.stream_id &&
header_length == h.header_length &&
message_length == h.message_length &&
message_padding == h.message_padding &&
trailer_length == h.trailer_length;
return type == h.type && stream_id == h.stream_id &&
payload_connection_id == h.payload_connection_id &&
payload_length == h.payload_length;
}
// Frame header size is fixed to 24 bytes.
static constexpr size_t kFrameHeaderSize = 24;
// Frame header size is fixed to 12 bytes.
enum { kFrameHeaderSize = 12 };
};
inline std::ostream& operator<<(std::ostream& out, const FrameHeader& h) {
return out << h.ToString();
}
} // namespace chaotic_good
} // namespace grpc_core

@ -34,7 +34,6 @@
#include "src/core/ext/transport/chaotic_good/frame.h"
#include "src/core/ext/transport/chaotic_good/frame_header.h"
#include "src/core/ext/transport/chaotic_good/server_transport.h"
#include "src/core/ext/transport/chaotic_good/settings_metadata.h"
#include "src/core/ext/transport/chaotic_good_legacy/server/chaotic_good_server.h"
#include "src/core/handshaker/handshaker.h"
#include "src/core/lib/channel/channel_args.h"
@ -225,51 +224,39 @@ auto ChaoticGoodServerListener::ActiveConnection::HandshakingState::
// Parse frame header
auto frame_header = FrameHeader::Parse(reinterpret_cast<const uint8_t*>(
GRPC_SLICE_START_PTR(slice.c_slice())));
if (frame_header.ok() && frame_header->type != FrameType::kSettings) {
frame_header = absl::InternalError("Not a settings frame");
}
return If(
frame_header.ok(),
[self, &frame_header]() {
return TrySeq(
self->connection_->endpoint_.Read(
frame_header->GetFrameLength()),
frame_header->payload_length),
[frame_header = *frame_header,
self](SliceBuffer buffer) -> absl::StatusOr<bool> {
// Read Setting frame.
SettingsFrame frame;
// Deserialize frame from read buffer.
BufferPair buffer_pair{std::move(buffer), SliceBuffer()};
auto status = frame.Deserialize(
&self->connection_->hpack_parser_, frame_header,
absl::BitGenRef(self->connection_->bitgen_),
GetContext<Arena>(), std::move(buffer_pair),
FrameLimits{});
auto status =
frame.Deserialize(frame_header, std::move(buffer));
if (!status.ok()) return status;
if (frame.headers == nullptr) {
return absl::UnavailableError("no settings headers");
}
auto settings_metadata =
SettingsMetadata::FromMetadataBatch(*frame.headers);
if (!settings_metadata.ok()) {
return settings_metadata.status();
}
const bool is_control_endpoint =
settings_metadata->connection_type ==
SettingsMetadata::ConnectionType::kControl;
if (!is_control_endpoint) {
if (!settings_metadata->connection_id.has_value()) {
if (frame.settings.data_channel()) {
if (frame.settings.connection_id().empty()) {
return absl::UnavailableError(
"no connection id in data endpoint settings frame");
}
if (!settings_metadata->alignment.has_value()) {
if (frame.settings.alignment() == 0) {
return absl::UnavailableError(
"no alignment in data endpoint settings frame");
}
// Get connection-id and data-alignment for data endpoint.
self->connection_->connection_id_ =
*settings_metadata->connection_id;
frame.settings.connection_id();
self->connection_->data_alignment_ =
*settings_metadata->alignment;
frame.settings.alignment();
}
return is_control_endpoint;
return !frame.settings.data_channel();
});
},
[&frame_header]() {
@ -309,9 +296,7 @@ auto ChaoticGoodServerListener::ActiveConnection::HandshakingState::
new ChaoticGoodServerTransport(
self->connection_->args(),
std::move(self->connection_->endpoint_), std::move(ret),
self->connection_->listener_->event_engine_,
std::move(self->connection_->hpack_parser_),
std::move(self->connection_->hpack_compressor_)),
self->connection_->listener_->event_engine_),
nullptr, self->connection_->args(), nullptr);
}),
// Set timeout for waiting data endpoint connect.
@ -331,33 +316,31 @@ auto ChaoticGoodServerListener::ActiveConnection::HandshakingState::
ControlEndpointWriteSettingsFrame(RefCountedPtr<HandshakingState> self) {
self->connection_->NewConnectionID();
SettingsFrame frame;
frame.headers =
SettingsMetadata{absl::nullopt, self->connection_->connection_id_,
absl::nullopt}
.ToMetadataBatch();
bool saw_encoding_errors = false;
auto write_buffer = frame.Serialize(&self->connection_->hpack_compressor_,
saw_encoding_errors);
frame.settings.set_data_channel(false);
frame.settings.set_connection_id(self->connection_->connection_id_);
SliceBuffer write_buffer;
frame.MakeHeader().Serialize(
write_buffer.AddTiny(FrameHeader::kFrameHeaderSize));
frame.SerializePayload(write_buffer);
// ignore encoding errors: they will be logged separately already
return TrySeq(
self->connection_->endpoint_.Write(std::move(write_buffer.control)),
WaitForDataEndpointSetup(self));
return TrySeq(self->connection_->endpoint_.Write(std::move(write_buffer)),
WaitForDataEndpointSetup(self));
}
auto ChaoticGoodServerListener::ActiveConnection::HandshakingState::
DataEndpointWriteSettingsFrame(RefCountedPtr<HandshakingState> self) {
// Send data endpoint setting frame
SettingsFrame frame;
frame.headers =
SettingsMetadata{absl::nullopt, self->connection_->connection_id_,
self->connection_->data_alignment_}
.ToMetadataBatch();
bool saw_encoding_errors = false;
auto write_buffer = frame.Serialize(&self->connection_->hpack_compressor_,
saw_encoding_errors);
frame.settings.set_data_channel(true);
frame.settings.set_connection_id(self->connection_->connection_id_);
frame.settings.set_alignment(self->connection_->data_alignment_);
SliceBuffer write_buffer;
frame.MakeHeader().Serialize(
write_buffer.AddTiny(FrameHeader::kFrameHeaderSize));
frame.SerializePayload(write_buffer);
// ignore encoding errors: they will be logged separately already
return TrySeq(
self->connection_->endpoint_.Write(std::move(write_buffer.control)),
self->connection_->endpoint_.Write(std::move(write_buffer)),
[self]() mutable {
MutexLock lock(&self->connection_->listener_->mu_);
// Set endpoint to latch

@ -29,8 +29,6 @@
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "src/core/channelz/channelz.h"
#include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
#include "src/core/ext/transport/chttp2/transport/hpack_parser.h"
#include "src/core/handshaker/handshaker.h"
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/iomgr/closure.h"
@ -119,8 +117,6 @@ class ChaoticGoodServerListener final : public Server::ListenerInterface {
ActivityPtr receive_settings_activity_ ABSL_GUARDED_BY(mu_);
bool orphaned_ ABSL_GUARDED_BY(mu_) = false;
PromiseEndpoint endpoint_;
HPackCompressor hpack_compressor_;
HPackParser hpack_parser_;
absl::BitGen bitgen_;
std::string connection_id_;
int32_t data_alignment_;

@ -32,7 +32,6 @@
#include "src/core/ext/transport/chaotic_good/chaotic_good_transport.h"
#include "src/core/ext/transport/chaotic_good/frame.h"
#include "src/core/ext/transport/chaotic_good/frame_header.h"
#include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
#include "src/core/lib/event_engine/event_engine_context.h"
#include "src/core/lib/iomgr/exec_ctx.h"
#include "src/core/lib/promise/activity.h"
@ -70,57 +69,44 @@ auto ChaoticGoodServerTransport::TransportWriteLoop(
});
}
auto ChaoticGoodServerTransport::PushFragmentIntoCall(
CallInitiator call_initiator, ClientFragmentFrame frame) {
DCHECK(frame.headers == nullptr);
auto ChaoticGoodServerTransport::PushFrameIntoCall(CallInitiator call_initiator,
MessageFrame frame) {
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "CHAOTIC_GOOD: PushFragmentIntoCall: frame=" << frame.ToString();
return Seq(If(
frame.message.has_value(),
[&call_initiator, &frame]() mutable {
return call_initiator.PushMessage(
std::move(frame.message->message));
},
[]() -> StatusFlag { return Success{}; }),
[call_initiator, end_of_stream = frame.end_of_stream](
StatusFlag status) mutable -> StatusFlag {
if (!status.ok() && GRPC_TRACE_FLAG_ENABLED(chaotic_good)) {
LOG(INFO) << "CHAOTIC_GOOD: Failed PushFragmentIntoCall";
}
if (end_of_stream || !status.ok()) {
call_initiator.FinishSends();
// Note that we cannot remove from the stream map yet, as we
// may yet receive a cancellation.
}
return Success{};
});
<< "CHAOTIC_GOOD: PushFrameIntoCall: frame=" << frame.ToString();
return call_initiator.PushMessage(std::move(frame.message));
}
auto ChaoticGoodServerTransport::MaybePushFragmentIntoCall(
absl::optional<CallInitiator> call_initiator, absl::Status error,
ClientFragmentFrame frame) {
return If(
call_initiator.has_value() && error.ok(),
[this, &call_initiator, &frame]() {
return Map(
call_initiator->SpawnWaitable(
"push-fragment",
[call_initiator, frame = std::move(frame), this]() mutable {
return call_initiator->CancelIfFails(
PushFragmentIntoCall(*call_initiator, std::move(frame)));
}),
[](StatusFlag status) { return StatusCast<absl::Status>(status); });
auto ChaoticGoodServerTransport::PushFrameIntoCall(CallInitiator call_initiator,
ClientEndOfStream) {
call_initiator.FinishSends();
// Note that we cannot remove from the stream map yet, as we
// may yet receive a cancellation.
return Immediate(Success{});
}
template <typename T>
auto ChaoticGoodServerTransport::DispatchFrame(ChaoticGoodTransport& transport,
const FrameHeader& header,
SliceBuffer payload) {
return TrySeq(
[&transport, header, payload = std::move(payload)]() mutable {
return transport.DeserializeFrame<T>(header, std::move(payload));
},
[&error, &frame]() {
// EOF frames may arrive after the call_initiator's OnDone callback
// has been invoked. In that case, the call_initiator would have
// already been removed from the stream_map and hence the EOF frame
// cannot be pushed into the call. No need to log such frames.
if (!frame.end_of_stream) {
LOG(INFO) << "CHAOTIC_GOOD: Cannot pass frame to stream. Error:"
<< error.ToString() << " Frame:" << frame.ToString();
}
return Immediate(std::move(error));
[this](T frame) {
absl::optional<CallInitiator> call_initiator =
LookupStream(frame.stream_id);
return If(
call_initiator.has_value(),
[this, &call_initiator, &frame]() {
return call_initiator->SpawnWaitable(
"push-frame", [this, call_initiator = *call_initiator,
frame = std::move(frame)]() mutable {
return Map(call_initiator.CancelIfFails(PushFrameIntoCall(
call_initiator, std::move(frame))),
[](StatusFlag) { return absl::OkStatus(); });
});
},
[]() { return absl::OkStatus(); });
});
}
@ -133,11 +119,9 @@ auto BooleanSuccessToTransportErrorCapturingInitiator(CallInitiator initiator) {
}
} // namespace
auto ChaoticGoodServerTransport::SendFragment(
ServerFragmentFrame frame, MpscSender<ServerFrame> outgoing_frames,
auto ChaoticGoodServerTransport::SendFrame(
ServerFrame frame, MpscSender<ServerFrame> outgoing_frames,
CallInitiator call_initiator) {
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "CHAOTIC_GOOD: SendFragment: frame=" << frame.ToString();
// Capture the call_initiator to ensure the underlying call spine is alive
// until the outgoing_frames.Send promise completes.
return Map(outgoing_frames.Send(std::move(frame)),
@ -145,11 +129,9 @@ auto ChaoticGoodServerTransport::SendFragment(
std::move(call_initiator)));
}
auto ChaoticGoodServerTransport::SendFragmentAcked(
ServerFragmentFrame frame, MpscSender<ServerFrame> outgoing_frames,
auto ChaoticGoodServerTransport::SendFrameAcked(
ServerFrame frame, MpscSender<ServerFrame> outgoing_frames,
CallInitiator call_initiator) {
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "CHAOTIC_GOOD: SendFragmentAcked: frame=" << frame.ToString();
// Capture the call_initiator to ensure the underlying call spine is alive
// until the outgoing_frames.Send promise completes.
return Map(outgoing_frames.SendAcked(std::move(frame)),
@ -160,30 +142,16 @@ auto ChaoticGoodServerTransport::SendFragmentAcked(
auto ChaoticGoodServerTransport::SendCallBody(
uint32_t stream_id, MpscSender<ServerFrame> outgoing_frames,
CallInitiator call_initiator) {
// Continuously send client frame with client to server
// messages.
return ForEach(
OutgoingMessages(call_initiator),
// Capture the call_initiator to ensure the underlying call
// spine is alive until the SendFragment promise completes.
[stream_id, outgoing_frames, call_initiator,
aligned_bytes = aligned_bytes_](MessageHandle message) mutable {
ServerFragmentFrame frame;
// Construct frame header (flags, header_length
// and trailer_length will be added in
// serialization).
const uint32_t message_length = message->payload()->Length();
const uint32_t padding =
message_length % aligned_bytes == 0
? 0
: aligned_bytes - (message_length % aligned_bytes);
CHECK_EQ((message_length + padding) % aligned_bytes, 0u);
frame.message =
FragmentMessage(std::move(message), padding, message_length);
frame.stream_id = stream_id;
return SendFragmentAcked(std::move(frame), outgoing_frames,
call_initiator);
});
// Continuously send client frame with client to server messages.
return ForEach(OutgoingMessages(call_initiator),
[this, stream_id, outgoing_frames = std::move(outgoing_frames),
call_initiator](MessageHandle message) mutable {
MessageFrame frame;
frame.message = std::move(message);
frame.stream_id = stream_id;
return SendFrameAcked(std::move(frame), outgoing_frames,
call_initiator);
});
}
auto ChaoticGoodServerTransport::SendCallInitialMetadataAndBody(
@ -200,12 +168,11 @@ auto ChaoticGoodServerTransport::SendCallInitialMetadataAndBody(
return If(
md.has_value(),
[&md, stream_id, &outgoing_frames, &call_initiator, this]() {
ServerFragmentFrame frame;
frame.headers = std::move(*md);
ServerInitialMetadataFrame frame;
frame.headers = ServerMetadataProtoFromGrpc(**md);
frame.stream_id = stream_id;
return TrySeq(
SendFragment(std::move(frame), outgoing_frames,
call_initiator),
SendFrame(std::move(frame), outgoing_frames, call_initiator),
SendCallBody(stream_id, outgoing_frames, call_initiator));
},
[]() { return absl::OkStatus(); });
@ -228,62 +195,48 @@ auto ChaoticGoodServerTransport::CallOutboundLoop(
call_initiator.PullServerTrailingMetadata(),
// Capture the call_initiator to ensure the underlying call_spine
// is alive until the SendFragment promise completes.
[stream_id, outgoing_frames,
[this, stream_id, outgoing_frames,
call_initiator](ServerMetadataHandle md) mutable {
ServerFragmentFrame frame;
frame.trailers = std::move(md);
ServerTrailingMetadataFrame frame;
frame.trailers = ServerMetadataProtoFromGrpc(*md);
frame.stream_id = stream_id;
return SendFragment(std::move(frame), outgoing_frames,
call_initiator);
return SendFrame(std::move(frame), outgoing_frames, call_initiator);
}));
}
auto ChaoticGoodServerTransport::DeserializeAndPushFragmentToNewCall(
FrameHeader frame_header, BufferPair buffers,
ChaoticGoodTransport& transport) {
ClientFragmentFrame fragment_frame;
absl::Status ChaoticGoodServerTransport::NewStream(
ChaoticGoodTransport& transport, const FrameHeader& header,
SliceBuffer payload) {
CHECK_EQ(header.payload_length, payload.Length());
auto client_initial_metadata_frame =
transport.DeserializeFrame<ClientInitialMetadataFrame>(
header, std::move(payload));
if (!client_initial_metadata_frame.ok()) {
return client_initial_metadata_frame.status();
}
auto md = ClientMetadataGrpcFromProto(client_initial_metadata_frame->headers);
if (!md.ok()) {
return md.status();
}
RefCountedPtr<Arena> arena(call_arena_allocator_->MakeArena());
arena->SetContext<grpc_event_engine::experimental::EventEngine>(
event_engine_.get());
absl::Status status = transport.DeserializeFrame(
frame_header, std::move(buffers), arena.get(), fragment_frame,
FrameLimits{1024 * 1024 * 1024, aligned_bytes_ - 1});
absl::optional<CallInitiator> call_initiator;
if (status.ok()) {
auto call =
MakeCallPair(std::move(fragment_frame.headers), std::move(arena));
call_initiator.emplace(std::move(call.initiator));
auto add_result = NewStream(frame_header.stream_id, *call_initiator);
if (add_result.ok()) {
call_initiator->SpawnGuarded(
"server-write", [this, stream_id = frame_header.stream_id,
call_initiator = *call_initiator,
call_handler = std::move(call.handler)]() mutable {
call_destination_->StartCall(std::move(call_handler));
return CallOutboundLoop(stream_id, call_initiator);
});
} else {
call_initiator.reset();
status = add_result;
}
auto call = MakeCallPair(std::move(*md), std::move(arena));
call_initiator.emplace(std::move(call.initiator));
const auto stream_id = client_initial_metadata_frame->stream_id;
auto add_result = NewStream(stream_id, *call_initiator);
if (!add_result.ok()) {
call_initiator.reset();
return add_result;
}
return MaybePushFragmentIntoCall(std::move(call_initiator), std::move(status),
std::move(fragment_frame));
}
auto ChaoticGoodServerTransport::DeserializeAndPushFragmentToExistingCall(
FrameHeader frame_header, BufferPair buffers,
ChaoticGoodTransport& transport) {
absl::optional<CallInitiator> call_initiator =
LookupStream(frame_header.stream_id);
Arena* arena = nullptr;
if (call_initiator.has_value()) arena = call_initiator->arena();
ClientFragmentFrame fragment_frame;
absl::Status status = transport.DeserializeFrame(
frame_header, std::move(buffers), arena, fragment_frame,
FrameLimits{1024 * 1024 * 1024, aligned_bytes_ - 1});
return MaybePushFragmentIntoCall(std::move(call_initiator), std::move(status),
std::move(fragment_frame));
call_initiator->SpawnGuarded(
"server-write", [this, stream_id, call_initiator = *call_initiator,
call_handler = std::move(call.handler)]() mutable {
call_destination_->StartCall(std::move(call_handler));
return CallOutboundLoop(stream_id, call_initiator);
});
return absl::OkStatus();
}
auto ChaoticGoodServerTransport::ReadOneFrame(ChaoticGoodTransport& transport) {
@ -292,51 +245,46 @@ auto ChaoticGoodServerTransport::ReadOneFrame(ChaoticGoodTransport& transport) {
TrySeq(
transport.ReadFrameBytes(),
[this, transport = &transport](
std::tuple<FrameHeader, BufferPair> frame_bytes) {
const auto& frame_header = std::get<0>(frame_bytes);
auto& buffers = std::get<1>(frame_bytes);
std::tuple<FrameHeader, SliceBuffer> frame_bytes) {
const auto& header = std::get<0>(frame_bytes);
SliceBuffer& payload = std::get<1>(frame_bytes);
CHECK_EQ(header.payload_length, payload.Length());
return Switch(
frame_header.type,
Case(FrameType::kSettings,
[]() -> absl::Status {
return absl::InternalError("Unexpected settings frame");
}),
Case(FrameType::kFragment,
[this, &frame_header, &buffers, transport]() {
return If(
frame_header.flags.is_set(0),
[this, &frame_header, &buffers, transport]() {
return DeserializeAndPushFragmentToNewCall(
frame_header, std::move(buffers), *transport);
},
[this, &frame_header, &buffers, transport]() {
return DeserializeAndPushFragmentToExistingCall(
frame_header, std::move(buffers), *transport);
});
}),
Case(FrameType::kCancel,
[this, &frame_header]() {
absl::optional<CallInitiator> call_initiator =
ExtractStream(frame_header.stream_id);
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "Cancel stream " << frame_header.stream_id
<< (call_initiator.has_value() ? " (active)"
: " (not found)");
return If(
call_initiator.has_value(),
[&call_initiator]() {
auto c = std::move(*call_initiator);
return c.SpawnWaitable("cancel", [c]() mutable {
c.Cancel();
return absl::OkStatus();
});
},
[]() -> absl::Status { return absl::OkStatus(); });
}),
Default([frame_header]() {
header.type,
Case<FrameType, FrameType::kClientInitialMetadata>([&, this]() {
return Immediate(
NewStream(*transport, header, std::move(payload)));
}),
Case<FrameType, FrameType::kMessage>([&, this]() {
return DispatchFrame<MessageFrame>(*transport, header,
std::move(payload));
}),
Case<FrameType, FrameType::kClientEndOfStream>([&, this]() {
return DispatchFrame<ClientEndOfStream>(*transport, header,
std::move(payload));
}),
Case<FrameType, FrameType::kCancel>([&, this]() {
absl::optional<CallInitiator> call_initiator =
ExtractStream(header.stream_id);
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "Cancel stream " << header.stream_id
<< (call_initiator.has_value() ? " (active)"
: " (not found)");
return If(
call_initiator.has_value(),
[&call_initiator]() {
auto c = std::move(*call_initiator);
return c.SpawnWaitable("cancel", [c]() mutable {
c.Cancel();
return absl::OkStatus();
});
},
[]() -> absl::Status { return absl::OkStatus(); });
}),
Default([&]() {
return absl::InternalError(
absl::StrCat("Unexpected frame type: ",
static_cast<uint8_t>(frame_header.type)));
static_cast<uint8_t>(header.type)));
}));
},
[]() -> LoopCtl<absl::Status> { return Continue{}; }));
@ -364,8 +312,7 @@ auto ChaoticGoodServerTransport::OnTransportActivityDone(
ChaoticGoodServerTransport::ChaoticGoodServerTransport(
const ChannelArgs& args, PromiseEndpoint control_endpoint,
PromiseEndpoint data_endpoint,
std::shared_ptr<grpc_event_engine::experimental::EventEngine> event_engine,
HPackParser hpack_parser, HPackCompressor hpack_encoder)
std::shared_ptr<grpc_event_engine::experimental::EventEngine> event_engine)
: call_arena_allocator_(MakeRefCounted<CallArenaAllocator>(
args.GetObject<ResourceQuota>()
->memory_quota()
@ -373,9 +320,12 @@ ChaoticGoodServerTransport::ChaoticGoodServerTransport(
1024)),
event_engine_(event_engine),
outgoing_frames_(4) {
ChaoticGoodTransport::Options options;
options.inlined_payload_size_threshold =
args.GetInt("grpc.chaotic_good.inlined_payload_size_threshold")
.value_or(options.inlined_payload_size_threshold);
auto transport = MakeRefCounted<ChaoticGoodTransport>(
std::move(control_endpoint), std::move(data_endpoint),
std::move(hpack_parser), std::move(hpack_encoder));
std::move(control_endpoint), std::move(data_endpoint), options);
auto party_arena = SimpleArenaAllocator(0)->MakeArena();
party_arena->SetContext<grpc_event_engine::experimental::EventEngine>(
event_engine.get());

@ -45,8 +45,6 @@
#include "src/core/ext/transport/chaotic_good/chaotic_good_transport.h"
#include "src/core/ext/transport/chaotic_good/frame.h"
#include "src/core/ext/transport/chaotic_good/frame_header.h"
#include "src/core/ext/transport/chttp2/transport/hpack_encoder.h"
#include "src/core/ext/transport/chttp2/transport/hpack_parser.h"
#include "src/core/lib/event_engine/default_event_engine.h" // IWYU pragma: keep
#include "src/core/lib/promise/activity.h"
#include "src/core/lib/promise/context.h"
@ -81,8 +79,7 @@ class ChaoticGoodServerTransport final : public ServerTransport {
const ChannelArgs& args, PromiseEndpoint control_endpoint,
PromiseEndpoint data_endpoint,
std::shared_ptr<grpc_event_engine::experimental::EventEngine>
event_engine,
HPackParser hpack_parser, HPackCompressor hpack_encoder);
event_engine);
FilterStackTransport* filter_stack_transport() override { return nullptr; }
ClientTransport* client_transport() override { return nullptr; }
@ -108,12 +105,6 @@ class ChaoticGoodServerTransport final : public ServerTransport {
CallInitiator call_initiator);
auto SendCallBody(uint32_t stream_id, MpscSender<ServerFrame> outgoing_frames,
CallInitiator call_initiator);
static auto SendFragment(ServerFragmentFrame frame,
MpscSender<ServerFrame> outgoing_frames,
CallInitiator call_initiator);
static auto SendFragmentAcked(ServerFragmentFrame frame,
MpscSender<ServerFrame> outgoing_frames,
CallInitiator call_initiator);
auto CallOutboundLoop(uint32_t stream_id, CallInitiator call_initiator);
auto OnTransportActivityDone(absl::string_view activity);
auto TransportReadLoop(RefCountedPtr<ChaoticGoodTransport> transport);
@ -130,10 +121,19 @@ class ChaoticGoodServerTransport final : public ServerTransport {
auto DeserializeAndPushFragmentToExistingCall(
FrameHeader frame_header, BufferPair buffers,
ChaoticGoodTransport& transport);
auto MaybePushFragmentIntoCall(absl::optional<CallInitiator> call_initiator,
absl::Status error, ClientFragmentFrame frame);
auto PushFragmentIntoCall(CallInitiator call_initiator,
ClientFragmentFrame frame);
absl::Status NewStream(ChaoticGoodTransport& transport,
const FrameHeader& header,
SliceBuffer initial_metadata_payload);
template <typename T>
auto DispatchFrame(ChaoticGoodTransport& transport, const FrameHeader& header,
SliceBuffer payload);
auto PushFrameIntoCall(CallInitiator call_initiator, MessageFrame frame);
auto PushFrameIntoCall(CallInitiator call_initiator, ClientEndOfStream frame);
auto SendFrame(ServerFrame frame, MpscSender<ServerFrame> outgoing_frames,
CallInitiator call_initiator);
auto SendFrameAcked(ServerFrame frame,
MpscSender<ServerFrame> outgoing_frames,
CallInitiator call_initiator);
RefCountedPtr<UnstartedCallDestination> call_destination_;
const RefCountedPtr<CallArenaAllocator> call_arena_allocator_;
@ -141,8 +141,6 @@ class ChaoticGoodServerTransport final : public ServerTransport {
event_engine_;
InterActivityLatch<void> got_acceptor_;
MpscReceiver<ServerFrame> outgoing_frames_;
// Assigned aligned bytes from setting frame.
size_t aligned_bytes_ = 64;
Mutex mu_;
// Map of stream incoming server frames, key is stream_id.
StreamMap stream_map_ ABSL_GUARDED_BY(mu_);

@ -1,79 +0,0 @@
// Copyright 2024 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.
#include "src/core/ext/transport/chaotic_good/settings_metadata.h"
#include <grpc/support/port_platform.h>
#include "absl/status/status.h"
#include "src/core/util/crash.h"
namespace grpc_core {
namespace chaotic_good {
Arena::PoolPtr<grpc_metadata_batch> SettingsMetadata::ToMetadataBatch() {
auto md = Arena::MakePooledForOverwrite<grpc_metadata_batch>();
auto add = [&md](absl::string_view key, std::string value) {
md->Append(key, Slice::FromCopiedString(value),
[key, value](absl::string_view error, const Slice&) {
Crash(absl::StrCat("Failed to add metadata '", key, "' = '",
value, "': ", error));
});
};
if (connection_type.has_value()) {
add("chaotic-good-connection-type",
connection_type.value() == ConnectionType::kControl ? "control"
: "data");
}
if (connection_id.has_value()) {
add("chaotic-good-connection-id", connection_id.value());
}
if (alignment.has_value()) {
add("chaotic-good-alignment", absl::StrCat(alignment.value()));
}
return md;
}
absl::StatusOr<SettingsMetadata> SettingsMetadata::FromMetadataBatch(
const grpc_metadata_batch& batch) {
SettingsMetadata md;
std::string buffer;
auto v = batch.GetStringValue("chaotic-good-connection-type", &buffer);
if (v.has_value()) {
if (*v == "control") {
md.connection_type = ConnectionType::kControl;
} else if (*v == "data") {
md.connection_type = ConnectionType::kData;
} else {
return absl::UnavailableError(
absl::StrCat("Invalid connection type: ", *v));
}
}
v = batch.GetStringValue("chaotic-good-connection-id", &buffer);
if (v.has_value()) {
md.connection_id = std::string(*v);
}
v = batch.GetStringValue("chaotic-good-alignment", &buffer);
if (v.has_value()) {
uint32_t alignment;
if (!absl::SimpleAtoi(*v, &alignment)) {
return absl::UnavailableError(absl::StrCat("Invalid alignment: ", *v));
}
md.alignment = alignment;
}
return md;
}
} // namespace chaotic_good
} // namespace grpc_core

@ -1,45 +0,0 @@
// Copyright 2024 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_SRC_CORE_EXT_TRANSPORT_CHAOTIC_GOOD_SETTINGS_METADATA_H
#define GRPC_SRC_CORE_EXT_TRANSPORT_CHAOTIC_GOOD_SETTINGS_METADATA_H
#include <grpc/support/port_platform.h>
#include "absl/types/optional.h"
#include "src/core/lib/resource_quota/arena.h"
#include "src/core/lib/transport/metadata_batch.h"
namespace grpc_core {
namespace chaotic_good {
// Captures metadata sent in a chaotic good settings frame.
struct SettingsMetadata {
enum class ConnectionType {
kControl,
kData,
};
absl::optional<ConnectionType> connection_type;
absl::optional<std::string> connection_id;
absl::optional<uint32_t> alignment;
Arena::PoolPtr<grpc_metadata_batch> ToMetadataBatch();
static absl::StatusOr<SettingsMetadata> FromMetadataBatch(
const grpc_metadata_batch& batch);
};
} // namespace chaotic_good
} // namespace grpc_core
#endif // GRPC_SRC_CORE_EXT_TRANSPORT_CHAOTIC_GOOD_SETTINGS_METADATA_H

@ -297,42 +297,40 @@ auto ChaoticGoodServerTransport::ReadOneFrame(ChaoticGoodTransport& transport) {
auto& buffers = std::get<1>(frame_bytes);
return Switch(
frame_header.type,
Case(FrameType::kSettings,
[]() -> absl::Status {
return absl::InternalError("Unexpected settings frame");
}),
Case(FrameType::kFragment,
[this, &frame_header, &buffers, transport]() {
return If(
frame_header.flags.is_set(0),
[this, &frame_header, &buffers, transport]() {
return DeserializeAndPushFragmentToNewCall(
frame_header, std::move(buffers), *transport);
},
[this, &frame_header, &buffers, transport]() {
return DeserializeAndPushFragmentToExistingCall(
frame_header, std::move(buffers), *transport);
});
}),
Case(FrameType::kCancel,
[this, &frame_header]() {
absl::optional<CallInitiator> call_initiator =
ExtractStream(frame_header.stream_id);
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "Cancel stream " << frame_header.stream_id
<< (call_initiator.has_value() ? " (active)"
: " (not found)");
return If(
call_initiator.has_value(),
[&call_initiator]() {
auto c = std::move(*call_initiator);
return c.SpawnWaitable("cancel", [c]() mutable {
c.Cancel();
return absl::OkStatus();
});
},
[]() -> absl::Status { return absl::OkStatus(); });
}),
Case<FrameType, FrameType::kSettings>([]() -> absl::Status {
return absl::InternalError("Unexpected settings frame");
}),
Case<FrameType, FrameType::kFragment>(
[this, &frame_header, &buffers, transport]() {
return If(
frame_header.flags.is_set(0),
[this, &frame_header, &buffers, transport]() {
return DeserializeAndPushFragmentToNewCall(
frame_header, std::move(buffers), *transport);
},
[this, &frame_header, &buffers, transport]() {
return DeserializeAndPushFragmentToExistingCall(
frame_header, std::move(buffers), *transport);
});
}),
Case<FrameType, FrameType::kCancel>([this, &frame_header]() {
absl::optional<CallInitiator> call_initiator =
ExtractStream(frame_header.stream_id);
GRPC_TRACE_LOG(chaotic_good, INFO)
<< "Cancel stream " << frame_header.stream_id
<< (call_initiator.has_value() ? " (active)"
: " (not found)");
return If(
call_initiator.has_value(),
[&call_initiator]() {
auto c = std::move(*call_initiator);
return c.SpawnWaitable("cancel", [c]() mutable {
c.Cancel();
return absl::OkStatus();
});
},
[]() -> absl::Status { return absl::OkStatus(); });
}),
Default([frame_header]() {
return absl::InternalError(
absl::StrCat("Unexpected frame type: ",

@ -86,6 +86,18 @@ const char* const additional_constraints_multiping = "{}";
const char* const description_pick_first_new =
"New pick_first impl with memory reduction.";
const char* const additional_constraints_pick_first_new = "{}";
const char* const description_promise_based_http2_client_transport =
"Use promises for the http2 client transport. We have kept client and "
"server transport experiments separate to help with smoother roll outs and "
"also help with interop testing.";
const char* const additional_constraints_promise_based_http2_client_transport =
"{}";
const char* const description_promise_based_http2_server_transport =
"Use promises for the http2 server transport. We have kept client and "
"server transport experiments separate to help with smoother roll outs and "
"also help with interop testing.";
const char* const additional_constraints_promise_based_http2_server_transport =
"{}";
const char* const description_promise_based_inproc_transport =
"Use promises for the in-process transport.";
const char* const additional_constraints_promise_based_inproc_transport = "{}";
@ -172,6 +184,14 @@ const ExperimentMetadata g_experiment_metadata[] = {
nullptr, 0, false, true},
{"pick_first_new", description_pick_first_new,
additional_constraints_pick_first_new, nullptr, 0, true, true},
{"promise_based_http2_client_transport",
description_promise_based_http2_client_transport,
additional_constraints_promise_based_http2_client_transport, nullptr, 0,
false, true},
{"promise_based_http2_server_transport",
description_promise_based_http2_server_transport,
additional_constraints_promise_based_http2_server_transport, nullptr, 0,
false, true},
{"promise_based_inproc_transport",
description_promise_based_inproc_transport,
additional_constraints_promise_based_inproc_transport, nullptr, 0, false,
@ -197,7 +217,7 @@ const ExperimentMetadata g_experiment_metadata[] = {
additional_constraints_unconstrained_max_quota_buffer_size, nullptr, 0,
false, true},
{"work_serializer_dispatch", description_work_serializer_dispatch,
additional_constraints_work_serializer_dispatch, nullptr, 0, false, true},
additional_constraints_work_serializer_dispatch, nullptr, 0, true, true},
};
} // namespace grpc_core
@ -267,6 +287,18 @@ const char* const additional_constraints_multiping = "{}";
const char* const description_pick_first_new =
"New pick_first impl with memory reduction.";
const char* const additional_constraints_pick_first_new = "{}";
const char* const description_promise_based_http2_client_transport =
"Use promises for the http2 client transport. We have kept client and "
"server transport experiments separate to help with smoother roll outs and "
"also help with interop testing.";
const char* const additional_constraints_promise_based_http2_client_transport =
"{}";
const char* const description_promise_based_http2_server_transport =
"Use promises for the http2 server transport. We have kept client and "
"server transport experiments separate to help with smoother roll outs and "
"also help with interop testing.";
const char* const additional_constraints_promise_based_http2_server_transport =
"{}";
const char* const description_promise_based_inproc_transport =
"Use promises for the in-process transport.";
const char* const additional_constraints_promise_based_inproc_transport = "{}";
@ -353,6 +385,14 @@ const ExperimentMetadata g_experiment_metadata[] = {
nullptr, 0, false, true},
{"pick_first_new", description_pick_first_new,
additional_constraints_pick_first_new, nullptr, 0, true, true},
{"promise_based_http2_client_transport",
description_promise_based_http2_client_transport,
additional_constraints_promise_based_http2_client_transport, nullptr, 0,
false, true},
{"promise_based_http2_server_transport",
description_promise_based_http2_server_transport,
additional_constraints_promise_based_http2_server_transport, nullptr, 0,
false, true},
{"promise_based_inproc_transport",
description_promise_based_inproc_transport,
additional_constraints_promise_based_inproc_transport, nullptr, 0, false,
@ -378,7 +418,7 @@ const ExperimentMetadata g_experiment_metadata[] = {
additional_constraints_unconstrained_max_quota_buffer_size, nullptr, 0,
false, true},
{"work_serializer_dispatch", description_work_serializer_dispatch,
additional_constraints_work_serializer_dispatch, nullptr, 0, false, true},
additional_constraints_work_serializer_dispatch, nullptr, 0, true, true},
};
} // namespace grpc_core
@ -448,6 +488,18 @@ const char* const additional_constraints_multiping = "{}";
const char* const description_pick_first_new =
"New pick_first impl with memory reduction.";
const char* const additional_constraints_pick_first_new = "{}";
const char* const description_promise_based_http2_client_transport =
"Use promises for the http2 client transport. We have kept client and "
"server transport experiments separate to help with smoother roll outs and "
"also help with interop testing.";
const char* const additional_constraints_promise_based_http2_client_transport =
"{}";
const char* const description_promise_based_http2_server_transport =
"Use promises for the http2 server transport. We have kept client and "
"server transport experiments separate to help with smoother roll outs and "
"also help with interop testing.";
const char* const additional_constraints_promise_based_http2_server_transport =
"{}";
const char* const description_promise_based_inproc_transport =
"Use promises for the in-process transport.";
const char* const additional_constraints_promise_based_inproc_transport = "{}";
@ -534,6 +586,14 @@ const ExperimentMetadata g_experiment_metadata[] = {
nullptr, 0, false, true},
{"pick_first_new", description_pick_first_new,
additional_constraints_pick_first_new, nullptr, 0, true, true},
{"promise_based_http2_client_transport",
description_promise_based_http2_client_transport,
additional_constraints_promise_based_http2_client_transport, nullptr, 0,
false, true},
{"promise_based_http2_server_transport",
description_promise_based_http2_server_transport,
additional_constraints_promise_based_http2_server_transport, nullptr, 0,
false, true},
{"promise_based_inproc_transport",
description_promise_based_inproc_transport,
additional_constraints_promise_based_inproc_transport, nullptr, 0, false,

@ -79,6 +79,8 @@ inline bool IsMonitoringExperimentEnabled() { return true; }
inline bool IsMultipingEnabled() { return false; }
#define GRPC_EXPERIMENT_IS_INCLUDED_PICK_FIRST_NEW
inline bool IsPickFirstNewEnabled() { return true; }
inline bool IsPromiseBasedHttp2ClientTransportEnabled() { return false; }
inline bool IsPromiseBasedHttp2ServerTransportEnabled() { return false; }
inline bool IsPromiseBasedInprocTransportEnabled() { return false; }
inline bool IsRqFastRejectEnabled() { return false; }
inline bool IsScheduleCancellationOverWriteEnabled() { return false; }
@ -90,7 +92,8 @@ inline bool IsTimeCachingInPartyEnabled() { return true; }
#define GRPC_EXPERIMENT_IS_INCLUDED_TRACE_RECORD_CALLOPS
inline bool IsTraceRecordCallopsEnabled() { return true; }
inline bool IsUnconstrainedMaxQuotaBufferSizeEnabled() { return false; }
inline bool IsWorkSerializerDispatchEnabled() { return false; }
#define GRPC_EXPERIMENT_IS_INCLUDED_WORK_SERIALIZER_DISPATCH
inline bool IsWorkSerializerDispatchEnabled() { return true; }
#elif defined(GPR_WINDOWS)
#define GRPC_EXPERIMENT_IS_INCLUDED_CALL_TRACER_IN_TRANSPORT
@ -118,6 +121,8 @@ inline bool IsMonitoringExperimentEnabled() { return true; }
inline bool IsMultipingEnabled() { return false; }
#define GRPC_EXPERIMENT_IS_INCLUDED_PICK_FIRST_NEW
inline bool IsPickFirstNewEnabled() { return true; }
inline bool IsPromiseBasedHttp2ClientTransportEnabled() { return false; }
inline bool IsPromiseBasedHttp2ServerTransportEnabled() { return false; }
inline bool IsPromiseBasedInprocTransportEnabled() { return false; }
inline bool IsRqFastRejectEnabled() { return false; }
inline bool IsScheduleCancellationOverWriteEnabled() { return false; }
@ -129,7 +134,8 @@ inline bool IsTimeCachingInPartyEnabled() { return true; }
#define GRPC_EXPERIMENT_IS_INCLUDED_TRACE_RECORD_CALLOPS
inline bool IsTraceRecordCallopsEnabled() { return true; }
inline bool IsUnconstrainedMaxQuotaBufferSizeEnabled() { return false; }
inline bool IsWorkSerializerDispatchEnabled() { return false; }
#define GRPC_EXPERIMENT_IS_INCLUDED_WORK_SERIALIZER_DISPATCH
inline bool IsWorkSerializerDispatchEnabled() { return true; }
#else
#define GRPC_EXPERIMENT_IS_INCLUDED_CALL_TRACER_IN_TRANSPORT
@ -157,6 +163,8 @@ inline bool IsMonitoringExperimentEnabled() { return true; }
inline bool IsMultipingEnabled() { return false; }
#define GRPC_EXPERIMENT_IS_INCLUDED_PICK_FIRST_NEW
inline bool IsPickFirstNewEnabled() { return true; }
inline bool IsPromiseBasedHttp2ClientTransportEnabled() { return false; }
inline bool IsPromiseBasedHttp2ServerTransportEnabled() { return false; }
inline bool IsPromiseBasedInprocTransportEnabled() { return false; }
inline bool IsRqFastRejectEnabled() { return false; }
inline bool IsScheduleCancellationOverWriteEnabled() { return false; }
@ -191,6 +199,8 @@ enum ExperimentIds {
kExperimentIdMonitoringExperiment,
kExperimentIdMultiping,
kExperimentIdPickFirstNew,
kExperimentIdPromiseBasedHttp2ClientTransport,
kExperimentIdPromiseBasedHttp2ServerTransport,
kExperimentIdPromiseBasedInprocTransport,
kExperimentIdRqFastReject,
kExperimentIdScheduleCancellationOverWrite,
@ -272,6 +282,14 @@ inline bool IsMultipingEnabled() {
inline bool IsPickFirstNewEnabled() {
return IsExperimentEnabled<kExperimentIdPickFirstNew>();
}
#define GRPC_EXPERIMENT_IS_INCLUDED_PROMISE_BASED_HTTP2_CLIENT_TRANSPORT
inline bool IsPromiseBasedHttp2ClientTransportEnabled() {
return IsExperimentEnabled<kExperimentIdPromiseBasedHttp2ClientTransport>();
}
#define GRPC_EXPERIMENT_IS_INCLUDED_PROMISE_BASED_HTTP2_SERVER_TRANSPORT
inline bool IsPromiseBasedHttp2ServerTransportEnabled() {
return IsExperimentEnabled<kExperimentIdPromiseBasedHttp2ServerTransport>();
}
#define GRPC_EXPERIMENT_IS_INCLUDED_PROMISE_BASED_INPROC_TRANSPORT
inline bool IsPromiseBasedInprocTransportEnabled() {
return IsExperimentEnabled<kExperimentIdPromiseBasedInprocTransport>();

@ -141,6 +141,24 @@
expiry: 2025/03/01
owner: roth@google.com
test_tags: ["lb_unit_test", "cpp_lb_end2end_test", "xds_end2end_test"]
- name: promise_based_http2_client_transport
description:
Use promises for the http2 client transport. We have kept client and
server transport experiments separate to help with smoother roll outs
and also help with interop testing.
expiry: 2025/06/03
owner: tjagtap@google.com
test_tags: []
allow_in_fuzzing_config: true
- name: promise_based_http2_server_transport
description:
Use promises for the http2 server transport. We have kept client and
server transport experiments separate to help with smoother roll outs
and also help with interop testing.
expiry: 2025/06/03
owner: tjagtap@google.com
test_tags: []
allow_in_fuzzing_config: true
- name: promise_based_inproc_transport
description:
Use promises for the in-process transport.

@ -94,6 +94,10 @@
ios: broken
windows: broken
posix: false
- name: promise_based_http2_client_transport
default: false
- name: promise_based_http2_server_transport
default: false
- name: rstpit
default: false
- name: schedule_cancellation_over_write
@ -113,9 +117,4 @@
- name: work_serializer_clears_time_cache
default: true
- name: work_serializer_dispatch
default:
# TODO(ysseung): Not fully tested.
ios: broken
posix: true
# TODO(ysseung): Test flakes not fully resolved.
windows: broken
default: true

@ -0,0 +1,50 @@
// Copyright 2024 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_SRC_CORE_LIB_PROMISE_DETAIL_PROMISE_VARIANT_H
#define GRPC_SRC_CORE_LIB_PROMISE_DETAIL_PROMISE_VARIANT_H
#include "absl/types/variant.h"
namespace grpc_core {
namespace promise_detail {
// Visitor function for PromiseVariant - calls the poll operator on the inner
// type
class PollVisitor {
public:
template <typename T>
auto operator()(T& x) {
return x();
}
};
// Helper type - given a variant V, provides the poll operator (which simply
// visits the inner type on the variant with PollVisitor)
template <typename V>
class PromiseVariant {
public:
explicit PromiseVariant(V variant) : variant_(std::move(variant)) {}
auto operator()() { return absl::visit(PollVisitor(), variant_); }
private:
V variant_;
};
} // namespace promise_detail
} // namespace grpc_core
#endif // GRPC_SRC_CORE_LIB_PROMISE_DETAIL_PROMISE_VARIANT_H

@ -18,6 +18,7 @@
#include "absl/types/variant.h"
#include "src/core/lib/promise/detail/promise_factory.h"
#include "src/core/lib/promise/detail/promise_like.h"
#include "src/core/lib/promise/detail/promise_variant.h"
#include "src/core/util/overload.h"
namespace grpc_core {
@ -56,28 +57,6 @@ struct ConstructPromiseVariantVisitor {
}
};
// Visitor function for PromiseVariant - calls the poll operator on the inner
// type
class PollVisitor {
public:
template <typename T>
auto operator()(T& x) {
return x();
}
};
// Helper type - given a variant V, provides the poll operator (which simply
// visits the inner type on the variant with PollVisitor)
template <typename V>
class PromiseVariant {
public:
explicit PromiseVariant(V variant) : variant_(std::move(variant)) {}
auto operator()() { return absl::visit(PollVisitor(), variant_); }
private:
V variant_;
};
} // namespace promise_detail
// Match for promises

@ -55,6 +55,13 @@ struct StatusCastImpl<absl::Status, Success> {
}
};
template <>
struct StatusCastImpl<absl::Status, Success&> {
GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION static absl::Status Cast(Success) {
return absl::OkStatus();
}
};
template <>
struct StatusCastImpl<absl::Status, const Success&> {
GPR_ATTRIBUTE_ALWAYS_INLINE_FUNCTION static absl::Status Cast(Success) {

@ -21,26 +21,54 @@
#include <utility>
#include "src/core/lib/promise/detail/promise_factory.h"
#include "src/core/lib/promise/detail/promise_variant.h"
#include "src/core/lib/promise/if.h"
#include "src/core/util/crash.h"
namespace grpc_core {
namespace promise_detail {
template <typename D, typename F>
template <typename D, D discriminator, typename F>
struct Case {
D discriminator;
F factory;
using Factory = OncePromiseFactory<void, F>;
explicit Case(F f) : factory(std::move(f)) {}
Factory factory;
static bool Matches(D value) { return value == discriminator; }
};
template <typename F>
struct Default {
F factory;
using Factory = OncePromiseFactory<void, F>;
explicit Default(F f) : factory(std::move(f)) {}
Factory factory;
};
template <typename Promise, typename D, typename F>
Promise ConstructSwitchPromise(D, Default<F>& def) {
return def.factory.Make();
}
template <typename Promise, typename D, typename Case, typename... OtherCases>
Promise ConstructSwitchPromise(D discriminator, Case& c, OtherCases&... cs) {
if (Case::Matches(discriminator)) return c.factory.Make();
return ConstructSwitchPromise<Promise>(discriminator, cs...);
}
template <typename D, typename... Cases>
auto SwitchImpl(D discriminator, Cases&... cases) {
using Promise = absl::variant<typename Cases::Factory::Promise...>;
return PromiseVariant<Promise>(
ConstructSwitchPromise<Promise>(discriminator, cases...));
}
} // namespace promise_detail
template <typename D, typename PromiseFactory>
auto Case(D discriminator, PromiseFactory f) {
return promise_detail::Case<D, PromiseFactory>{discriminator, std::move(f)};
// TODO(ctiller): when we have C++17, make this
// template <auto D, typename PromiseFactory>.
// (this way we don't need to list the type on /every/ case)
template <typename D, D discriminator, typename PromiseFactory>
auto Case(PromiseFactory f) {
return promise_detail::Case<D, discriminator, PromiseFactory>{std::move(f)};
}
template <typename PromiseFactory>
@ -55,16 +83,9 @@ auto Default(PromiseFactory f) {
// resolves to 43.
// TODO(ctiller): consider writing a code-generator like we do for seq/join
// so that this lowers into a C switch statement.
template <typename D, typename F>
auto Switch(D, promise_detail::Default<F> def) {
return promise_detail::OncePromiseFactory<void, F>(std::move(def.factory))
.Make();
}
template <typename D, typename F, typename... Others>
auto Switch(D discriminator, promise_detail::Case<D, F> c, Others... others) {
return If(discriminator == c.discriminator, std::move(c.factory),
Switch(discriminator, std::move(others)...));
template <typename D, typename... C>
auto Switch(D discriminator, C... cases) {
return promise_detail::SwitchImpl(discriminator, cases...);
}
} // namespace grpc_core

@ -441,6 +441,7 @@ void grpc_slice_buffer_copy_first_into_buffer(grpc_slice_buffer* src, size_t n,
template <bool allow_inline>
void grpc_slice_buffer_trim_end_impl(grpc_slice_buffer* sb, size_t n,
grpc_slice_buffer* garbage) {
if (n == 0) return;
CHECK(n <= sb->length);
sb->length -= n;
for (;;) {

@ -79,8 +79,9 @@ OrphanablePtr<HttpRequest> HttpRequest::Get(
}
std::string name =
absl::StrFormat("HTTP:GET:%s:%s", uri.authority(), uri.path());
const grpc_slice request_text = grpc_httpcli_format_get_request(
request, uri.authority().c_str(), uri.path().c_str());
const grpc_slice request_text =
grpc_httpcli_format_get_request(request, uri.authority().c_str(),
uri.EncodedPathAndQueryParams().c_str());
return MakeOrphanable<HttpRequest>(
std::move(uri), request_text, response, deadline, channel_args, on_done,
pollent, name.c_str(), std::move(test_only_generate_response),
@ -103,8 +104,9 @@ OrphanablePtr<HttpRequest> HttpRequest::Post(
}
std::string name =
absl::StrFormat("HTTP:POST:%s:%s", uri.authority(), uri.path());
const grpc_slice request_text = grpc_httpcli_format_post_request(
request, uri.authority().c_str(), uri.path().c_str());
const grpc_slice request_text =
grpc_httpcli_format_post_request(request, uri.authority().c_str(),
uri.EncodedPathAndQueryParams().c_str());
return MakeOrphanable<HttpRequest>(
std::move(uri), request_text, response, deadline, channel_args, on_done,
pollent, name.c_str(), std::move(test_only_generate_response),
@ -127,8 +129,9 @@ OrphanablePtr<HttpRequest> HttpRequest::Put(
}
std::string name =
absl::StrFormat("HTTP:PUT:%s:%s", uri.authority(), uri.path());
const grpc_slice request_text = grpc_httpcli_format_put_request(
request, uri.authority().c_str(), uri.path().c_str());
const grpc_slice request_text =
grpc_httpcli_format_put_request(request, uri.authority().c_str(),
uri.EncodedPathAndQueryParams().c_str());
return MakeOrphanable<HttpRequest>(
std::move(uri), request_text, response, deadline, channel_args, on_done,
pollent, name.c_str(), std::move(test_only_generate_response),
@ -241,6 +244,8 @@ void HttpRequest::AppendError(grpc_error_handle error) {
void HttpRequest::OnReadInternal(grpc_error_handle error) {
for (size_t i = 0; i < incoming_.count; i++) {
GRPC_TRACE_LOG(http1, INFO)
<< "HTTP response data: " << StringViewFromSlice(incoming_.slices[i]);
if (GRPC_SLICE_LENGTH(incoming_.slices[i])) {
have_read_byte_ = 1;
grpc_error_handle err =
@ -275,6 +280,8 @@ void HttpRequest::ContinueDoneWriteAfterScheduleOnExecCtx(
}
void HttpRequest::StartWrite() {
GRPC_TRACE_LOG(http1, INFO)
<< "Sending HTTP1 request: " << StringViewFromSlice(request_text_);
CSliceRef(request_text_);
grpc_slice_buffer_add(&outgoing_, request_text_);
Ref().release(); // ref held by pending write

@ -352,6 +352,16 @@ std::string URI::ToString() const {
parts.emplace_back("//");
parts.emplace_back(PercentEncode(authority_, IsAuthorityChar));
}
parts.emplace_back(EncodedPathAndQueryParams());
if (!fragment_.empty()) {
parts.push_back("#");
parts.push_back(PercentEncode(fragment_, IsQueryOrFragmentChar));
}
return absl::StrJoin(parts, "");
}
std::string URI::EncodedPathAndQueryParams() const {
std::vector<std::string> parts;
if (!path_.empty()) {
parts.emplace_back(PercentEncode(path_, IsPathChar));
}
@ -360,10 +370,6 @@ std::string URI::ToString() const {
parts.push_back(
absl::StrJoin(query_parameter_pairs_, "&", QueryParameterFormatter()));
}
if (!fragment_.empty()) {
parts.push_back("#");
parts.push_back(PercentEncode(fragment_, IsQueryOrFragmentChar));
}
return absl::StrJoin(parts, "");
}

@ -76,7 +76,7 @@ class URI {
return query_parameter_map_;
}
// A vector of key:value query parameter pairs, kept in order of appearance
// within the URI search string. Repeated keys are represented as separate
// within the URI string. Repeated keys are represented as separate
// key:value elements.
const std::vector<QueryParam>& query_parameter_pairs() const {
return query_parameter_pairs_;
@ -85,6 +85,10 @@ class URI {
std::string ToString() const;
// Returns the encoded path and query params, such as would be used on
// the wire in an HTTP request.
std::string EncodedPathAndQueryParams() const;
private:
URI(std::string scheme, std::string authority, std::string path,
std::vector<QueryParam> query_parameter_pairs, std::string fragment);

@ -18,7 +18,7 @@
# instead. This file can be regenerated from the template by running
# `tools/buildgen/generate_projects.sh`.
"""Patches the compile() to allow enable parallel compilation of C/C++.
"""Patches the compile() to enable parallel compilation of C/C++.
build_ext has lots of C/C++ files and normally them one by one.
Enabling parallel build helps a lot.

@ -273,7 +273,7 @@ class BuildExt(build_ext.build_ext):
# behavior in gcc and clang. The clang doesn't take --stdc++11
# flags but gcc does. Since the setuptools of Python only support
# all C or all C++ compilation, the mix of C and C++ will crash.
# *By default*, macOS and FreBSD use clang and Linux use gcc
# *By default*, macOS and FreeBSD use clang and Linux use gcc
#
# If we are not using a permissive compiler that's OK with being
# passed wrong std flags, swap out compile function by adding a filter

@ -1231,7 +1231,7 @@ class ServicerContext(RpcContext, metaclass=abc.ABCMeta):
def abort(self, code, details):
"""Raises an exception to terminate the RPC with a non-OK status.
The code and details passed as arguments will supercede any existing
The code and details passed as arguments will supersede any existing
ones.
Args:
@ -1250,7 +1250,7 @@ class ServicerContext(RpcContext, metaclass=abc.ABCMeta):
def abort_with_status(self, status):
"""Raises an exception to terminate the RPC with a non-OK status.
The status passed as argument will supercede any existing status code,
The status passed as argument will supersede any existing status code,
status message and trailing metadata.
This is an EXPERIMENTAL API.

@ -214,7 +214,7 @@ cdef class _AioCall(GrpcCallWrapper):
"""Returns if the RPC call has finished.
Checks if the status has been provided, either
because the RPC finished or because was cancelled..
because the RPC finished or because was cancelled.
Returns:
True if the RPC can be considered finished.
@ -235,7 +235,7 @@ cdef class _AioCall(GrpcCallWrapper):
async def status(self):
"""Returns the status of the RPC call.
It returns the finshed status of the RPC. If the RPC
It returns the finished status of the RPC. If the RPC
has not finished yet this function will wait until the RPC
gets finished.
@ -277,7 +277,7 @@ cdef class _AioCall(GrpcCallWrapper):
"""Returns if the RPC was cancelled locally.
Returns:
True when was cancelled locally, False when was cancelled remotelly or
True when was cancelled locally, False when was cancelled remotely or
is still ongoing.
"""
if self._is_locally_cancelled:
@ -397,7 +397,7 @@ cdef class _AioCall(GrpcCallWrapper):
tuple outbound_initial_metadata,
object context = None):
"""Implementation of the start of a unary-stream call."""
# Peer may prematurely end this RPC at any point. We need a corutine
# Peer may prematurely end this RPC at any point. We need a coroutine
# that watches if the server sends the final status.
status_task = self._loop.create_task(self._handle_status_once_received())
@ -503,7 +503,7 @@ cdef class _AioCall(GrpcCallWrapper):
propagate the final status exception, then we have to raise it.
Othersize, it would end normally and raise `StopAsyncIteration()`.
"""
# Peer may prematurely end this RPC at any point. We need a corutine
# Peer may prematurely end this RPC at any point. We need a coroutine
# that watches if the server sends the final status.
status_task = self._loop.create_task(self._handle_status_once_received())

@ -43,7 +43,7 @@ cdef class CallbackWrapper:
self._reference_of_future = future
self._reference_of_failure_handler = failure_handler
# NOTE(lidiz) We need to ensure when Core invokes our callback, the
# callback function itself is not deallocated. Othersise, we will get
# callback function itself is not deallocated. Otherwise, we will get
# a segfault. We can view this as Core holding a ref.
cpython.Py_INCREF(self)
@ -114,7 +114,7 @@ cdef prepend_send_initial_metadata_op(tuple ops, tuple metadata):
async def _receive_message(GrpcCallWrapper grpc_call_wrapper,
object loop):
"""Retrives parsed messages from Core.
"""Retrieves parsed messages from Core.
The messages maybe already in Core's buffer, so there isn't a 1-to-1
mapping between this and the underlying "socket.read()". Also, eventually,

@ -53,7 +53,7 @@ cdef class _BoundEventLoop:
)
# NOTE(lidiz) There isn't a way to cleanly pre-check if fd monitoring
# support is available or not. Checking the event loop policy is not
# good enough. The application can has its own loop implementation, or
# good enough. The application can have its own loop implementation, or
# uses different types of event loops (e.g., 1 Proactor, 3 Selectors).
if _has_fd_monitoring:
try:
@ -117,7 +117,7 @@ cdef class PollerCompletionQueue(BaseCompletionQueue):
else:
with gil:
# Event loops can be paused or killed at any time. So,
# instead of deligate to any thread, the polling thread
# instead of delegate to any thread, the polling thread
# should handle the distribution of the event.
self._handle_events(None)

@ -17,7 +17,7 @@
cdef class AioRpcStatus(Exception):
# The final status of gRPC is represented by three trailing metadata:
# `grpc-status`, `grpc-status-message`, abd `grpc-status-details`.
# `grpc-status`, `grpc-status-message`, and `grpc-status-details`.
def __cinit__(self,
grpc_status_code code,
str details,

@ -542,7 +542,7 @@ async def _handle_unary_unary_rpc(object method_handler,
request_raw,
)
# Creates a dedecated ServicerContext
# Creates a dedicated ServicerContext
cdef _ServicerContext servicer_context = _ServicerContext(
rpc_state,
None,
@ -575,7 +575,7 @@ async def _handle_unary_stream_rpc(object method_handler,
request_raw,
)
# Creates a dedecated ServicerContext
# Creates a dedicated ServicerContext
cdef _ServicerContext servicer_context = _ServicerContext(
rpc_state,
method_handler.request_deserializer,
@ -623,7 +623,7 @@ cdef class _MessageReceiver:
async def _handle_stream_unary_rpc(object method_handler,
RPCState rpc_state,
object loop):
# Creates a dedecated ServicerContext
# Creates a dedicated ServicerContext
cdef _ServicerContext servicer_context = _ServicerContext(
rpc_state,
method_handler.request_deserializer,
@ -655,7 +655,7 @@ async def _handle_stream_unary_rpc(object method_handler,
async def _handle_stream_stream_rpc(object method_handler,
RPCState rpc_state,
object loop):
# Creates a dedecated ServicerContext
# Creates a dedicated ServicerContext
cdef _ServicerContext servicer_context = _ServicerContext(
rpc_state,
method_handler.request_deserializer,
@ -871,7 +871,7 @@ cdef class _ConcurrentRpcLimiter:
def __cinit__(self, int maximum_concurrent_rpcs):
if maximum_concurrent_rpcs <= 0:
raise ValueError("maximum_concurrent_rpcs should be a postive integer")
raise ValueError("maximum_concurrent_rpcs should be a positive integer")
self._maximum_concurrent_rpcs = maximum_concurrent_rpcs
self._active_rpcs = 0
self.limiter_concurrency_exceeded = False

@ -98,7 +98,7 @@ cdef class ServerCredentials:
cdef grpc_ssl_pem_key_cert_pair *c_ssl_pem_key_cert_pairs
cdef size_t c_ssl_pem_key_cert_pairs_count
cdef list references
# the cert config related state is used only if this credentials is
# the cert config related state is used only if these credentials are
# created with cert config/fetcher
cdef object initial_cert_config
cdef object cert_config_fetcher

@ -61,7 +61,7 @@ class ObservabilityPlugin(
the gRPC team.*
The ClientCallTracerCapsule and ClientCallTracerCapsule created by this
plugin should be inject to gRPC core using observability_init at the
plugin should be injected to gRPC core using observability_init at the
start of a program, before any channels/servers are built.
Any future methods added to this interface cannot have the
@ -93,7 +93,7 @@ class ObservabilityPlugin(
Args:
method_name: The method name of the call in byte format.
target: The channel target of the call in byte format.
registered_method: Wether this method is pre-registered.
registered_method: Whether this method is pre-registered.
Returns:
A PyCapsule which stores a ClientCallTracer object.

@ -88,7 +88,7 @@ def protos(protobuf_path): # pylint: disable=unused-argument
The returned module object corresponds to the _pb2.py file generated
by protoc. The path is expected to be relative to an entry on sys.path
and all transitive dependencies of the file should also be resolveable
and all transitive dependencies of the file should also be resolvable
from an entry on sys.path.
To completely disable the machinery behind this function, set the
@ -96,7 +96,7 @@ def protos(protobuf_path): # pylint: disable=unused-argument
Args:
protobuf_path: The path to the .proto file on the filesystem. This path
must be resolveable from an entry on sys.path and so must all of its
must be resolvable from an entry on sys.path and so must all of its
transitive dependencies.
Returns:
@ -125,7 +125,7 @@ def services(protobuf_path): # pylint: disable=unused-argument
The returned module object corresponds to the _pb2_grpc.py file generated
by protoc. The path is expected to be relative to an entry on sys.path
and all transitive dependencies of the file should also be resolveable
and all transitive dependencies of the file should also be resolvable
from an entry on sys.path.
To completely disable the machinery behind this function, set the
@ -133,7 +133,7 @@ def services(protobuf_path): # pylint: disable=unused-argument
Args:
protobuf_path: The path to the .proto file on the filesystem. This path
must be resolveable from an entry on sys.path and so must all of its
must be resolvable from an entry on sys.path and so must all of its
transitive dependencies.
Returns:
@ -156,7 +156,7 @@ def protos_and_services(protobuf_path): # pylint: disable=unused-argument
Args:
protobuf_path: The path to the .proto file on the filesystem. This path
must be resolveable from an entry on sys.path and so must all of its
must be resolvable from an entry on sys.path and so must all of its
transitive dependencies.
Returns:

@ -1075,7 +1075,7 @@ def _handle_call(
) -> Tuple[Optional[_RPCState], Optional[futures.Future]]:
"""Handles RPC based on provided handlers.
When receiving a call event from Core, registered method will have it's
When receiving a call event from Core, registered method will have its
name as tag, we pass the tag as registered_method_name to this method,
then we can find the handler in registered_method_handlers based on
the method name.

@ -138,7 +138,7 @@ class Call(RpcContext, metaclass=ABCMeta):
class UnaryUnaryCall(
Generic[RequestType, ResponseType], Call, metaclass=ABCMeta
):
"""The abstract base class of an unary-unary RPC on the client-side."""
"""The abstract base class of a unary-unary RPC on the client-side."""
@abstractmethod
def __await__(self) -> Generator[Any, None, ResponseType]:

@ -183,7 +183,7 @@ class Channel(abc.ABC):
"""Enables asynchronous RPC invocation as a client.
Channel objects implement the Asynchronous Context Manager (aka. async
with) type, although they are not supportted to be entered and exited
with) type, although they are not supported to be entered and exited
multiple times.
"""
@ -312,7 +312,7 @@ class Channel(abc.ABC):
whether the method is registered.
Returns:
A UnarySteramMultiCallable value for the named unary-stream method.
A UnaryStreamMultiCallable value for the named unary-stream method.
"""
@abc.abstractmethod

@ -197,7 +197,7 @@ class ServicerContext(Generic[RequestType, ResponseType], abc.ABC):
) -> NoReturn:
"""Raises an exception to terminate the RPC with a non-OK status.
The code and details passed as arguments will supercede any existing
The code and details passed as arguments will supersede any existing
ones.
Args:

@ -477,8 +477,8 @@ class _InterceptedStreamResponseMixin:
_response_aiter: Optional[AsyncIterable[ResponseType]]
def _init_stream_response_mixin(self) -> None:
# Is initalized later, otherwise if the iterator is not finally
# consumed a logging warning is emmited by Asyncio.
# Is initialized later, otherwise if the iterator is not finally
# consumed a logging warning is emitted by Asyncio.
self._response_aiter = None
async def _wait_for_interceptor_task_response_iterator(
@ -1143,10 +1143,10 @@ class _StreamCallResponseIterator:
class UnaryStreamCallResponseIterator(
_StreamCallResponseIterator, _base_call.UnaryStreamCall
):
"""UnaryStreamCall class wich uses an alternative response iterator."""
"""UnaryStreamCall class which uses an alternative response iterator."""
async def read(self) -> Union[EOFType, ResponseType]:
# Behind the scenes everyting goes through the
# Behind the scenes everything goes through the
# async iterator. So this path should not be reached.
raise NotImplementedError()
@ -1154,21 +1154,21 @@ class UnaryStreamCallResponseIterator(
class StreamStreamCallResponseIterator(
_StreamCallResponseIterator, _base_call.StreamStreamCall
):
"""StreamStreamCall class wich uses an alternative response iterator."""
"""StreamStreamCall class which uses an alternative response iterator."""
async def read(self) -> Union[EOFType, ResponseType]:
# Behind the scenes everyting goes through the
# Behind the scenes everything goes through the
# async iterator. So this path should not be reached.
raise NotImplementedError()
async def write(self, request: RequestType) -> None:
# Behind the scenes everyting goes through the
# Behind the scenes everything goes through the
# async iterator provided by the InterceptedStreamStreamCall.
# So this path should not be reached.
raise NotImplementedError()
async def done_writing(self) -> None:
# Behind the scenes everyting goes through the
# Behind the scenes everything goes through the
# async iterator provided by the InterceptedStreamStreamCall.
# So this path should not be reached.
raise NotImplementedError()

@ -83,7 +83,7 @@ class Completion(abc.ABC):
"""An aggregate of the values exchanged upon operation completion.
Attributes:
terminal_metadata: A terminal metadata value for the operaton.
terminal_metadata: A terminal metadata value for the operation.
code: A code value for the operation.
message: A message value for the operation.
"""

@ -57,7 +57,7 @@ def completion(terminal_metadata, code, message):
"""Creates a base.Completion aggregating the given operation values.
Args:
terminal_metadata: A terminal metadata value for an operaton.
terminal_metadata: A terminal metadata value for an operation.
code: A code value for an operation.
message: A message value for an operation.

@ -174,7 +174,7 @@ def unary_stream_event(behavior):
Args:
behavior: The implementation of a unary-stream RPC method as a callable
value that takes a request value, a stream.Consumer to which to pass the
the response values of the RPC, and an face.ServicerContext.
response values of the RPC, and an face.ServicerContext.
Returns:
An face.MethodImplementation derived from the given behavior.

@ -207,7 +207,7 @@ class CsmOpenTelemetryPluginOption(OpenTelemetryPluginOption):
target: Required. The target for the RPC.
Returns:
True if this this plugin option is active on the channel, false otherwise.
True if this plugin option is active on the channel, false otherwise.
"""
# CSM channels should have an "xds" scheme
if not target.startswith("xds:"):
@ -237,7 +237,7 @@ class CsmOpenTelemetryPluginOption(OpenTelemetryPluginOption):
xds: Required. if this server is build for xds.
Returns:
True if this this plugin option is active on the server, false otherwise.
True if this plugin option is active on the server, false otherwise.
"""
return True

@ -18,7 +18,7 @@
# instead. This file can be regenerated from the template by running
# `tools/buildgen/generate_projects.sh`.
"""Patches the compile() to allow enable parallel compilation of C/C++.
"""Patches the compile() to enable parallel compilation of C/C++.
build_ext has lots of C/C++ files and normally them one by one.
Enabling parallel build helps a lot.

@ -179,7 +179,7 @@ def _c_measurement_to_measurement(object measurement
"""Convert Cython Measurement to Python measurement.
Args:
measurement: Actual measurement repesented by Cython type Measurement, using object here
measurement: Actual measurement represented by Cython type Measurement, using object here
since Cython refuse to automatically convert a union with unsafe type combinations.
Returns:
@ -308,7 +308,7 @@ cdef void _export_census_data(object exporter):
while not GLOBAL_SHUTDOWN_EXPORT_THREAD:
lk = new unique_lock[mutex](g_census_data_buffer_mutex)
# Wait for next batch of census data OR timeout at fixed interval.
# Batch export census data to minimize the time we acquiring the GIL.
# Batch export census data to minimize the time we acquire the GIL.
AwaitNextBatchLocked(dereference(lk), export_interval_ms)
# Break only when buffer have data

@ -17,7 +17,7 @@ from opencensus.stats import measure
# These measure definitions should be kept in sync across opencensus implementations.
# https://github.com/census-instrumentation/opencensus-java/blob/master/contrib/grpc_metrics/src/main/java/io/opencensus/contrib/grpc/metrics/RpcMeasureConstants.java.
# Unit constatns
# Unit constants
UNIT_BYTES = "By"
UNIT_MILLISECONDS = "ms"
UNIT_COUNT = "1"

@ -123,7 +123,7 @@ class OpenCensusExporter(_observability.Exporter):
if not measure:
continue
# Create a measurement map for each metric, otherwise metrics will
# be override instead of accumulate.
# be overridden instead of accumulate.
measurement_map = self.stats_recorder.new_measurement_map()
# Add data label to default labels.
labels = data.labels

@ -383,7 +383,7 @@ class OpenTelemetryObservability(grpc._observability.ObservabilityPlugin):
try:
_cyobservability.cyobservability_init(self._exporter)
# TODO(xuanwn): Use specific exceptons
# TODO(xuanwn): Use specific exceptions
except Exception as e: # pylint: disable=broad-except
_LOGGER.exception("Initiate observability failed with: %s", e)

@ -208,11 +208,11 @@ class Span final {
uint64_t child_span_count_ = 0;
};
// PythonCensusContext is associated with each clientCallTrcer,
// PythonCensusContext is associated with each clientCallTracer,
// clientCallAttemptTracer and ServerCallTracer to help manage the span,
// spanContext and labels for each tracer. Craete a new PythonCensusContext will
// always reasult in creating a new span (and a new SpanContext for that span).
// It's created during callTraceer initialization and will be destroyed after
// spanContext and labels for each tracer. Create a new PythonCensusContext will
// always result in creating a new span (and a new SpanContext for that span).
// It's created during callTracer initialization and will be destroyed after
// the destruction of each callTracer.
class PythonCensusContext {
public:

@ -55,7 +55,7 @@ class Loader(object):
Attributes:
suite (unittest.TestSuite): All tests collected by the loader.
loader (unittest.TestLoader): Standard Python unittest loader to be ran per
loader (unittest.TestLoader): Standard Python unittest loader to be run per
module discovered.
module_matcher (re.RegexObject): A regular expression object to match
against module names and determine whether or not the discovered module

@ -70,7 +70,7 @@ class ClosedLoopClientRunner(ClientRunner):
super(ClosedLoopClientRunner, self).__init__(client)
self._is_running = False
self._request_count = request_count
# For server-streaming RPC, don't spawn new RPC after each responses.
# For server-streaming RPC, don't spawn new RPC after each response.
# This yield at most ~17% for single RPC scenarios.
if not no_ping_pong:
# Send a new request on each response for closed loop

@ -200,7 +200,7 @@ class StatusTest(unittest.TestCase):
).with_call(_REQUEST)
rpc_error = exception_context.exception
self.assertEqual(rpc_error.code(), grpc.StatusCode.UNKNOWN)
# Invalid status code exception raised during coversion
# Invalid status code exception raised during conversion
self.assertIn("Invalid status code", rpc_error.details())

@ -296,7 +296,7 @@ class CompressionTest(unittest.TestCase):
self.assertGreaterEqual(
compression_ratio,
-1.0 * _COMPRESSION_RATIO_THRESHOLD,
msg="Actual compession ratio: {}".format(compression_ratio),
msg="Actual compression ratio: {}".format(compression_ratio),
)
def assertConfigurationCompressed(

@ -11,7 +11,7 @@
# 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.
"""Test of propagation of contextvars to AuthMetadataPlugin threads.."""
"""Test of propagation of contextvars to AuthMetadataPlugin threads."""
import contextlib
import logging

@ -251,7 +251,7 @@ class ReadSomeButNotAllResponsesTest(unittest.TestCase):
)
server_call_driver.events()
client_recieve_initial_metadata_event = (
client_receive_initial_metadata_event = (
client_receive_initial_metadata_event_future.result()
)

@ -62,7 +62,7 @@ atexit.register(cleanup_processes)
def _process_wait_with_timeout(process, timeout=WAIT_CHECK_DEFAULT_TIMEOUT):
"""A funciton to mimic 3.3+ only timeout argument in process.wait."""
"""A function to mimic 3.3+ only timeout argument in process.wait."""
deadline = datetime.datetime.now() + timeout
while (process.poll() is None) and (datetime.datetime.now() < deadline):
time.sleep(WAIT_CHECK_INTERVAL.total_seconds())

@ -39,7 +39,7 @@ class GrpcShutdownTest(unittest.TestCase):
):
connection_failed.set()
# Connects to an void address, and subscribes state changes
# Connects to a void address, and subscribes state changes
channel = grpc.insecure_channel("0.1.1.1:12345")
channel.subscribe(on_state_change, True)

@ -234,7 +234,7 @@ class MetadataFlagsTest(unittest.TestCase):
# To test the wait mechanism, Python thread is required to make
# client set up first without handling them case by case.
# Also, Python thread don't pass the unhandled exceptions to
# main thread. So, it need another method to store the
# main thread. So, it needs another method to store the
# exceptions and raise them again in main thread.
unhandled_exceptions = queue.Queue()

@ -0,0 +1,36 @@
# Copyright 2024 gRPC authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import unittest
class TestAllModulesInstalled(unittest.TestCase):
def test_import_all_modules(self):
import grpc_admin
import grpc_channelz
import grpc_csds
import grpc_csm_observability
import grpc_health
import grpc_observability
import grpc_reflection
import grpc_status
# This test simply imports all the modules.
# If any module fails to import, the test will fail.
if __name__ == "__main__":
logging.basicConfig()
unittest.main(verbosity=3)

@ -174,7 +174,7 @@ class StatusTest(AioTestBase):
await self._channel.unary_unary(_INVALID_CODE)(_REQUEST)
rpc_error = exception_context.exception
self.assertEqual(rpc_error.code(), grpc.StatusCode.UNKNOWN)
# Invalid status code exception raised during coversion
# Invalid status code exception raised during conversion
self.assertIn("Invalid status code", rpc_error.details())

@ -51,7 +51,7 @@ def inject_callbacks(call: aio.Call):
first_callback_ran = asyncio.Event()
def first_callback(call):
# Validate that all resopnses have been received
# Validate that all responses have been received
# and the call is an end state.
assert call.done()
first_callback_ran.set()

@ -100,7 +100,7 @@ class TestTypeMetadata(unittest.TestCase):
def test_init_metadata(self):
test_cases = {
"emtpy": (),
"empty": (),
"with-single-data": self._DEFAULT_DATA,
"with-multi-data": self._MULTI_ENTRY_DATA,
}

@ -102,7 +102,7 @@ class TestServiceServicer(test_pb2_grpc.TestServiceServicer):
else:
yield messages_pb2.StreamingOutputCallResponse()
# Next methods are extra ones that are registred programatically
# Next methods are extra ones that are registered programmatically
# when the sever is instantiated. They are not being provided by
# the proto file.
async def UnaryCallWithSleep(self, unused_request, unused_context):
@ -144,7 +144,7 @@ class TestServiceServicer(test_pb2_grpc.TestServiceServicer):
def _create_extra_generic_handler(servicer: TestServiceServicer):
# Add programatically extra methods not provided by the proto file
# Add programmatically extra methods not provided by the proto file
# that are used during the tests
rpc_method_handlers = {
"UnaryCallWithSleep": grpc.unary_unary_rpc_method_handler(

@ -411,7 +411,7 @@ class TestUnaryStreamCall(_MulticallableTestMixin, AioTestBase):
Certain classes of error only appear for very specific interleavings of
coroutines. Rather than inserting semi-private asyncio.Events throughout
the implementation on which to coordinate and explicilty waiting on those
the implementation on which to coordinate and explicitly waiting on those
in tests, we instead search for bugs over the space of interleavings by
stochastically varying the durations of certain events within the test.
"""

@ -431,7 +431,7 @@ class TestStreamUnaryClientInterceptor(AioTestBase):
await channel.close()
async def test_cancel_while_writing(self):
# Test cancelation before making any write or after doing at least 1
# Test cancellation before making any write or after doing at least 1
for num_writes_before_cancel in (0, 1):
with self.subTest(
name="Num writes before cancel: {}".format(

@ -225,7 +225,7 @@ class TestUnaryUnaryClientInterceptor(AioTestBase):
self.assertEqual(grpc.StatusCode.OK, await call.code())
# Check that two calls were made, first one finishing with
# a deadline and second one finishing ok..
# a deadline and second one finishing ok.
self.assertEqual(len(interceptor.calls), 2)
self.assertEqual(
await interceptor.calls[0].code(),

@ -116,7 +116,7 @@ class TestConnectivityState(AioTestBase):
# Make sure there isn't any exception in the task
await pending_task
# It can raise exceptions since it is an usage error, but it should not
# It can raise exceptions since it is a usage error, but it should not
# segfault or abort.
with self.assertRaises(aio.UsageError):
await channel.wait_for_state_change(

@ -47,7 +47,7 @@ def start_test_server(port: int = 0) -> Tuple[str, Any]:
def _create_extra_generic_handler(servicer: TestServiceServicer) -> Any:
# Add programatically extra methods not provided by the proto file
# Add programmatically extra methods not provided by the proto file
# that are used during the tests
rpc_method_handlers = {
"UnaryCallWithSleep": grpc.unary_unary_rpc_method_handler(

@ -1,4 +1,4 @@
"""Patches the compile() to allow enable parallel compilation of C/C++.
"""Patches the compile() to enable parallel compilation of C/C++.
build_ext has lots of C/C++ files and normally them one by one.
Enabling parallel build helps a lot.

@ -87,6 +87,7 @@ grpc_internal_proto_library(
srcs = ["fuzzer_input.proto"],
deps = [
"api_fuzzer_proto",
"//src/core:chaotic_good_frame_proto",
"//test/core/event_engine/fuzzing_event_engine:fuzzing_event_engine_proto",
"//test/core/test_util:fuzz_config_vars_proto",
"//test/core/test_util:fuzzing_channel_args_proto",

@ -20,6 +20,7 @@ import "test/core/end2end/fuzzers/api_fuzzer.proto";
import "test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.proto";
import "test/core/test_util/fuzz_config_vars.proto";
import "test/core/test_util/fuzzing_channel_args.proto";
import "src/core/ext/transport/chaotic_good/chaotic_good_frame.proto";
message Empty{};
@ -145,37 +146,37 @@ message ChaoticGoodServerFragment {
}
}
message ChaoticGoodMessageData {
uint32 length = 1;
uint32 padding = 2;
message ChaoticGoodPayloadOtherConnection {
uint32 connection_id = 1;
uint32 length = 2;
}
message ChaoticGoodFrame {
enum FrameType {
SETTINGS = 0;
FRAGMENT = 1;
CANCEL = 2;
CLIENT_INITIAL_METADATA = 1;
MESSAGE = 2;
CLIENT_END_OF_STREAM = 3;
SERVER_INITIAL_METADATA = 4;
SERVER_TRAILING_METADATA = 5;
CANCEL = 6;
};
uint32 stream_id = 1;
FrameType type = 2;
oneof headers {
Empty headers_none = 11;
bytes headers_raw_bytes = 12;
SimpleHeaders headers_simple_header = 13;
}
oneof data {
Empty data_none = 21;
ChaoticGoodMessageData data_sized = 23;
oneof frame_type {
FrameType known_type = 2;
uint32 unknown_type = 3;
}
oneof trailers {
Empty trailers_none = 31;
bytes trailers_raw_bytes = 32;
SimpleHeaders trailers_simple_header = 33;
oneof payload {
ChaoticGoodPayloadOtherConnection payload_other_connection_id = 10;
Empty payload_none = 11;
bytes payload_raw_bytes = 12;
uint32 payload_empty_of_length = 13;
chaotic_good_frame.Settings settings = 14;
chaotic_good_frame.ClientMetadata client_metadata = 15;
chaotic_good_frame.ServerMetadata server_metadata = 16;
}
}
message ChaoticGoodSettings {}
message FakeTransportFrame {
enum MessageString {
CLIENT_INIT = 0;

@ -198,69 +198,83 @@ SliceBuffer ChaoticGoodFrame(const fuzzer_input::ChaoticGoodFrame& frame) {
chaotic_good::FrameHeader h;
SliceBuffer suffix;
h.stream_id = frame.stream_id();
switch (frame.type()) {
case fuzzer_input::ChaoticGoodFrame::SETTINGS:
h.type = chaotic_good::FrameType::kSettings;
break;
case fuzzer_input::ChaoticGoodFrame::FRAGMENT:
h.type = chaotic_good::FrameType::kFragment;
switch (frame.frame_type_case()) {
case fuzzer_input::ChaoticGoodFrame::kKnownType:
switch (frame.known_type()) {
case fuzzer_input::ChaoticGoodFrame::SETTINGS:
h.type = chaotic_good::FrameType::kSettings;
break;
case fuzzer_input::ChaoticGoodFrame::CLIENT_INITIAL_METADATA:
h.type = chaotic_good::FrameType::kClientInitialMetadata;
break;
case fuzzer_input::ChaoticGoodFrame::MESSAGE:
h.type = chaotic_good::FrameType::kMessage;
break;
case fuzzer_input::ChaoticGoodFrame::CLIENT_END_OF_STREAM:
h.type = chaotic_good::FrameType::kClientEndOfStream;
break;
case fuzzer_input::ChaoticGoodFrame::SERVER_INITIAL_METADATA:
h.type = chaotic_good::FrameType::kServerInitialMetadata;
break;
case fuzzer_input::ChaoticGoodFrame::SERVER_TRAILING_METADATA:
h.type = chaotic_good::FrameType::kServerTrailingMetadata;
break;
case fuzzer_input::ChaoticGoodFrame::CANCEL:
h.type = chaotic_good::FrameType::kCancel;
break;
default:
break;
}
break;
case fuzzer_input::ChaoticGoodFrame::CANCEL:
h.type = chaotic_good::FrameType::kCancel;
case fuzzer_input::ChaoticGoodFrame::kUnknownType:
h.type = static_cast<chaotic_good::FrameType>(frame.unknown_type());
break;
default:
case fuzzer_input::ChaoticGoodFrame::FRAME_TYPE_NOT_SET:
h.type = chaotic_good::FrameType::kMessage;
break;
}
switch (frame.headers_case()) {
case fuzzer_input::ChaoticGoodFrame::kHeadersNone:
case fuzzer_input::ChaoticGoodFrame::HEADERS_NOT_SET:
h.stream_id = frame.stream_id();
h.payload_connection_id = 0;
h.payload_length = 0;
auto proto_payload = [&](auto payload) {
std::string temp = payload.SerializeAsString();
h.payload_length = temp.length();
suffix.Append(Slice::FromCopiedString(temp));
};
switch (frame.payload_case()) {
case fuzzer_input::ChaoticGoodFrame::kPayloadNone:
case fuzzer_input::ChaoticGoodFrame::PAYLOAD_NOT_SET:
break;
case fuzzer_input::ChaoticGoodFrame::kHeadersRawBytes:
if (frame.headers_raw_bytes().empty()) break;
h.header_length = frame.headers_raw_bytes().size();
h.flags.Set(0, true);
suffix.Append(Slice::FromCopiedString(frame.headers_raw_bytes()));
case fuzzer_input::ChaoticGoodFrame::kPayloadRawBytes:
if (frame.payload_raw_bytes().empty()) break;
h.payload_length = frame.payload_raw_bytes().length();
suffix.Append(Slice::FromCopiedString(frame.payload_raw_bytes()));
break;
case fuzzer_input::ChaoticGoodFrame::kHeadersSimpleHeader: {
SliceBuffer append =
SliceBufferFromSimpleHeaders(frame.headers_simple_header());
if (append.Length() == 0) break;
h.header_length = append.Length();
h.flags.Set(0, true);
suffix.Append(append.JoinIntoSlice());
} break;
}
switch (frame.data_case()) {
case fuzzer_input::ChaoticGoodFrame::kDataNone:
case fuzzer_input::ChaoticGoodFrame::DATA_NOT_SET:
case fuzzer_input::ChaoticGoodFrame::kPayloadEmptyOfLength:
h.payload_length = frame.payload_empty_of_length();
suffix.Append(Slice::FromCopiedString(
std::string(frame.payload_empty_of_length(), 'a')));
break;
case fuzzer_input::ChaoticGoodFrame::kDataSized:
h.flags.Set(1, true);
h.message_length = frame.data_sized().length();
h.message_padding = frame.data_sized().padding();
case fuzzer_input::ChaoticGoodFrame::kPayloadOtherConnectionId:
h.payload_connection_id =
frame.payload_other_connection_id().connection_id();
h.payload_length = frame.payload_other_connection_id().length();
break;
}
switch (frame.trailers_case()) {
case fuzzer_input::ChaoticGoodFrame::kTrailersNone:
case fuzzer_input::ChaoticGoodFrame::TRAILERS_NOT_SET:
case fuzzer_input::ChaoticGoodFrame::kSettings:
proto_payload(frame.settings());
break;
case fuzzer_input::ChaoticGoodFrame::kTrailersRawBytes:
h.trailer_length = frame.trailers_raw_bytes().size();
h.flags.Set(2, true);
suffix.Append(Slice::FromCopiedString(frame.trailers_raw_bytes()));
case fuzzer_input::ChaoticGoodFrame::kClientMetadata:
proto_payload(frame.client_metadata());
break;
case fuzzer_input::ChaoticGoodFrame::kServerMetadata:
proto_payload(frame.server_metadata());
break;
case fuzzer_input::ChaoticGoodFrame::kTrailersSimpleHeader: {
SliceBuffer append =
SliceBufferFromSimpleHeaders(frame.trailers_simple_header());
h.trailer_length = append.Length();
h.flags.Set(2, true);
suffix.Append(append.JoinIntoSlice());
} break;
}
uint8_t bytes[24];
uint8_t bytes[chaotic_good::FrameHeader::kFrameHeaderSize];
h.Serialize(bytes);
SliceBuffer out;
out.Append(Slice::FromCopiedBuffer(bytes, 24));
out.Append(Slice::FromCopiedBuffer(
bytes, chaotic_good::FrameHeader::kFrameHeaderSize));
out.Append(suffix);
return out;
}

@ -121,7 +121,7 @@ class VerifyLogNoiseLogSink : public absl::LogSink {
// If we reach here means we have log noise. log_noise_absent_ will make the
// test fail.
log_noise_absent_ = false;
LOG(ERROR) << "Unwanted log at location : " << entry.source_filename()
LOG(ERROR) << "🛑 Unwanted log at location : " << entry.source_filename()
<< ":" << entry.source_line() << " " << entry.text_message();
}

@ -193,8 +193,10 @@ TEST_F(HttpRequestTest, Get) {
std::string host = absl::StrFormat("localhost:%d", g_server_port);
LOG(INFO) << "requesting from " << host;
memset(&req, 0, sizeof(req));
auto uri = grpc_core::URI::Create("http", host, "/get", {} /* query params */,
"" /* fragment */);
auto uri = grpc_core::URI::Create(
"http", host, "/get",
/*query_parameter_pairs=*/{{"foo", "bar"}, {"baz", "quux"}},
/*fragment=*/"");
CHECK(uri.ok());
grpc_core::OrphanablePtr<grpc_core::HttpRequest> http_request =
grpc_core::HttpRequest::Get(
@ -219,8 +221,10 @@ TEST_F(HttpRequestTest, Post) {
memset(&req, 0, sizeof(req));
req.body = const_cast<char*>("hello");
req.body_length = 5;
auto uri = grpc_core::URI::Create("http", host, "/post",
{} /* query params */, "" /* fragment */);
auto uri = grpc_core::URI::Create(
"http", host, "/post",
/*query_parameter_pairs=*/{{"foo", "bar"}, {"mumble", "frotz"}},
/*fragment=*/"");
CHECK(uri.ok());
grpc_core::OrphanablePtr<grpc_core::HttpRequest> http_request =
grpc_core::HttpRequest::Post(

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

Loading…
Cancel
Save