diff --git a/.bazelrc b/.bazelrc index 10c6b7439f..9bfd858fe7 100644 --- a/.bazelrc +++ b/.bazelrc @@ -25,6 +25,7 @@ build --copt=-DABSL_MIN_LOG_LEVEL=4 build --define envoy_mobile_listener=enabled build --experimental_repository_downloader_retries=2 build --enable_platform_specific_config +build --incompatible_merge_fixed_and_default_shell_env # Pass CC, CXX and LLVM_CONFIG variables from the environment. # We assume they have stable values, so this won't cause action cache misses. diff --git a/OWNERS.md b/OWNERS.md index c001eb66a1..3ba38ffed2 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -76,7 +76,6 @@ without further review. * All senior maintainers * Tony Allen ([tonya11en](https://github.com/tonya11en)) (tony@allen.gg) -* Otto van der Schaaf ([oschaaf](https://github.com/oschaaf)) (oschaaf@redhat.com) * Tim Walsh ([twghu](https://github.com/twghu)) (twalsh@redhat.com) * Pradeep Rao ([pradeepcrao](https://github.com/pradeepcrao)) (pcrao@google.com) * Kateryna Nezdolii ([nezdolik](https://github.com/nezdolik)) (kateryna.nezdolii@gmail.com) diff --git a/api/bazel/repository_locations.bzl b/api/bazel/repository_locations.bzl index 6c353c8591..dc3c4389c3 100644 --- a/api/bazel/repository_locations.bzl +++ b/api/bazel/repository_locations.bzl @@ -79,9 +79,9 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Google APIs", project_desc = "Public interface definitions of Google APIs", project_url = "https://github.com/googleapis/googleapis", - version = "114a745b2841a044e98cdbb19358ed29fcf4a5f1", - sha256 = "9b4e0d0a04a217c06b426aefd03b82581a9510ca766d2d1c70e52bb2ad4a0703", - release_date = "2023-01-10", + version = "fd52b5754b2b268bc3a22a10f29844f206abb327", + sha256 = "97fc354dddfd3ea03e7bf2ad74129291ed6fad7ff39d3bd8daec738a3672eb8a", + release_date = "2024-09-16", strip_prefix = "googleapis-{version}", urls = ["https://github.com/googleapis/googleapis/archive/{version}.tar.gz"], use_category = ["api"], diff --git a/api/envoy/config/listener/v3/quic_config.proto b/api/envoy/config/listener/v3/quic_config.proto index 6ba5bbc56b..6c0a5bd201 100644 --- a/api/envoy/config/listener/v3/quic_config.proto +++ b/api/envoy/config/listener/v3/quic_config.proto @@ -25,7 +25,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: QUIC listener config] // Configuration specific to the UDP QUIC listener. -// [#next-free-field: 13] +// [#next-free-field: 14] message QuicProtocolOptions { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.listener.QuicProtocolOptions"; @@ -94,4 +94,9 @@ message QuicProtocolOptions { // If not specified, no cmsg will be saved to QuicReceivedPacket. repeated core.v3.SocketCmsgHeaders save_cmsg_config = 12 [(validate.rules).repeated = {max_items: 1}]; + + // If true, the listener will reject connection-establishing packets at the + // QUIC layer by replying with an empty version negotiation packet to the + // client. + bool reject_new_connections = 13; } diff --git a/bazel/external/cargo/remote/BUILD.protobuf-2.24.1.bazel b/bazel/external/cargo/remote/BUILD.protobuf-2.24.1.bazel index 9917db62f5..b818daadb7 100644 --- a/bazel/external/cargo/remote/BUILD.protobuf-2.24.1.bazel +++ b/bazel/external/cargo/remote/BUILD.protobuf-2.24.1.bazel @@ -33,7 +33,7 @@ licenses([ # buildifier: disable=out-of-order-load # buildifier: disable=load-on-top load( - "@rules_rust//cargo:cargo_build_script.bzl", + "@rules_rust//cargo:defs.bzl", "cargo_build_script", ) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index d382a58b13..6752ec066c 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -33,11 +33,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Bazel features", project_desc = "Support Bazel feature detection from starlark", project_url = "https://github.com/bazel-contrib/bazel_features", - version = "1.15.0", - sha256 = "ba1282c1aa1d1fffdcf994ab32131d7c7551a9bc960fbf05f42d55a1b930cbfb", + version = "1.17.0", + sha256 = "bdc12fcbe6076180d835c9dd5b3685d509966191760a0eb10b276025fcb76158", urls = ["https://github.com/bazel-contrib/bazel_features/releases/download/v{version}/bazel_features-v{version}.tar.gz"], strip_prefix = "bazel_features-{version}", - release_date = "2024-08-09", + release_date = "2024-09-13", use_category = ["build"], license = "Apache-2.0", license_url = "https://github.com/bazel-contrib/bazel_features/blob/v{version}/LICENSE", @@ -1333,13 +1333,13 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Kafka (source)", project_desc = "Open-source distributed event streaming platform", project_url = "https://kafka.apache.org", - version = "3.5.1", - sha256 = "9715589a02148fb21bc80d79f29763dbd371457bedcbbeab3db4f5c7fdd2d29c", + version = "3.8.0", + sha256 = "8761a0c22738201d3049f11f78c8e6c0f201203ba799157e498ef7eb04c259f3", strip_prefix = "kafka-{version}/clients/src/main/resources/common/message", urls = ["https://github.com/apache/kafka/archive/{version}.zip"], use_category = ["dataplane_ext"], extensions = ["envoy.filters.network.kafka_broker", "envoy.filters.network.kafka_mesh"], - release_date = "2023-07-14", + release_date = "2024-07-23", cpe = "cpe:2.3:a:apache:kafka:*", license = "Apache-2.0", license_url = "https://github.com/apache/kafka/blob/{version}/LICENSE", @@ -1363,11 +1363,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Kafka (server binary)", project_desc = "Open-source distributed event streaming platform", project_url = "https://kafka.apache.org", - version = "3.5.1", - sha256 = "f7b74d544023f2c0ec52a179de59975cb64e34ea03650d829328b407b560e4da", + version = "3.8.0", + sha256 = "e0297cc6fdb09ef9d9905751b25d2b629c17528f8629b60561eeff87ce29099c", strip_prefix = "kafka_2.13-{version}", urls = ["https://archive.apache.org/dist/kafka/{version}/kafka_2.13-{version}.tgz"], - release_date = "2023-07-21", + release_date = "2024-07-23", use_category = ["test_only"], ), proxy_wasm_cpp_sdk = dict( @@ -1424,12 +1424,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (Rust SDK)", project_desc = "WebAssembly for Proxies (Rust SDK)", project_url = "https://github.com/proxy-wasm/proxy-wasm-rust-sdk", - version = "0.2.1", - sha256 = "23f3f2d8c4c8069a2e72693b350d7442b7722d334f73169eea78804ff70cde20", + version = "0.2.2", + sha256 = "3d9e8f39f0356016c8ae6c74c0224eae1b44168be0ddf79e387d918a8f2cb4c6", strip_prefix = "proxy-wasm-rust-sdk-{version}", urls = ["https://github.com/proxy-wasm/proxy-wasm-rust-sdk/archive/v{version}.tar.gz"], use_category = ["test_only"], - release_date = "2022-11-22", + release_date = "2024-07-21", cpe = "N/A", license = "Apache-2.0", license_url = "https://github.com/proxy-wasm/proxy-wasm-rust-sdk/blob/v{version}/LICENSE", @@ -1452,9 +1452,9 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Bazel rust rules", project_desc = "Bazel rust rules (used by Wasm)", project_url = "https://github.com/bazelbuild/rules_rust", - version = "0.35.0", + version = "0.50.1", strip_prefix = "rules_rust-{version}", - sha256 = "3120c7aa3a146dfe6be8d5f23f4cf10af7d0f74a5aed8b94a818f88643bd24c3", + sha256 = "ddfc0210b19498086d09c458672ef2a6fb7790103dbb1b2da73c54677c330ed1", urls = ["https://github.com/bazelbuild/rules_rust/archive/{version}.tar.gz"], use_category = [ "controlplane", @@ -1463,7 +1463,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( ], implied_untracked_deps = ["rules_cc"], extensions = ["envoy.wasm.runtime.wasmtime"], - release_date = "2023-12-27", + release_date = "2024-09-11", cpe = "N/A", license = "Apache-2.0", license_url = "https://github.com/bazelbuild/rules_rust/blob/{version}/LICENSE.txt", diff --git a/changelogs/current.yaml b/changelogs/current.yaml index a8f9ca6d88..e60a462af2 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -248,6 +248,11 @@ new_features: QUIC server and client support certificate compression, which can in some cases reduce the number of round trips required to setup a connection. This change temporarily disabled by setting the runtime flag ``envoy.reloadable_features.quic_support_certificate_compression`` to ``false``. +- area: quic + change: | + Added QUIC protocol option :ref:`reject_new_connections + ` to reject connection-establishing + packets at the QUIC layer. - area: tls change: | Added an extension point :ref:`custom_tls_certificate_selector diff --git a/contrib/golang/filters/http/source/golang_filter.cc b/contrib/golang/filters/http/source/golang_filter.cc index 226a6656ec..cc4bd5c294 100644 --- a/contrib/golang/filters/http/source/golang_filter.cc +++ b/contrib/golang/filters/http/source/golang_filter.cc @@ -1377,16 +1377,13 @@ void Filter::deferredDeleteRequest(HttpRequestInternal* req) { uint64_t Filter::getMergedConfigId() { Http::StreamFilterCallbacks* callbacks = decoding_state_.getFilterCallbacks(); + auto id = config_->getConfigId(); + // get all of the per route config auto route_config_list = Http::Utility::getAllPerFilterConfig(callbacks); - ENVOY_LOG(debug, "golang filter route config list length: {}.", route_config_list.size()); - - auto id = config_->getConfigId(); - for (auto it : route_config_list) { - ASSERT(it != nullptr, "route config should not be null"); - auto route_config = *it; - id = route_config.getPluginConfigId(id, config_->pluginName()); + for (const FilterConfigPerRoute& typed_config : route_config_list) { + id = typed_config.getPluginConfigId(id, config_->pluginName()); } return id; diff --git a/contrib/kafka/filters/network/source/protocol/generator.py b/contrib/kafka/filters/network/source/protocol/generator.py index 31762b1757..656856219d 100755 --- a/contrib/kafka/filters/network/source/protocol/generator.py +++ b/contrib/kafka/filters/network/source/protocol/generator.py @@ -153,9 +153,8 @@ def parse_messages(self, input_files): amended = re.sub(r'-2147483648', 'INT32_MIN', without_empty_newlines) message_spec = json.loads(amended) api_key = message_spec['apiKey'] - # (adam.kotwasinski) Higher API keys in the future versions of Kafka need - # some more changes to parse. - if api_key < 68 or api_key == 69: + # (adam.kotwasinski) Telemetry is not supported for now. + if api_key not in [71, 72]: message = self.parse_top_level_element(message_spec) messages.append(message) except Exception as e: @@ -224,8 +223,9 @@ def parse_complex_type(self, type_name, field_spec, versions): fields.append(child) # Some structures share the same name, use request/response as prefix. - if cpp_name in ['EntityData', 'EntryData', 'PartitionData', 'PartitionSnapshot', - 'SnapshotId', 'TopicData', 'TopicPartitions', 'TopicSnapshot']: + if cpp_name in ['Cursor', 'DirectoryData', 'EntityData', 'EntryData', 'PartitionData', + 'PartitionSnapshot', 'SnapshotId', 'TopicData', 'TopicPartitions', + 'TopicSnapshot']: cpp_name = self.type.capitalize() + type_name # Some of the types repeat multiple times (e.g. AlterableConfig). @@ -370,9 +370,9 @@ def example_value(self): class FieldSpec: """ - Represents a field present in a structure (request, or child structure thereof). - Contains name, type, and versions when it is used (nullable or not). - """ + Represents a field present in a structure (request, or child structure thereof). + Contains name, type, and versions when it is used (nullable or not). + """ def __init__(self, name, type, version_usage, version_usage_as_nullable): import re @@ -387,10 +387,10 @@ def is_nullable(self): def is_nullable_in_version(self, version): """ - Whether the field is nullable in given version. - Fields can be non-nullable in earlier versions. - See https://github.com/apache/kafka/tree/2.2.0-rc0/clients/src/main/resources/common/message#nullable-fields - """ + Whether the field is nullable in given version. + Fields can be non-nullable in earlier versions. + See https://github.com/apache/kafka/tree/3.8.0/clients/src/main/resources/common/message#nullable-fields + """ return version in self.version_usage_as_nullable def used_in_version(self, version): @@ -428,13 +428,21 @@ def example_value_for_test(self, version): def deserializer_name_in_version(self, version, compact): if self.is_nullable_in_version(version): - return 'Nullable%s' % self.type.deserializer_name_in_version(version, compact) + underlying_deserializer = self.type.deserializer_name_in_version(version, compact) + # Handles KAFKA-14425 - structs (complex types) can now be nullable. + if isinstance(self.type, Complex): + return 'NullableStructDeserializer<%s>' % underlying_deserializer + else: + return 'Nullable%s' % underlying_deserializer else: return self.type.deserializer_name_in_version(version, compact) def is_printable(self): return self.type.is_printable() + def __str__(self): + return '%s(%s)' % (self.name, self.type) + class TypeSpecification: @@ -471,10 +479,10 @@ def is_printable(self): class Array(TypeSpecification): """ - Represents array complex type. - To use instance of this type, it is necessary to declare structures required by self.underlying - (e.g. to use Array, we need to have `struct Foo {...}`). - """ + Represents array complex type. + To use instance of this type, it is necessary to declare structures required by self.underlying + (e.g. to use Array, we need to have `struct Foo {...}`). + """ def __init__(self, underlying): self.underlying = underlying @@ -505,6 +513,9 @@ def example_value_for_test(self, version): def is_printable(self): return self.underlying.is_printable() + def __str__(self): + return self.name + class Primitive(TypeSpecification): """ @@ -643,6 +654,9 @@ def example_value_for_test(self, version): def is_printable(self): return self.name not in ['Bytes'] + def __str__(self): + return self.name + class FieldSerializationSpec(): @@ -679,9 +693,9 @@ def register_flexible_versions(self, flexible_versions): def compute_declaration_chain(self): """ - Computes all dependencies, what means all non-primitive types used by this type. - They need to be declared before this struct is declared. - """ + Computes all dependencies, what means all non-primitive types used by this type. + They need to be declared before this struct is declared. + """ result = [] for field in self.fields: field_dependencies = field.type.compute_declaration_chain() @@ -700,10 +714,10 @@ def get_extra(self, key): def compute_constructors(self): """ - Field lists for different versions may not differ (as Kafka can bump version without any - changes). But constructors need to be unique, so we need to remove duplicates if the signatures - match. - """ + Field lists for different versions may not differ (as Kafka can bump version without any + changes). But constructors need to be unique, so we need to remove duplicates + if the signatures match. + """ signature_to_constructor = {} for field_list in self.compute_field_lists(): signature = field_list.constructor_signature() @@ -724,8 +738,8 @@ def compute_constructors(self): def compute_field_lists(self): """ - Return field lists representing each of structure versions. - """ + Return field lists representing each of structure versions. + """ field_lists = [] for version in self.versions: field_list = FieldList(version, version in self.flexible_versions, self.fields) @@ -772,6 +786,9 @@ def example_value_for_test(self, version): def is_printable(self): return True + def __str__(self): + return self.name + class RenderingHelper: """ diff --git a/contrib/kafka/filters/network/source/serialization.h b/contrib/kafka/filters/network/source/serialization.h index 902fbd9c00..f2ea6a7545 100644 --- a/contrib/kafka/filters/network/source/serialization.h +++ b/contrib/kafka/filters/network/source/serialization.h @@ -918,6 +918,72 @@ class NullableCompactArrayDeserializer bool ready_{false}; }; +/** + * Nullable objects are sent as single byte and following data. + * Reference: https://issues.apache.org/jira/browse/KAFKA-14425 + */ +template +class NullableStructDeserializer + : public Deserializer> { +public: + using ResponseType = absl::optional; + + uint32_t feed(absl::string_view& data) override { + + if (data.empty()) { + return 0; + } + + uint32_t bytes_read = 0; + + if (!marker_consumed_) { + // Read marker byte from input. + int8_t marker; + safeMemcpy(&marker, data.data()); + data = {data.data() + 1, data.size() - 1}; + bytes_read += 1; + marker_consumed_ = true; + + if (marker >= 0) { + data_buf_ = absl::make_optional(DeserializerType()); + } else { + return bytes_read; + } + } + + if (data_buf_) { + bytes_read += data_buf_->feed(data); + } + + return bytes_read; + } + + bool ready() const override { + if (marker_consumed_) { + if (data_buf_) { + return data_buf_->ready(); + } else { + return true; // It's an empty optional. + } + } else { + return false; + } + } + + ResponseType get() const override { + if (data_buf_) { + const typename ResponseType::value_type deserialized_form = data_buf_->get(); + return absl::make_optional(deserialized_form); + } else { + return absl::nullopt; + } + } + +private: + bool marker_consumed_{false}; + absl::optional data_buf_; // Present if marker was consumed and was 0 or more. +}; + /** * Kafka UUID is basically two longs, so we are going to keep model them the same way. * Reference: @@ -996,6 +1062,12 @@ class EncodingContext { */ template uint32_t computeSize(const NullableArray& arg) const; + /** + * Compute size of given nullable object, if it were to be encoded. + * @return serialized size of argument. + */ + template uint32_t computeSize(const absl::optional& arg) const; + /** * Compute size of given reference, if it were to be compactly encoded. * @return serialized size of argument. @@ -1032,6 +1104,12 @@ class EncodingContext { */ template uint32_t encode(const NullableArray& arg, Buffer::Instance& dst); + /** + * Encode given nullable object in a buffer. + * @return bytes written + */ + template uint32_t encode(const absl::optional& arg, Buffer::Instance& dst); + /** * Compactly encode given reference in a buffer. * @return bytes written. @@ -1135,6 +1213,15 @@ inline uint32_t EncodingContext::computeSize(const NullableArray& arg) const return arg ? computeSize(*arg) : sizeof(int32_t); } +/** + * Template overload for nullable T. + * The size of nullable object is 1 (for market byte) and the size of real object (if any). + */ +template +inline uint32_t EncodingContext::computeSize(const absl::optional& arg) const { + return 1 + (arg ? computeSize(*arg) : 0); +} + /** * Template overload for Uuid. */ @@ -1388,6 +1475,23 @@ uint32_t EncodingContext::encode(const NullableArray& arg, Buffer::Instance& } } +/** + * Encode nullable object as marker byte (1 if present, -1 otherwise), then if object is present, + * have it serialise itself. + */ +template +uint32_t EncodingContext::encode(const absl::optional& arg, Buffer::Instance& dst) { + if (arg) { + const int8_t marker = 1; + encode(marker, dst); + const uint32_t written = encode(*arg, dst); + return 1 + written; + } else { + const int8_t marker = -1; + return encode(marker, dst); + } +} + /** * Template overload for Uuid. */ diff --git a/contrib/kafka/filters/network/test/serialization_test.cc b/contrib/kafka/filters/network/test/serialization_test.cc index 59aa0e567b..bbb80c3bd8 100644 --- a/contrib/kafka/filters/network/test/serialization_test.cc +++ b/contrib/kafka/filters/network/test/serialization_test.cc @@ -41,6 +41,8 @@ TEST_EmptyDeserializerShouldNotBeReady(BytesDeserializer); TEST_EmptyDeserializerShouldNotBeReady(CompactBytesDeserializer); TEST_EmptyDeserializerShouldNotBeReady(NullableBytesDeserializer); TEST_EmptyDeserializerShouldNotBeReady(NullableCompactBytesDeserializer); +using ExampleNullableStructDeserializer = NullableStructDeserializer; +TEST_EmptyDeserializerShouldNotBeReady(ExampleNullableStructDeserializer); TEST_EmptyDeserializerShouldNotBeReady(UuidDeserializer); TEST(ArrayDeserializer, EmptyBufferShouldNotBeReady) { @@ -544,6 +546,20 @@ TEST(NullableCompactArrayDeserializer, ShouldConsumeCorrectAmountOfDataForLargeI NullableCompactArrayDeserializer>(value); } +// Nullable struct. + +using ExampleNSD = NullableStructDeserializer; + +TEST(NullableStructDeserializer, ShouldConsumeCorrectAmountOfData) { + const ExampleNSD::ResponseType value = {42}; + serializeThenDeserializeAndCheckEquality(value); +} + +TEST(NullableStructDeserializer, ShouldConsumeNullStruct) { + const ExampleNSD::ResponseType value = absl::nullopt; + serializeThenDeserializeAndCheckEquality(value); +} + // UUID. TEST(UuidDeserializer, ShouldDeserialize) { diff --git a/docs/root/configuration/listeners/network_filters/kafka_broker_filter.rst b/docs/root/configuration/listeners/network_filters/kafka_broker_filter.rst index b8d0302796..2025137c17 100644 --- a/docs/root/configuration/listeners/network_filters/kafka_broker_filter.rst +++ b/docs/root/configuration/listeners/network_filters/kafka_broker_filter.rst @@ -5,9 +5,8 @@ Kafka Broker filter The Apache Kafka broker filter decodes the client protocol for `Apache Kafka `_, both the requests and responses in the payload. -The message versions in `Kafka 3.5.1 `_ -are supported (apart from ConsumerGroupHeartbeat - what means consumers configured with -``group.protocol=consumer``). +The message versions in `Kafka 3.8.0 `_ +are supported. By default the filter attempts not to influence the communication between client and brokers, so the messages that could not be decoded (due to Kafka client or broker running a newer version than diff --git a/docs/root/configuration/listeners/network_filters/kafka_mesh_filter.rst b/docs/root/configuration/listeners/network_filters/kafka_mesh_filter.rst index 4729473489..6da435ad83 100644 --- a/docs/root/configuration/listeners/network_filters/kafka_mesh_filter.rst +++ b/docs/root/configuration/listeners/network_filters/kafka_mesh_filter.rst @@ -12,7 +12,7 @@ clients. The requests received by this filter instance can be forwarded to one of multiple clusters, depending on the configured forwarding rules. -Corresponding message versions from Kafka 3.5.1 are supported. +Corresponding message versions from Kafka 3.8.0 are supported. * This filter should be configured with the type URL ``type.googleapis.com/envoy.extensions.filters.network.kafka_mesh.v3alpha.KafkaMesh``. * :ref:`v3 API reference ` diff --git a/source/common/api/posix/os_sys_calls_impl.cc b/source/common/api/posix/os_sys_calls_impl.cc index 3fab0afc1b..ad078c7f4d 100644 --- a/source/common/api/posix/os_sys_calls_impl.cc +++ b/source/common/api/posix/os_sys_calls_impl.cc @@ -109,9 +109,6 @@ bool OsSysCallsImpl::supportsUdpGro() const { #else static const bool is_supported = [] { int fd = ::socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP); - if (fd < 0) { - return false; - } int val = 1; bool result = (0 == ::setsockopt(fd, IPPROTO_UDP, UDP_GRO, &val, sizeof(val))); ::close(fd); @@ -127,9 +124,6 @@ bool OsSysCallsImpl::supportsUdpGso() const { #else static const bool is_supported = [] { int fd = ::socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP); - if (fd < 0) { - return false; - } int optval; socklen_t optlen = sizeof(optval); bool result = (0 <= ::getsockopt(fd, IPPROTO_UDP, UDP_SEGMENT, &optval, &optlen)); @@ -160,9 +154,6 @@ bool OsSysCallsImpl::supportsIpTransparent(Network::Address::IpVersion ip_versio static constexpr auto transparent_supported = [](int family) { auto opt_tp = family == AF_INET ? ENVOY_SOCKET_IP_TRANSPARENT : ENVOY_SOCKET_IPV6_TRANSPARENT; int fd = ::socket(family, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP); - if (fd < 0) { - return false; - } int val = 1; bool result = (0 == ::setsockopt(fd, opt_tp.level(), opt_tp.option(), &val, sizeof(val))); ::close(fd); @@ -348,9 +339,9 @@ SysCallBoolResult OsSysCallsImpl::socketTcpInfo([[maybe_unused]] os_fd_t sockfd, tcp_info->tcpi_snd_cwnd = unix_tcp_info.tcpi_snd_cwnd * mss; } return {!SOCKET_FAILURE(result), !SOCKET_FAILURE(result) ? 0 : errno}; -#endif - +#else return {false, EOPNOTSUPP}; +#endif } bool OsSysCallsImpl::supportsGetifaddrs() const { return true; } diff --git a/source/common/formatter/substitution_format_string.h b/source/common/formatter/substitution_format_string.h index fc1d6f5e6e..c66c77eaff 100644 --- a/source/common/formatter/substitution_format_string.h +++ b/source/common/formatter/substitution_format_string.h @@ -29,7 +29,7 @@ class SubstitutionFormatStringUtils { * Parse list of formatter configurations to commands. */ template - static std::vector> + static absl::StatusOr>> parseFormatters(const FormattersConfig& formatters, Server::Configuration::GenericFactoryContext& context) { std::vector> commands; @@ -37,13 +37,13 @@ class SubstitutionFormatStringUtils { auto* factory = Envoy::Config::Utility::getFactory>(formatter); if (!factory) { - throwEnvoyExceptionOrPanic(absl::StrCat("Formatter not found: ", formatter.name())); + return absl::InvalidArgumentError(absl::StrCat("Formatter not found: ", formatter.name())); } auto typed_config = Envoy::Config::Utility::translateAnyToFactoryConfig( formatter.typed_config(), context.messageValidationVisitor(), *factory); auto parser = factory->createCommandParserFromProto(*typed_config, context); if (!parser) { - throwEnvoyExceptionOrPanic( + return absl::InvalidArgumentError( absl::StrCat("Failed to create command parser: ", formatter.name())); } commands.push_back(std::move(parser)); @@ -56,26 +56,28 @@ class SubstitutionFormatStringUtils { * Generate a formatter object from config SubstitutionFormatString. */ template - static FormatterBasePtr + static absl::StatusOr> fromProtoConfig(const envoy::config::core::v3::SubstitutionFormatString& config, Server::Configuration::GenericFactoryContext& context) { // Instantiate formatter extensions. auto commands = parseFormatters(config.formatters(), context); + RETURN_IF_NOT_OK_REF(commands.status()); switch (config.format_case()) { case envoy::config::core::v3::SubstitutionFormatString::FormatCase::kTextFormat: return std::make_unique>( - config.text_format(), config.omit_empty_values(), commands); + config.text_format(), config.omit_empty_values(), *commands); case envoy::config::core::v3::SubstitutionFormatString::FormatCase::kJsonFormat: return createJsonFormatter( config.json_format(), true, config.omit_empty_values(), config.has_json_format_options() ? config.json_format_options().sort_properties() : false, - commands); - case envoy::config::core::v3::SubstitutionFormatString::FormatCase::kTextFormatSource: + *commands); + case envoy::config::core::v3::SubstitutionFormatString::FormatCase::kTextFormatSource: { + auto data_source_or_error = Config::DataSource::read(config.text_format_source(), true, + context.serverFactoryContext().api()); + RETURN_IF_NOT_OK(data_source_or_error.status()); return std::make_unique>( - THROW_OR_RETURN_VALUE(Config::DataSource::read(config.text_format_source(), true, - context.serverFactoryContext().api()), - std::string), - config.omit_empty_values(), commands); + *data_source_or_error, config.omit_empty_values(), *commands); + } case envoy::config::core::v3::SubstitutionFormatString::FormatCase::FORMAT_NOT_SET: PANIC_DUE_TO_PROTO_UNSET; } diff --git a/source/common/http/utility.h b/source/common/http/utility.h index d1b2d0d093..eaee0c34fa 100644 --- a/source/common/http/utility.h +++ b/source/common/http/utility.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -572,11 +573,11 @@ const ConfigType* resolveMostSpecificPerFilterConfig(const Http::StreamFilterCal * and their lifetime is the same as the matched route. */ template -absl::InlinedVector +absl::InlinedVector, 4> getAllPerFilterConfig(const Http::StreamFilterCallbacks* callbacks) { ASSERT(callbacks != nullptr); - absl::InlinedVector all_configs; + absl::InlinedVector, 4> all_configs; for (const auto* config : callbacks->perFilterConfigs()) { const ConfigType* typed_config = dynamic_cast(config); @@ -584,7 +585,7 @@ getAllPerFilterConfig(const Http::StreamFilterCallbacks* callbacks) { ENVOY_LOG_MISC(debug, "Failed to retrieve the correct type of route specific filter config"); continue; } - all_configs.push_back(typed_config); + all_configs.push_back(*typed_config); } return all_configs; diff --git a/source/common/local_reply/local_reply.cc b/source/common/local_reply/local_reply.cc index 8de608a225..10bd821388 100644 --- a/source/common/local_reply/local_reply.cc +++ b/source/common/local_reply/local_reply.cc @@ -22,7 +22,9 @@ class BodyFormatter { BodyFormatter(const envoy::config::core::v3::SubstitutionFormatString& config, Server::Configuration::GenericFactoryContext& context) - : formatter_(Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config, context)), + : formatter_(THROW_OR_RETURN_VALUE( + Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config, context), + Formatter::FormatterBasePtr)), content_type_( !config.content_type().empty() ? config.content_type() : config.format_case() == diff --git a/source/common/quic/active_quic_listener.cc b/source/common/quic/active_quic_listener.cc index 002b8f5866..691c4a9c29 100644 --- a/source/common/quic/active_quic_listener.cc +++ b/source/common/quic/active_quic_listener.cc @@ -37,14 +37,17 @@ ActiveQuicListener::ActiveQuicListener( EnvoyQuicCryptoServerStreamFactoryInterface& crypto_server_stream_factory, EnvoyQuicProofSourceFactoryInterface& proof_source_factory, QuicConnectionIdGeneratorPtr&& cid_generator, QuicConnectionIdWorkerSelector worker_selector, - EnvoyQuicConnectionDebugVisitorFactoryInterfaceOptRef debug_visitor_factory) + EnvoyQuicConnectionDebugVisitorFactoryInterfaceOptRef debug_visitor_factory, + bool reject_new_connections) : Server::ActiveUdpListenerBase( worker_index, concurrency, parent, *listen_socket, std::make_unique( dispatcher, listen_socket, *this, dispatcher.timeSource(), listener_config.udpListenerConfig()->config().downstream_socket_config()), &listener_config), - dispatcher_(dispatcher), version_manager_(quic::CurrentSupportedHttp3Versions()), + dispatcher_(dispatcher), + version_manager_(reject_new_connections ? quic::ParsedQuicVersionVector() + : quic::CurrentSupportedHttp3Versions()), kernel_worker_routing_(kernel_worker_routing), packets_to_read_to_connection_count_ratio_(packets_to_read_to_connection_count_ratio), crypto_server_stream_factory_(crypto_server_stream_factory), @@ -264,7 +267,7 @@ ActiveQuicListenerFactory::ActiveQuicListenerFactory( PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, packets_to_read_to_connection_count_ratio, DEFAULT_PACKETS_TO_READ_PER_CONNECTION)), receive_ecn_(Runtime::runtimeFeatureEnabled("envoy.reloadable_features.quic_receive_ecn")), - context_(context) { + context_(context), reject_new_connections_(config.reject_new_connections()) { const int64_t idle_network_timeout_ms = config.has_idle_timeout() ? DurationUtil::durationToMilliseconds(config.idle_timeout()) : 300000; @@ -434,7 +437,7 @@ ActiveQuicListenerFactory::createActiveQuicListener( listener_config, quic_config, kernel_worker_routing, enabled, quic_stat_names, packets_to_read_to_connection_count_ratio, receive_ecn_, crypto_server_stream_factory, proof_source_factory, std::move(cid_generator), worker_selector_, - connection_debug_visitor_factory_); + connection_debug_visitor_factory_, reject_new_connections_); } } // namespace Quic diff --git a/source/common/quic/active_quic_listener.h b/source/common/quic/active_quic_listener.h index e0f62c0298..0ec62ac7df 100644 --- a/source/common/quic/active_quic_listener.h +++ b/source/common/quic/active_quic_listener.h @@ -41,7 +41,8 @@ class ActiveQuicListener : public Envoy::Server::ActiveUdpListenerBase, EnvoyQuicProofSourceFactoryInterface& proof_source_factory, QuicConnectionIdGeneratorPtr&& cid_generator, QuicConnectionIdWorkerSelector worker_selector, - EnvoyQuicConnectionDebugVisitorFactoryInterfaceOptRef debug_visitor_factory); + EnvoyQuicConnectionDebugVisitorFactoryInterfaceOptRef debug_visitor_factory, + bool reject_new_connections = false); ~ActiveQuicListener() override; @@ -159,6 +160,7 @@ class ActiveQuicListenerFactory : public Network::ActiveUdpListenerFactory, QuicConnectionIdWorkerSelector worker_selector_; bool kernel_worker_routing_{}; Server::Configuration::ServerFactoryContext& context_; + bool reject_new_connections_{}; static bool disable_kernel_bpf_packet_routing_for_test_; }; diff --git a/source/common/tcp_proxy/tcp_proxy.cc b/source/common/tcp_proxy/tcp_proxy.cc index 6de501b25d..c4e2e58722 100644 --- a/source/common/tcp_proxy/tcp_proxy.cc +++ b/source/common/tcp_proxy/tcp_proxy.cc @@ -707,8 +707,10 @@ TunnelingConfigHelperImpl::TunnelingConfigHelperImpl( envoy::config::core::v3::SubstitutionFormatString substitution_format_config; substitution_format_config.mutable_text_format_source()->set_inline_string( config_message.tunneling_config().hostname()); - hostname_fmt_ = Formatter::SubstitutionFormatStringUtils::fromProtoConfig( - substitution_format_config, context); + hostname_fmt_ = + THROW_OR_RETURN_VALUE(Formatter::SubstitutionFormatStringUtils::fromProtoConfig( + substitution_format_config, context), + Formatter::FormatterBasePtr); } std::string TunnelingConfigHelperImpl::host(const StreamInfo::StreamInfo& stream_info) const { diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index 805ceb849c..a336aa2689 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -1388,7 +1388,8 @@ ClusterInfoImpl::ClusterInfoImpl( if (http_filters.empty()) { auto* codec_filter = http_filters.Add(); codec_filter->set_name("envoy.filters.http.upstream_codec"); - codec_filter->mutable_typed_config()->set_type_url(upstream_codec_type_url); + codec_filter->mutable_typed_config()->set_type_url( + absl::StrCat("type.googleapis.com/", upstream_codec_type_url)); } else { const auto last_type_url = Config::Utility::getFactoryType(http_filters[http_filters.size() - 1].typed_config()); diff --git a/source/extensions/access_loggers/common/stream_access_log_common_impl.h b/source/extensions/access_loggers/common/stream_access_log_common_impl.h index 1713b91825..5b07233124 100644 --- a/source/extensions/access_loggers/common/stream_access_log_common_impl.h +++ b/source/extensions/access_loggers/common/stream_access_log_common_impl.h @@ -18,8 +18,9 @@ createStreamAccessLogInstance(const Protobuf::Message& config, AccessLog::Filter MessageUtil::downcastAndValidate(config, context.messageValidationVisitor()); Formatter::FormatterPtr formatter; if (fal_config.access_log_format_case() == T::AccessLogFormatCase::kLogFormat) { - formatter = - Formatter::SubstitutionFormatStringUtils::fromProtoConfig(fal_config.log_format(), context); + formatter = THROW_OR_RETURN_VALUE( + Formatter::SubstitutionFormatStringUtils::fromProtoConfig(fal_config.log_format(), context), + Formatter::FormatterBasePtr); } else if (fal_config.access_log_format_case() == T::AccessLogFormatCase::ACCESS_LOG_FORMAT_NOT_SET) { formatter = Formatter::HttpSubstitutionFormatUtils::defaultSubstitutionFormatter(); diff --git a/source/extensions/access_loggers/file/config.cc b/source/extensions/access_loggers/file/config.cc index a21c693436..2761aa5064 100644 --- a/source/extensions/access_loggers/file/config.cc +++ b/source/extensions/access_loggers/file/config.cc @@ -35,7 +35,9 @@ FileAccessLogFactory::createAccessLogInstance(const Protobuf::Message& config, } else { envoy::config::core::v3::SubstitutionFormatString sff_config; sff_config.mutable_text_format_source()->set_inline_string(fal_config.format()); - formatter = Formatter::SubstitutionFormatStringUtils::fromProtoConfig(sff_config, context); + formatter = THROW_OR_RETURN_VALUE( + Formatter::SubstitutionFormatStringUtils::fromProtoConfig(sff_config, context), + Formatter::FormatterBasePtr); } break; case envoy::extensions::access_loggers::file::v3::FileAccessLog::AccessLogFormatCase::kJsonFormat: @@ -46,12 +48,15 @@ FileAccessLogFactory::createAccessLogInstance(const Protobuf::Message& config, kTypedJsonFormat: { envoy::config::core::v3::SubstitutionFormatString sff_config; *sff_config.mutable_json_format() = fal_config.typed_json_format(); - formatter = Formatter::SubstitutionFormatStringUtils::fromProtoConfig(sff_config, context); + formatter = THROW_OR_RETURN_VALUE( + Formatter::SubstitutionFormatStringUtils::fromProtoConfig(sff_config, context), + Formatter::FormatterBasePtr); break; } case envoy::extensions::access_loggers::file::v3::FileAccessLog::AccessLogFormatCase::kLogFormat: - formatter = - Formatter::SubstitutionFormatStringUtils::fromProtoConfig(fal_config.log_format(), context); + formatter = THROW_OR_RETURN_VALUE( + Formatter::SubstitutionFormatStringUtils::fromProtoConfig(fal_config.log_format(), context), + Formatter::FormatterBasePtr); break; case envoy::extensions::access_loggers::file::v3::FileAccessLog::AccessLogFormatCase:: ACCESS_LOG_FORMAT_NOT_SET: diff --git a/source/extensions/access_loggers/fluentd/config.cc b/source/extensions/access_loggers/fluentd/config.cc index 877bd69de2..a6614137e6 100644 --- a/source/extensions/access_loggers/fluentd/config.cc +++ b/source/extensions/access_loggers/fluentd/config.cc @@ -60,8 +60,10 @@ FluentdAccessLogFactory::createAccessLogInstance(const Protobuf::Message& config // payload. // TODO(ohadvano): Improve the formatting operation by creating a dedicated formatter that // will directly serialize the record to msgpack payload. - auto commands = - Formatter::SubstitutionFormatStringUtils::parseFormatters(proto_config.formatters(), context); + auto commands = THROW_OR_RETURN_VALUE( + Formatter::SubstitutionFormatStringUtils::parseFormatters(proto_config.formatters(), context), + std::vector>); + Formatter::FormatterPtr json_formatter = Formatter::SubstitutionFormatStringUtils::createJsonFormatter(proto_config.record(), true, false, false, commands); diff --git a/source/extensions/access_loggers/open_telemetry/config.cc b/source/extensions/access_loggers/open_telemetry/config.cc index 237c5c9fa8..5b9e817f52 100644 --- a/source/extensions/access_loggers/open_telemetry/config.cc +++ b/source/extensions/access_loggers/open_telemetry/config.cc @@ -41,8 +41,9 @@ AccessLogFactory::createAccessLogInstance(const Protobuf::Message& config, const envoy::extensions::access_loggers::open_telemetry::v3::OpenTelemetryAccessLogConfig&>( config, context.messageValidationVisitor()); - auto commands = - Formatter::SubstitutionFormatStringUtils::parseFormatters(proto_config.formatters(), context); + auto commands = THROW_OR_RETURN_VALUE( + Formatter::SubstitutionFormatStringUtils::parseFormatters(proto_config.formatters(), context), + std::vector>); return std::make_shared( std::move(filter), proto_config, context.serverFactoryContext().threadLocal(), diff --git a/source/extensions/common/async_files/BUILD b/source/extensions/common/async_files/BUILD index 8b9a420829..0858edabe4 100644 --- a/source/extensions/common/async_files/BUILD +++ b/source/extensions/common/async_files/BUILD @@ -11,7 +11,6 @@ envoy_extension_package() envoy_cc_library( name = "async_files_base", srcs = [ - "async_file_action.cc", "async_file_context_base.cc", ], hdrs = [ @@ -22,6 +21,7 @@ envoy_cc_library( ], deps = [ ":status_after_file_error", + "//envoy/event:dispatcher_interface", "//source/common/buffer:buffer_lib", "//source/common/common:utility_lib", "@com_google_absl//absl/base", @@ -53,7 +53,6 @@ envoy_cc_library( envoy_cc_library( name = "async_files", srcs = [ - "async_file_manager.cc", "async_file_manager_factory.cc", ], hdrs = [ diff --git a/source/extensions/common/async_files/README.md b/source/extensions/common/async_files/README.md index 822e0827da..ecbd7f42a2 100644 --- a/source/extensions/common/async_files/README.md +++ b/source/extensions/common/async_files/README.md @@ -3,62 +3,26 @@ An `AsyncFileManager` should be a singleton or similarly long-lived scope. It represents a thread pool for performing file operations asynchronously. -`AsyncFileManager` can create `AsyncFileHandle`s via `createAnonymousFile` or `openExistingFile`, -can postpone queuing file actions using `whenReady`, and can delete files via `unlink`. +`AsyncFileManager` can create `AsyncFileHandle`s via `createAnonymousFile` or `openExistingFile`, can stat a file by name with `stat`, and can delete files via `unlink`. # AsyncFileHandle An `AsyncFileHandle` represents a context in which asynchronous file operations can be performed. It is associated with at most one file at a time. -Each action on an AsyncFileHandle is effectively an "enqueue" action, in that it places the action in the manager's execution queue, it does not immediately perform the requested action. Actions on an `AsyncFileHandle` can be *chained*, by enqueuing another action during the callback from a previous action, e.g. - -``` -manager->createAnonymousFile("/tmp", [](absl::StatusOr opened) { - if (!opened.ok()) { - std::cout << "oh no, an error: " << opened.status() << std::endl; - return; - } - auto handle = opened.value(); - handle->write(someBuffer, 0, [handle](absl::StatusOr written) { - if (!written.ok()) { - std::cout << "oh no, an error: " << written.status() << std::endl; - return; - } - std::cout << "wrote " << written.value() << " bytes" << std::endl; - handle->close([](absl::Status closed) { - if (!closed.ok()) { - std::cout << "oh no, an error: " << closed << std::endl; - } - }).IgnoreError(); // A returned error only occurs if the file handle was closed. - }).IgnoreError(); // A returned error only occurs if the file handle was closed. -}); -``` - -Will open an unnamed file, write 5 bytes, and close it. (This is just for explanatory purposes, in practice you would most likely want the callbacks to call something on `this` rather than nesting lambdas!) - -Chaining actions, as opposed to enqueuing, passing the result to a main thread, and from there enqueuing again, will not yield the thread in a thread-pool based implementation. An advantage of this is that, for example, if 5 workers all wanted to write a 100kb file at the same moment, with unchained requests in a one-thread threadpool the sequence would most likely resemble - -``` -OPEN-OPEN-OPEN-OPEN-OPEN-WRITE-WRITE-WRITE-WRITE-WRITE-CLOSE-CLOSE-CLOSE-CLOSE-CLOSE -``` - -Versus with appropriately chained requests in a one-thread threadpool the sequence would be guaranteed to be - -``` -OPEN-WRITE-CLOSE-OPEN-WRITE-CLOSE-OPEN-WRITE-CLOSE-OPEN-WRITE-CLOSE-OPEN-WRITE-CLOSE -``` - -Expand this concept to 100+ files all asking to be written at once and you can immediately see the advantages of chaining; not having the resource issues of many files open at the same time, more localized access, etc. +Each action on an AsyncFileHandle is effectively an "enqueue" action, in that it places the action in the manager's execution queue, it does not immediately perform the requested action. ## cancellation -Each action function returns a cancellation function which can be called to remove an action from the queue and prevent the callback from being called. If the execution is already in progress, it may be undone (e.g. a file open operation will close the file if it is opening when cancel is called). The cancel function will block if the callback is already in progress when cancel is called, until the callback completes. This should not be a long block, as callbacks should be short (see callbacks below). - -As such, a client should ensure that the cleanup order is consistent - if a callback captures a file handle, the client should clean up that file handle (if present) *after* calling cancel, in case the file was opened during the call to cancel. +Each action function returns a cancellation function which can be called to remove an action from the queue and prevent the callback from being called. If the execution is already in progress, it may be undone (e.g. a file open operation will close the file if it is opening when cancel is called). The cancel function must only be called from the same thread as the +dispatcher that was provided to the original request, to ensure that cancellation and callback +cannot be happening concurrently. ## callbacks -The callbacks passed to `AsyncFileHandle` and `AsyncFileManager` are scheduled in a thread or thread pool belonging to the AsyncFileManager - therefore they should be doing minimal work, not blocking (for more than a trivial data-guard lock), and return promptly. If any significant work or blocking is required, the result of the previous action should be passed from the callback to another thread (via some dispatcher or other queuing mechanism) so the manager's thread can continue performing file operations for other clients. +The callbacks passed to `AsyncFileHandle` and `AsyncFileManager` functions are called from +the thread associated with the provided `dispatcher`, if the action was not cancelled first. + +The implementation of `AsyncFileManager` ensures that a `cancel` call from the thread associated with the dispatcher, if called prior to the callback's execution, is guaranteed to prevent the callback from being called, there is no race. ## Possible actions diff --git a/source/extensions/common/async_files/async_file_action.cc b/source/extensions/common/async_files/async_file_action.cc deleted file mode 100644 index 9687039cbe..0000000000 --- a/source/extensions/common/async_files/async_file_action.cc +++ /dev/null @@ -1,26 +0,0 @@ -#include "source/extensions/common/async_files/async_file_action.h" - -#include - -namespace Envoy { -namespace Extensions { -namespace Common { -namespace AsyncFiles { - -void AsyncFileAction::cancel() { - auto previousState = state_.exchange(State::Cancelled); - if (previousState == State::InCallback) { - // A gentle spin-lock. This situation should be rare, and callbacks are - // supposed to be quick, so we don't need a real lock here. - while (state_.load() != State::Done) { - std::this_thread::yield(); - } - } else if (previousState == State::Done) { - state_.store(State::Done); - } -} - -} // namespace AsyncFiles -} // namespace Common -} // namespace Extensions -} // namespace Envoy diff --git a/source/extensions/common/async_files/async_file_action.h b/source/extensions/common/async_files/async_file_action.h index ea8bfed063..6afa7c809f 100644 --- a/source/extensions/common/async_files/async_file_action.h +++ b/source/extensions/common/async_files/async_file_action.h @@ -19,33 +19,28 @@ namespace AsyncFiles { // * If the action is already executing, CancelFunction causes the removal of any resource-consuming // return value (e.g. file handles), and prevents the callback. // * If the action is still just queued, CancelFunction prevents its execution. -using CancelFunction = std::function; +using CancelFunction = absl::AnyInvocable; // Actions to be passed to asyncFileManager->enqueue. class AsyncFileAction { public: virtual ~AsyncFileAction() = default; - // Cancel the action, as much as possible. - // - // If the action has not been started, it will become a no-op. - // - // If the action has started, onCancelledBeforeCallback will be called, - // and the callback will not. - // - // If the callback is already being called, cancel will block until the - // callback has completed. - // - // If the action is already complete, cancel does nothing. - void cancel(); - - // Performs the action represented by this instance, and calls the callback - // on completion or on error. + // Performs the action represented by this instance, and captures the + // result. virtual void execute() PURE; -protected: - enum class State { Queued, Executing, InCallback, Done, Cancelled }; - std::atomic state_{State::Queued}; + // Calls the captured callback with the captured result. + virtual void onComplete() PURE; + + // Performs any action to undo side-effects of the execution if the callback + // has not yet been called (e.g. closing a file that was just opened, removing + // a hard-link that was just created). + // Not necessary for things that don't make persistent resources, + // e.g. cancelling a write does not have to undo the write. + virtual void onCancelledBeforeCallback() {} + virtual bool hasActionIfCancelledBeforeCallback() const { return false; } + virtual bool executesEvenIfCancelled() const { return false; } }; // All concrete AsyncFileActions are a subclass of AsyncFileActionWithResult. @@ -59,38 +54,19 @@ class AsyncFileAction { // the result should be passed to another thread for handling. template class AsyncFileActionWithResult : public AsyncFileAction { public: - explicit AsyncFileActionWithResult(std::function on_complete) - : on_complete_(on_complete) {} + explicit AsyncFileActionWithResult(absl::AnyInvocable on_complete) + : on_complete_(std::move(on_complete)) {} - void execute() final { - State expected = State::Queued; - if (!state_.compare_exchange_strong(expected, State::Executing)) { - ASSERT(expected == State::Cancelled); - return; - } - expected = State::Executing; - T result = executeImpl(); - if (!state_.compare_exchange_strong(expected, State::InCallback)) { - ASSERT(expected == State::Cancelled); - onCancelledBeforeCallback(std::move(result)); - return; - } - on_complete_(std::move(result)); - state_.store(State::Done); - } + void execute() final { result_ = executeImpl(); } + void onComplete() final { std::move(on_complete_)(std::move(result_.value())); } protected: - // Performs any action to undo side-effects of the execution if the callback - // has not yet been called (e.g. closing a file that was just opened). - // Not necessary for things that don't make persistent resources, - // e.g. cancelling a write does not have to undo the write. - virtual void onCancelledBeforeCallback(T){}; - + absl::optional result_; // Implementation of the actual action. virtual T executeImpl() PURE; private: - std::function on_complete_; + absl::AnyInvocable on_complete_; }; } // namespace AsyncFiles diff --git a/source/extensions/common/async_files/async_file_context_base.cc b/source/extensions/common/async_files/async_file_context_base.cc index 20c7fc9948..070d710224 100644 --- a/source/extensions/common/async_files/async_file_context_base.cc +++ b/source/extensions/common/async_files/async_file_context_base.cc @@ -17,8 +17,9 @@ namespace AsyncFiles { AsyncFileContextBase::AsyncFileContextBase(AsyncFileManager& manager) : manager_(manager) {} -CancelFunction AsyncFileContextBase::enqueue(std::shared_ptr action) { - return manager_.enqueue(std::move(action)); +CancelFunction AsyncFileContextBase::enqueue(Event::Dispatcher* dispatcher, + std::unique_ptr action) { + return manager_.enqueue(dispatcher, std::move(action)); } } // namespace AsyncFiles diff --git a/source/extensions/common/async_files/async_file_context_base.h b/source/extensions/common/async_files/async_file_context_base.h index 37614b450d..b01652b62c 100644 --- a/source/extensions/common/async_files/async_file_context_base.h +++ b/source/extensions/common/async_files/async_file_context_base.h @@ -2,6 +2,8 @@ #include +#include "envoy/event/dispatcher.h" + #include "source/extensions/common/async_files/async_file_handle.h" #include "absl/status/statusor.h" @@ -23,7 +25,7 @@ class AsyncFileContextBase : public AsyncFileContext { protected: // Queue up an action with the AsyncFileManager. - CancelFunction enqueue(std::shared_ptr action); + CancelFunction enqueue(Event::Dispatcher* dispatcher, std::unique_ptr action); explicit AsyncFileContextBase(AsyncFileManager& manager); diff --git a/source/extensions/common/async_files/async_file_context_thread_pool.cc b/source/extensions/common/async_files/async_file_context_thread_pool.cc index 43a07ef051..5391559ff9 100644 --- a/source/extensions/common/async_files/async_file_context_thread_pool.cc +++ b/source/extensions/common/async_files/async_file_context_thread_pool.cc @@ -21,8 +21,9 @@ namespace { template class AsyncFileActionThreadPool : public AsyncFileActionWithResult { public: - explicit AsyncFileActionThreadPool(AsyncFileHandle handle, std::function on_complete) - : AsyncFileActionWithResult(on_complete), handle_(std::move(handle)) {} + explicit AsyncFileActionThreadPool(AsyncFileHandle handle, + absl::AnyInvocable on_complete) + : AsyncFileActionWithResult(std::move(on_complete)), handle_(std::move(handle)) {} protected: int& fileDescriptor() { return context()->fileDescriptor(); } @@ -39,8 +40,9 @@ template class AsyncFileActionThreadPool : public AsyncFileActionWi class ActionStat : public AsyncFileActionThreadPool> { public: - ActionStat(AsyncFileHandle handle, std::function)> on_complete) - : AsyncFileActionThreadPool>(handle, on_complete) {} + ActionStat(AsyncFileHandle handle, + absl::AnyInvocable)> on_complete) + : AsyncFileActionThreadPool>(handle, std::move(on_complete)) {} absl::StatusOr executeImpl() override { ASSERT(fileDescriptor() != -1); @@ -56,8 +58,9 @@ class ActionStat : public AsyncFileActionThreadPool> class ActionCreateHardLink : public AsyncFileActionThreadPool { public: ActionCreateHardLink(AsyncFileHandle handle, absl::string_view filename, - std::function on_complete) - : AsyncFileActionThreadPool(handle, on_complete), filename_(filename) {} + absl::AnyInvocable on_complete) + : AsyncFileActionThreadPool(handle, std::move(on_complete)), + filename_(filename) {} absl::Status executeImpl() override { ASSERT(fileDescriptor() != -1); @@ -70,11 +73,12 @@ class ActionCreateHardLink : public AsyncFileActionThreadPool { return absl::OkStatus(); } - void onCancelledBeforeCallback(absl::Status result) override { - if (result.ok()) { + void onCancelledBeforeCallback() override { + if (result_.value().ok()) { posix().unlink(filename_.c_str()); } } + bool hasActionIfCancelledBeforeCallback() const override { return true; } private: const std::string filename_; @@ -85,8 +89,9 @@ class ActionCloseFile : public AsyncFileActionThreadPool { // Here we take a copy of the AsyncFileContext's file descriptor, because the close function // sets the AsyncFileContext's file descriptor to -1. This way there will be no race of trying // to use the handle again while the close is in flight. - explicit ActionCloseFile(AsyncFileHandle handle, std::function on_complete) - : AsyncFileActionThreadPool(handle, on_complete), + explicit ActionCloseFile(AsyncFileHandle handle, + absl::AnyInvocable on_complete) + : AsyncFileActionThreadPool(handle, std::move(on_complete)), file_descriptor_(fileDescriptor()) {} absl::Status executeImpl() override { @@ -97,6 +102,8 @@ class ActionCloseFile : public AsyncFileActionThreadPool { return absl::OkStatus(); } + bool executesEvenIfCancelled() const override { return true; } + private: const int file_descriptor_; }; @@ -104,8 +111,9 @@ class ActionCloseFile : public AsyncFileActionThreadPool { class ActionReadFile : public AsyncFileActionThreadPool> { public: ActionReadFile(AsyncFileHandle handle, off_t offset, size_t length, - std::function)> on_complete) - : AsyncFileActionThreadPool>(handle, on_complete), + absl::AnyInvocable)> on_complete) + : AsyncFileActionThreadPool>(handle, + std::move(on_complete)), offset_(offset), length_(length) {} absl::StatusOr executeImpl() override { @@ -133,8 +141,9 @@ class ActionReadFile : public AsyncFileActionThreadPool> { public: ActionWriteFile(AsyncFileHandle handle, Buffer::Instance& contents, off_t offset, - std::function)> on_complete) - : AsyncFileActionThreadPool>(handle, on_complete), offset_(offset) { + absl::AnyInvocable)> on_complete) + : AsyncFileActionThreadPool>(handle, std::move(on_complete)), + offset_(offset) { contents_.move(contents); } @@ -166,8 +175,9 @@ class ActionWriteFile : public AsyncFileActionThreadPool> class ActionDuplicateFile : public AsyncFileActionThreadPool> { public: ActionDuplicateFile(AsyncFileHandle handle, - std::function)> on_complete) - : AsyncFileActionThreadPool>(handle, on_complete) {} + absl::AnyInvocable)> on_complete) + : AsyncFileActionThreadPool>(handle, std::move(on_complete)) { + } absl::StatusOr executeImpl() override { ASSERT(fileDescriptor() != -1); @@ -178,61 +188,69 @@ class ActionDuplicateFile : public AsyncFileActionThreadPool(context()->manager(), newfd.return_value_); } - void onCancelledBeforeCallback(absl::StatusOr result) override { - if (result.ok()) { - result.value()->close([](absl::Status) {}).IgnoreError(); + void onCancelledBeforeCallback() override { + if (result_.value().ok()) { + result_.value().value()->close(nullptr, [](absl::Status) {}).IgnoreError(); } } + bool hasActionIfCancelledBeforeCallback() const override { return true; } }; } // namespace -absl::StatusOr -AsyncFileContextThreadPool::stat(std::function)> on_complete) { - return checkFileAndEnqueue(std::make_shared(handle(), std::move(on_complete))); +absl::StatusOr AsyncFileContextThreadPool::stat( + Event::Dispatcher* dispatcher, + absl::AnyInvocable)> on_complete) { + return checkFileAndEnqueue(dispatcher, + std::make_unique(handle(), std::move(on_complete))); } absl::StatusOr -AsyncFileContextThreadPool::createHardLink(absl::string_view filename, - std::function on_complete) { - return checkFileAndEnqueue( - std::make_shared(handle(), filename, std::move(on_complete))); +AsyncFileContextThreadPool::createHardLink(Event::Dispatcher* dispatcher, + absl::string_view filename, + absl::AnyInvocable on_complete) { + return checkFileAndEnqueue(dispatcher, std::make_unique( + handle(), filename, std::move(on_complete))); } -absl::Status AsyncFileContextThreadPool::close(std::function on_complete) { - auto status = - checkFileAndEnqueue(std::make_shared(handle(), std::move(on_complete))) - .status(); +absl::StatusOr +AsyncFileContextThreadPool::close(Event::Dispatcher* dispatcher, + absl::AnyInvocable on_complete) { + auto ret = checkFileAndEnqueue( + dispatcher, std::make_unique(handle(), std::move(on_complete))); fileDescriptor() = -1; - return status; + return ret; } absl::StatusOr AsyncFileContextThreadPool::read( - off_t offset, size_t length, - std::function)> on_complete) { - return checkFileAndEnqueue( - std::make_shared(handle(), offset, length, std::move(on_complete))); + Event::Dispatcher* dispatcher, off_t offset, size_t length, + absl::AnyInvocable)> on_complete) { + return checkFileAndEnqueue(dispatcher, std::make_unique(handle(), offset, length, + std::move(on_complete))); } absl::StatusOr -AsyncFileContextThreadPool::write(Buffer::Instance& contents, off_t offset, - std::function)> on_complete) { - return checkFileAndEnqueue( - std::make_shared(handle(), contents, offset, std::move(on_complete))); +AsyncFileContextThreadPool::write(Event::Dispatcher* dispatcher, Buffer::Instance& contents, + off_t offset, + absl::AnyInvocable)> on_complete) { + return checkFileAndEnqueue(dispatcher, std::make_unique( + handle(), contents, offset, std::move(on_complete))); } absl::StatusOr AsyncFileContextThreadPool::duplicate( - std::function)> on_complete) { + Event::Dispatcher* dispatcher, + absl::AnyInvocable)> on_complete) { return checkFileAndEnqueue( - std::make_shared(handle(), std::move(on_complete))); + dispatcher, std::make_unique(handle(), std::move(on_complete))); } absl::StatusOr -AsyncFileContextThreadPool::checkFileAndEnqueue(std::shared_ptr action) { +AsyncFileContextThreadPool::checkFileAndEnqueue(Event::Dispatcher* dispatcher, + std::unique_ptr action) { if (fileDescriptor() == -1) { return absl::FailedPreconditionError("file was already closed"); } - return enqueue(action); + return enqueue(dispatcher, std::move(action)); } AsyncFileContextThreadPool::AsyncFileContextThreadPool(AsyncFileManager& manager, int fd) diff --git a/source/extensions/common/async_files/async_file_context_thread_pool.h b/source/extensions/common/async_files/async_file_context_thread_pool.h index 8c14d75be8..6f344375fc 100644 --- a/source/extensions/common/async_files/async_file_context_thread_pool.h +++ b/source/extensions/common/async_files/async_file_context_thread_pool.h @@ -21,27 +21,35 @@ class AsyncFileContextThreadPool final : public AsyncFileContextBase { public: explicit AsyncFileContextThreadPool(AsyncFileManager& manager, int fd); + // CancelFunction should not be called during or after the callback. + // CancelFunction should only be called from the same thread that created + // the context. + // The callback will be dispatched to the same thread that created the context. absl::StatusOr - stat(std::function)> on_complete) override; + stat(Event::Dispatcher* dispatcher, + absl::AnyInvocable)> on_complete) override; absl::StatusOr - createHardLink(absl::string_view filename, - std::function on_complete) override; - absl::Status close(std::function on_complete) override; + createHardLink(Event::Dispatcher* dispatcher, absl::string_view filename, + absl::AnyInvocable on_complete) override; + absl::StatusOr close(Event::Dispatcher* dispatcher, + absl::AnyInvocable on_complete) override; absl::StatusOr - read(off_t offset, size_t length, - std::function)> on_complete) override; + read(Event::Dispatcher* dispatcher, off_t offset, size_t length, + absl::AnyInvocable)> on_complete) override; absl::StatusOr - write(Buffer::Instance& contents, off_t offset, - std::function)> on_complete) override; + write(Event::Dispatcher* dispatcher, Buffer::Instance& contents, off_t offset, + absl::AnyInvocable)> on_complete) override; absl::StatusOr - duplicate(std::function)> on_complete) override; + duplicate(Event::Dispatcher* dispatcher, + absl::AnyInvocable)> on_complete) override; int& fileDescriptor() { return file_descriptor_; } ~AsyncFileContextThreadPool() override; protected: - absl::StatusOr checkFileAndEnqueue(std::shared_ptr action); + absl::StatusOr checkFileAndEnqueue(Event::Dispatcher* dispatcher, + std::unique_ptr action); int file_descriptor_; }; diff --git a/source/extensions/common/async_files/async_file_handle.h b/source/extensions/common/async_files/async_file_handle.h index a8e54a4166..e52885dc2c 100644 --- a/source/extensions/common/async_files/async_file_handle.h +++ b/source/extensions/common/async_files/async_file_handle.h @@ -3,6 +3,8 @@ #include #include +#include "envoy/event/dispatcher.h" + #include "source/common/buffer/buffer_impl.h" #include "source/extensions/common/async_files/async_file_action.h" @@ -19,14 +21,16 @@ class AsyncFileContext : public std::enable_shared_from_this { public: // Gets a stat struct for the file. virtual absl::StatusOr - stat(std::function)> on_complete) PURE; + stat(Event::Dispatcher* dispatcher, + absl::AnyInvocable)> on_complete) PURE; // Action to hard link the file that is currently open. Typically for use in tandem with // createAnonymousFile to turn that file into a named file after finishing writing its contents. // // If cancelled before the callback is called but after creating the file, unlinks the file. virtual absl::StatusOr - createHardLink(absl::string_view filename, std::function on_complete) PURE; + createHardLink(Event::Dispatcher* dispatcher, absl::string_view filename, + absl::AnyInvocable on_complete) PURE; // Enqueues an action to close the currently open file. // It is an error to use an AsyncFileContext after calling close. @@ -35,18 +39,18 @@ class AsyncFileContext : public std::enable_shared_from_this { // Note that because an AsyncFileHandle is a shared_ptr, it is okay to // call close during the handle's owner's destructor - that will queue the close // event, which will keep the handle alive until after the close operation - // is completed. (But be careful that the on_complete callback doesn't require any - // context from the destroyed owner!) - // close cannot be cancelled. - virtual absl::Status close(std::function on_complete) PURE; + // is completed. + // Cancelling close can abort the callback, but the close action will always complete. + virtual absl::StatusOr + close(Event::Dispatcher* dispatcher, absl::AnyInvocable on_complete) PURE; // Enqueues an action to read from the currently open file, at position offset, up to the number // of bytes specified by length. The size of the buffer passed to on_complete informs you if less // than the requested amount was read. It is an error to read on an AsyncFileContext that does not // have a file open. There must not already be an action queued for this handle. virtual absl::StatusOr - read(off_t offset, size_t length, - std::function)> on_complete) PURE; + read(Event::Dispatcher* dispatcher, off_t offset, size_t length, + absl::AnyInvocable)> on_complete) PURE; // Enqueues an action to write to the currently open file, at position offset, the bytes contained // by contents. It is an error to call write on an AsyncFileContext that does not have a file @@ -58,15 +62,16 @@ class AsyncFileContext : public std::enable_shared_from_this { // on_complete is called with the number of bytes written on success. // There must not already be an action queued for this handle. virtual absl::StatusOr - write(Buffer::Instance& contents, off_t offset, - std::function)> on_complete) PURE; + write(Event::Dispatcher* dispatcher, Buffer::Instance& contents, off_t offset, + absl::AnyInvocable)> on_complete) PURE; // Creates a new AsyncFileHandle referencing the same file. // Note that a file handle duplicated in this way shares positioning and permissions // with the original. Since AsyncFileContext functions are all position-explicit, this should not // matter. virtual absl::StatusOr duplicate( - std::function>)> on_complete) PURE; + Event::Dispatcher* dispatcher, + absl::AnyInvocable>)> on_complete) PURE; protected: virtual ~AsyncFileContext() = default; diff --git a/source/extensions/common/async_files/async_file_manager.cc b/source/extensions/common/async_files/async_file_manager.cc deleted file mode 100644 index 2de25a67dc..0000000000 --- a/source/extensions/common/async_files/async_file_manager.cc +++ /dev/null @@ -1,31 +0,0 @@ -#include "source/extensions/common/async_files/async_file_manager.h" - -#include -#include - -#include "source/common/common/macros.h" -#include "source/extensions/common/async_files/async_file_action.h" - -namespace Envoy { -namespace Extensions { -namespace Common { -namespace AsyncFiles { - -namespace { -class ActionWhenReady : public AsyncFileActionWithResult { -public: - explicit ActionWhenReady(std::function on_complete) - : AsyncFileActionWithResult(on_complete) {} - - absl::Status executeImpl() override { return absl::OkStatus(); } -}; -} // namespace - -CancelFunction AsyncFileManager::whenReady(std::function on_complete) { - return enqueue(std::make_shared(std::move(on_complete))); -} - -} // namespace AsyncFiles -} // namespace Common -} // namespace Extensions -} // namespace Envoy diff --git a/source/extensions/common/async_files/async_file_manager.h b/source/extensions/common/async_files/async_file_manager.h index d515d49c63..c9f0c2c250 100644 --- a/source/extensions/common/async_files/async_file_manager.h +++ b/source/extensions/common/async_files/async_file_manager.h @@ -2,6 +2,8 @@ #include +#include "envoy/event/dispatcher.h" + #include "source/extensions/common/async_files/async_file_action.h" #include "source/extensions/common/async_files/async_file_handle.h" @@ -34,8 +36,8 @@ class AsyncFileManager { // Returns a cancellation function, which aborts the operation (and closes // the file if opened) unless the callback has already been called. virtual CancelFunction - createAnonymousFile(absl::string_view path, - std::function)> on_complete) PURE; + createAnonymousFile(Event::Dispatcher* dispatcher, absl::string_view path, + absl::AnyInvocable)> on_complete) PURE; // A mode for opening existing files. enum class Mode { ReadOnly, WriteOnly, ReadWrite }; @@ -46,53 +48,56 @@ class AsyncFileManager { // Returns a cancellation function, which aborts the operation (and closes // the file if opened) unless the callback has already been called. virtual CancelFunction - openExistingFile(absl::string_view filename, Mode mode, - std::function)> on_complete) PURE; + openExistingFile(Event::Dispatcher* dispatcher, absl::string_view filename, Mode mode, + absl::AnyInvocable)> on_complete) PURE; // Action to stat a file. // on_complete receives a stat structure on success, or an error on failure. // // Returns a cancellation function, which aborts the operation // unless the callback has already been called. - virtual CancelFunction stat(absl::string_view filename, - std::function)> on_complete) PURE; + virtual CancelFunction + stat(Event::Dispatcher* dispatcher, absl::string_view filename, + absl::AnyInvocable)> on_complete) PURE; // Action to delete a named file. // on_complete receives OK on success, or an error on failure. // // Returns a cancellation function, which aborts the operation // unless it has already been performed. - virtual CancelFunction unlink(absl::string_view filename, - std::function on_complete) PURE; - - // whenReady can be used to only perform an action when the caller hits the - // front of the thread pool's queue - this can be used to defer requesting - // a file action until it could actually take place. For example, if you're - // offloading data from memory to disk temporarily, if you queue the write - // immediately then the filesystem thread owns the data until the write - // completes, which may be blocked by heavy traffic, and it turns out you - // want the data back before then - you can't get it back, you have to wait - // for the write to complete and then read it back. - // - // If you used whenReady, you could keep the data belonging to the client - // until it's actually the client's turn to do disk access. When whenReady's - // callback is called, if you request the write at that time the performance - // will be almost identical to if you had requested the write earlier, but - // you have the opportunity to change your mind and do something different - // in the meantime. - // - // The cost of using whenReady is that it requires the client to be lock - // controlled (since the callback occurs in a different thread than the thread - // the state belongs to), versus simpler unchained operations can use queue - // based actions and not worry about ownership. - CancelFunction whenReady(std::function on_complete); + virtual CancelFunction unlink(Event::Dispatcher* dispatcher, absl::string_view filename, + absl::AnyInvocable on_complete) PURE; // Return a string description of the configuration of the manager. // (This is mostly to facilitate testing.) virtual std::string describe() const PURE; + // To facilitate testing, blocks until all queued actions have been performed and + // callbacks posted. + // This does not guarantee that the callbacks have been executed, only that they + // have been sent to the dispatcher. + virtual void waitForIdle() PURE; + +protected: + class QueuedAction { + public: + QueuedAction(std::unique_ptr action, Event::Dispatcher* dispatcher) + : action_(std::move(action)), dispatcher_(dispatcher), + state_(std::make_shared>(State::Queued)) {} + QueuedAction() = default; + std::unique_ptr action_; + Event::Dispatcher* dispatcher_ = nullptr; + enum class State { Queued, Executing, InCallback, Done, Cancelled }; + std::shared_ptr> state_; + }; + private: - virtual CancelFunction enqueue(const std::shared_ptr context) PURE; + // Puts an action in the queue for execution. + virtual CancelFunction enqueue(Event::Dispatcher* dispatcher, + std::unique_ptr action) PURE; + // Puts an action in the queue for its `onCancelledBeforeCallback` function to be + // called. + virtual void postCancelledActionForCleanup(std::unique_ptr action) PURE; friend class AsyncFileContextBase; friend class AsyncFileManagerTest; diff --git a/source/extensions/common/async_files/async_file_manager_thread_pool.cc b/source/extensions/common/async_files/async_file_manager_thread_pool.cc index 1dfd7b84e4..dd6c0a3a86 100644 --- a/source/extensions/common/async_files/async_file_manager_thread_pool.cc +++ b/source/extensions/common/async_files/async_file_manager_thread_pool.cc @@ -15,17 +15,6 @@ namespace Extensions { namespace Common { namespace AsyncFiles { -namespace { -// ThreadNextAction is per worker thread; if enqueue is called from a callback -// the action goes directly into ThreadNextAction, otherwise it goes into the -// queue and is eventually pulled out into ThreadNextAction by a worker thread. -thread_local std::shared_ptr ThreadNextAction; - -// ThreadIsWorker is set to true for worker threads, and will be false -// for all other threads. -thread_local bool ThreadIsWorker = false; -} // namespace - AsyncFileManagerThreadPool::AsyncFileManagerThreadPool( const envoy::extensions::common::async_files::v3::AsyncFileManagerConfig& config, Api::OsSysCalls& posix) @@ -50,6 +39,7 @@ AsyncFileManagerThreadPool::~AsyncFileManagerThreadPool() ABSL_LOCKS_EXCLUDED(qu absl::MutexLock lock(&queue_mutex_); terminate_ = true; } + // This destructor will be blocked by this loop until all queued file actions are complete. while (!thread_pool_.empty()) { thread_pool_.back().join(); thread_pool_.pop_back(); @@ -60,46 +50,123 @@ std::string AsyncFileManagerThreadPool::describe() const { return absl::StrCat("thread_pool_size = ", thread_pool_.size()); } -std::function AsyncFileManagerThreadPool::enqueue(std::shared_ptr action) { - auto cancel_func = [action]() { action->cancel(); }; - // If an action is being enqueued from within a callback, we don't have to actually queue it, - // we can just set it as the thread's next action - this acts to chain the actions without - // yielding to another file. - if (ThreadIsWorker) { - ASSERT(!ThreadNextAction); // only do one file action per callback. - ThreadNextAction = std::move(action); - return cancel_func; - } +void AsyncFileManagerThreadPool::waitForIdle() { + const auto condition = [this]() ABSL_EXCLUSIVE_LOCKS_REQUIRED(queue_mutex_) { + return active_workers_ == 0 && queue_.empty() && cleanup_queue_.empty(); + }; absl::MutexLock lock(&queue_mutex_); - queue_.push(std::move(action)); + queue_mutex_.Await(absl::Condition(&condition)); +} + +absl::AnyInvocable +AsyncFileManagerThreadPool::enqueue(Event::Dispatcher* dispatcher, + std::unique_ptr action) { + QueuedAction entry{std::move(action), dispatcher}; + auto cancel_func = [dispatcher, state = entry.state_]() { + ASSERT(dispatcher == nullptr || dispatcher->isThreadSafe()); + state->store(QueuedAction::State::Cancelled); + }; + absl::MutexLock lock(&queue_mutex_); + queue_.push(std::move(entry)); return cancel_func; } +void AsyncFileManagerThreadPool::postCancelledActionForCleanup( + std::unique_ptr action) { + absl::MutexLock lock(&queue_mutex_); + cleanup_queue_.push(std::move(action)); +} + +void AsyncFileManagerThreadPool::executeAction(QueuedAction&& queued_action) { + using State = QueuedAction::State; + State expected = State::Queued; + std::shared_ptr> state = std::move(queued_action.state_); + std::unique_ptr action = std::move(queued_action.action_); + if (!state->compare_exchange_strong(expected, State::Executing)) { + ASSERT(expected == State::Cancelled); + if (action->executesEvenIfCancelled()) { + action->execute(); + } + return; + } + action->execute(); + expected = State::Executing; + if (!state->compare_exchange_strong(expected, State::InCallback)) { + ASSERT(expected == State::Cancelled); + action->onCancelledBeforeCallback(); + return; + } + if (queued_action.dispatcher_ == nullptr) { + // No need to bother arranging the callback, because a dispatcher was not provided. + return; + } + // If it is necessary to explicitly undo an action on cancel then the lambda will need a + // pointer to this manager that is guaranteed to outlive the lambda, in order to be able + // to perform that cancel operation on a thread belonging to the file manager. + // So capture a shared_ptr if necessary, but, to avoid unnecessary shared_ptr wrangling, + // leave it empty if the action doesn't have an associated cancel operation. + std::shared_ptr manager; + if (action->hasActionIfCancelledBeforeCallback()) { + manager = shared_from_this(); + } + queued_action.dispatcher_->post([manager = std::move(manager), action = std::move(action), + state = std::move(state)]() mutable { + // This callback runs on the caller's thread. + State expected = State::InCallback; + if (state->compare_exchange_strong(expected, State::Done)) { + // Action was not cancelled; run the captured callback on the caller's thread. + action->onComplete(); + return; + } + ASSERT(expected == State::Cancelled); + if (manager == nullptr) { + // Action had a "do nothing" cancellation so we don't need to post a cleanup action. + return; + } + // If an action with side-effects was cancelled after being posted, its + // side-effects need to be undone as the caller can no longer receive the + // returned context. That undo action will need to be done on one of the + // file manager's threads, as it is file related, so post it to the thread pool. + manager->postCancelledActionForCleanup(std::move(action)); + }); +} + void AsyncFileManagerThreadPool::worker() { - ThreadIsWorker = true; + const auto condition = [this]() ABSL_EXCLUSIVE_LOCKS_REQUIRED(queue_mutex_) { + return !queue_.empty() || !cleanup_queue_.empty() || terminate_; + }; + { + absl::MutexLock lock(&queue_mutex_); + active_workers_++; + } while (true) { - const auto condition = [this]() ABSL_EXCLUSIVE_LOCKS_REQUIRED(queue_mutex_) { - return !queue_.empty() || terminate_; - }; + QueuedAction action; + std::unique_ptr cleanup_action; { absl::MutexLock lock(&queue_mutex_); + active_workers_--; queue_mutex_.Await(absl::Condition(&condition)); - if (terminate_) { + if (terminate_ && queue_.empty() && cleanup_queue_.empty()) { return; } - ThreadNextAction = std::move(queue_.front()); - queue_.pop(); + active_workers_++; + if (!queue_.empty()) { + action = std::move(queue_.front()); + queue_.pop(); + } + if (!cleanup_queue_.empty()) { + cleanup_action = std::move(cleanup_queue_.front()); + cleanup_queue_.pop(); + } + } + if (action.action_ != nullptr) { + executeAction(std::move(action)); + action.action_ = nullptr; + } + if (cleanup_action != nullptr) { + std::move(cleanup_action)->onCancelledBeforeCallback(); + cleanup_action = nullptr; } - resolveActions(); - } -} - -void AsyncFileManagerThreadPool::resolveActions() { - while (ThreadNextAction) { - // Move the action out of ThreadNextAction so that its callback can enqueue - // a different ThreadNextAction without self-destructing. - std::shared_ptr action = std::move(ThreadNextAction); - action->execute(); } } @@ -108,15 +175,17 @@ namespace { class ActionWithFileResult : public AsyncFileActionWithResult> { public: ActionWithFileResult(AsyncFileManagerThreadPool& manager, - std::function)> on_complete) - : AsyncFileActionWithResult(on_complete), manager_(manager) {} + absl::AnyInvocable)> on_complete) + : AsyncFileActionWithResult(std::move(on_complete)), manager_(manager) {} protected: - void onCancelledBeforeCallback(absl::StatusOr result) override { - if (result.ok()) { - result.value()->close([](absl::Status) {}).IgnoreError(); + void onCancelledBeforeCallback() override { + if (result_.value().ok()) { + result_.value().value()->close(nullptr, [](absl::Status) {}).IgnoreError(); } } + bool hasActionIfCancelledBeforeCallback() const override { return true; } + AsyncFileManagerThreadPool& manager_; Api::OsSysCalls& posix() { return manager_.posix(); } }; @@ -124,8 +193,8 @@ class ActionWithFileResult : public AsyncFileActionWithResult)> on_complete) - : ActionWithFileResult(manager, on_complete), path_(path) {} + absl::AnyInvocable)> on_complete) + : ActionWithFileResult(manager, std::move(on_complete)), path_(path) {} absl::StatusOr executeImpl() override { Api::SysCallIntResult open_result; @@ -187,8 +256,8 @@ class ActionOpenExistingFile : public ActionWithFileResult { public: ActionOpenExistingFile(AsyncFileManagerThreadPool& manager, absl::string_view filename, AsyncFileManager::Mode mode, - std::function)> on_complete) - : ActionWithFileResult(manager, on_complete), filename_(filename), mode_(mode) {} + absl::AnyInvocable)> on_complete) + : ActionWithFileResult(manager, std::move(on_complete)), filename_(filename), mode_(mode) {} absl::StatusOr executeImpl() override { auto open_result = posix().open(filename_.c_str(), openFlags()); @@ -217,8 +286,8 @@ class ActionOpenExistingFile : public ActionWithFileResult { class ActionStat : public AsyncFileActionWithResult> { public: ActionStat(Api::OsSysCalls& posix, absl::string_view filename, - std::function)> on_complete) - : AsyncFileActionWithResult(on_complete), posix_(posix), filename_(filename) {} + absl::AnyInvocable)> on_complete) + : AsyncFileActionWithResult(std::move(on_complete)), posix_(posix), filename_(filename) {} absl::StatusOr executeImpl() override { struct stat ret; @@ -237,8 +306,8 @@ class ActionStat : public AsyncFileActionWithResult> class ActionUnlink : public AsyncFileActionWithResult { public: ActionUnlink(Api::OsSysCalls& posix, absl::string_view filename, - std::function on_complete) - : AsyncFileActionWithResult(on_complete), posix_(posix), filename_(filename) {} + absl::AnyInvocable on_complete) + : AsyncFileActionWithResult(std::move(on_complete)), posix_(posix), filename_(filename) {} absl::Status executeImpl() override { Api::SysCallIntResult unlink_result = posix_.unlink(filename_.c_str()); @@ -256,25 +325,31 @@ class ActionUnlink : public AsyncFileActionWithResult { } // namespace CancelFunction AsyncFileManagerThreadPool::createAnonymousFile( - absl::string_view path, std::function)> on_complete) { - return enqueue(std::make_shared(*this, path, on_complete)); + Event::Dispatcher* dispatcher, absl::string_view path, + absl::AnyInvocable)> on_complete) { + return enqueue(dispatcher, + std::make_unique(*this, path, std::move(on_complete))); } CancelFunction AsyncFileManagerThreadPool::openExistingFile( - absl::string_view filename, Mode mode, - std::function)> on_complete) { - return enqueue(std::make_shared(*this, filename, mode, on_complete)); + Event::Dispatcher* dispatcher, absl::string_view filename, Mode mode, + absl::AnyInvocable)> on_complete) { + return enqueue(dispatcher, std::make_unique(*this, filename, mode, + std::move(on_complete))); } -CancelFunction -AsyncFileManagerThreadPool::stat(absl::string_view filename, - std::function)> on_complete) { - return enqueue(std::make_shared(posix(), filename, on_complete)); +CancelFunction AsyncFileManagerThreadPool::stat( + Event::Dispatcher* dispatcher, absl::string_view filename, + absl::AnyInvocable)> on_complete) { + return enqueue(dispatcher, + std::make_unique(posix(), filename, std::move(on_complete))); } -CancelFunction AsyncFileManagerThreadPool::unlink(absl::string_view filename, - std::function on_complete) { - return enqueue(std::make_shared(posix(), filename, on_complete)); +CancelFunction +AsyncFileManagerThreadPool::unlink(Event::Dispatcher* dispatcher, absl::string_view filename, + absl::AnyInvocable on_complete) { + return enqueue(dispatcher, + std::make_unique(posix(), filename, std::move(on_complete))); } } // namespace AsyncFiles diff --git a/source/extensions/common/async_files/async_file_manager_thread_pool.h b/source/extensions/common/async_files/async_file_manager_thread_pool.h index 2a82202384..81c927eda7 100644 --- a/source/extensions/common/async_files/async_file_manager_thread_pool.h +++ b/source/extensions/common/async_files/async_file_manager_thread_pool.h @@ -24,23 +24,25 @@ namespace AsyncFiles { // performed the previous action in the chain immediately performs the newly chained // action. class AsyncFileManagerThreadPool : public AsyncFileManager, + public std::enable_shared_from_this, protected Logger::Loggable { public: explicit AsyncFileManagerThreadPool( const envoy::extensions::common::async_files::v3::AsyncFileManagerConfig& config, Api::OsSysCalls& posix); ~AsyncFileManagerThreadPool() ABSL_LOCKS_EXCLUDED(queue_mutex_) override; + CancelFunction createAnonymousFile( + Event::Dispatcher* dispatcher, absl::string_view path, + absl::AnyInvocable)> on_complete) override; CancelFunction - createAnonymousFile(absl::string_view path, - std::function)> on_complete) override; - CancelFunction - openExistingFile(absl::string_view filename, Mode mode, - std::function)> on_complete) override; - CancelFunction stat(absl::string_view filename, - std::function)> on_complete) override; - CancelFunction unlink(absl::string_view filename, - std::function on_complete) override; + openExistingFile(Event::Dispatcher* dispatcher, absl::string_view filename, Mode mode, + absl::AnyInvocable)> on_complete) override; + CancelFunction stat(Event::Dispatcher* dispatcher, absl::string_view filename, + absl::AnyInvocable)> on_complete) override; + CancelFunction unlink(Event::Dispatcher* dispatcher, absl::string_view filename, + absl::AnyInvocable on_complete) override; std::string describe() const override; + void waitForIdle() override; Api::OsSysCalls& posix() const { return posix_; } #ifdef O_TMPFILE @@ -56,13 +58,18 @@ class AsyncFileManagerThreadPool : public AsyncFileManager, #endif // O_TMPFILE private: - std::function enqueue(std::shared_ptr action) + absl::AnyInvocable enqueue(Event::Dispatcher* dispatcher, + std::unique_ptr action) + ABSL_LOCKS_EXCLUDED(queue_mutex_) override; + void postCancelledActionForCleanup(std::unique_ptr action) ABSL_LOCKS_EXCLUDED(queue_mutex_) override; void worker() ABSL_LOCKS_EXCLUDED(queue_mutex_); - void resolveActions(); absl::Mutex queue_mutex_; - std::queue> queue_ ABSL_GUARDED_BY(queue_mutex_); + void executeAction(QueuedAction&& action); + std::queue queue_ ABSL_GUARDED_BY(queue_mutex_); + int active_workers_ ABSL_GUARDED_BY(queue_mutex_) = 0; + std::queue> cleanup_queue_ ABSL_GUARDED_BY(queue_mutex_); bool terminate_ ABSL_GUARDED_BY(queue_mutex_) = false; std::vector thread_pool_; diff --git a/source/extensions/filters/common/set_filter_state/filter_config.cc b/source/extensions/filters/common/set_filter_state/filter_config.cc index 5188b097af..a14e672e89 100644 --- a/source/extensions/filters/common/set_filter_state/filter_config.cc +++ b/source/extensions/filters/common/set_filter_state/filter_config.cc @@ -50,8 +50,10 @@ Config::parse(const Protobuf::RepeatedPtrField& proto_val break; } value.skip_if_empty_ = proto_value.skip_if_empty(); - value.value_ = Formatter::SubstitutionFormatStringUtils::fromProtoConfig( - proto_value.format_string(), context); + value.value_ = + THROW_OR_RETURN_VALUE(Formatter::SubstitutionFormatStringUtils::fromProtoConfig( + proto_value.format_string(), context), + Formatter::FormatterBasePtr); values.push_back(std::move(value)); } return values; diff --git a/source/extensions/filters/http/cors/cors_filter.cc b/source/extensions/filters/http/cors/cors_filter.cc index 98ecb2a17c..49fba6d4a5 100644 --- a/source/extensions/filters/http/cors/cors_filter.cc +++ b/source/extensions/filters/http/cors/cors_filter.cc @@ -66,10 +66,17 @@ void CorsFilter::initializeCorsPolicies() { // If no cors policy is configured in the per filter config, then the cors policy fields in the // route configuration will be ignored. if (policies_.empty()) { - policies_ = { - decoder_callbacks_->route()->routeEntry()->corsPolicy(), - decoder_callbacks_->route()->virtualHost().corsPolicy(), - }; + const auto route = decoder_callbacks_->route(); + ASSERT(route != nullptr); + ASSERT(route->routeEntry() != nullptr); + + if (auto* typed_cfg = route->routeEntry()->corsPolicy(); typed_cfg != nullptr) { + policies_.push_back(*typed_cfg); + } + + if (auto* typed_cfg = route->virtualHost().corsPolicy(); typed_cfg != nullptr) { + policies_.push_back(*typed_cfg); + } } } @@ -204,11 +211,7 @@ void CorsFilter::setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& c } bool CorsFilter::isOriginAllowed(const Http::HeaderString& origin) { - const auto allow_origins = allowOrigins(); - if (allow_origins == nullptr) { - return false; - } - for (const auto& allow_origin : *allow_origins) { + for (const auto& allow_origin : allowOrigins()) { if (allow_origin->match("*") || allow_origin->match(origin.getStringView())) { return true; } @@ -216,92 +219,88 @@ bool CorsFilter::isOriginAllowed(const Http::HeaderString& origin) { return false; } -const std::vector* CorsFilter::allowOrigins() { - for (const auto policy : policies_) { - if (policy && !policy->allowOrigins().empty()) { - return &policy->allowOrigins(); +absl::Span CorsFilter::allowOrigins() { + for (const Router::CorsPolicy& policy : policies_) { + if (!policy.allowOrigins().empty()) { + return policy.allowOrigins(); } } - return nullptr; + return {}; } bool CorsFilter::forwardNotMatchingPreflights() { - for (const auto policy : policies_) { - if (policy && policy->forwardNotMatchingPreflights()) { - return policy->forwardNotMatchingPreflights().value(); + for (const Router::CorsPolicy& policy : policies_) { + if (policy.forwardNotMatchingPreflights()) { + return policy.forwardNotMatchingPreflights().value(); } } return true; } -const std::string& CorsFilter::allowMethods() { - for (const auto policy : policies_) { - if (policy && !policy->allowMethods().empty()) { - return policy->allowMethods(); +absl::string_view CorsFilter::allowMethods() { + for (const Router::CorsPolicy& policy : policies_) { + if (!policy.allowMethods().empty()) { + return policy.allowMethods(); } } return EMPTY_STRING; } -const std::string& CorsFilter::allowHeaders() { - for (const auto policy : policies_) { - if (policy && !policy->allowHeaders().empty()) { - return policy->allowHeaders(); +absl::string_view CorsFilter::allowHeaders() { + for (const Router::CorsPolicy& policy : policies_) { + if (!policy.allowHeaders().empty()) { + return policy.allowHeaders(); } } return EMPTY_STRING; } -const std::string& CorsFilter::exposeHeaders() { - for (const auto policy : policies_) { - if (policy && !policy->exposeHeaders().empty()) { - return policy->exposeHeaders(); +absl::string_view CorsFilter::exposeHeaders() { + for (const Router::CorsPolicy& policy : policies_) { + if (!policy.exposeHeaders().empty()) { + return policy.exposeHeaders(); } } return EMPTY_STRING; } -const std::string& CorsFilter::maxAge() { - for (const auto policy : policies_) { - if (policy && !policy->maxAge().empty()) { - return policy->maxAge(); +absl::string_view CorsFilter::maxAge() { + for (const Router::CorsPolicy& policy : policies_) { + if (!policy.maxAge().empty()) { + return policy.maxAge(); } } return EMPTY_STRING; } bool CorsFilter::allowCredentials() { - for (const auto policy : policies_) { - if (policy && policy->allowCredentials()) { - return policy->allowCredentials().value(); + for (const Router::CorsPolicy& policy : policies_) { + if (policy.allowCredentials()) { + return policy.allowCredentials().value(); } } return false; } bool CorsFilter::allowPrivateNetworkAccess() { - for (const auto policy : policies_) { - if (policy && policy->allowPrivateNetworkAccess()) { - return policy->allowPrivateNetworkAccess().value(); + for (const Router::CorsPolicy& policy : policies_) { + if (policy.allowPrivateNetworkAccess()) { + return policy.allowPrivateNetworkAccess().value(); } } return false; } bool CorsFilter::shadowEnabled() { - for (const auto policy : policies_) { - if (policy) { - return policy->shadowEnabled(); - } + for (const Router::CorsPolicy& policy : policies_) { + return policy.shadowEnabled(); } return false; } bool CorsFilter::enabled() { - for (const auto policy : policies_) { - if (policy) { - return policy->enabled(); - } + for (const Router::CorsPolicy& policy : policies_) { + return policy.enabled(); } return false; } diff --git a/source/extensions/filters/http/cors/cors_filter.h b/source/extensions/filters/http/cors/cors_filter.h index 0c8cf535a9..a89a488b3f 100644 --- a/source/extensions/filters/http/cors/cors_filter.h +++ b/source/extensions/filters/http/cors/cors_filter.h @@ -88,11 +88,11 @@ class CorsFilter : public Http::StreamFilter { private: friend class CorsFilterTest; - const std::vector* allowOrigins(); - const std::string& allowMethods(); - const std::string& allowHeaders(); - const std::string& exposeHeaders(); - const std::string& maxAge(); + absl::Span allowOrigins(); + absl::string_view allowMethods(); + absl::string_view allowHeaders(); + absl::string_view exposeHeaders(); + absl::string_view maxAge(); bool allowCredentials(); bool allowPrivateNetworkAccess(); bool shadowEnabled(); @@ -102,7 +102,7 @@ class CorsFilter : public Http::StreamFilter { Http::StreamDecoderFilterCallbacks* decoder_callbacks_{}; Http::StreamEncoderFilterCallbacks* encoder_callbacks_{}; - absl::InlinedVector policies_; + absl::InlinedVector, 4> policies_; bool is_cors_request_{}; std::string latched_origin_; diff --git a/source/extensions/filters/http/custom_response/custom_response_filter.cc b/source/extensions/filters/http/custom_response/custom_response_filter.cc index d133c8b753..52e5aefe3e 100644 --- a/source/extensions/filters/http/custom_response/custom_response_filter.cc +++ b/source/extensions/filters/http/custom_response/custom_response_filter.cc @@ -48,13 +48,12 @@ Http::FilterHeadersStatus CustomResponseFilter::encodeHeaders(Http::ResponseHead // policy. Note that since the traversal is least to most specific, we can't // return early when a match is found. PolicySharedPtr policy; - for (const auto* typed_config : + for (const FilterConfig& typed_config : Http::Utility::getAllPerFilterConfig(encoder_callbacks_)) { - ASSERT(typed_config != nullptr); // Check if a match is found first to avoid overwriting policy with an // empty shared_ptr. - auto maybe_policy = typed_config->getPolicy(headers, encoder_callbacks_->streamInfo()); + auto maybe_policy = typed_config.getPolicy(headers, encoder_callbacks_->streamInfo()); if (maybe_policy) { policy = maybe_policy; } diff --git a/source/extensions/filters/http/ext_authz/ext_authz.cc b/source/extensions/filters/http/ext_authz/ext_authz.cc index b1713614ca..962aede2c2 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.cc +++ b/source/extensions/filters/http/ext_authz/ext_authz.cc @@ -192,13 +192,12 @@ void Filter::initiateCall(const Http::RequestHeaderMap& headers) { } absl::optional maybe_merged_per_route_config; - for (const auto* cfg : + for (const FilterConfigPerRoute& cfg : Http::Utility::getAllPerFilterConfig(decoder_callbacks_)) { - ASSERT(cfg != nullptr); if (maybe_merged_per_route_config.has_value()) { - maybe_merged_per_route_config.value().merge(*cfg); + maybe_merged_per_route_config.value().merge(cfg); } else { - maybe_merged_per_route_config = *cfg; + maybe_merged_per_route_config = cfg; } } diff --git a/source/extensions/filters/http/ext_proc/ext_proc.cc b/source/extensions/filters/http/ext_proc/ext_proc.cc index 8af46dc4da..3013ea1d82 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.cc +++ b/source/extensions/filters/http/ext_proc/ext_proc.cc @@ -1279,13 +1279,12 @@ void Filter::mergePerRouteConfig() { route_config_merged_ = true; absl::optional merged_config; - for (const auto* typed_cfg : + for (const FilterConfigPerRoute& typed_cfg : Http::Utility::getAllPerFilterConfig(decoder_callbacks_)) { - ASSERT(typed_cfg != nullptr); if (!merged_config.has_value()) { - merged_config.emplace(*typed_cfg); + merged_config.emplace(typed_cfg); } else { - merged_config.emplace(FilterConfigPerRoute(merged_config.value(), *typed_cfg)); + merged_config.emplace(FilterConfigPerRoute(merged_config.value(), typed_cfg)); } } diff --git a/source/extensions/filters/http/file_system_buffer/filter.cc b/source/extensions/filters/http/file_system_buffer/filter.cc index 56764975b0..fbb4420a7d 100644 --- a/source/extensions/filters/http/file_system_buffer/filter.cc +++ b/source/extensions/filters/http/file_system_buffer/filter.cc @@ -283,7 +283,7 @@ bool FileSystemBufferFilter::maybeOutputResponse() { void BufferedStreamState::close() { if (async_file_handle_) { - auto queued = async_file_handle_->close([](absl::Status) {}); + auto queued = async_file_handle_->close(nullptr, [](absl::Status) {}); ASSERT(queued.ok()); } } @@ -375,11 +375,11 @@ bool FileSystemBufferFilter::maybeStorage(BufferedStreamState& state, state.storage_used_ -= size; state.memory_used_ += size; ENVOY_STREAM_LOG(debug, "retrieving buffer fragment (size={}) from storage", callbacks, size); - auto queued = - (**earliest_storage_fragment) - .fromStorage(state.async_file_handle_, getSafeDispatch(), getOnFileActionCompleted()); + auto queued = (**earliest_storage_fragment) + .fromStorage(state.async_file_handle_, request_callbacks_->dispatcher(), + getOnFileActionCompleted()); ASSERT(queued.ok()); - cancel_in_flight_async_action_ = queued.value(); + cancel_in_flight_async_action_ = std::move(queued.value()); return true; } } @@ -389,30 +389,19 @@ bool FileSystemBufferFilter::maybeStorage(BufferedStreamState& state, if (!state.async_file_handle_) { // File isn't open yet - open it and then check again if we still need to store data. ENVOY_STREAM_LOG(debug, "memory buffer exceeded - creating buffer file", callbacks); - // We can't use getSafeDispatcher here because we need to close the file if the filter - // was deleted before the callback, not just do nothing. + // The callback won't be called if the filter was destroyed, and if the file was + // racily created it will be closed. cancel_in_flight_async_action_ = config_->asyncFileManager().createAnonymousFile( - config_->storageBufferPath(), - [this, is_destroyed = is_destroyed_, dispatcher = &request_callbacks_->dispatcher(), - &state](absl::StatusOr file_handle) { - dispatcher->post([this, is_destroyed, &state, file_handle]() { - if (*is_destroyed) { - // If we opened a file but the filter went away in the meantime, close the file - // to avoid leaving a dangling file handle. - if (file_handle.ok()) { - file_handle.value()->close([](absl::Status) {}).IgnoreError(); - } - return; - } - if (!file_handle.ok()) { - filterError(fmt::format("{} failed to create buffer file: {}", filterName(), - file_handle.status().ToString())); - return; - } - state.async_file_handle_ = std::move(file_handle.value()); - cancel_in_flight_async_action_ = nullptr; - onStateChange(); - }); + &request_callbacks_->dispatcher(), config_->storageBufferPath(), + [this, &state](absl::StatusOr file_handle) { + if (!file_handle.ok()) { + filterError(fmt::format("{} failed to create buffer file: {}", filterName(), + file_handle.status().ToString())); + return; + } + state.async_file_handle_ = std::move(file_handle.value()); + cancel_in_flight_async_action_ = nullptr; + onStateChange(); }); return true; } @@ -424,18 +413,19 @@ bool FileSystemBufferFilter::maybeStorage(BufferedStreamState& state, state.storage_consumed_ += size; state.memory_used_ -= size; ENVOY_STREAM_LOG(debug, "sending buffer fragment (size={}) to storage", callbacks, size); - auto to_storage = fragment->toStorage(state.async_file_handle_, state.storage_offset_, - getSafeDispatch(), getOnFileActionCompleted()); + auto to_storage = + fragment->toStorage(state.async_file_handle_, state.storage_offset_, + request_callbacks_->dispatcher(), getOnFileActionCompleted()); ASSERT(to_storage.ok()); - cancel_in_flight_async_action_ = to_storage.value(); + cancel_in_flight_async_action_ = std::move(to_storage.value()); state.storage_offset_ += size; return true; } return false; } -std::function FileSystemBufferFilter::getOnFileActionCompleted() { - // This callback is only run via getSafeDispatch, so is safe to capture 'this' - +absl::AnyInvocable FileSystemBufferFilter::getOnFileActionCompleted() { + // This callback is aborted in onDestroy, so is safe to capture 'this' - // it won't be called if the filter has been deleted. return [this](absl::Status status) { cancel_in_flight_async_action_ = nullptr; @@ -447,19 +437,17 @@ std::function FileSystemBufferFilter::getOnFileActionComplet }; } -std::function)> FileSystemBufferFilter::getSafeDispatch() { - return [is_destroyed = is_destroyed_, - dispatcher = &request_callbacks_->dispatcher()](std::function callback) { - dispatcher->post([is_destroyed, callback = std::move(callback)]() { - if (!*is_destroyed) { - callback(); - } - }); - }; +void FileSystemBufferFilter::safeDispatch(absl::AnyInvocable fn) { + request_callbacks_->dispatcher().post( + [is_destroyed = is_destroyed_, fn = std::move(fn)]() mutable { + if (!*is_destroyed) { + std::move(fn)(); + } + }); } void FileSystemBufferFilter::dispatchStateChanged() { - getSafeDispatch()([this]() { onStateChange(); }); + safeDispatch([this]() { onStateChange(); }); } } // namespace FileSystemBuffer diff --git a/source/extensions/filters/http/file_system_buffer/filter.h b/source/extensions/filters/http/file_system_buffer/filter.h index d53feb0794..be452c41d6 100644 --- a/source/extensions/filters/http/file_system_buffer/filter.h +++ b/source/extensions/filters/http/file_system_buffer/filter.h @@ -102,14 +102,15 @@ class FileSystemBufferFilter : public Http::StreamFilter, // These operations are asynchronous; the impacted buffer fragments are in an unusable state until // the operation completes. bool maybeStorage(BufferedStreamState& state, Http::StreamFilterCallbacks& callbacks); - std::function getOnFileActionCompleted(); + absl::AnyInvocable getOnFileActionCompleted(); // Called if an unrecoverable error occurs in the filter (e.g. a file operation fails). Internal // server error. void filterError(absl::string_view err); - // Returns a safe dispatch function that aborts if the filter has been destroyed. - std::function)> getSafeDispatch(); + // Dispatch a callback wrapped such that it is not called if the filter has been destroyed + // by the time it pops off the dispatch queue. + void safeDispatch(absl::AnyInvocable fn); // Queue an onStateChange in the dispatcher. This is used to get the next piece of work back // into the Envoy thread from an AsyncFiles thread, or to queue work that may not be allowed diff --git a/source/extensions/filters/http/file_system_buffer/fragment.cc b/source/extensions/filters/http/file_system_buffer/fragment.cc index d2da3bbd1c..bebde1364e 100644 --- a/source/extensions/filters/http/file_system_buffer/fragment.cc +++ b/source/extensions/filters/http/file_system_buffer/fragment.cc @@ -35,60 +35,53 @@ std::unique_ptr Fragment::extract() { std::unique_ptr MemoryFragment::extract() { return std::move(buffer_); } -absl::StatusOr -Fragment::toStorage(AsyncFileHandle file, off_t offset, - std::function)> dispatch, - std::function on_done) { +absl::StatusOr Fragment::toStorage(AsyncFileHandle file, off_t offset, + Event::Dispatcher& dispatcher, + absl::AnyInvocable on_done) { ASSERT(isMemory()); auto data = absl::get(data_).extract(); data_.emplace(); + // This callback is only called if the filter was not destroyed in the meantime, + // so it is safe to use `this`. return file->write( - *data, offset, - [this, dispatch = std::move(dispatch), size = size_, on_done = std::move(on_done), - offset](absl::StatusOr result) { - // size is captured because we can't safely use 'this' until we're in the dispatch callback. + &dispatcher, *data, offset, + [this, on_done = std::move(on_done), offset](absl::StatusOr result) mutable { if (!result.ok()) { - dispatch([status = result.status(), on_done = std::move(on_done)]() { on_done(status); }); - } else if (result.value() != size) { + std::move(on_done)(result.status()); + } else if (result.value() != size_) { auto status = absl::AbortedError( - fmt::format("buffer write wrote {} bytes, wanted {}", result.value(), size)); - dispatch( - [on_done = std::move(on_done), status = std::move(status)]() { on_done(status); }); + fmt::format("buffer write wrote {} bytes, wanted {}", result.value(), size_)); + std::move(on_done)(status); } else { - dispatch([this, status = result.status(), offset, on_done = std::move(on_done)] { - data_.emplace(offset); - on_done(absl::OkStatus()); - }); + data_.emplace(offset); + std::move(on_done)(absl::OkStatus()); } }); } absl::StatusOr -Fragment::fromStorage(AsyncFileHandle file, std::function)> dispatch, - std::function on_done) { +Fragment::fromStorage(AsyncFileHandle file, Event::Dispatcher& dispatcher, + absl::AnyInvocable on_done) { ASSERT(isStorage()); off_t offset = absl::get(data_).offset(); data_.emplace(); - return file->read( - offset, size_, - [this, dispatch = std::move(dispatch), size = size_, - on_done = std::move(on_done)](absl::StatusOr> result) { - // size is captured because we can't safely use 'this' until we're in the dispatch callback. - if (!result.ok()) { - dispatch([on_done = std::move(on_done), status = result.status()]() { on_done(status); }); - } else if (result.value()->length() != size) { - auto status = absl::AbortedError( - fmt::format("buffer read got {} bytes, wanted {}", result.value()->length(), size)); - dispatch( - [on_done = std::move(on_done), status = std::move(status)]() { on_done(status); }); - } else { - auto buffer = std::shared_ptr(std::move(result.value())); - dispatch([this, on_done = std::move(on_done), buffer = std::move(buffer)]() { - data_.emplace(*buffer); - on_done(absl::OkStatus()); - }); - } - }); + // This callback is only called if the filter was not destroyed in the meantime, + // so it is safe to use `this`. + return file->read(&dispatcher, offset, size_, + [this, on_done = std::move(on_done)]( + absl::StatusOr> result) mutable { + if (!result.ok()) { + std::move(on_done)(result.status()); + } else if (result.value()->length() != size_) { + auto status = + absl::AbortedError(fmt::format("buffer read got {} bytes, wanted {}", + result.value()->length(), size_)); + std::move(on_done)(status); + } else { + data_.emplace(*result.value()); + std::move(on_done)(absl::OkStatus()); + } + }); } } // namespace FileSystemBuffer diff --git a/source/extensions/filters/http/file_system_buffer/fragment.h b/source/extensions/filters/http/file_system_buffer/fragment.h index 2e6efe2704..f38667da8e 100644 --- a/source/extensions/filters/http/file_system_buffer/fragment.h +++ b/source/extensions/filters/http/file_system_buffer/fragment.h @@ -70,8 +70,8 @@ class Fragment { // When called from a filter, the dispatcher function must abort without calling the // callback if the filter or fragment has been destroyed. absl::StatusOr toStorage(AsyncFileHandle file, off_t offset, - std::function)> dispatch, - std::function on_done); + Event::Dispatcher& dispatcher, + absl::AnyInvocable on_done); // Starts the transition for this fragment from storage to memory. // @@ -79,9 +79,8 @@ class Fragment { // // When called from a filter, the dispatcher function must abort without calling the // callback if the filter or fragment has been destroyed. - absl::StatusOr fromStorage(AsyncFileHandle file, - std::function)> dispatch, - std::function on_done); + absl::StatusOr fromStorage(AsyncFileHandle file, Event::Dispatcher& dispatcher, + absl::AnyInvocable on_done); // Moves the buffer from a memory instance to the returned value and resets the fragment to // size 0. diff --git a/source/extensions/filters/http/header_mutation/header_mutation.cc b/source/extensions/filters/http/header_mutation/header_mutation.cc index e61635a580..259f180d17 100644 --- a/source/extensions/filters/http/header_mutation/header_mutation.cc +++ b/source/extensions/filters/http/header_mutation/header_mutation.cc @@ -46,12 +46,11 @@ Http::FilterHeadersStatus HeaderMutation::decodeHeaders(Http::RequestHeaderMap& // `getAllPerFilterConfig` above returns). // Thus, here we reverse iterate the vector when `most_specific_wins` is false. for (auto it = route_configs_.rbegin(); it != route_configs_.rend(); ++it) { - (*it)->mutations().mutateRequestHeaders(headers, ctx, decoder_callbacks_->streamInfo()); + (*it).get().mutations().mutateRequestHeaders(headers, ctx, decoder_callbacks_->streamInfo()); } } else { - for (const auto* route_config : route_configs_) { - route_config->mutations().mutateRequestHeaders(headers, ctx, - decoder_callbacks_->streamInfo()); + for (const PerRouteHeaderMutation& route_config : route_configs_) { + route_config.mutations().mutateRequestHeaders(headers, ctx, decoder_callbacks_->streamInfo()); } } @@ -70,12 +69,12 @@ Http::FilterHeadersStatus HeaderMutation::encodeHeaders(Http::ResponseHeaderMap& if (!config_->mostSpecificHeaderMutationsWins()) { for (auto it = route_configs_.rbegin(); it != route_configs_.rend(); ++it) { - (*it)->mutations().mutateResponseHeaders(headers, ctx, encoder_callbacks_->streamInfo()); + (*it).get().mutations().mutateResponseHeaders(headers, ctx, encoder_callbacks_->streamInfo()); } } else { - for (const auto* route_config : route_configs_) { - route_config->mutations().mutateResponseHeaders(headers, ctx, - encoder_callbacks_->streamInfo()); + for (const PerRouteHeaderMutation& route_config : route_configs_) { + route_config.mutations().mutateResponseHeaders(headers, ctx, + encoder_callbacks_->streamInfo()); } } diff --git a/source/extensions/filters/http/header_mutation/header_mutation.h b/source/extensions/filters/http/header_mutation/header_mutation.h index 5286d83404..254c9ca076 100644 --- a/source/extensions/filters/http/header_mutation/header_mutation.h +++ b/source/extensions/filters/http/header_mutation/header_mutation.h @@ -82,7 +82,7 @@ class HeaderMutation : public Http::PassThroughFilter, public Logger::Loggable route_configs_{}; + absl::InlinedVector, 4> route_configs_{}; }; } // namespace HeaderMutation diff --git a/source/extensions/filters/http/rate_limit_quota/client.h b/source/extensions/filters/http/rate_limit_quota/client.h index a8d31c39b0..198b919d5b 100644 --- a/source/extensions/filters/http/rate_limit_quota/client.h +++ b/source/extensions/filters/http/rate_limit_quota/client.h @@ -37,7 +37,7 @@ class RateLimitClient { public: virtual ~RateLimitClient() = default; - virtual absl::Status startStream(const StreamInfo::StreamInfo& stream_info) PURE; + virtual absl::Status startStream(const StreamInfo::StreamInfo* stream_info) PURE; virtual void closeStream() PURE; virtual void sendUsageReport(absl::optional bucket_id) PURE; diff --git a/source/extensions/filters/http/rate_limit_quota/client_impl.cc b/source/extensions/filters/http/rate_limit_quota/client_impl.cc index 7d886a49e7..876060592e 100644 --- a/source/extensions/filters/http/rate_limit_quota/client_impl.cc +++ b/source/extensions/filters/http/rate_limit_quota/client_impl.cc @@ -64,14 +64,17 @@ RateLimitQuotaUsageReports RateLimitClientImpl::buildReport(absl::optional bucket_id) { - if (stream_ != nullptr) { - // Build the report and then send the report to RLQS server. - // `end_stream` should always be set to false as we don't want to close the stream locally. - stream_->sendMessage(buildReport(bucket_id), /*end_stream=*/false); - } else { - // Don't send any reports if stream has already been closed. - ENVOY_LOG(debug, "The stream has already been closed; no reports will be sent."); + if (stream_ == nullptr) { + ENVOY_LOG(debug, "The RLQS stream has been closed and must be restarted to send reports."); + if (absl::Status err = startStream(nullptr); !err.ok()) { + ENVOY_LOG(error, "Failed to start the stream to send reports."); + return; + } } + + // Build the report and then send the report to RLQS server. + // `end_stream` should always be set to false as we don't want to close the stream locally. + stream_->sendMessage(buildReport(bucket_id), /*end_stream=*/false); } void RateLimitClientImpl::onReceiveMessage(RateLimitQuotaResponsePtr&& response) { @@ -165,20 +168,27 @@ void RateLimitClientImpl::onRemoteClose(Grpc::Status::GrpcStatus status, stream_ = nullptr; } -absl::Status RateLimitClientImpl::startStream(const StreamInfo::StreamInfo& stream_info) { +absl::Status RateLimitClientImpl::startStream(const StreamInfo::StreamInfo* stream_info) { // Starts stream if it has not been opened yet. if (stream_ == nullptr) { ENVOY_LOG(debug, "Trying to start the new gRPC stream"); + auto stream_options = Http::AsyncClient::RequestOptions(); + if (stream_info) { + stream_options.setParentContext(Http::AsyncClient::ParentContext{stream_info}); + } stream_ = aync_client_.start( *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.rate_limit_quota.v3.RateLimitQuotaService.StreamRateLimitQuotas"), - *this, - Http::AsyncClient::RequestOptions().setParentContext( - Http::AsyncClient::ParentContext{&stream_info})); + *this, stream_options); + } + + // If still null after attempting a start. + if (stream_ == nullptr) { + return absl::InternalError("Failed to start the stream"); } - // Returns error status if start failed (i.e., stream_ is nullptr). - return stream_ == nullptr ? absl::InternalError("Failed to start the stream") : absl::OkStatus(); + ENVOY_LOG(debug, "gRPC stream has been started"); + return absl::OkStatus(); } } // namespace RateLimitQuota diff --git a/source/extensions/filters/http/rate_limit_quota/client_impl.h b/source/extensions/filters/http/rate_limit_quota/client_impl.h index b471755d24..c4584c26b8 100644 --- a/source/extensions/filters/http/rate_limit_quota/client_impl.h +++ b/source/extensions/filters/http/rate_limit_quota/client_impl.h @@ -45,7 +45,7 @@ class RateLimitClientImpl : public RateLimitClient, void onRemoteClose(Grpc::Status::GrpcStatus status, const std::string& message) override; // RateLimitClient methods. - absl::Status startStream(const StreamInfo::StreamInfo& stream_info) override; + absl::Status startStream(const StreamInfo::StreamInfo* stream_info) override; void closeStream() override; // Send the usage report to RLQS server void sendUsageReport(absl::optional bucket_id) override; diff --git a/source/extensions/filters/http/rate_limit_quota/filter.cc b/source/extensions/filters/http/rate_limit_quota/filter.cc index 65535753c3..e5671033cc 100644 --- a/source/extensions/filters/http/rate_limit_quota/filter.cc +++ b/source/extensions/filters/http/rate_limit_quota/filter.cc @@ -185,7 +185,7 @@ RateLimitQuotaFilter::sendImmediateReport(const size_t bucket_id, // Start the streaming on the first request. // It will be a no-op if the stream is already active. - auto status = client_.rate_limit_client->startStream(callbacks_->streamInfo()); + auto status = client_.rate_limit_client->startStream(&callbacks_->streamInfo()); if (!status.ok()) { ENVOY_LOG(error, "Failed to start the gRPC stream: ", status.message()); // TODO(tyxia) Check `NoAssignmentBehavior` behavior instead of fail-open here. diff --git a/source/extensions/filters/network/generic_proxy/file_access_log.h b/source/extensions/filters/network/generic_proxy/file_access_log.h index 779b4c08fc..30eabb239e 100644 --- a/source/extensions/filters/network/generic_proxy/file_access_log.h +++ b/source/extensions/filters/network/generic_proxy/file_access_log.h @@ -59,8 +59,9 @@ class FileAccessLogFactoryBase : public AccessLog::AccessLogInstanceFactoryBase< } else { envoy::config::core::v3::SubstitutionFormatString sff_config; sff_config.mutable_text_format_source()->set_inline_string(typed_config.format()); - formatter = - Formatter::SubstitutionFormatStringUtils::fromProtoConfig(sff_config, context); + formatter = THROW_OR_RETURN_VALUE( + Formatter::SubstitutionFormatStringUtils::fromProtoConfig(sff_config, context), + Formatter::FormatterBasePtr); } break; case envoy::extensions::access_loggers::file::v3::FileAccessLog::AccessLogFormatCase:: @@ -72,14 +73,17 @@ class FileAccessLogFactoryBase : public AccessLog::AccessLogInstanceFactoryBase< kTypedJsonFormat: { envoy::config::core::v3::SubstitutionFormatString sff_config; *sff_config.mutable_json_format() = typed_config.typed_json_format(); - formatter = - Formatter::SubstitutionFormatStringUtils::fromProtoConfig(sff_config, context); + formatter = THROW_OR_RETURN_VALUE( + Formatter::SubstitutionFormatStringUtils::fromProtoConfig(sff_config, context), + Formatter::FormatterBasePtr); break; } case envoy::extensions::access_loggers::file::v3::FileAccessLog::AccessLogFormatCase:: kLogFormat: - formatter = Formatter::SubstitutionFormatStringUtils::fromProtoConfig( - typed_config.log_format(), context); + formatter = + THROW_OR_RETURN_VALUE(Formatter::SubstitutionFormatStringUtils::fromProtoConfig( + typed_config.log_format(), context), + Formatter::FormatterBasePtr); break; case envoy::extensions::access_loggers::file::v3::FileAccessLog::AccessLogFormatCase:: ACCESS_LOG_FORMAT_NOT_SET: diff --git a/source/extensions/filters/udp/udp_proxy/config.cc b/source/extensions/filters/udp/udp_proxy/config.cc index 436368faf0..200e7099c7 100644 --- a/source/extensions/filters/udp/udp_proxy/config.cc +++ b/source/extensions/filters/udp/udp_proxy/config.cc @@ -61,8 +61,10 @@ TunnelingConfigImpl::TunnelingConfigImpl(const TunnelingConfig& config, envoy::config::core::v3::SubstitutionFormatString proxy_substitution_format_config; proxy_substitution_format_config.mutable_text_format_source()->set_inline_string( config.proxy_host()); - proxy_host_formatter_ = Formatter::SubstitutionFormatStringUtils::fromProtoConfig( - proxy_substitution_format_config, context); + proxy_host_formatter_ = + THROW_OR_RETURN_VALUE(Formatter::SubstitutionFormatStringUtils::fromProtoConfig( + proxy_substitution_format_config, context), + Formatter::FormatterBasePtr); if (config.has_proxy_port()) { uint32_t port = config.proxy_port().value(); @@ -76,8 +78,10 @@ TunnelingConfigImpl::TunnelingConfigImpl(const TunnelingConfig& config, envoy::config::core::v3::SubstitutionFormatString target_substitution_format_config; target_substitution_format_config.mutable_text_format_source()->set_inline_string( config.target_host()); - target_host_formatter_ = Formatter::SubstitutionFormatStringUtils::fromProtoConfig( - target_substitution_format_config, context); + target_host_formatter_ = + THROW_OR_RETURN_VALUE(Formatter::SubstitutionFormatStringUtils::fromProtoConfig( + target_substitution_format_config, context), + Formatter::FormatterBasePtr); } UdpProxyFilterConfigImpl::UdpProxyFilterConfigImpl( diff --git a/source/extensions/http/cache/file_system_http_cache/file_system_http_cache.cc b/source/extensions/http/cache/file_system_http_cache/file_system_http_cache.cc index 687a0e40d8..1e5a49c6c6 100644 --- a/source/extensions/http/cache/file_system_http_cache/file_system_http_cache.cc +++ b/source/extensions/http/cache/file_system_http_cache/file_system_http_cache.cc @@ -25,7 +25,7 @@ const size_t FileSystemHttpCache::max_update_headers_copy_chunk_size_ = 128 * 10 const CacheStats& FileSystemHttpCache::stats() const { return shared_->stats_; } const ConfigProto& FileSystemHttpCache::config() const { return shared_->config_; } -void FileSystemHttpCache::writeVaryNodeToDisk(const Key& key, +void FileSystemHttpCache::writeVaryNodeToDisk(Event::Dispatcher& dispatcher, const Key& key, const Http::ResponseHeaderMap& response_headers, std::shared_ptr cleanup) { auto vary_values = VaryHeaderUtils::getVaryValues(response_headers); @@ -35,8 +35,9 @@ void FileSystemHttpCache::writeVaryNodeToDisk(const Key& key, h->set_value(absl::StrJoin(vary_values, ",")); std::string filename = absl::StrCat(cachePath(), generateFilename(key)); async_file_manager_->createAnonymousFile( - cachePath(), [headers, filename = std::move(filename), - cleanup](absl::StatusOr open_result) { + &dispatcher, cachePath(), + [headers, filename = std::move(filename), cleanup, + dispatcher = &dispatcher](absl::StatusOr open_result) { if (!open_result.ok()) { ENVOY_LOG(warn, "writing vary node, failed to createAnonymousFile: {}", open_result.status()); @@ -51,20 +52,20 @@ void FileSystemHttpCache::writeVaryNodeToDisk(const Key& key, buf2.add(buf); size_t sz = buf2.length(); auto queued = file_handle->write( - buf2, 0, - [file_handle, cleanup, sz, + dispatcher, buf2, 0, + [dispatcher, file_handle, cleanup, sz, filename = std::move(filename)](absl::StatusOr write_result) { if (!write_result.ok() || write_result.value() != sz) { ENVOY_LOG(warn, "writing vary node, failed to write: {}", write_result.status()); - file_handle->close([](absl::Status) {}).IgnoreError(); + file_handle->close(nullptr, [](absl::Status) {}).IgnoreError(); return; } auto queued = file_handle->createHardLink( - filename, [cleanup, file_handle](absl::Status link_result) { + dispatcher, filename, [cleanup, file_handle](absl::Status link_result) { if (!link_result.ok()) { ENVOY_LOG(warn, "writing vary node, failed to link: {}", link_result); } - file_handle->close([](absl::Status) {}).IgnoreError(); + file_handle->close(nullptr, [](absl::Status) {}).IgnoreError(); }); ASSERT(queued.ok()); }); @@ -114,35 +115,37 @@ FileSystemHttpCache::makeVaryKey(const Key& base, const VaryAllowList& vary_allo return vary_key; } -LookupContextPtr FileSystemHttpCache::makeLookupContext(LookupRequest&& lookup, - Http::StreamDecoderFilterCallbacks&) { - return std::make_unique(*this, std::move(lookup)); +LookupContextPtr +FileSystemHttpCache::makeLookupContext(LookupRequest&& lookup, + Http::StreamDecoderFilterCallbacks& callbacks) { + return std::make_unique(callbacks.dispatcher(), *this, std::move(lookup)); } // Helper class to reduce the lambda depth of updateHeaders. class HeaderUpdateContext : public Logger::Loggable { public: - HeaderUpdateContext(const FileSystemHttpCache& cache, const Key& key, - std::shared_ptr cleanup, + HeaderUpdateContext(Event::Dispatcher& dispatcher, const FileSystemHttpCache& cache, + const Key& key, std::shared_ptr cleanup, const Http::ResponseHeaderMap& response_headers, const ResponseMetadata& metadata, std::function on_complete) - : filepath_(absl::StrCat(cache.cachePath(), cache.generateFilename(key))), + : dispatcher_(dispatcher), + filepath_(absl::StrCat(cache.cachePath(), cache.generateFilename(key))), cache_path_(cache.cachePath()), cleanup_(cleanup), async_file_manager_(cache.asyncFileManager()), response_headers_(Http::createHeaderMap(response_headers)), response_metadata_(metadata), on_complete_(on_complete) {} void begin(std::shared_ptr ctx) { - async_file_manager_->openExistingFile(filepath_, - Common::AsyncFiles::AsyncFileManager::Mode::ReadOnly, - [ctx, this](absl::StatusOr open_result) { - if (!open_result.ok()) { - fail("failed to open", open_result.status()); - return; - } - read_handle_ = std::move(open_result.value()); - unlinkOriginal(ctx); - }); + async_file_manager_->openExistingFile( + dispatcher(), filepath_, Common::AsyncFiles::AsyncFileManager::Mode::ReadOnly, + [ctx = std::move(ctx), this](absl::StatusOr open_result) { + if (!open_result.ok()) { + fail("failed to open", open_result.status()); + return; + } + read_handle_ = std::move(open_result.value()); + unlinkOriginal(std::move(ctx)); + }); } ~HeaderUpdateContext() { @@ -150,44 +153,46 @@ class HeaderUpdateContext : public Logger::Loggable { // write_handle_ can only be set if read_handle_ is set, so this ordering is safe. if (read_handle_) { read_handle_ - ->close([write_handle = write_handle_](absl::Status) { - if (write_handle) { - write_handle->close([](absl::Status) {}).IgnoreError(); - } - }) + ->close(dispatcher(), + [write_handle = write_handle_](absl::Status) { + if (write_handle) { + write_handle->close(nullptr, [](absl::Status) {}).IgnoreError(); + } + }) .IgnoreError(); } } private: void unlinkOriginal(std::shared_ptr ctx) { - async_file_manager_->unlink(filepath_, [ctx, this](absl::Status unlink_result) { - if (!unlink_result.ok()) { - ENVOY_LOG(warn, "file_system_http_cache: {} for update cache file {}: {}", "unlink failed", - filepath_, unlink_result); - // But keep going, because unlink might have failed because the file was already - // deleted after we opened it. Worth a try to replace it! - } - readHeaderBlock(ctx); - }); + async_file_manager_->unlink( + dispatcher(), filepath_, [ctx = std::move(ctx), this](absl::Status unlink_result) { + if (!unlink_result.ok()) { + ENVOY_LOG(warn, "file_system_http_cache: {} for update cache file {}: {}", + "unlink failed", filepath_, unlink_result); + // But keep going, because unlink might have failed because the file was already + // deleted after we opened it. Worth a try to replace it! + } + readHeaderBlock(std::move(ctx)); + }); } void readHeaderBlock(std::shared_ptr ctx) { auto queued = read_handle_->read( - 0, CacheFileFixedBlock::size(), - [ctx, this](absl::StatusOr read_result) { + dispatcher(), 0, CacheFileFixedBlock::size(), + [ctx = std::move(ctx), this](absl::StatusOr read_result) { if (!read_result.ok() || read_result.value()->length() != CacheFileFixedBlock::size()) { fail("failed to read header block", read_result.status()); return; } header_block_.populateFromStringView(read_result.value()->toString()); - readHeaders(ctx); + readHeaders(std::move(ctx)); }); ASSERT(queued.ok()); } void readHeaders(std::shared_ptr ctx) { auto queued = read_handle_->read( - header_block_.offsetToHeaders(), header_block_.headerSize(), - [ctx, this](absl::StatusOr read_result) { + dispatcher(), header_block_.offsetToHeaders(), header_block_.headerSize(), + [ctx = std::move(ctx), this](absl::StatusOr read_result) { if (!read_result.ok() || read_result.value()->length() != header_block_.headerSize()) { fail("failed to read headers", read_result.status()); return; @@ -207,19 +212,20 @@ class HeaderUpdateContext : public Logger::Loggable { size_t new_header_size = headerProtoSize(header_proto_); header_size_difference_ = header_block_.headerSize() - new_header_size; header_block_.setHeadersSize(new_header_size); - startWriting(ctx); + startWriting(std::move(ctx)); }); ASSERT(queued.ok()); } void startWriting(std::shared_ptr ctx) { async_file_manager_->createAnonymousFile( - cache_path_, [ctx, this](absl::StatusOr create_result) { + dispatcher(), cache_path_, + [ctx = std::move(ctx), this](absl::StatusOr create_result) { if (!create_result.ok()) { fail("failed to open new cache file", create_result.status()); return; } write_handle_ = std::move(create_result.value()); - writeHeaderBlockAndHeaders(ctx); + writeHeaderBlockAndHeaders(std::move(ctx)); }); } void writeHeaderBlockAndHeaders(std::shared_ptr ctx) { @@ -227,13 +233,14 @@ class HeaderUpdateContext : public Logger::Loggable { header_block_.serializeToBuffer(buf); buf.add(bufferFromProto(header_proto_)); auto sz = buf.length(); - auto queued = - write_handle_->write(buf, 0, [ctx, sz, this](absl::StatusOr write_result) { + auto queued = write_handle_->write( + dispatcher(), buf, 0, + [ctx = std::move(ctx), sz, this](absl::StatusOr write_result) { if (!write_result.ok() || write_result.value() != sz) { fail("failed to write header block and headers", write_result.status()); return; } - copyBodyAndTrailers(ctx, header_block_.offsetToBody()); + copyBodyAndTrailers(std::move(ctx), header_block_.offsetToBody()); }); ASSERT(queued.ok()); } @@ -245,33 +252,34 @@ class HeaderUpdateContext : public Logger::Loggable { } sz = std::min(sz, FileSystemHttpCache::max_update_headers_copy_chunk_size_); auto queued = read_handle_->read( - offset + header_size_difference_, sz, - [ctx, offset, sz, this](absl::StatusOr read_result) { + dispatcher(), offset + header_size_difference_, sz, + [ctx = std::move(ctx), offset, sz, this](absl::StatusOr read_result) { if (!read_result.ok() || read_result.value()->length() != sz) { fail("failed to read body chunk", read_result.status()); return; } - auto queued = - write_handle_->write(*read_result.value(), offset, - [ctx, offset, sz, this](absl::StatusOr write_result) { - if (!write_result.ok() || write_result.value() != sz) { - fail("failed to write body chunk", write_result.status()); - return; - } - copyBodyAndTrailers(ctx, offset + sz); - }); + auto queued = write_handle_->write( + dispatcher(), *read_result.value(), offset, + [ctx = std::move(ctx), offset, sz, this](absl::StatusOr write_result) { + if (!write_result.ok() || write_result.value() != sz) { + fail("failed to write body chunk", write_result.status()); + return; + } + copyBodyAndTrailers(std::move(ctx), offset + sz); + }); ASSERT(queued.ok()); }); ASSERT(queued.ok()); } void linkNewFile(std::shared_ptr ctx) { - auto queued = write_handle_->createHardLink(filepath_, [ctx, this](absl::Status link_result) { - if (!link_result.ok()) { - fail("failed to link new cache file", link_result); - return; - } - on_complete_(true); - }); + auto queued = write_handle_->createHardLink( + dispatcher(), filepath_, [ctx = std::move(ctx), this](absl::Status link_result) { + if (!link_result.ok()) { + fail("failed to link new cache file", link_result); + return; + } + on_complete_(true); + }); ASSERT(queued.ok()); } void fail(absl::string_view msg, absl::Status status) { @@ -279,6 +287,8 @@ class HeaderUpdateContext : public Logger::Loggable { status); on_complete_(false); } + Event::Dispatcher* dispatcher() { return &dispatcher_; } + Event::Dispatcher& dispatcher_; std::string filepath_; std::string cache_path_; std::shared_ptr cleanup_; @@ -293,17 +303,19 @@ class HeaderUpdateContext : public Logger::Loggable { std::function on_complete_; }; -void FileSystemHttpCache::updateHeaders(const LookupContext& lookup_context, +void FileSystemHttpCache::updateHeaders(const LookupContext& base_lookup_context, const Http::ResponseHeaderMap& response_headers, const ResponseMetadata& metadata, std::function on_complete) { - const Key& key = dynamic_cast(lookup_context).key(); + const FileLookupContext& lookup_context = + dynamic_cast(base_lookup_context); + const Key& key = lookup_context.key(); auto cleanup = maybeStartWritingEntry(key); if (!cleanup) { return; } - auto ctx = std::make_shared(*this, key, cleanup, response_headers, metadata, - on_complete); + auto ctx = std::make_shared( + *lookup_context.dispatcher(), *this, key, cleanup, response_headers, metadata, on_complete); ctx->begin(ctx); } @@ -326,10 +338,10 @@ std::shared_ptr FileSystemHttpCache::maybeStartWritingEntry(const Key& } std::shared_ptr -FileSystemHttpCache::setCacheEntryToVary(const Key& key, +FileSystemHttpCache::setCacheEntryToVary(Event::Dispatcher& dispatcher, const Key& key, const Http::ResponseHeaderMap& response_headers, const Key& varied_key, std::shared_ptr cleanup) { - writeVaryNodeToDisk(key, response_headers, cleanup); + writeVaryNodeToDisk(dispatcher, key, response_headers, cleanup); return maybeStartWritingEntry(varied_key); } diff --git a/source/extensions/http/cache/file_system_http_cache/file_system_http_cache.h b/source/extensions/http/cache/file_system_http_cache/file_system_http_cache.h index 2c32d110ec..9e81bf0410 100644 --- a/source/extensions/http/cache/file_system_http_cache/file_system_http_cache.h +++ b/source/extensions/http/cache/file_system_http_cache/file_system_http_cache.h @@ -137,9 +137,9 @@ class FileSystemHttpCache : public HttpCache, * if varied_key was added. */ ABSL_MUST_USE_RESULT std::shared_ptr - setCacheEntryToVary(const Key& key, const Http::ResponseHeaderMap& response_headers, - const Key& varied_key, std::shared_ptr cleanup) - ABSL_LOCKS_EXCLUDED(cache_mu_); + setCacheEntryToVary(Event::Dispatcher& dispatcher, const Key& key, + const Http::ResponseHeaderMap& response_headers, const Key& varied_key, + std::shared_ptr cleanup) ABSL_LOCKS_EXCLUDED(cache_mu_); /** * Returns the extension name. @@ -187,6 +187,9 @@ class FileSystemHttpCache : public HttpCache, using PostEvictionCallback = std::function; + // Waits for all queued actions to be completed. + void drainAsyncFileActionsForTest() { async_file_manager_->waitForIdle(); }; + private: /** * Writes a vary node to disk for the given key. A vary node in the cache consists of @@ -196,7 +199,8 @@ class FileSystemHttpCache : public HttpCache, * be extracted. * @param cleanup the cleanup operation to be performed when the write completes. */ - void writeVaryNodeToDisk(const Key& key, const Http::ResponseHeaderMap& response_headers, + void writeVaryNodeToDisk(Event::Dispatcher& dispatcher, const Key& key, + const Http::ResponseHeaderMap& response_headers, std::shared_ptr cleanup); // A shared_ptr to keep the cache singleton alive as long as any of its caches are in use. diff --git a/source/extensions/http/cache/file_system_http_cache/insert_context.cc b/source/extensions/http/cache/file_system_http_cache/insert_context.cc index 53c066b9b0..98e0565773 100644 --- a/source/extensions/http/cache/file_system_http_cache/insert_context.cc +++ b/source/extensions/http/cache/file_system_http_cache/insert_context.cc @@ -31,7 +31,7 @@ FileInsertContext::FileInsertContext(std::shared_ptr cache, void FileInsertContext::insertHeaders(const Http::ResponseHeaderMap& response_headers, const ResponseMetadata& metadata, InsertCallback insert_complete, bool end_stream) { - absl::MutexLock lock(&mu_); + ASSERT(dispatcher()->isThreadSafe()); callback_in_flight_ = insert_complete; const VaryAllowList& vary_allow_list = lookup_context_->lookup().varyAllowList(); const Http::RequestHeaderMap& request_headers = lookup_context_->lookup().requestHeaders(); @@ -47,7 +47,8 @@ void FileInsertContext::insertHeaders(const Http::ResponseHeaderMap& response_he cancelInsert(); return; } - cleanup_ = cache_->setCacheEntryToVary(old_key, response_headers, key_, cleanup_); + cleanup_ = + cache_->setCacheEntryToVary(*dispatcher(), old_key, response_headers, key_, cleanup_); } else { cleanup_ = cache_->maybeStartWritingEntry(key_); } @@ -56,74 +57,98 @@ void FileInsertContext::insertHeaders(const Http::ResponseHeaderMap& response_he cancelInsert(); return; } - auto header_proto = makeCacheFileHeaderProto(key_, response_headers, metadata); - // Open the file. + cache_file_header_proto_ = makeCacheFileHeaderProto(key_, response_headers, metadata); + end_stream_after_headers_ = end_stream; + on_insert_complete_ = std::move(insert_complete); + createFile(); +} + +void FileInsertContext::createFile() { + ASSERT(dispatcher()->isThreadSafe()); + ASSERT(!cancel_action_in_flight_); + ASSERT(callback_in_flight_ != nullptr); cancel_action_in_flight_ = cache_->asyncFileManager()->createAnonymousFile( - cache_->cachePath(), [this, end_stream, header_proto, - insert_complete](absl::StatusOr open_result) { - absl::MutexLock lock(&mu_); + dispatcher(), cache_->cachePath(), [this](absl::StatusOr open_result) { cancel_action_in_flight_ = nullptr; if (!open_result.ok()) { cancelInsert("failed to create anonymous file"); return; } file_handle_ = std::move(open_result.value()); - Buffer::OwnedImpl unset_header; - header_block_.serializeToBuffer(unset_header); - // Write an empty header block. - auto queued = file_handle_->write( - unset_header, 0, [this, end_stream, header_proto](absl::StatusOr write_result) { - absl::MutexLock lock(&mu_); - cancel_action_in_flight_ = nullptr; - if (!write_result.ok() || write_result.value() != CacheFileFixedBlock::size()) { - cancelInsert(writeFailureMessage("empty header block", write_result, - CacheFileFixedBlock::size())); - return; - } - auto buf = bufferFromProto(header_proto); - auto sz = buf.length(); - auto queued = file_handle_->write( - buf, header_block_.offsetToHeaders(), - [this, end_stream, sz](absl::StatusOr write_result) { - absl::MutexLock lock(&mu_); - cancel_action_in_flight_ = nullptr; - if (!write_result.ok() || write_result.value() != sz) { - cancelInsert(writeFailureMessage("headers", write_result, sz)); - return; - } - header_block_.setHeadersSize(sz); - if (end_stream) { - commit(callback_in_flight_); - return; - } - auto cb = callback_in_flight_; - callback_in_flight_ = nullptr; - cb(true); - }); - ASSERT(queued.ok(), queued.status().ToString()); - cancel_action_in_flight_ = queued.value(); - }); - ASSERT(queued.ok(), queued.status().ToString()); - cancel_action_in_flight_ = queued.value(); + writeEmptyHeaderBlock(); }); } +void FileInsertContext::writeEmptyHeaderBlock() { + ASSERT(dispatcher()->isThreadSafe()); + ASSERT(!cancel_action_in_flight_); + ASSERT(callback_in_flight_ != nullptr); + Buffer::OwnedImpl unset_header; + header_block_.serializeToBuffer(unset_header); + // Write an empty header block. + auto queued = file_handle_->write( + dispatcher(), unset_header, 0, [this](absl::StatusOr write_result) { + cancel_action_in_flight_ = nullptr; + if (!write_result.ok() || write_result.value() != CacheFileFixedBlock::size()) { + cancelInsert( + writeFailureMessage("empty header block", write_result, CacheFileFixedBlock::size())); + return; + } + writeHeaderProto(); + }); + ASSERT(queued.ok(), queued.status().ToString()); + cancel_action_in_flight_ = std::move(queued.value()); +} + +void FileInsertContext::succeedCurrentAction() { + ASSERT(!cancel_action_in_flight_); + ASSERT(callback_in_flight_ != nullptr); + auto cb = std::move(callback_in_flight_); + callback_in_flight_ = nullptr; + cb(true); +} + +void FileInsertContext::writeHeaderProto() { + ASSERT(dispatcher()->isThreadSafe()); + ASSERT(!cancel_action_in_flight_); + ASSERT(callback_in_flight_ != nullptr); + auto buf = bufferFromProto(cache_file_header_proto_); + auto sz = buf.length(); + auto queued = + file_handle_->write(dispatcher(), buf, header_block_.offsetToHeaders(), + [this, sz](absl::StatusOr write_result) { + cancel_action_in_flight_ = nullptr; + if (!write_result.ok() || write_result.value() != sz) { + cancelInsert(writeFailureMessage("headers", write_result, sz)); + return; + } + header_block_.setHeadersSize(sz); + if (end_stream_after_headers_) { + commit(); + return; + } + succeedCurrentAction(); + }); + ASSERT(queued.ok(), queued.status().ToString()); + cancel_action_in_flight_ = std::move(queued.value()); +} + void FileInsertContext::insertBody(const Buffer::Instance& fragment, InsertCallback ready_for_next_fragment, bool end_stream) { - absl::MutexLock lock(&mu_); + ASSERT(dispatcher()->isThreadSafe()); + ASSERT(!cancel_action_in_flight_, "should be no actions in flight when receiving new data"); + ASSERT(!callback_in_flight_); if (!cleanup_) { // Already cancelled, do nothing, return failure. ready_for_next_fragment(false); return; } - ASSERT(!cancel_action_in_flight_, "should be no actions in flight when receiving new data"); callback_in_flight_ = ready_for_next_fragment; size_t sz = fragment.length(); Buffer::OwnedImpl consumable_fragment(fragment); auto queued = file_handle_->write( - consumable_fragment, header_block_.offsetToBody() + header_block_.bodySize(), + dispatcher(), consumable_fragment, header_block_.offsetToBody() + header_block_.bodySize(), [this, sz, end_stream](absl::StatusOr write_result) { - absl::MutexLock lock(&mu_); cancel_action_in_flight_ = nullptr; if (!write_result.ok() || write_result.value() != sz) { cancelInsert(writeFailureMessage("body chunk", write_result, sz)); @@ -131,116 +156,121 @@ void FileInsertContext::insertBody(const Buffer::Instance& fragment, } header_block_.setBodySize(header_block_.bodySize() + sz); if (end_stream) { - commit(callback_in_flight_); + commit(); } else { - auto cb = callback_in_flight_; - callback_in_flight_ = nullptr; - cb(true); + succeedCurrentAction(); } }); ASSERT(queued.ok(), queued.status().ToString()); - cancel_action_in_flight_ = queued.value(); + cancel_action_in_flight_ = std::move(queued.value()); } void FileInsertContext::insertTrailers(const Http::ResponseTrailerMap& trailers, InsertCallback insert_complete) { - absl::MutexLock lock(&mu_); + ASSERT(dispatcher()->isThreadSafe()); + ASSERT(!cancel_action_in_flight_, "should be no actions in flight when receiving new data"); + ASSERT(!callback_in_flight_); if (!cleanup_) { // Already cancelled, do nothing, return failure. insert_complete(false); return; } - ASSERT(!cancel_action_in_flight_, "should be no actions in flight when receiving new data"); callback_in_flight_ = insert_complete; CacheFileTrailer file_trailer = makeCacheFileTrailerProto(trailers); Buffer::OwnedImpl consumable_buffer = bufferFromProto(file_trailer); size_t sz = consumable_buffer.length(); auto queued = - file_handle_->write(consumable_buffer, header_block_.offsetToTrailers(), + file_handle_->write(dispatcher(), consumable_buffer, header_block_.offsetToTrailers(), [this, sz](absl::StatusOr write_result) { - absl::MutexLock lock(&mu_); cancel_action_in_flight_ = nullptr; if (!write_result.ok() || write_result.value() != sz) { cancelInsert(writeFailureMessage("trailer chunk", write_result, sz)); return; } header_block_.setTrailersSize(sz); - commit(callback_in_flight_); + commit(); }); ASSERT(queued.ok(), queued.status().ToString()); - cancel_action_in_flight_ = queued.value(); + cancel_action_in_flight_ = std::move(queued.value()); } -void FileInsertContext::onDestroy() { - absl::MutexLock lock(&mu_); - cancelInsert("InsertContext destroyed prematurely"); -} +void FileInsertContext::onDestroy() { cancelInsert("InsertContext destroyed prematurely"); } -void FileInsertContext::commit(InsertCallback callback) { - mu_.AssertHeld(); +void FileInsertContext::commit() { + ASSERT(dispatcher()->isThreadSafe()); + ASSERT(!cancel_action_in_flight_); + ASSERT(callback_in_flight_ != nullptr); // Write the file header block now that we know the sizes of the pieces. Buffer::OwnedImpl block_buffer; - callback_in_flight_ = callback; header_block_.serializeToBuffer(block_buffer); - auto queued = file_handle_->write(block_buffer, 0, [this](absl::StatusOr write_result) { - absl::MutexLock lock(&mu_); - cancel_action_in_flight_ = nullptr; - if (!write_result.ok() || write_result.value() != CacheFileFixedBlock::size()) { - cancelInsert(writeFailureMessage("header block", write_result, CacheFileFixedBlock::size())); - return; - } - // Unlink any existing cache entry with this filename. - cancel_action_in_flight_ = cache_->asyncFileManager()->stat( - absl::StrCat(cache_->cachePath(), cache_->generateFilename(key_)), - [this](absl::StatusOr stat_result) { - absl::MutexLock lock(&mu_); - cancel_action_in_flight_ = nullptr; - size_t file_size = 0; - if (stat_result.ok()) { - file_size = stat_result.value().st_size; - } - cancel_action_in_flight_ = cache_->asyncFileManager()->unlink( - absl::StrCat(cache_->cachePath(), cache_->generateFilename(key_)), - [this, file_size](absl::Status unlink_result) { - if (unlink_result.ok()) { - cache_->trackFileRemoved(file_size); - } - // We can ignore failure of unlink - the file may or may not have previously - // existed. - absl::MutexLock lock(&mu_); - cancel_action_in_flight_ = nullptr; - // Link the file to its filename. - auto queued = file_handle_->createHardLink( - absl::StrCat(cache_->cachePath(), cache_->generateFilename(key_)), - [this](absl::Status link_result) { - absl::MutexLock lock(&mu_); - cancel_action_in_flight_ = nullptr; - if (!link_result.ok()) { - cancelInsert(absl::StrCat("failed to link file (", link_result.ToString(), - "): ", cache_->cachePath(), - cache_->generateFilename(key_))); - return; - } - ENVOY_LOG(debug, "created cache file {}", cache_->generateFilename(key_)); - callback_in_flight_(true); - callback_in_flight_ = nullptr; - uint64_t file_size = - header_block_.offsetToTrailers() + header_block_.trailerSize(); - cache_->trackFileAdded(file_size); - // By clearing cleanup before destructor, we prevent logging an error. - cleanup_ = nullptr; - }); - ASSERT(queued.ok(), queued.status().ToString()); - cancel_action_in_flight_ = queued.value(); - }); - }); - }); + auto queued = file_handle_->write( + dispatcher(), block_buffer, 0, [this](absl::StatusOr write_result) { + cancel_action_in_flight_ = nullptr; + if (!write_result.ok() || write_result.value() != CacheFileFixedBlock::size()) { + cancelInsert( + writeFailureMessage("header block", write_result, CacheFileFixedBlock::size())); + return; + } + commitMeasureExisting(); + }); ASSERT(queued.ok(), queued.status().ToString()); - cancel_action_in_flight_ = queued.value(); + cancel_action_in_flight_ = std::move(queued.value()); +} + +std::string FileInsertContext::pathAndFilename() { + return absl::StrCat(cache_->cachePath(), cache_->generateFilename(key_)); +} + +void FileInsertContext::commitMeasureExisting() { + ASSERT(!cancel_action_in_flight_); + ASSERT(callback_in_flight_ != nullptr); + cancel_action_in_flight_ = cache_->asyncFileManager()->stat( + dispatcher(), pathAndFilename(), [this](absl::StatusOr stat_result) { + cancel_action_in_flight_ = nullptr; + if (stat_result.ok()) { + commitUnlinkExisting(stat_result.value().st_size); + } else { + commitUnlinkExisting(0); + } + }); +} + +void FileInsertContext::commitUnlinkExisting(size_t file_size) { + ASSERT(!cancel_action_in_flight_); + ASSERT(callback_in_flight_ != nullptr); + cancel_action_in_flight_ = cache_->asyncFileManager()->unlink( + dispatcher(), pathAndFilename(), [this, file_size](absl::Status unlink_result) { + cancel_action_in_flight_ = nullptr; + if (unlink_result.ok()) { + cache_->trackFileRemoved(file_size); + } + commitCreateHardLink(); + }); +} + +void FileInsertContext::commitCreateHardLink() { + ASSERT(!cancel_action_in_flight_); + ASSERT(callback_in_flight_ != nullptr); + auto queued = file_handle_->createHardLink( + dispatcher(), pathAndFilename(), [this](absl::Status link_result) { + cancel_action_in_flight_ = nullptr; + if (!link_result.ok()) { + cancelInsert(absl::StrCat("failed to link file (", link_result.ToString(), + "): ", pathAndFilename())); + return; + } + ENVOY_LOG(debug, "created cache file {}", cache_->generateFilename(key_)); + succeedCurrentAction(); + uint64_t file_size = header_block_.offsetToTrailers() + header_block_.trailerSize(); + cache_->trackFileAdded(file_size); + // By clearing cleanup before destructor, we prevent logging an error. + cleanup_ = nullptr; + }); + ASSERT(queued.ok(), queued.status().ToString()); + cancel_action_in_flight_ = std::move(queued.value()); } void FileInsertContext::cancelInsert(absl::string_view error) { - mu_.AssertHeld(); if (cancel_action_in_flight_) { cancel_action_in_flight_(); cancel_action_in_flight_ = nullptr; @@ -256,12 +286,14 @@ void FileInsertContext::cancelInsert(absl::string_view error) { } } if (file_handle_) { - auto close_status = file_handle_->close([](absl::Status) {}); + auto close_status = file_handle_->close(nullptr, [](absl::Status) {}); ASSERT(close_status.ok()); file_handle_ = nullptr; } } +Event::Dispatcher* FileInsertContext::dispatcher() const { return lookup_context_->dispatcher(); } + } // namespace FileSystemHttpCache } // namespace Cache } // namespace HttpFilters diff --git a/source/extensions/http/cache/file_system_http_cache/insert_context.h b/source/extensions/http/cache/file_system_http_cache/insert_context.h index 71e6f4d77b..1f8e666533 100644 --- a/source/extensions/http/cache/file_system_http_cache/insert_context.h +++ b/source/extensions/http/cache/file_system_http_cache/insert_context.h @@ -48,15 +48,55 @@ class FileInsertContext : public InsertContext, public Logger::Loggable lookup_context_; Key key_; std::shared_ptr cache_; - absl::Mutex mu_; // guards file operations - std::shared_ptr cleanup_ ABSL_GUARDED_BY(mu_); - AsyncFileHandle file_handle_ ABSL_GUARDED_BY(mu_); - std::function callback_in_flight_ ABSL_GUARDED_BY(mu_); - CancelFunction cancel_action_in_flight_ ABSL_GUARDED_BY(mu_); - CacheFileFixedBlock header_block_ ABSL_GUARDED_BY(mu_); + std::shared_ptr cleanup_; + AsyncFileHandle file_handle_; + absl::AnyInvocable callback_in_flight_; + CancelFunction cancel_action_in_flight_; + CacheFileFixedBlock header_block_; /** * If seen_end_stream_ is not true (i.e. InsertContext has not yet delivered the @@ -69,19 +109,7 @@ class FileInsertContext : public InsertContext, public Logger::LoggableopenExistingFile( - filepath(), Common::AsyncFiles::AsyncFileManager::Mode::ReadOnly, - [this, cb](absl::StatusOr open_result) { - absl::MutexLock lock(&mu_); + dispatcher(), filepath(), Common::AsyncFiles::AsyncFileManager::Mode::ReadOnly, + [this](absl::StatusOr open_result) { cancel_action_in_flight_ = nullptr; if (!open_result.ok()) { - cache_.stats().cache_miss_.inc(); - cb(LookupResult{}, /* end_stream (ignored) = */ false); - return; + return doCacheMiss(); } ASSERT(!file_handle_); file_handle_ = std::move(open_result.value()); - auto queued = file_handle_->read( - 0, CacheFileFixedBlock::size(), - [this, cb](absl::StatusOr read_result) { - absl::MutexLock lock(&mu_); - cancel_action_in_flight_ = nullptr; - if (!read_result.ok() || - read_result.value()->length() != CacheFileFixedBlock::size()) { - invalidateCacheEntry(); - cache_.stats().cache_miss_.inc(); - cb(LookupResult{}, /* end_stream (ignored) = */ false); - return; - } - header_block_.populateFromStringView(read_result.value()->toString()); - if (!header_block_.isValid()) { - invalidateCacheEntry(); - cache_.stats().cache_miss_.inc(); - cb(LookupResult{}, /* end_stream (ignored) = */ false); - return; - } - auto queued = file_handle_->read( - header_block_.offsetToHeaders(), header_block_.headerSize(), - [this, cb](absl::StatusOr read_result) { - absl::MutexLock lock(&mu_); - cancel_action_in_flight_ = nullptr; - if (!read_result.ok() || - read_result.value()->length() != header_block_.headerSize()) { - invalidateCacheEntry(); - cache_.stats().cache_miss_.inc(); - cb(LookupResult{}, /* end_stream (ignored) = */ false); - return; - } - auto header_proto = makeCacheFileHeaderProto(*read_result.value()); - if (header_proto.headers_size() == 1 && - header_proto.headers().at(0).key() == "vary") { - auto maybe_vary_key = cache_.makeVaryKey( - key_, lookup().varyAllowList(), - absl::StrSplit(header_proto.headers().at(0).value(), ','), - lookup().requestHeaders()); - if (!maybe_vary_key.has_value()) { - cache_.stats().cache_miss_.inc(); - cb(LookupResult{}, /* end_stream (ignored) = */ false); - return; - } - key_ = maybe_vary_key.value(); - auto fh = std::move(file_handle_); - file_handle_ = nullptr; - // It should be possible to cancel close, to make this safe. - // (it should still close the file, but cancel the callback.) - auto queued = fh->close([this, cb](absl::Status) { - absl::MutexLock lock(&mu_); - // Restart getHeaders with the new key. - return getHeadersWithLock(cb); - }); - ASSERT(queued.ok(), queued.ToString()); - return; - } - cache_.stats().cache_hit_.inc(); - cb(lookup().makeLookupResult(headersFromHeaderProto(header_proto), - metadataFromHeaderProto(header_proto), - header_block_.bodySize()), - /* end_stream = */ header_block_.trailerSize() == 0 && - header_block_.bodySize() == 0); - }); - ASSERT(queued.ok(), queued.status().ToString()); - cancel_action_in_flight_ = queued.value(); - }); - ASSERT(queued.ok(), queued.status().ToString()); - cancel_action_in_flight_ = queued.value(); + getHeaderBlockFromFile(); + }); +} + +void FileLookupContext::doCacheMiss() { + cache_.stats().cache_miss_.inc(); + std::move(lookup_headers_callback_)(LookupResult{}, /* end_stream (ignored) = */ false); + lookup_headers_callback_ = nullptr; +} + +void FileLookupContext::doCacheEntryInvalid() { + invalidateCacheEntry(); + doCacheMiss(); +} + +void FileLookupContext::getHeaderBlockFromFile() { + ASSERT(dispatcher()->isThreadSafe()); + auto queued = file_handle_->read( + dispatcher(), 0, CacheFileFixedBlock::size(), + [this](absl::StatusOr read_result) { + ASSERT(dispatcher()->isThreadSafe()); + cancel_action_in_flight_ = nullptr; + if (!read_result.ok() || read_result.value()->length() != CacheFileFixedBlock::size()) { + return doCacheEntryInvalid(); + } + header_block_.populateFromStringView(read_result.value()->toString()); + if (!header_block_.isValid()) { + return doCacheEntryInvalid(); + } + getHeadersFromFile(); + }); + ASSERT(queued.ok(), queued.status().ToString()); + cancel_action_in_flight_ = std::move(queued.value()); +} + +void FileLookupContext::getHeadersFromFile() { + ASSERT(dispatcher()->isThreadSafe()); + auto queued = file_handle_->read( + dispatcher(), header_block_.offsetToHeaders(), header_block_.headerSize(), + [this](absl::StatusOr read_result) { + ASSERT(dispatcher()->isThreadSafe()); + cancel_action_in_flight_ = nullptr; + if (!read_result.ok() || read_result.value()->length() != header_block_.headerSize()) { + return doCacheEntryInvalid(); + } + auto header_proto = makeCacheFileHeaderProto(*read_result.value()); + if (header_proto.headers_size() == 1 && header_proto.headers().at(0).key() == "vary") { + auto maybe_vary_key = cache_.makeVaryKey( + key_, lookup().varyAllowList(), + absl::StrSplit(header_proto.headers().at(0).value(), ','), lookup().requestHeaders()); + if (!maybe_vary_key.has_value()) { + return doCacheMiss(); + } + key_ = maybe_vary_key.value(); + return closeFileAndGetHeadersAgainWithNewVaryKey(); + } + cache_.stats().cache_hit_.inc(); + std::move(lookup_headers_callback_)( + lookup().makeLookupResult(headersFromHeaderProto(header_proto), + metadataFromHeaderProto(header_proto), + header_block_.bodySize()), + /* end_stream = */ header_block_.trailerSize() == 0 && header_block_.bodySize() == 0); }); + ASSERT(queued.ok(), queued.status().ToString()); + cancel_action_in_flight_ = std::move(queued.value()); +} + +void FileLookupContext::closeFileAndGetHeadersAgainWithNewVaryKey() { + ASSERT(dispatcher()->isThreadSafe()); + auto queued = file_handle_->close(dispatcher(), [this](absl::Status) { + ASSERT(dispatcher()->isThreadSafe()); + file_handle_ = nullptr; + // Restart with the new key. + return tryOpenCacheFile(); + }); + ASSERT(queued.ok(), queued.status().ToString()); + cancel_action_in_flight_ = std::move(queued.value()); } void FileLookupContext::invalidateCacheEntry() { + ASSERT(dispatcher()->isThreadSafe()); + // We don't capture the cancel action here because we want these operations to continue even + // if the filter was destroyed in the meantime. For the same reason, we must not capture 'this'. cache_.asyncFileManager()->stat( - filepath(), [file = filepath(), - cache = cache_.shared_from_this()](absl::StatusOr stat_result) { + dispatcher(), filepath(), + [file = filepath(), cache = cache_.shared_from_this(), + dispatcher = dispatcher()](absl::StatusOr stat_result) { + ASSERT(dispatcher->isThreadSafe()); size_t file_size = 0; if (stat_result.ok()) { file_size = stat_result.value().st_size; } - cache->asyncFileManager()->unlink(file, [cache, file_size](absl::Status unlink_result) { - if (unlink_result.ok()) { - cache->trackFileRemoved(file_size); - } - }); + cache->asyncFileManager()->unlink(dispatcher, file, + [cache, file_size](absl::Status unlink_result) { + if (unlink_result.ok()) { + cache->trackFileRemoved(file_size); + } + }); }); } void FileLookupContext::getBody(const AdjustedByteRange& range, LookupBodyCallback&& cb) { - absl::MutexLock lock(&mu_); + ASSERT(dispatcher()->isThreadSafe()); + ASSERT(cb); ASSERT(!cancel_action_in_flight_); + ASSERT(file_handle_); auto queued = file_handle_->read( - header_block_.offsetToBody() + range.begin(), range.length(), - [this, cb, range](absl::StatusOr read_result) { - absl::MutexLock lock(&mu_); + dispatcher(), header_block_.offsetToBody() + range.begin(), range.length(), + [this, cb = std::move(cb), range](absl::StatusOr read_result) { + ASSERT(dispatcher()->isThreadSafe()); cancel_action_in_flight_ = nullptr; if (!read_result.ok() || read_result.value()->length() != range.length()) { invalidateCacheEntry(); // Calling callback with nullptr fails the request. - cb(nullptr, /* end_stream (ignored) = */ false); + std::move(cb)(nullptr, /* end_stream (ignored) = */ false); return; } - cb(std::move(read_result.value()), - /* end_stream = */ range.end() == header_block_.bodySize() && - header_block_.trailerSize() == 0); + std::move(cb)(std::move(read_result.value()), + /* end_stream = */ range.end() == header_block_.bodySize() && + header_block_.trailerSize() == 0); }); ASSERT(queued.ok(), queued.status().ToString()); - cancel_action_in_flight_ = queued.value(); + cancel_action_in_flight_ = std::move(queued.value()); } void FileLookupContext::getTrailers(LookupTrailersCallback&& cb) { + ASSERT(dispatcher()->isThreadSafe()); ASSERT(cb); - absl::MutexLock lock(&mu_); ASSERT(!cancel_action_in_flight_); - auto queued = file_handle_->read(header_block_.offsetToTrailers(), header_block_.trailerSize(), - [this, cb](absl::StatusOr read_result) { - absl::MutexLock lock(&mu_); - cancel_action_in_flight_ = nullptr; - if (!read_result.ok() || read_result.value()->length() != - header_block_.trailerSize()) { - invalidateCacheEntry(); - // There is no failure response for getTrailers, so we just - // say there were no trailers in the event of this failure. - cb(Http::ResponseTrailerMapImpl::create()); - return; - } - CacheFileTrailer trailer; - trailer.ParseFromString(read_result.value()->toString()); - cb(trailersFromTrailerProto(trailer)); - }); + ASSERT(file_handle_); + auto queued = file_handle_->read( + dispatcher(), header_block_.offsetToTrailers(), header_block_.trailerSize(), + [this, cb = std::move(cb)](absl::StatusOr read_result) { + ASSERT(dispatcher()->isThreadSafe()); + cancel_action_in_flight_ = nullptr; + if (!read_result.ok() || read_result.value()->length() != header_block_.trailerSize()) { + invalidateCacheEntry(); + // There is no failure response for getTrailers, so we just + // say there were no trailers in the event of this failure. + std::move(cb)(Http::ResponseTrailerMapImpl::create()); + return; + } + CacheFileTrailer trailer; + trailer.ParseFromString(read_result.value()->toString()); + std::move(cb)(trailersFromTrailerProto(trailer)); + }); ASSERT(queued.ok(), queued.status().ToString()); - cancel_action_in_flight_ = queued.value(); + cancel_action_in_flight_ = std::move(queued.value()); } void FileLookupContext::onDestroy() { - CancelFunction cancel; - { - absl::MutexLock lock(&mu_); - cancel = std::move(cancel_action_in_flight_); + if (cancel_action_in_flight_) { + std::move(cancel_action_in_flight_)(); cancel_action_in_flight_ = nullptr; } - while (cancel) { - // We mustn't hold the lock while calling cancel, as it can potentially wait for - // a callback to complete, and the callback might take the lock. - cancel(); - { - // It's possible that while calling cancel, another action was started - if - // that happened, we must cancel that one too! - absl::MutexLock lock(&mu_); - cancel = std::move(cancel_action_in_flight_); - cancel_action_in_flight_ = nullptr; - } - } - { - absl::MutexLock lock(&mu_); - if (file_handle_) { - auto status = file_handle_->close([](absl::Status) {}); - ASSERT(status.ok(), status.ToString()); - file_handle_ = nullptr; - } + if (file_handle_) { + auto status = file_handle_->close(nullptr, [](absl::Status) {}); + ASSERT(status.ok(), status.status().ToString()); + file_handle_ = nullptr; } } diff --git a/source/extensions/http/cache/file_system_http_cache/lookup_context.h b/source/extensions/http/cache/file_system_http_cache/lookup_context.h index db4668798f..f2454fba5b 100644 --- a/source/extensions/http/cache/file_system_http_cache/lookup_context.h +++ b/source/extensions/http/cache/file_system_http_cache/lookup_context.h @@ -19,8 +19,9 @@ using Envoy::Extensions::Common::AsyncFiles::CancelFunction; class FileLookupContext : public LookupContext { public: - FileLookupContext(FileSystemHttpCache& cache, LookupRequest&& lookup) - : cache_(cache), key_(lookup.key()), lookup_(std::move(lookup)) {} + FileLookupContext(Event::Dispatcher& dispatcher, FileSystemHttpCache& cache, + LookupRequest&& lookup) + : dispatcher_(dispatcher), cache_(cache), key_(lookup.key()), lookup_(std::move(lookup)) {} // From LookupContext void getHeaders(LookupHeadersCallback&& cb) final; @@ -34,28 +35,34 @@ class FileLookupContext : public LookupContext { const LookupRequest& lookup() const { return lookup_; } const Key& key() const { return key_; } bool workInProgress() const; + Event::Dispatcher* dispatcher() const { return &dispatcher_; } private: - void getHeadersWithLock(LookupHeadersCallback cb) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_); + void tryOpenCacheFile(); + void doCacheMiss(); + void doCacheEntryInvalid(); + void getHeaderBlockFromFile(); + void getHeadersFromFile(); + void closeFileAndGetHeadersAgainWithNewVaryKey(); // In the event that the cache failed to retrieve, remove the cache entry from the // cache so we don't keep repeating the same failure. - void invalidateCacheEntry() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_); + void invalidateCacheEntry(); - std::string filepath() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mu_); + std::string filepath(); + + Event::Dispatcher& dispatcher_; // We can safely use a reference here, because the shared_ptr to a cache is guaranteed to outlive // all filters that use it. FileSystemHttpCache& cache_; - // File actions may be initiated in the file thread or the filter thread, and cancelled or - // completed from either, therefore must be guarded by a mutex. - absl::Mutex mu_; - AsyncFileHandle file_handle_ ABSL_GUARDED_BY(mu_); - CancelFunction cancel_action_in_flight_ ABSL_GUARDED_BY(mu_); - CacheFileFixedBlock header_block_ ABSL_GUARDED_BY(mu_); - Key key_ ABSL_GUARDED_BY(mu_); + AsyncFileHandle file_handle_; + CancelFunction cancel_action_in_flight_; + CacheFileFixedBlock header_block_; + Key key_; + LookupHeadersCallback lookup_headers_callback_; const LookupRequest lookup_; }; diff --git a/source/extensions/http/custom_response/local_response_policy/local_response_policy.cc b/source/extensions/http/custom_response/local_response_policy/local_response_policy.cc index 4880fb2098..4777165781 100644 --- a/source/extensions/http/custom_response/local_response_policy/local_response_policy.cc +++ b/source/extensions/http/custom_response/local_response_policy/local_response_policy.cc @@ -39,8 +39,10 @@ LocalResponsePolicy::LocalResponsePolicy( // by this PR and will be fixed in the future. Server::GenericFactoryContextImpl generic_context(context, context.messageValidationVisitor()); if (config.has_body_format()) { - formatter_ = Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config.body_format(), - generic_context); + formatter_ = + THROW_OR_RETURN_VALUE(Formatter::SubstitutionFormatStringUtils::fromProtoConfig( + config.body_format(), generic_context), + Formatter::FormatterBasePtr); } } diff --git a/source/extensions/matching/actions/format_string/config.cc b/source/extensions/matching/actions/format_string/config.cc index ff8cb31418..3de72f9ec1 100644 --- a/source/extensions/matching/actions/format_string/config.cc +++ b/source/extensions/matching/actions/format_string/config.cc @@ -32,8 +32,9 @@ ActionFactory::createActionFactoryCb(const Protobuf::Message& proto_config, proto_config, validator); Server::GenericFactoryContextImpl generic_context(context, validator); - Formatter::FormatterConstSharedPtr formatter = - Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config, generic_context); + Formatter::FormatterConstSharedPtr formatter = THROW_OR_RETURN_VALUE( + Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config, generic_context), + Formatter::FormatterBasePtr); return [formatter]() { return std::make_unique(formatter); }; } diff --git a/test/common/api/os_sys_calls_test.cc b/test/common/api/os_sys_calls_test.cc index 126e109409..96e4d35e95 100644 --- a/test/common/api/os_sys_calls_test.cc +++ b/test/common/api/os_sys_calls_test.cc @@ -68,4 +68,19 @@ TEST(OsSyscallsTest, OpenPwritePreadFstatCloseStatUnlink) { TestEnvironment::removePath(path); } +TEST(OsSyscallsTest, SupportsIpTransparent) { + bool supported = Api::OsSysCallsSingleton::get().supportsIpTransparent( + TestEnvironment::getIpVersionsForTest()[0]); + EXPECT_FALSE(supported); +} + +TEST(OsSyscallsTest, SupportsMptcp) { + bool supported = Api::OsSysCallsSingleton::get().supportsMptcp(); + EXPECT_TRUE(supported); +} + +TEST(OsSyscallsTest, IoCtlInvalidFd) { + EXPECT_NE(0, Api::OsSysCallsSingleton::get().ioctl(0, 0, nullptr, 0, nullptr, 0, nullptr).errno_); +} + } // namespace Envoy diff --git a/test/common/formatter/substitution_format_string_test.cc b/test/common/formatter/substitution_format_string_test.cc index 6d74620d2a..e2d01dd133 100644 --- a/test/common/formatter/substitution_format_string_test.cc +++ b/test/common/formatter/substitution_format_string_test.cc @@ -47,7 +47,7 @@ TEST_F(SubstitutionFormatStringUtilsTest, TestFromProtoConfigText) { )EOF"; TestUtility::loadFromYaml(yaml, config_); - auto formatter = SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); + auto formatter = *SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); EXPECT_EQ("plain text, path=/bar/foo, code=200", formatter->formatWithContext(formatter_context_, stream_info_)); } @@ -63,7 +63,7 @@ TEST_F(SubstitutionFormatStringUtilsTest, TestFromProtoConfigJson) { )EOF"; TestUtility::loadFromYaml(yaml, config_); - auto formatter = SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); + auto formatter = *SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); const auto out_json = formatter->formatWithContext(formatter_context_, stream_info_); const std::string expected = R"EOF({ @@ -86,10 +86,11 @@ TEST_F(SubstitutionFormatStringUtilsTest, TestInvalidConfigs) { }; for (const auto& yaml : invalid_configs) { TestUtility::loadFromYaml(yaml, config_); - EXPECT_THROW_WITH_MESSAGE(SubstitutionFormatStringUtils::fromProtoConfig(config_, context_), - EnvoyException, - "Only string values, nested structs, list values and number values " - "are supported in structured access log format."); + EXPECT_THROW_WITH_MESSAGE( + SubstitutionFormatStringUtils::fromProtoConfig(config_, context_).IgnoreError(), + EnvoyException, + "Only string values, nested structs, list values and number values " + "are supported in structured access log format."); } } @@ -107,7 +108,7 @@ TEST_F(SubstitutionFormatStringUtilsTest, TestFromProtoConfigFormatterExtension) )EOF"; TestUtility::loadFromYaml(yaml, config_); - auto formatter = SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); + auto formatter = *SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); EXPECT_EQ("plain text TestFormatter", formatter->formatWithContext(formatter_context_, stream_info_)); } @@ -127,9 +128,8 @@ TEST_F(SubstitutionFormatStringUtilsTest, )EOF"; TestUtility::loadFromYaml(yaml, config_); - EXPECT_THROW_WITH_MESSAGE(SubstitutionFormatStringUtils::fromProtoConfig(config_, context_), - EnvoyException, - "Failed to create command parser: envoy.formatter.FailFormatter"); + EXPECT_EQ(SubstitutionFormatStringUtils::fromProtoConfig(config_, context_).status().message(), + "Failed to create command parser: envoy.formatter.FailFormatter"); } TEST_F(SubstitutionFormatStringUtilsTest, TestFromProtoConfigFormatterExtensionUnknown) { @@ -143,9 +143,8 @@ TEST_F(SubstitutionFormatStringUtilsTest, TestFromProtoConfigFormatterExtensionU )EOF"; TestUtility::loadFromYaml(yaml, config_); - EXPECT_THROW_WITH_MESSAGE(SubstitutionFormatStringUtils::fromProtoConfig(config_, context_), - EnvoyException, - "Formatter not found: envoy.formatter.TestFormatterUnknown"); + EXPECT_EQ(SubstitutionFormatStringUtils::fromProtoConfig(config_, context_).status().message(), + "Formatter not found: envoy.formatter.TestFormatterUnknown"); } TEST_F(SubstitutionFormatStringUtilsTest, TestFromProtoConfigJsonWithExtension) { @@ -166,7 +165,7 @@ TEST_F(SubstitutionFormatStringUtilsTest, TestFromProtoConfigJsonWithExtension) )EOF"; TestUtility::loadFromYaml(yaml, config_); - auto formatter = SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); + auto formatter = *SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); const auto out_json = formatter->formatWithContext(formatter_context_, stream_info_); const std::string expected = R"EOF({ @@ -201,7 +200,7 @@ TEST_F(SubstitutionFormatStringUtilsTest, TestFromProtoConfigJsonWithMultipleExt )EOF"; TestUtility::loadFromYaml(yaml, config_); - auto formatter = SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); + auto formatter = *SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); const auto out_json = formatter->formatWithContext(formatter_context_, stream_info_); const std::string expected = R"EOF({ @@ -225,9 +224,8 @@ TEST_F(SubstitutionFormatStringUtilsTest, TestParseFormattersWithUnknownExtensio TestUtility::loadFromYaml(yaml, proto); *entry1 = proto; - EXPECT_THROW_WITH_MESSAGE(SubstitutionFormatStringUtils::parseFormatters(config, context_), - EnvoyException, - "Formatter not found: envoy.formatter.TestFormatterUnknown"); + EXPECT_EQ(SubstitutionFormatStringUtils::parseFormatters(config, context_).status().message(), + "Formatter not found: envoy.formatter.TestFormatterUnknown"); } TEST_F(SubstitutionFormatStringUtilsTest, TestParseFormattersWithInvalidFormatter) { @@ -246,9 +244,8 @@ TEST_F(SubstitutionFormatStringUtilsTest, TestParseFormattersWithInvalidFormatte TestUtility::loadFromYaml(yaml, proto); *entry1 = proto; - EXPECT_THROW_WITH_MESSAGE(SubstitutionFormatStringUtils::parseFormatters(config, context_), - EnvoyException, - "Failed to create command parser: envoy.formatter.FailFormatter"); + EXPECT_EQ(SubstitutionFormatStringUtils::parseFormatters(config, context_).status().message(), + "Failed to create command parser: envoy.formatter.FailFormatter"); } TEST_F(SubstitutionFormatStringUtilsTest, TestParseFormattersWithSingleExtension) { @@ -267,7 +264,7 @@ TEST_F(SubstitutionFormatStringUtilsTest, TestParseFormattersWithSingleExtension TestUtility::loadFromYaml(yaml, proto); *entry1 = proto; - auto commands = SubstitutionFormatStringUtils::parseFormatters(config, context_); + auto commands = *SubstitutionFormatStringUtils::parseFormatters(config, context_); ASSERT_EQ(1, commands.size()); absl::optional max_length = {}; @@ -306,7 +303,7 @@ TEST_F(SubstitutionFormatStringUtilsTest, TestParseFormattersWithMultipleExtensi TestUtility::loadFromYaml(additional_command_yaml, additional_command_proto); *entry2 = additional_command_proto; - auto commands = SubstitutionFormatStringUtils::parseFormatters(config, context_); + auto commands = *SubstitutionFormatStringUtils::parseFormatters(config, context_); ASSERT_EQ(2, commands.size()); absl::optional max_length = {}; diff --git a/test/extensions/access_loggers/open_telemetry/access_log_impl_test.cc b/test/extensions/access_loggers/open_telemetry/access_log_impl_test.cc index 532d2b288e..db253e6680 100644 --- a/test/extensions/access_loggers/open_telemetry/access_log_impl_test.cc +++ b/test/extensions/access_loggers/open_telemetry/access_log_impl_test.cc @@ -84,7 +84,7 @@ class AccessLogTest : public testing::Test { return logger_; }); auto commands = - Formatter::SubstitutionFormatStringUtils::parseFormatters(config_.formatters(), context_); + *Formatter::SubstitutionFormatStringUtils::parseFormatters(config_.formatters(), context_); return std::make_unique(FilterPtr{filter_}, config_, tls_, logger_cache_, commands); } diff --git a/test/extensions/access_loggers/open_telemetry/substitution_formatter_test.cc b/test/extensions/access_loggers/open_telemetry/substitution_formatter_test.cc index 54fe710a47..ab51c3eb2e 100644 --- a/test/extensions/access_loggers/open_telemetry/substitution_formatter_test.cc +++ b/test/extensions/access_loggers/open_telemetry/substitution_formatter_test.cc @@ -915,7 +915,7 @@ TEST(SubstitutionFormatterTest, CELFormatterTest) { "@type": type.googleapis.com/envoy.extensions.formatter.cel.v3.Cel )EOF", otel_config); - auto commands = Formatter::SubstitutionFormatStringUtils::parseFormatters( + auto commands = *Formatter::SubstitutionFormatStringUtils::parseFormatters( otel_config.formatters(), context); OpenTelemetryFormatter formatter(otel_config.resource_attributes(), commands); diff --git a/test/extensions/bootstrap/wasm/test_data/BUILD b/test/extensions/bootstrap/wasm/test_data/BUILD index e0003964ed..53f36ba5ab 100644 --- a/test/extensions/bootstrap/wasm/test_data/BUILD +++ b/test/extensions/bootstrap/wasm/test_data/BUILD @@ -15,7 +15,7 @@ wasm_rust_binary( wasi = True, deps = [ "@proxy_wasm_rust_sdk//:proxy_wasm", - "@proxy_wasm_rust_sdk//bazel/cargo:log", + "@proxy_wasm_rust_sdk//bazel/cargo/remote:log", ], ) diff --git a/test/extensions/common/async_files/async_file_handle_thread_pool_test.cc b/test/extensions/common/async_files/async_file_handle_thread_pool_test.cc index b4e63ed44d..668ae5b98d 100644 --- a/test/extensions/common/async_files/async_file_handle_thread_pool_test.cc +++ b/test/extensions/common/async_files/async_file_handle_thread_pool_test.cc @@ -34,38 +34,48 @@ using ::testing::StrictMock; class AsyncFileHandleHelpers { public: + void resolveFileActions() { + manager_->waitForIdle(); + dispatcher_->run(Event::Dispatcher::RunType::Block); + } void close(AsyncFileHandle& handle) { - std::promise close_result; - EXPECT_OK(handle->close([&](absl::Status status) { close_result.set_value(status); })); - EXPECT_OK(close_result.get_future().get()); + absl::Status close_result; + EXPECT_OK( + handle->close(dispatcher_.get(), [&](absl::Status status) { close_result = status; })); + resolveFileActions(); + EXPECT_OK(close_result); } AsyncFileHandle createAnonymousFile() { - std::promise create_result; - manager_->createAnonymousFile(tmpdir_, [&](absl::StatusOr result) { - create_result.set_value(result.value()); - }); - return create_result.get_future().get(); + AsyncFileHandle create_result; + manager_->createAnonymousFile( + dispatcher_.get(), tmpdir_, + [&](absl::StatusOr result) { create_result = result.value(); }); + resolveFileActions(); + return create_result; } AsyncFileHandle openExistingFile(absl::string_view filename, AsyncFileManager::Mode mode) { - std::promise open_result; - manager_->openExistingFile(filename, mode, [&](absl::StatusOr result) { - open_result.set_value(result.value()); - }); - return open_result.get_future().get(); + AsyncFileHandle open_result; + manager_->openExistingFile( + dispatcher_.get(), filename, mode, + [&](absl::StatusOr result) { open_result = result.value(); }); + resolveFileActions(); + return open_result; } const char* test_tmpdir = std::getenv("TEST_TMPDIR"); std::string tmpdir_ = test_tmpdir ? test_tmpdir : "/tmp"; - std::unique_ptr singleton_manager_; - std::shared_ptr factory_; + std::unique_ptr singleton_manager_ = + std::make_unique(); + std::shared_ptr factory_ = + AsyncFileManagerFactory::singleton(singleton_manager_.get()); std::shared_ptr manager_; + Api::ApiPtr api_ = Api::createApiForTest(); + Event::DispatcherPtr dispatcher_ = api_->allocateDispatcher("test_thread"); }; class AsyncFileHandleTest : public testing::Test, public AsyncFileHandleHelpers { public: void SetUp() override { - singleton_manager_ = std::make_unique(); - factory_ = AsyncFileManagerFactory::singleton(singleton_manager_.get()); envoy::extensions::common::async_files::v3::AsyncFileManagerConfig config; config.mutable_thread_pool()->set_thread_count(1); manager_ = factory_->getAsyncFileManager(config); @@ -77,8 +87,6 @@ class AsyncFileHandleWithMockPosixTest : public testing::Test, public AsyncFileH void SetUp() override { EXPECT_CALL(mock_posix_file_operations_, supportsAllPosixFileOperations()) .WillRepeatedly(Return(true)); - singleton_manager_ = std::make_unique(); - factory_ = AsyncFileManagerFactory::singleton(singleton_manager_.get()); envoy::extensions::common::async_files::v3::AsyncFileManagerConfig config; config.mutable_thread_pool()->set_thread_count(1); manager_ = factory_->getAsyncFileManager(config, &mock_posix_file_operations_); @@ -103,49 +111,41 @@ TEST_F(AsyncFileHandleTest, WriteReadClose) { absl::StatusOr write_status, second_write_status; absl::StatusOr read_status, second_read_status; Buffer::OwnedImpl hello("hello"); - std::promise close_status; - EXPECT_OK(handle->write(hello, 0, [&](absl::StatusOr status) { + ASSERT_OK(handle->write(dispatcher_.get(), hello, 0, [&](absl::StatusOr status) { write_status = std::move(status); - // Make sure writing at an offset works - Buffer::OwnedImpl two_chars("p!"); - EXPECT_OK(handle->write(two_chars, 3, [&](absl::StatusOr status) { - second_write_status = std::move(status); - EXPECT_OK(handle->read(0, 5, [&](absl::StatusOr status) { - read_status = std::move(status); - // Verify reading at an offset. - EXPECT_OK(handle->read(2, 3, [&](absl::StatusOr status) { - second_read_status = std::move(status); - EXPECT_OK(handle->close( - [&](absl::Status status) { close_status.set_value(std::move(status)); })); - })); - })); - })); })); - ASSERT_OK(close_status.get_future().get()); - // The first write was 5 characters. + resolveFileActions(); EXPECT_THAT(write_status, IsOkAndHolds(5U)); - - // The second write was 2 characters. + Buffer::OwnedImpl two_chars("p!"); + ASSERT_OK(handle->write(dispatcher_.get(), two_chars, 3, [&](absl::StatusOr status) { + second_write_status = std::move(status); + })); + resolveFileActions(); EXPECT_THAT(second_write_status, IsOkAndHolds(2U)); - - // This should be "hello" from the first write, with the last two characters replaced with "p!" - // from the second write. - EXPECT_OK(read_status); + ASSERT_OK(handle->read(dispatcher_.get(), 0, 5, [&](absl::StatusOr status) { + read_status = std::move(status); + })); + resolveFileActions(); + ASSERT_OK(read_status); EXPECT_THAT(*read_status.value(), BufferStringEqual("help!")); - - // Second read should have three characters in it. - EXPECT_OK(second_read_status); + ASSERT_OK(handle->read(dispatcher_.get(), 2, 3, [&](absl::StatusOr status) { + second_read_status = std::move(status); + })); + resolveFileActions(); + ASSERT_OK(second_read_status); EXPECT_THAT(*second_read_status.value(), BufferStringEqual("lp!")); + close(handle); } TEST_F(AsyncFileHandleTest, LinkCreatesNamedFile) { auto handle = createAnonymousFile(); - std::promise> write_status_promise; + absl::StatusOr write_status; // Write "hello" to the anonymous file. Buffer::OwnedImpl data("hello"); - EXPECT_OK(handle->write( - data, 0, [&](absl::StatusOr status) { write_status_promise.set_value(status); })); - absl::StatusOr write_status = write_status_promise.get_future().get(); + EXPECT_OK(handle->write(dispatcher_.get(), data, 0, [&](absl::StatusOr status) { + write_status = std::move(status); + })); + resolveFileActions(); ASSERT_THAT(write_status, IsOkAndHolds(5U)); char filename[1024]; snprintf(filename, sizeof(filename), "%s/async_link_test.XXXXXX", tmpdir_.c_str()); @@ -160,12 +160,13 @@ TEST_F(AsyncFileHandleTest, LinkCreatesNamedFile) { posix.unlink(filename); // Link the anonymous file into our tmp file name. - std::promise link_status; + absl::Status link_status = absl::InternalError("not set"); std::cout << "Linking as " << filename << std::endl; - EXPECT_OK(handle->createHardLink(std::string(filename), - [&](absl::Status status) { link_status.set_value(status); })); - ASSERT_OK(link_status.get_future().get()); + EXPECT_OK(handle->createHardLink(dispatcher_.get(), std::string(filename), + [&](absl::Status status) { link_status = status; })); + resolveFileActions(); + ASSERT_OK(link_status); // Read the contents of the linked file back, raw. char fileContents[6]; fileContents[5] = '\0'; @@ -183,11 +184,10 @@ TEST_F(AsyncFileHandleTest, LinkCreatesNamedFile) { TEST_F(AsyncFileHandleTest, LinkReturnsErrorIfLinkFails) { auto handle = createAnonymousFile(); - std::promise link_status_promise; - EXPECT_OK(handle->createHardLink("/some/path/that/does/not/exist", [&](absl::Status status) { - link_status_promise.set_value(status); - })); - absl::Status link_status = link_status_promise.get_future().get(); + absl::Status link_status = absl::InternalError("not set"); + EXPECT_OK(handle->createHardLink(dispatcher_.get(), "/some/path/that/does/not/exist", + [&](absl::Status status) { link_status = std::move(status); })); + resolveFileActions(); ASSERT_EQ(absl::StatusCode::kNotFound, link_status.code()) << link_status; close(handle); } @@ -219,11 +219,11 @@ TEST_F(AsyncFileHandleTest, OpenExistingWriteOnlyFailsOnRead) { TestTmpFile tmpfile(tmpdir_); auto handle = openExistingFile(tmpfile.name(), AsyncFileManager::Mode::WriteOnly); - std::promise> read_status_promise; - EXPECT_OK(handle->read(0, 5, [&](absl::StatusOr status) { - read_status_promise.set_value(std::move(status)); + absl::StatusOr read_status; + EXPECT_OK(handle->read(dispatcher_.get(), 0, 5, [&](absl::StatusOr status) { + read_status = std::move(status); })); - absl::StatusOr read_status = read_status_promise.get_future().get(); + resolveFileActions(); ASSERT_EQ(absl::StatusCode::kFailedPrecondition, read_status.status().code()) << read_status.status(); close(handle); @@ -234,11 +234,14 @@ TEST_F(AsyncFileHandleTest, OpenExistingWriteOnlyCanWrite) { TestTmpFile tmpfile(tmpdir_); auto handle = openExistingFile(tmpfile.name(), AsyncFileManager::Mode::WriteOnly); - std::promise> write_status; + absl::StatusOr write_status; Buffer::OwnedImpl buf("nine char"); - EXPECT_OK(handle->write( - buf, 0, [&](absl::StatusOr status) { write_status.set_value(std::move(status)); })); - ASSERT_EQ(9, write_status.get_future().get().value()); + EXPECT_OK(handle->write(dispatcher_.get(), buf, 0, [&](absl::StatusOr status) { + write_status = std::move(status); + })); + resolveFileActions(); + ASSERT_OK(write_status); + EXPECT_EQ(9, write_status.value()); close(handle); } @@ -247,12 +250,12 @@ TEST_F(AsyncFileHandleTest, OpenExistingReadOnlyFailsOnWrite) { TestTmpFile tmpfile(tmpdir_); auto handle = openExistingFile(tmpfile.name(), AsyncFileManager::Mode::ReadOnly); - std::promise> write_status_promise; + absl::StatusOr write_status; Buffer::OwnedImpl buf("hello"); - EXPECT_OK(handle->write(buf, 0, [&](absl::StatusOr status) { - write_status_promise.set_value(std::move(status)); + EXPECT_OK(handle->write(dispatcher_.get(), buf, 0, [&](absl::StatusOr status) { + write_status = std::move(status); })); - auto write_status = write_status_promise.get_future().get(); + resolveFileActions(); ASSERT_EQ(absl::StatusCode::kFailedPrecondition, write_status.status().code()) << write_status.status(); close(handle); @@ -263,11 +266,13 @@ TEST_F(AsyncFileHandleTest, OpenExistingReadOnlyCanRead) { TestTmpFile tmpfile(tmpdir_); auto handle = openExistingFile(tmpfile.name(), AsyncFileManager::Mode::ReadOnly); - std::promise> read_status; - EXPECT_OK(handle->read(0, 5, [&](absl::StatusOr status) { - read_status.set_value(std::move(status)); + absl::StatusOr read_status; + EXPECT_OK(handle->read(dispatcher_.get(), 0, 5, [&](absl::StatusOr status) { + read_status = std::move(status); })); - ASSERT_EQ("hello", read_status.get_future().get().value()->toString()); + resolveFileActions(); + ASSERT_OK(read_status); + ASSERT_EQ("hello", read_status.value()->toString()); close(handle); } @@ -276,38 +281,40 @@ TEST_F(AsyncFileHandleTest, OpenExistingReadWriteCanReadAndWrite) { TestTmpFile tmpfile(tmpdir_); auto handle = openExistingFile(tmpfile.name(), AsyncFileManager::Mode::ReadWrite); - std::promise> write_status_promise; + absl::StatusOr write_status; Buffer::OwnedImpl buf("p me!"); - EXPECT_OK(handle->write(buf, 3, [&](absl::StatusOr status) { - write_status_promise.set_value(std::move(status)); + EXPECT_OK(handle->write(dispatcher_.get(), buf, 3, [&](absl::StatusOr status) { + write_status = std::move(status); })); - auto write_status = write_status_promise.get_future().get(); + resolveFileActions(); ASSERT_THAT(write_status, IsOkAndHolds(5U)); - std::promise> read_status_promise; - EXPECT_OK(handle->read(0, 8, [&](absl::StatusOr status) { - read_status_promise.set_value(std::move(status)); + absl::StatusOr read_status; + EXPECT_OK(handle->read(dispatcher_.get(), 0, 8, [&](absl::StatusOr status) { + read_status = std::move(status); })); - auto read_status = read_status_promise.get_future().get(); - EXPECT_OK(read_status); + resolveFileActions(); + ASSERT_OK(read_status); EXPECT_THAT(*read_status.value(), BufferStringEqual("help me!")); close(handle); } TEST_F(AsyncFileHandleTest, DuplicateCreatesIndependentHandle) { auto handle = createAnonymousFile(); - std::promise> duplicate_status_promise; - EXPECT_OK(handle->duplicate( - [&](absl::StatusOr status) { duplicate_status_promise.set_value(status); })); - auto duplicate_status = duplicate_status_promise.get_future().get(); + absl::StatusOr duplicate_status; + EXPECT_OK(handle->duplicate(dispatcher_.get(), [&](absl::StatusOr status) { + duplicate_status = std::move(status); + })); + resolveFileActions(); ASSERT_OK(duplicate_status); AsyncFileHandle dup_file = std::move(duplicate_status.value()); // Close the original file. close(handle); - std::promise> write_status_promise; + absl::StatusOr write_status; Buffer::OwnedImpl buf("hello"); - EXPECT_OK(dup_file->write( - buf, 0, [&](absl::StatusOr result) { write_status_promise.set_value(result); })); - auto write_status = write_status_promise.get_future().get(); + EXPECT_OK(dup_file->write(dispatcher_.get(), buf, 0, [&](absl::StatusOr result) { + write_status = std::move(result); + })); + resolveFileActions(); // writing to the duplicate file should still work. EXPECT_THAT(write_status, IsOkAndHolds(5U)); close(dup_file); @@ -320,11 +327,11 @@ TEST_F(AsyncFileHandleWithMockPosixTest, PartialReadReturnsPartialResult) { memcpy(buf, "hel", 3); return Api::SysCallSizeResult{3, 0}; }); - std::promise> read_status_promise; - EXPECT_OK(handle->read(0, 5, [&](absl::StatusOr status) { - read_status_promise.set_value(std::move(status.value())); + absl::StatusOr read_status; + EXPECT_OK(handle->read(dispatcher_.get(), 0, 5, [&](absl::StatusOr status) { + read_status = std::move(status.value()); })); - auto read_status = read_status_promise.get_future().get(); + resolveFileActions(); EXPECT_OK(read_status); EXPECT_THAT(*read_status.value(), BufferStringEqual("hel")); close(handle); @@ -344,11 +351,11 @@ TEST_F(AsyncFileHandleWithMockPosixTest, PartialWriteRetries) { .WillOnce(Return(Api::SysCallSizeResult{3, 0})); EXPECT_CALL(mock_posix_file_operations_, pwrite(_, IsMemoryMatching("lo"), 2, 3)) .WillOnce(Return(Api::SysCallSizeResult{2, 0})); - std::promise> write_status_promise; - EXPECT_OK(handle->write(write_value, 0, [&](absl::StatusOr status) { - write_status_promise.set_value(std::move(status.value())); + absl::StatusOr write_status; + EXPECT_OK(handle->write(dispatcher_.get(), write_value, 0, [&](absl::StatusOr status) { + write_status = std::move(status.value()); })); - auto write_status = write_status_promise.get_future().get(); + resolveFileActions(); EXPECT_THAT(write_status, IsOkAndHolds(5U)); close(handle); } @@ -361,9 +368,8 @@ TEST_F(AsyncFileHandleWithMockPosixTest, CancellingDuplicateInProgressClosesTheF finishing_dup.get_future().wait(); return Api::SysCallSocketResult{4242, 0}; }); - auto cancel_dup = handle->duplicate([](absl::StatusOr) { - // Callback is not called if we cancel (already validated in manager tests) - // so this is unimportant. + auto cancel_dup = handle->duplicate(dispatcher_.get(), [](absl::StatusOr) { + FAIL() << "cancelled callback should not be called"; }); entering_dup.get_future().wait(); cancel_dup.value()(); @@ -386,8 +392,8 @@ TEST_F(AsyncFileHandleWithMockPosixTest, CancellingCreateHardLinkInProgressRemov finishing_hardlink.get_future().wait(); return Api::SysCallIntResult{0, 0}; }); - auto cancel_hardlink = handle->createHardLink(filename, [](absl::Status) { - // Callback is not called if we cancel, so this is unimportant. + auto cancel_hardlink = handle->createHardLink(dispatcher_.get(), filename, [](absl::Status) { + FAIL() << "cancelled callback should not be called"; }); entering_hardlink.get_future().wait(); cancel_hardlink.value()(); @@ -410,8 +416,8 @@ TEST_F(AsyncFileHandleWithMockPosixTest, CancellingFailedCreateHardLinkInProgres finishing_hardlink.get_future().wait(); return Api::SysCallIntResult{-1, EBADF}; }); - auto cancel_hardlink = handle->createHardLink(filename, [](absl::Status) { - // Callback is not called if we cancel, so this is unimportant. + auto cancel_hardlink = handle->createHardLink(dispatcher_.get(), filename, [](absl::Status) { + FAIL() << "cancelled callback should not be called"; }); entering_hardlink.get_future().wait(); cancel_hardlink.value()(); @@ -429,11 +435,11 @@ TEST_F(AsyncFileHandleWithMockPosixTest, StatSuccessReturnsPopulatedStatStruct) *buffer = expected_stat; return Api::SysCallIntResult{0, 0}; }); - std::promise> fstat_status_promise; - EXPECT_OK(handle->stat([&](absl::StatusOr status) { - fstat_status_promise.set_value(std::move(status)); + absl::StatusOr fstat_status; + EXPECT_OK(handle->stat(dispatcher_.get(), [&](absl::StatusOr status) { + fstat_status = std::move(status); })); - auto fstat_status = fstat_status_promise.get_future().get(); + resolveFileActions(); EXPECT_THAT(fstat_status, IsOkAndHolds(testing::Field(&stat::st_size, expected_stat.st_size))); close(handle); } @@ -442,11 +448,11 @@ TEST_F(AsyncFileHandleWithMockPosixTest, StatFailureReportsError) { auto handle = createAnonymousFile(); EXPECT_CALL(mock_posix_file_operations_, fstat(_, _)) .WillOnce(Return(Api::SysCallIntResult{-1, EBADF})); - std::promise> fstat_status_promise; - EXPECT_OK(handle->stat([&](absl::StatusOr status) { - fstat_status_promise.set_value(std::move(status)); + absl::StatusOr fstat_status; + EXPECT_OK(handle->stat(dispatcher_.get(), [&](absl::StatusOr status) { + fstat_status = std::move(status); })); - auto fstat_status = fstat_status_promise.get_future().get(); + resolveFileActions(); EXPECT_THAT(fstat_status, StatusIs(absl::StatusCode::kFailedPrecondition)); close(handle); } @@ -455,9 +461,10 @@ TEST_F(AsyncFileHandleWithMockPosixTest, CloseFailureReportsError) { auto handle = createAnonymousFile(); EXPECT_CALL(mock_posix_file_operations_, close(1)) .WillOnce(Return(Api::SysCallIntResult{-1, EBADF})); - std::promise close_status_promise; - EXPECT_OK(handle->close([&](absl::Status status) { close_status_promise.set_value(status); })); - auto close_status = close_status_promise.get_future().get(); + absl::Status close_status; + EXPECT_OK(handle->close(dispatcher_.get(), + [&](absl::Status status) { close_status = std::move(status); })); + resolveFileActions(); EXPECT_EQ(absl::StatusCode::kFailedPrecondition, close_status.code()) << close_status; } @@ -465,19 +472,20 @@ TEST_F(AsyncFileHandleWithMockPosixTest, DuplicateFailureReportsError) { auto handle = createAnonymousFile(); EXPECT_CALL(mock_posix_file_operations_, duplicate(_)) .WillOnce(Return(Api::SysCallIntResult{-1, EBADF})); - std::promise> dup_status_promise; - EXPECT_OK(handle->duplicate( - [&](absl::StatusOr status) { dup_status_promise.set_value(status); })); - auto dup_status = dup_status_promise.get_future().get(); + absl::StatusOr dup_status; + EXPECT_OK(handle->duplicate(dispatcher_.get(), [&](absl::StatusOr status) { + dup_status = std::move(status); + })); + resolveFileActions(); EXPECT_THAT(dup_status, StatusIs(absl::StatusCode::kFailedPrecondition)); close(handle); } TEST_F(AsyncFileHandleWithMockPosixTest, EnqueuingActionAfterCloseReturnsError) { auto handle = createAnonymousFile(); - EXPECT_OK(handle->close([](absl::Status) {})); - auto failed_status = handle->close([](absl::Status) {}); - EXPECT_EQ(absl::StatusCode::kFailedPrecondition, failed_status.code()) << failed_status; + EXPECT_OK(handle->close(dispatcher_.get(), [](absl::Status) {})); + auto failed_status = handle->close(dispatcher_.get(), [](absl::Status) {}); + EXPECT_THAT(failed_status, StatusIs(absl::StatusCode::kFailedPrecondition)); } } // namespace AsyncFiles diff --git a/test/extensions/common/async_files/async_file_manager_thread_pool_test.cc b/test/extensions/common/async_files/async_file_manager_thread_pool_test.cc index d0c262ddf9..7956b61db2 100644 --- a/test/extensions/common/async_files/async_file_manager_thread_pool_test.cc +++ b/test/extensions/common/async_files/async_file_manager_thread_pool_test.cc @@ -38,82 +38,29 @@ enum class BlockerState { class AsyncFileActionBlockedUntilReleased : public AsyncFileActionWithResult { public: - explicit AsyncFileActionBlockedUntilReleased(std::atomic& state_out) - : AsyncFileActionWithResult([this](bool result) { onComplete(result); }), - state_out_(state_out) { - absl::MutexLock lock(&blocking_mutex_); - state_out_.store(BlockerState::Start); - } - void setState(BlockerState state) ABSL_EXCLUSIVE_LOCKS_REQUIRED(blocking_mutex_) { - stage_ = state; - state_out_.store(state); - } + using AsyncFileActionWithResult::AsyncFileActionWithResult; bool executeImpl() final { - absl::MutexLock lock(&blocking_mutex_); - ASSERT(stage_ == BlockerState::Start); - setState(BlockerState::BlockingDuringExecution); - auto condition = [this]() ABSL_EXCLUSIVE_LOCKS_REQUIRED(blocking_mutex_) { - return stage_ == BlockerState::UnblockedExecution; - }; - blocking_mutex_.Await(absl::Condition(&condition)); - setState(BlockerState::ExecutionFinished); - return true; - } - void onComplete(bool result ABSL_ATTRIBUTE_UNUSED) { - absl::MutexLock lock(&blocking_mutex_); - setState(BlockerState::BlockingDuringCallback); - auto condition = [this]() ABSL_EXCLUSIVE_LOCKS_REQUIRED(blocking_mutex_) { - return stage_ == BlockerState::UnblockedCallback; - }; - blocking_mutex_.Await(absl::Condition(&condition)); + executing_.set_value(); + bool ret = continue_executing_.get_future().wait_for(std::chrono::seconds(1)) == + std::future_status::ready; + return ret; } - bool waitUntilExecutionBlocked() ABSL_LOCKS_EXCLUDED(blocking_mutex_) { - absl::MutexLock lock(&blocking_mutex_); - auto condition = [this]() ABSL_EXCLUSIVE_LOCKS_REQUIRED(blocking_mutex_) { - return stage_ == BlockerState::BlockingDuringExecution; - }; - return blocking_mutex_.AwaitWithTimeout(absl::Condition(&condition), absl::Seconds(1)); + bool waitUntilExecutionBlocked() { + return executing_future_.wait_for(std::chrono::seconds(1)) == std::future_status::ready; } - bool waitUntilCallbackBlocked() ABSL_LOCKS_EXCLUDED(blocking_mutex_) { - absl::MutexLock lock(&blocking_mutex_); - auto condition = [this]() ABSL_EXCLUSIVE_LOCKS_REQUIRED(blocking_mutex_) { - return stage_ == BlockerState::BlockingDuringCallback; - }; - return blocking_mutex_.AwaitWithTimeout(absl::Condition(&condition), absl::Seconds(1)); + bool isStarted() { + return executing_future_.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready; } - bool unblockExecution() ABSL_LOCKS_EXCLUDED(blocking_mutex_) { - absl::MutexLock lock(&blocking_mutex_); - if (stage_ != BlockerState::BlockingDuringExecution) { - return false; - } - setState(BlockerState::UnblockedExecution); + bool unblockExecution() { + continue_executing_.set_value(); return true; } - bool unblockCallback() ABSL_LOCKS_EXCLUDED(blocking_mutex_) { - absl::MutexLock lock(&blocking_mutex_); - if (stage_ != BlockerState::BlockingDuringCallback) { - return false; - } - setState(BlockerState::UnblockedCallback); - return true; - } - bool isStarted() ABSL_LOCKS_EXCLUDED(blocking_mutex_) { - absl::MutexLock lock(&blocking_mutex_); - auto condition = [this]() ABSL_EXCLUSIVE_LOCKS_REQUIRED(blocking_mutex_) { - return stage_ != BlockerState::Start; - }; - // Very short timeout because we can be expecting to fail this. - return blocking_mutex_.AwaitWithTimeout(absl::Condition(&condition), absl::Milliseconds(30)); - } - bool doWholeFlow() ABSL_LOCKS_EXCLUDED(blocking_mutex_) { - return waitUntilExecutionBlocked() && unblockExecution() && waitUntilCallbackBlocked() && - unblockCallback(); - } + bool doWholeFlow() { return waitUntilExecutionBlocked() && unblockExecution(); } private: - absl::Mutex blocking_mutex_; - BlockerState stage_ ABSL_GUARDED_BY(blocking_mutex_) = BlockerState::Start; - std::atomic& state_out_; + std::promise executing_; + std::future executing_future_ = executing_.get_future(); + std::promise continue_executing_; }; class AsyncFileManagerTest : public testing::Test { @@ -123,6 +70,11 @@ class AsyncFileManagerTest : public testing::Test { factory_ = AsyncFileManagerFactory::singleton(singleton_manager_.get()); } + void resolveFileActions() { + manager_->waitForIdle(); + dispatcher_->run(Event::Dispatcher::RunType::Block); + } + protected: std::unique_ptr singleton_manager_; std::shared_ptr factory_; @@ -132,14 +84,16 @@ class AsyncFileManagerTest : public testing::Test { std::shared_ptr manager_; AsyncFileActionBlockedUntilReleased* blocker_[3]; - std::atomic blocker_last_state_[3]; + std::vector blocker_callback_result_ = std::vector(3); // returns the cancellation function. - std::function enqueueBlocker(int index) { - auto blocker = - std::make_shared(blocker_last_state_[index]); + CancelFunction enqueueBlocker(int index) { + auto blocker = std::make_unique( + [this, index](bool result) { blocker_callback_result_[index] = result; }); blocker_[index] = blocker.get(); - return manager_->enqueue(std::move(blocker)); + return manager_->enqueue(dispatcher_.get(), std::move(blocker)); } + Api::ApiPtr api_ = Api::createApiForTest(); + Event::DispatcherPtr dispatcher_ = api_->allocateDispatcher("test_thread"); }; TEST_F(AsyncFileManagerTest, WorksWithThreadPoolSizeZero) { @@ -151,6 +105,7 @@ TEST_F(AsyncFileManagerTest, WorksWithThreadPoolSizeZero) { EXPECT_THAT(manager_->describe(), testing::ContainsRegex("thread_pool_size = [1-9]\\d*")); enqueueBlocker(0); EXPECT_TRUE(blocker_[0]->doWholeFlow()); + resolveFileActions(); factory_.reset(); } @@ -168,9 +123,9 @@ TEST_F(AsyncFileManagerTest, ThreadsBlockAppropriately) { EXPECT_FALSE(blocker_[2]->isStarted()); ASSERT_TRUE(blocker_[0]->doWholeFlow()); // When one of the workers finishes, the third action should be able to start. - EXPECT_TRUE(blocker_[2]->isStarted()); - EXPECT_TRUE(blocker_[1]->doWholeFlow()); EXPECT_TRUE(blocker_[2]->doWholeFlow()); + EXPECT_TRUE(blocker_[1]->doWholeFlow()); + resolveFileActions(); factory_.reset(); } @@ -184,19 +139,23 @@ class AsyncFileManagerSingleThreadTest : public AsyncFileManagerTest { manager_ = factory->getAsyncFileManager(config); } -private: +protected: std::unique_ptr singleton_manager_; }; -TEST_F(AsyncFileManagerSingleThreadTest, AbortingDuringExecutionCancelsTheCallback) { - auto cancelBlocker0 = enqueueBlocker(0); +TEST_F(AsyncFileManagerSingleThreadTest, CancellingDuringExecutionCancelsTheCallback) { + EXPECT_FALSE(blocker_callback_result_[0]); + CancelFunction cancelBlocker0 = enqueueBlocker(0); + EXPECT_FALSE(blocker_callback_result_[0]); ASSERT_TRUE(blocker_[0]->waitUntilExecutionBlocked()); enqueueBlocker(1); ASSERT_FALSE(blocker_[1]->isStarted()); cancelBlocker0(); blocker_[0]->unblockExecution(); ASSERT_TRUE(blocker_[1]->doWholeFlow()); - EXPECT_EQ(BlockerState::ExecutionFinished, blocker_last_state_[0].load()); + resolveFileActions(); + EXPECT_FALSE(blocker_callback_result_[0]); + EXPECT_TRUE(blocker_callback_result_[1]); } TEST_F(AsyncFileManagerSingleThreadTest, AbortingBeforeExecutionCancelsTheExecution) { @@ -208,66 +167,42 @@ TEST_F(AsyncFileManagerSingleThreadTest, AbortingBeforeExecutionCancelsTheExecut // Blocker 1 should never start, having been cancelled before it // was popped from the queue. We can't check its internal value because // it should also have been deleted, so we can only check its output state. - EXPECT_EQ(BlockerState::Start, blocker_last_state_[1].load()); -} - -TEST_F(AsyncFileManagerSingleThreadTest, AbortingDuringCallbackBlocksUntilCallbackCompletes) { - auto cancel = enqueueBlocker(0); - blocker_[0]->waitUntilExecutionBlocked(); - blocker_[0]->unblockExecution(); - blocker_[0]->waitUntilCallbackBlocked(); - std::atomic delayed_action_occurred; - std::thread callback_unblocker([&] { - // Using future::wait_for because lint forbids us from sleeping in - // real-time, but here we're forcing a race to go a specific way, using - // real-time because there's no other practical option here. - std::promise pauser; - pauser.get_future().wait_for(std::chrono::milliseconds(50)); - delayed_action_occurred.store(true); - blocker_[0]->unblockCallback(); - }); - cancel(); - EXPECT_TRUE(delayed_action_occurred.load()); - EXPECT_EQ(BlockerState::UnblockedCallback, blocker_last_state_[0].load()); - callback_unblocker.join(); + resolveFileActions(); + EXPECT_TRUE(blocker_callback_result_[0]); + EXPECT_FALSE(blocker_callback_result_[1]); } TEST_F(AsyncFileManagerSingleThreadTest, AbortingAfterCallbackHasNoObservableEffect) { auto cancel = enqueueBlocker(0); EXPECT_TRUE(blocker_[0]->doWholeFlow()); + resolveFileActions(); cancel(); - EXPECT_EQ(BlockerState::UnblockedCallback, blocker_last_state_[0].load()); } -template class WaitForResult { -public: - std::function callback() { - return [this](T result) { saveResult(result); }; - } - void saveResult(T result) { result_.set_value(std::move(result)); } - T getResult() { return result_.get_future().get(); } - -private: - std::promise result_; -}; - TEST_F(AsyncFileManagerSingleThreadTest, CreateAnonymousFileWorks) { - WaitForResult> handle_blocker; - manager_->createAnonymousFile(tmpdir_, handle_blocker.callback()); - AsyncFileHandle handle = handle_blocker.getResult().value(); + AsyncFileHandle handle; + manager_->createAnonymousFile(dispatcher_.get(), tmpdir_, [&](absl::StatusOr h) { + handle = std::move(h.value()); + }); + resolveFileActions(); // Open a second one, to ensure we get two distinct files // (and for coverage, because the second one doesn't use the once_flag path) - WaitForResult> second_handle_blocker; - manager_->createAnonymousFile(tmpdir_, second_handle_blocker.callback()); - AsyncFileHandle second_handle = second_handle_blocker.getResult().value(); - WaitForResult close_blocker; - EXPECT_OK(handle->close(close_blocker.callback())); - absl::Status status = close_blocker.getResult(); - EXPECT_OK(status); - WaitForResult second_close_blocker; - EXPECT_OK(second_handle->close(second_close_blocker.callback())); - status = second_close_blocker.getResult(); - EXPECT_OK(status); + AsyncFileHandle second_handle; + manager_->createAnonymousFile(dispatcher_.get(), tmpdir_, [&](absl::StatusOr h) { + second_handle = std::move(h.value()); + }); + resolveFileActions(); + EXPECT_THAT(handle, testing::NotNull()); + EXPECT_THAT(second_handle, testing::NotNull()); + EXPECT_NE(handle, second_handle); + absl::Status close_result = absl::InternalError("not set"); + EXPECT_OK(handle->close(dispatcher_.get(), [&](absl::Status s) { close_result = std::move(s); })); + absl::Status second_close_result = absl::InternalError("not set"); + EXPECT_OK(second_handle->close(dispatcher_.get(), + [&](absl::Status s) { second_close_result = std::move(s); })); + resolveFileActions(); + EXPECT_OK(close_result); + EXPECT_OK(second_close_result); } TEST_F(AsyncFileManagerSingleThreadTest, OpenExistingFileStatAndUnlinkWork) { @@ -276,47 +211,53 @@ TEST_F(AsyncFileManagerSingleThreadTest, OpenExistingFileStatAndUnlinkWork) { Api::OsSysCalls& posix = Api::OsSysCallsSingleton().get(); auto fd = posix.mkstemp(filename); posix.close(fd.return_value_); - WaitForResult> handle_blocker; - manager_->openExistingFile(filename, AsyncFileManager::Mode::ReadWrite, - handle_blocker.callback()); - AsyncFileHandle handle = handle_blocker.getResult().value(); - WaitForResult close_blocker; - EXPECT_OK(handle->close(close_blocker.callback())); - absl::Status status = close_blocker.getResult(); - EXPECT_OK(status); - WaitForResult> stat_blocker; - manager_->stat(filename, stat_blocker.callback()); - absl::StatusOr stat_result = stat_blocker.getResult(); + AsyncFileHandle handle; + manager_->openExistingFile( + dispatcher_.get(), filename, AsyncFileManager::Mode::ReadWrite, + [&](absl::StatusOr h) { handle = std::move(h.value()); }); + resolveFileActions(); + absl::Status close_result; + EXPECT_OK(handle->close(dispatcher_.get(), [&](absl::Status s) { close_result = std::move(s); })); + EXPECT_OK(close_result); + absl::StatusOr stat_result; + manager_->stat(dispatcher_.get(), filename, + [&](absl::StatusOr result) { stat_result = std::move(result); }); + resolveFileActions(); EXPECT_OK(stat_result); EXPECT_EQ(0, stat_result.value().st_size); - WaitForResult unlink_blocker; - manager_->unlink(filename, unlink_blocker.callback()); - status = unlink_blocker.getResult(); - EXPECT_OK(status); + absl::Status unlink_result = absl::InternalError("not set"); + manager_->unlink(dispatcher_.get(), filename, + [&](absl::Status s) { unlink_result = std::move(s); }); + resolveFileActions(); + EXPECT_OK(unlink_result); + // Make sure unlink deleted the file. struct stat s; EXPECT_EQ(-1, stat(filename, &s)); } TEST_F(AsyncFileManagerSingleThreadTest, OpenExistingFileFailsForNonexistent) { - WaitForResult> handle_blocker; - manager_->openExistingFile(absl::StrCat(tmpdir_, "/nonexistent_file"), - AsyncFileManager::Mode::ReadWrite, handle_blocker.callback()); - absl::Status status = handle_blocker.getResult().status(); - EXPECT_THAT(status, HasStatusCode(absl::StatusCode::kNotFound)); + absl::StatusOr handle_result; + manager_->openExistingFile(dispatcher_.get(), absl::StrCat(tmpdir_, "/nonexistent_file"), + AsyncFileManager::Mode::ReadWrite, + [&](absl::StatusOr r) { handle_result = r; }); + resolveFileActions(); + EXPECT_THAT(handle_result, HasStatusCode(absl::StatusCode::kNotFound)); } TEST_F(AsyncFileManagerSingleThreadTest, StatFailsForNonexistent) { - WaitForResult> stat_blocker; - manager_->stat(absl::StrCat(tmpdir_, "/nonexistent_file"), stat_blocker.callback()); - absl::StatusOr result = stat_blocker.getResult(); - EXPECT_THAT(result, HasStatusCode(absl::StatusCode::kNotFound)); + absl::StatusOr stat_result; + manager_->stat(dispatcher_.get(), absl::StrCat(tmpdir_, "/nonexistent_file"), + [&](absl::StatusOr r) { stat_result = std::move(r); }); + resolveFileActions(); + EXPECT_THAT(stat_result, HasStatusCode(absl::StatusCode::kNotFound)); } TEST_F(AsyncFileManagerSingleThreadTest, UnlinkFailsForNonexistent) { - WaitForResult handle_blocker; - manager_->unlink(absl::StrCat(tmpdir_, "/nonexistent_file"), handle_blocker.callback()); - absl::Status status = handle_blocker.getResult(); - EXPECT_THAT(status, HasStatusCode(absl::StatusCode::kNotFound)); + absl::Status unlink_result; + manager_->unlink(dispatcher_.get(), absl::StrCat(tmpdir_, "/nonexistent_file"), + [&](absl::Status s) { unlink_result = std::move(s); }); + resolveFileActions(); + EXPECT_THAT(unlink_result, HasStatusCode(absl::StatusCode::kNotFound)); } } // namespace AsyncFiles diff --git a/test/extensions/common/async_files/async_file_manager_thread_pool_with_mocks_test.cc b/test/extensions/common/async_files/async_file_manager_thread_pool_with_mocks_test.cc index 03a854da6b..8e05843621 100644 --- a/test/extensions/common/async_files/async_file_manager_thread_pool_with_mocks_test.cc +++ b/test/extensions/common/async_files/async_file_manager_thread_pool_with_mocks_test.cc @@ -46,68 +46,48 @@ class AsyncFileManagerWithMockFilesTest : public ::testing::Test { manager_ = factory_->getAsyncFileManager(config, &mock_posix_file_operations_); } + void resolveFileActions(int cycles = 1) { + for (int i = 0; i < cycles; i++) { + manager_->waitForIdle(); + dispatcher_->run(Event::Dispatcher::RunType::Block); + } + } + protected: std::unique_ptr singleton_manager_; StrictMock mock_posix_file_operations_; std::shared_ptr factory_; std::shared_ptr manager_; static constexpr absl::string_view tmpdir_{"/mocktmp"}; + Api::ApiPtr api_ = Api::createApiForTest(); + Event::DispatcherPtr dispatcher_ = api_->allocateDispatcher("test_thread"); }; -TEST_F(AsyncFileManagerWithMockFilesTest, ChainedOperationsWorkAndSkipQueue) { - int fd = 1; - std::promise write_blocker; - EXPECT_CALL(mock_posix_file_operations_, open(Eq(tmpdir_), O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR)) - .WillOnce(Return(Api::SysCallIntResult{fd, 0})); - EXPECT_CALL(mock_posix_file_operations_, pwrite(fd, _, 5, 0)) - .WillOnce([&write_blocker](int, const void*, size_t, off_t) { - write_blocker.get_future().wait(); - return Api::SysCallSizeResult{5, 0}; - }); - // Chain open/write/close. Write will block because of the mock expectation. - manager_->createAnonymousFile(tmpdir_, [&](absl::StatusOr result) { - AsyncFileHandle handle = result.value(); - Buffer::OwnedImpl buf("hello"); - EXPECT_OK(handle->write(buf, 0, [handle](absl::StatusOr result) { - EXPECT_THAT(result, IsOkAndHolds(5U)); - EXPECT_OK(handle->close([](absl::Status result) { EXPECT_OK(result); })); - })); - }); - // Separately queue another action. - std::promise did_second_action; - manager_->whenReady([&](absl::Status) { did_second_action.set_value(); }); - auto second_action_future = did_second_action.get_future(); - // Ensure that the second action didn't get a turn while the file operations are still blocking. - EXPECT_EQ(std::future_status::timeout, - second_action_future.wait_for(std::chrono::milliseconds(1))); - EXPECT_CALL(mock_posix_file_operations_, close(fd)).WillOnce(Return(Api::SysCallIntResult{0, 0})); - // Unblock the write. - write_blocker.set_value(); - // Ensure that the second action does get a turn after the file operations completed. - EXPECT_EQ(std::future_status::ready, second_action_future.wait_for(std::chrono::seconds(1))); -} - TEST_F(AsyncFileManagerWithMockFilesTest, CancellingAQueuedActionPreventsItFromExecuting) { std::promise ready; - // Add a blocking action so we can guarantee cancel is called before unlink begins. - manager_->whenReady([&](absl::Status) { ready.get_future().wait(); }); + EXPECT_CALL(mock_posix_file_operations_, stat(_, _)).WillOnce([&](const char*, struct stat*) { + ready.get_future().wait(); + return Api::SysCallIntResult{0, 0}; + }); + // Queue a blocking action so we can guarantee cancel is called before unlink begins. + manager_->stat(dispatcher_.get(), tmpdir_, [&](absl::StatusOr) {}); // Ensure that unlink doesn't get called. EXPECT_CALL(mock_posix_file_operations_, unlink(_)).Times(0); - auto cancel_unlink = manager_->unlink("irrelevant", [](absl::Status) {}); + auto cancel_unlink = manager_->unlink(dispatcher_.get(), "irrelevant", [](absl::Status) { + FAIL() << "canceled action should not call callback"; + }); cancel_unlink(); - std::promise done; - // Add a notifying action so we can ensure that the unlink action was passed by the time - // the test ends. - manager_->whenReady([&](absl::Status) { done.set_value(); }); ready.set_value(); - done.get_future().wait(); + resolveFileActions(); } TEST_F(AsyncFileManagerWithMockFilesTest, CancellingACompletedActionDoesNothingImportant) { - std::promise ready; - auto cancel = manager_->whenReady([&](absl::Status) { ready.set_value(); }); - ready.get_future().wait(); - std::this_thread::yield(); + EXPECT_CALL(mock_posix_file_operations_, stat(_, _)); + auto cancel = + manager_->stat(dispatcher_.get(), "irrelevant", [&](absl::StatusOr) {}); + resolveFileActions(); + // This is to make sure cancel() doesn't end up with a dangling pointer or something + // when the action is resolved. cancel(); } @@ -122,14 +102,14 @@ TEST_F(AsyncFileManagerWithMockFilesTest, allow_open_to_finish.get_future().wait(); return Api::SysCallIntResult{fd, 0}; }); - std::atomic callback_was_called{false}; - // Queue opening the file, record if the callback was called (it shouldn't be). + EXPECT_CALL(mock_posix_file_operations_, stat(_, _)); auto cancelOpen = manager_->createAnonymousFile( - tmpdir_, - [&callback_was_called](absl::StatusOr) { callback_was_called.store(true); }); + dispatcher_.get(), tmpdir_, + [&](absl::StatusOr) { FAIL() << "callback should not be called"; }); // Separately queue another action. - std::promise did_second_action; - manager_->whenReady([&](absl::Status) { did_second_action.set_value(true); }); + bool did_second_action = false; + manager_->stat(dispatcher_.get(), "", + [&](absl::StatusOr) { did_second_action = true; }); // Wait for the open operation to be entered. wait_for_open_to_be_executing.get_future().wait(); // Cancel the open request (but too late to actually stop it!) @@ -138,12 +118,10 @@ TEST_F(AsyncFileManagerWithMockFilesTest, EXPECT_CALL(mock_posix_file_operations_, close(fd)).WillOnce(Return(Api::SysCallIntResult{0, 0})); // Allow the open operation to complete. allow_open_to_finish.set_value(); + resolveFileActions(); // Ensure that the second action does get a turn after the file operation relinquishes the thread. // (This also ensures that the file operation reached the end, so the file should be closed now.) - ASSERT_EQ(std::future_status::ready, - did_second_action.get_future().wait_for(std::chrono::milliseconds(100))); - // Ensure the callback for the open operation was *not* called, because it was cancelled. - EXPECT_FALSE(callback_was_called.load()); + EXPECT_TRUE(did_second_action); } TEST_F(AsyncFileManagerWithMockFilesTest, @@ -157,14 +135,14 @@ TEST_F(AsyncFileManagerWithMockFilesTest, allow_open_to_finish.get_future().wait(); return Api::SysCallIntResult{fd, 0}; }); - std::atomic callback_was_called{false}; - // Queue opening the file, record if the callback was called (it shouldn't be). + EXPECT_CALL(mock_posix_file_operations_, stat(_, _)); auto cancelOpen = manager_->openExistingFile( - filename, AsyncFileManager::Mode::ReadWrite, - [&callback_was_called](absl::StatusOr) { callback_was_called.store(true); }); + dispatcher_.get(), filename, AsyncFileManager::Mode::ReadWrite, + [&](absl::StatusOr) { FAIL() << "callback should not be called"; }); // Separately queue another action. - std::promise did_second_action; - manager_->whenReady([&](absl::Status) { did_second_action.set_value(true); }); + bool did_second_action; + manager_->stat(dispatcher_.get(), "irrelevant", + [&](absl::StatusOr) { did_second_action = true; }); // Wait for the open operation to be entered. wait_for_open_to_be_executing.get_future().wait(); // Cancel the open request (but too late to actually stop it!) @@ -173,12 +151,57 @@ TEST_F(AsyncFileManagerWithMockFilesTest, EXPECT_CALL(mock_posix_file_operations_, close(fd)).WillOnce(Return(Api::SysCallIntResult{0, 0})); // Allow the open operation to complete. allow_open_to_finish.set_value(); + resolveFileActions(); // Ensure that the second action does get a turn after the file operation relinquishes the thread. // (This also ensures that the file operation reached the end, so the file should be closed now.) - ASSERT_EQ(std::future_status::ready, - did_second_action.get_future().wait_for(std::chrono::milliseconds(100))); - // Ensure the callback for the open operation was *not* called, because it was cancelled. - EXPECT_FALSE(callback_was_called.load()); + EXPECT_TRUE(did_second_action); +} + +TEST_F(AsyncFileManagerWithMockFilesTest, CloseActionExecutesEvenIfCancelled) { + int fd = 1; + // First do a successful open so we have a file handle. + EXPECT_CALL(mock_posix_file_operations_, open(Eq(tmpdir_), O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR)) + .WillOnce(Return(Api::SysCallIntResult{fd, 0})); + AsyncFileHandle handle; + manager_->createAnonymousFile( + dispatcher_.get(), tmpdir_, + [&](absl::StatusOr result) { handle = std::move(result.value()); }); + resolveFileActions(); + ASSERT_THAT(handle, testing::NotNull()); + EXPECT_CALL(mock_posix_file_operations_, close(fd)); + CancelFunction cancel = handle->close(nullptr, [](absl::Status) {}).value(); + cancel(); + resolveFileActions(); +} + +TEST_F(AsyncFileManagerWithMockFilesTest, CancellingBeforeCallbackUndoesActionsWithSideEffects) { + int fd = 1; + // First do a successful open so we have a file handle. + EXPECT_CALL(mock_posix_file_operations_, open(Eq(tmpdir_), O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR)) + .WillOnce(Return(Api::SysCallIntResult{fd, 0})); + AsyncFileHandle handle; + manager_->createAnonymousFile( + dispatcher_.get(), tmpdir_, + [&](absl::StatusOr result) { handle = std::move(result.value()); }); + resolveFileActions(); + ASSERT_THAT(handle, testing::NotNull()); + EXPECT_CALL(mock_posix_file_operations_, linkat(fd, _, _, Eq(tmpdir_), _)); + CancelFunction cancel_hard_link = + handle + ->createHardLink( + dispatcher_.get(), tmpdir_, + [&](absl::Status) { FAIL() << "callback should not be called in this test"; }) + .value(); + // wait for the manager to post to the dispatcher, but don't consume it yet. + manager_->waitForIdle(); + // cancel while it's in the dispatcher queue. + cancel_hard_link(); + // Cancellation should remove the link because the callback was not executed. + EXPECT_CALL(mock_posix_file_operations_, unlink(Eq(tmpdir_))); + resolveFileActions(); + EXPECT_CALL(mock_posix_file_operations_, close(fd)).WillOnce(Return(Api::SysCallIntResult{0, 0})); + ASSERT_OK(handle->close(nullptr, [](absl::Status) {})); + resolveFileActions(); } TEST_F(AsyncFileManagerWithMockFilesTest, OpenFailureInCreateAnonymousReturnsAnError) { @@ -188,22 +211,23 @@ TEST_F(AsyncFileManagerWithMockFilesTest, OpenFailureInCreateAnonymousReturnsAnE EXPECT_CALL(mock_posix_file_operations_, open(Eq(tmpdir_), O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR)) .WillOnce(Return(Api::SysCallIntResult{fd, 0})); EXPECT_CALL(mock_posix_file_operations_, close(fd)).WillOnce(Return(Api::SysCallIntResult{0, 0})); - std::promise first_open_was_called; - manager_->createAnonymousFile(tmpdir_, [&](absl::StatusOr result) { - EXPECT_OK(result.value()->close([](absl::Status) {})); - first_open_was_called.set_value(); - }); - // We have to synchronize on this to avoid racily adding a different matching expectation for the - // first 'open'. - first_open_was_called.get_future().wait(); + AsyncFileHandle handle; + manager_->createAnonymousFile( + dispatcher_.get(), tmpdir_, + [&](absl::StatusOr result) { handle = std::move(result.value()); }); + resolveFileActions(); + ASSERT_THAT(handle, testing::NotNull()); + EXPECT_OK(handle->close(nullptr, [](absl::Status) {})); + resolveFileActions(); EXPECT_CALL(mock_posix_file_operations_, open(Eq(tmpdir_), O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR)) .WillOnce(Return(Api::SysCallIntResult{-1, EMFILE})); // Capture the result of the second open call, to verify that the error code came through. - std::promise captured_result; - manager_->createAnonymousFile(tmpdir_, [&](absl::StatusOr result) { - captured_result.set_value(result.status()); - }); - EXPECT_EQ(absl::StatusCode::kResourceExhausted, captured_result.get_future().get().code()); + absl::Status captured_result; + manager_->createAnonymousFile( + dispatcher_.get(), tmpdir_, + [&](absl::StatusOr result) { captured_result = result.status(); }); + resolveFileActions(); + EXPECT_EQ(absl::StatusCode::kResourceExhausted, captured_result.code()) << captured_result; } TEST_F(AsyncFileManagerWithMockFilesTest, CreateAnonymousFallbackMkstempReturnsAnErrorOnFailure) { @@ -212,11 +236,12 @@ TEST_F(AsyncFileManagerWithMockFilesTest, CreateAnonymousFallbackMkstempReturnsA EXPECT_CALL(mock_posix_file_operations_, mkstemp(Eq(std::string(tmpdir_) + "/buffer.XXXXXX"))) .WillOnce(Return(Api::SysCallIntResult{-1, EMFILE})); // Capture the result of the open call, to verify that the error code came through. - std::promise captured_result; - manager_->createAnonymousFile(tmpdir_, [&](absl::StatusOr result) { - captured_result.set_value(result.status()); - }); - EXPECT_EQ(absl::StatusCode::kResourceExhausted, captured_result.get_future().get().code()); + absl::Status captured_result; + manager_->createAnonymousFile( + dispatcher_.get(), tmpdir_, + [&](absl::StatusOr result) { captured_result = result.status(); }); + resolveFileActions(); + EXPECT_EQ(absl::StatusCode::kResourceExhausted, captured_result.code()) << captured_result; } TEST_F(AsyncFileManagerWithMockFilesTest, CreateAnonymousFallbackReturnsAnErrorIfPathTooLong) { @@ -228,11 +253,12 @@ TEST_F(AsyncFileManagerWithMockFilesTest, CreateAnonymousFallbackReturnsAnErrorI open(Eq(too_long_tmpdir), O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR)) .WillOnce(Return(Api::SysCallIntResult{-1, EBADF})); // Capture the result of the open call, to verify that the error code came through. - std::promise captured_result; - manager_->createAnonymousFile(too_long_tmpdir, [&](absl::StatusOr result) { - captured_result.set_value(result.status()); - }); - EXPECT_EQ(absl::StatusCode::kInvalidArgument, captured_result.get_future().get().code()); + absl::Status captured_result; + manager_->createAnonymousFile( + dispatcher_.get(), too_long_tmpdir, + [&](absl::StatusOr result) { captured_result = result.status(); }); + resolveFileActions(); + EXPECT_EQ(absl::StatusCode::kInvalidArgument, captured_result.code()) << captured_result; } TEST_F(AsyncFileManagerWithMockFilesTest, @@ -255,11 +281,12 @@ TEST_F(AsyncFileManagerWithMockFilesTest, EXPECT_CALL(mock_posix_file_operations_, unlink(Eq(absl::StrCat(tmpdir_, "/buffer.ABCDEF")))) .WillOnce(Return(Api::SysCallIntResult{0, 0})); } - std::promise captured_result; - manager_->createAnonymousFile(tmpdir_, [&](absl::StatusOr result) { - captured_result.set_value(result.status()); - }); - EXPECT_EQ(absl::StatusCode::kUnimplemented, captured_result.get_future().get().code()); + absl::Status captured_result; + manager_->createAnonymousFile( + dispatcher_.get(), tmpdir_, + [&](absl::StatusOr result) { captured_result = result.status(); }); + resolveFileActions(); + EXPECT_EQ(absl::StatusCode::kUnimplemented, captured_result.code()) << captured_result; } TEST_F(AsyncFileManagerWithMockFilesTest, @@ -280,15 +307,17 @@ TEST_F(AsyncFileManagerWithMockFilesTest, EXPECT_CALL(mock_posix_file_operations_, close(fd)) .WillOnce(Return(Api::SysCallIntResult{0, 0})); } - std::promise callback_complete; - manager_->createAnonymousFile(tmpdir_, [&](absl::StatusOr result) { - EXPECT_OK(result); - EXPECT_OK(result.value()->close([&](absl::Status result) { - EXPECT_OK(result); - callback_complete.set_value(); - })); - }); - callback_complete.get_future().wait(); + bool callbacks_complete = false; + manager_->createAnonymousFile( + dispatcher_.get(), tmpdir_, [&](absl::StatusOr result) { + EXPECT_OK(result); + EXPECT_OK(result.value()->close(dispatcher_.get(), [&](absl::Status result) { + EXPECT_OK(result); + callbacks_complete = true; + })); + }); + resolveFileActions(2); + EXPECT_TRUE(callbacks_complete); } } // namespace AsyncFiles diff --git a/test/extensions/common/async_files/mocks.cc b/test/extensions/common/async_files/mocks.cc index 347f61af0a..69e60d6d97 100644 --- a/test/extensions/common/async_files/mocks.cc +++ b/test/extensions/common/async_files/mocks.cc @@ -11,69 +11,88 @@ using ::testing::_; MockAsyncFileContext::MockAsyncFileContext(std::shared_ptr manager) : manager_(manager) { - ON_CALL(*this, stat(_)) - .WillByDefault([this](std::function)> on_complete) { - return manager_->enqueue( - std::shared_ptr(new TypedMockAsyncFileAction(on_complete))); + ON_CALL(*this, stat(_, _)) + .WillByDefault([this](Event::Dispatcher* dispatcher, + absl::AnyInvocable)> on_complete) { + return manager_->enqueue(dispatcher, + std::unique_ptr( + new TypedMockAsyncFileAction(std::move(on_complete)))); }); - ON_CALL(*this, createHardLink(_, _)) - .WillByDefault([this](absl::string_view, std::function on_complete) { - return manager_->enqueue( - std::shared_ptr(new TypedMockAsyncFileAction(on_complete))); + ON_CALL(*this, createHardLink(_, _, _)) + .WillByDefault([this](Event::Dispatcher* dispatcher, absl::string_view, + absl::AnyInvocable on_complete) { + return manager_->enqueue(dispatcher, + std::unique_ptr( + new TypedMockAsyncFileAction(std::move(on_complete)))); }); - EXPECT_CALL(*this, close(_)).WillOnce([this](std::function on_complete) { - manager_->enqueue( - std::shared_ptr(new TypedMockAsyncFileAction(on_complete))); - return absl::OkStatus(); - }); - ON_CALL(*this, read(_, _, _)) - .WillByDefault([this](off_t, size_t, - std::function)> on_complete) { - return manager_->enqueue( - std::shared_ptr(new TypedMockAsyncFileAction(on_complete))); + EXPECT_CALL(*this, close(_, _)) + .WillOnce([this](Event::Dispatcher* dispatcher, + absl::AnyInvocable on_complete) mutable { + return manager_->enqueue(dispatcher, + std::unique_ptr( + new TypedMockAsyncFileAction(std::move(on_complete)))); }); - ON_CALL(*this, write(_, _, _)) - .WillByDefault([this](Buffer::Instance&, off_t, - std::function)> on_complete) { - return manager_->enqueue( - std::shared_ptr(new TypedMockAsyncFileAction(on_complete))); + ON_CALL(*this, read(_, _, _, _)) + .WillByDefault( + [this](Event::Dispatcher* dispatcher, off_t, size_t, + absl::AnyInvocable)> on_complete) { + return manager_->enqueue(dispatcher, + std::unique_ptr( + new TypedMockAsyncFileAction(std::move(on_complete)))); + }); + ON_CALL(*this, write(_, _, _, _)) + .WillByDefault([this](Event::Dispatcher* dispatcher, Buffer::Instance&, off_t, + absl::AnyInvocable)> on_complete) { + return manager_->enqueue(dispatcher, + std::unique_ptr( + new TypedMockAsyncFileAction(std::move(on_complete)))); }); - ON_CALL(*this, duplicate(_)) + ON_CALL(*this, duplicate(_, _)) .WillByDefault( - [this]( - std::function>)> on_complete) { - return manager_->enqueue( - std::shared_ptr(new TypedMockAsyncFileAction(on_complete))); + [this](Event::Dispatcher* dispatcher, + absl::AnyInvocable>)> + on_complete) { + return manager_->enqueue(dispatcher, + std::unique_ptr( + new TypedMockAsyncFileAction(std::move(on_complete)))); }); }; MockAsyncFileManager::MockAsyncFileManager() { - ON_CALL(*this, enqueue(_)).WillByDefault([this](const std::shared_ptr action) { - queue_.push_back(std::dynamic_pointer_cast(action)); - return [this]() { mockCancel(); }; - }); - ON_CALL(*this, stat(_, _)) + ON_CALL(*this, enqueue(_, _)) .WillByDefault( - [this](absl::string_view, std::function)> on_complete) { - return enqueue( - std::shared_ptr(new TypedMockAsyncFileAction(on_complete))); + [this](Event::Dispatcher* dispatcher, std::unique_ptr action) { + auto entry = QueuedAction{std::move(action), dispatcher}; + auto cancel_func = [this, state = entry.state_]() { + state->store(QueuedAction::State::Cancelled); + mockCancel(); + }; + queue_.push(std::move(entry)); + return cancel_func; }); - ON_CALL(*this, createAnonymousFile(_, _)) - .WillByDefault([this](absl::string_view, - std::function)> on_complete) { - return enqueue( - std::shared_ptr(new TypedMockAsyncFileAction(on_complete))); + ON_CALL(*this, stat(_, _, _)) + .WillByDefault([this](Event::Dispatcher* dispatcher, absl::string_view, + absl::AnyInvocable)> on_complete) { + return enqueue(dispatcher, std::unique_ptr( + new TypedMockAsyncFileAction(std::move(on_complete)))); + }); + ON_CALL(*this, createAnonymousFile(_, _, _)) + .WillByDefault([this](Event::Dispatcher* dispatcher, absl::string_view, + absl::AnyInvocable)> on_complete) { + return enqueue(dispatcher, std::unique_ptr( + new TypedMockAsyncFileAction(std::move(on_complete)))); }); - ON_CALL(*this, openExistingFile(_, _, _)) - .WillByDefault([this](absl::string_view, Mode, - std::function)> on_complete) { - return enqueue( - std::shared_ptr(new TypedMockAsyncFileAction(on_complete))); + ON_CALL(*this, openExistingFile(_, _, _, _)) + .WillByDefault([this](Event::Dispatcher* dispatcher, absl::string_view, Mode, + absl::AnyInvocable)> on_complete) { + return enqueue(dispatcher, std::unique_ptr( + new TypedMockAsyncFileAction(std::move(on_complete)))); }); - ON_CALL(*this, unlink(_, _)) - .WillByDefault([this](absl::string_view, std::function on_complete) { - return enqueue( - std::shared_ptr(new TypedMockAsyncFileAction(on_complete))); + ON_CALL(*this, unlink(_, _, _)) + .WillByDefault([this](Event::Dispatcher* dispatcher, absl::string_view, + absl::AnyInvocable on_complete) { + return enqueue(dispatcher, std::unique_ptr( + new TypedMockAsyncFileAction(std::move(on_complete)))); }); } diff --git a/test/extensions/common/async_files/mocks.h b/test/extensions/common/async_files/mocks.h index f5f62d6fae..0c890fe95e 100644 --- a/test/extensions/common/async_files/mocks.h +++ b/test/extensions/common/async_files/mocks.h @@ -1,8 +1,11 @@ #pragma once +#include + #include "source/extensions/common/async_files/async_file_handle.h" #include "source/extensions/common/async_files/async_file_manager.h" #include "source/extensions/common/async_files/async_file_manager_factory.h" +#include "source/extensions/common/async_files/async_file_manager_thread_pool.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -23,18 +26,23 @@ class MockAsyncFileContext : public Extensions::Common::AsyncFiles::AsyncFileCon // These can be consumed by calling MockAsyncFileManager::nextActionCompletes // with the desired parameter to the on_complete callback. MOCK_METHOD(absl::StatusOr, stat, - (std::function)> on_complete)); + (Event::Dispatcher * dispatcher, + absl::AnyInvocable)> on_complete)); MOCK_METHOD(absl::StatusOr, createHardLink, - (absl::string_view filename, std::function on_complete)); - MOCK_METHOD(absl::Status, close, (std::function on_complete)); + (Event::Dispatcher * dispatcher, absl::string_view filename, + absl::AnyInvocable on_complete)); + MOCK_METHOD(absl::StatusOr, close, + (Event::Dispatcher * dispatcher, absl::AnyInvocable on_complete)); MOCK_METHOD(absl::StatusOr, read, - (off_t offset, size_t length, - std::function)> on_complete)); + (Event::Dispatcher * dispatcher, off_t offset, size_t length, + absl::AnyInvocable)> on_complete)); MOCK_METHOD(absl::StatusOr, write, - (Buffer::Instance & contents, off_t offset, - std::function)> on_complete)); - MOCK_METHOD(absl::StatusOr, duplicate, - (std::function>)> on_complete)); + (Event::Dispatcher * dispatcher, Buffer::Instance& contents, off_t offset, + absl::AnyInvocable)> on_complete)); + MOCK_METHOD( + absl::StatusOr, duplicate, + (Event::Dispatcher * dispatcher, + absl::AnyInvocable>)> on_complete)); private: std::shared_ptr manager_; @@ -50,7 +58,8 @@ class MockAsyncFileAction : public AsyncFileAction { template class TypedMockAsyncFileAction : public MockAsyncFileAction { public: - explicit TypedMockAsyncFileAction(T on_complete) : on_complete_(on_complete) {} + explicit TypedMockAsyncFileAction(T on_complete) : on_complete_(std::move(on_complete)) {} + void onComplete() override {} T on_complete_; std::string describe() const override { return typeid(T).name(); } }; @@ -60,17 +69,20 @@ class MockAsyncFileManager : public Extensions::Common::AsyncFiles::AsyncFileMan MockAsyncFileManager(); // The default behavior of the methods that would enqueue an action is to enqueue a mock action. MOCK_METHOD(CancelFunction, createAnonymousFile, - (absl::string_view path, - std::function)> on_complete)); + (Event::Dispatcher * dispatcher, absl::string_view path, + absl::AnyInvocable)> on_complete)); MOCK_METHOD(CancelFunction, openExistingFile, - (absl::string_view filename, Mode mode, - std::function)> on_complete)); + (Event::Dispatcher * dispatcher, absl::string_view filename, Mode mode, + absl::AnyInvocable)> on_complete)); MOCK_METHOD(CancelFunction, stat, - (absl::string_view filename, - std::function)> on_complete)); + (Event::Dispatcher * dispatcher, absl::string_view filename, + absl::AnyInvocable)> on_complete)); MOCK_METHOD(CancelFunction, unlink, - (absl::string_view filename, std::function on_complete)); + (Event::Dispatcher * dispatcher, absl::string_view filename, + absl::AnyInvocable on_complete)); MOCK_METHOD(std::string, describe, (), (const)); + MOCK_METHOD(void, waitForIdle, ()); + MOCK_METHOD(void, postCancelledActionForCleanup, (std::unique_ptr action)); // mockCancel is called any time any action queued by mock is cancelled. It isn't overriding // a function from the real class, it's just used for verification. @@ -83,19 +95,29 @@ class MockAsyncFileManager : public Extensions::Common::AsyncFiles::AsyncFileMan // not just a Status or a T. template void nextActionCompletes(T result) { ASSERT_FALSE(queue_.empty()); + auto entry = std::move(queue_.front()); + queue_.pop(); auto action = - std::dynamic_pointer_cast>>(queue_.front()); - ASSERT_TRUE(action.get() != nullptr) - << "mismatched type for nextActionCompletes: action is " << queue_.front()->describe() + dynamic_cast>*>(entry.action_.get()); + ASSERT_TRUE(action != nullptr) + << "mismatched type for nextActionCompletes: action is " << action->describe() << ", nextActionCompletes was given " << typeid(T).name(); - queue_.pop_front(); - action->on_complete_(std::move(result)); + if (entry.dispatcher_) { + entry.dispatcher_->post([action = std::move(entry.action_), state = std::move(entry.state_), + result = std::move(result)]() mutable { + if (state->load() != QueuedAction::State::Cancelled) { + dynamic_cast>*>(action.get()) + ->on_complete_(std::move(result)); + } + }); + } } - std::deque> queue_; + std::queue queue_; private: - MOCK_METHOD(CancelFunction, enqueue, (const std::shared_ptr action)); + MOCK_METHOD(CancelFunction, enqueue, + (Event::Dispatcher * dispatcher, std::unique_ptr action)); friend class MockAsyncFileContext; }; @@ -107,15 +129,16 @@ class MockAsyncFileManagerFactory : public Extensions::Common::AsyncFiles::Async }; // Add deduction guides for comping with the ctad-maybe-unsupported warning -TypedMockAsyncFileAction(std::function) - ->TypedMockAsyncFileAction>; -TypedMockAsyncFileAction(std::function)>) - ->TypedMockAsyncFileAction)>>; -TypedMockAsyncFileAction(std::function)>) - ->TypedMockAsyncFileAction)>>; -TypedMockAsyncFileAction(std::function>)>) +TypedMockAsyncFileAction(absl::AnyInvocable) + ->TypedMockAsyncFileAction>; +TypedMockAsyncFileAction(absl::AnyInvocable)>) + ->TypedMockAsyncFileAction)>>; +TypedMockAsyncFileAction(absl::AnyInvocable)>) + ->TypedMockAsyncFileAction)>>; +TypedMockAsyncFileAction( + absl::AnyInvocable>)>) ->TypedMockAsyncFileAction< - std::function>)>>; + absl::AnyInvocable>)>>; } // namespace AsyncFiles } // namespace Common diff --git a/test/extensions/filters/http/cache/http_cache_implementation_test_common.cc b/test/extensions/filters/http/cache/http_cache_implementation_test_common.cc index e1db6c9d44..c5df0fa728 100644 --- a/test/extensions/filters/http/cache/http_cache_implementation_test_common.cc +++ b/test/extensions/filters/http/cache/http_cache_implementation_test_common.cc @@ -40,6 +40,17 @@ MATCHER(IsOk, "") { return arg.ok(); } } // namespace +void HttpCacheTestDelegate::pumpDispatcher() { + // There may be multiple steps in a cache operation going back and forth with work + // on a cache's thread and work on the filter's thread. So drain both things up to + // 10 times each. This number is arbitrary and could be increased if necessary for + // a cache implementation. + for (int i = 0; i < 10; i++) { + beforePumpingDispatcher(); + dispatcher().run(Event::Dispatcher::RunType::Block); + } +} + HttpCacheImplementationTest::HttpCacheImplementationTest() : delegate_(GetParam()()), vary_allow_list_(getConfig().allowed_vary_headers(), factory_context_) { @@ -47,11 +58,9 @@ HttpCacheImplementationTest::HttpCacheImplementationTest() request_headers_.setHost("example.com"); request_headers_.setScheme("https"); request_headers_.setCopy(Http::CustomHeaders::get().CacheControl, "max-age=3600"); - - EXPECT_CALL(dispatcher_, post(_)).Times(AnyNumber()); - EXPECT_CALL(dispatcher_, isThreadSafe()).Times(AnyNumber()); - - delegate_->setUp(dispatcher_); + ON_CALL(encoder_callbacks_, dispatcher()).WillByDefault(testing::ReturnRef(dispatcher())); + ON_CALL(decoder_callbacks_, dispatcher()).WillByDefault(testing::ReturnRef(dispatcher())); + delegate_->setUp(); } HttpCacheImplementationTest::~HttpCacheImplementationTest() { @@ -64,155 +73,134 @@ bool HttpCacheImplementationTest::updateHeaders( absl::string_view request_path, const Http::TestResponseHeaderMapImpl& response_headers, const ResponseMetadata& metadata) { LookupContextPtr lookup_context = lookup(request_path); - auto update_promise = std::make_shared>(); + bool captured_result = false; + bool seen_result = false; cache()->updateHeaders(*lookup_context, response_headers, metadata, - [update_promise](bool result) { update_promise->set_value(result); }); - auto update_future = update_promise->get_future(); - if (std::future_status::ready != update_future.wait_for(std::chrono::seconds(5))) { - EXPECT_TRUE(false) << "timed out in updateHeaders " << request_path; - return false; - } - return update_future.get(); + [&captured_result, &seen_result](bool result) { + captured_result = result; + seen_result = true; + }); + pumpDispatcher(); + EXPECT_TRUE(seen_result); + return captured_result; } LookupContextPtr HttpCacheImplementationTest::lookup(absl::string_view request_path) { LookupRequest request = makeLookupRequest(request_path); LookupContextPtr context = cache()->makeLookupContext(std::move(request), decoder_callbacks_); - auto headers_promise = std::make_shared>>(); - context->getHeaders([headers_promise](LookupResult&& result, bool end_stream) { - headers_promise->set_value(std::make_pair(std::move(result), end_stream)); + bool seen_result = false; + context->getHeaders([this, &seen_result](LookupResult&& result, bool end_stream) { + lookup_result_ = std::move(result); + lookup_end_stream_after_headers_ = end_stream; + seen_result = true; }); - auto headers_future = headers_promise->get_future(); - if (std::future_status::ready == headers_future.wait_for(std::chrono::seconds(5))) { - auto result_pair = headers_future.get(); - lookup_result_ = std::move(result_pair.first); - lookup_end_stream_after_headers_ = result_pair.second; - } else { - EXPECT_TRUE(false) << "timed out in lookup " << request_path; - } - + pumpDispatcher(); + EXPECT_TRUE(seen_result); return context; } absl::Status HttpCacheImplementationTest::insert(LookupContextPtr lookup, const Http::TestResponseHeaderMapImpl& headers, - const absl::string_view body, - std::chrono::milliseconds timeout) { - return insert(std::move(lookup), headers, body, absl::nullopt, timeout); + const absl::string_view body) { + return insert(std::move(lookup), headers, body, absl::nullopt); } absl::Status HttpCacheImplementationTest::insert( LookupContextPtr lookup, const Http::TestResponseHeaderMapImpl& headers, - const absl::string_view body, const absl::optional trailers, - std::chrono::milliseconds timeout) { + const absl::string_view body, const absl::optional trailers) { // For responses with body, we must wait for insertBody's callback before // calling insertTrailers or completing. Note, in a multipart body test this // would need to check for the callback having been called for *every* body part, // but since the test only uses single-part bodies, inserting trailers or // completing in direct response to the callback works. - std::shared_ptr> insert_promise; - auto make_insert_callback = [&insert_promise]() { - insert_promise = std::make_shared>(); - return [insert_promise](bool success_ready_for_more) { - insert_promise->set_value(success_ready_for_more); - }; - }; - auto wait_for_insert = [&insert_promise, timeout](absl::string_view fn) { - auto future = insert_promise->get_future(); - auto result = future.wait_for(timeout); - if (result == std::future_status::timeout) { - return absl::DeadlineExceededError(absl::StrCat("Timed out waiting for ", fn)); - } - if (!future.get()) { - return absl::UnknownError(absl::StrCat("Insert was aborted by cache in ", fn)); - } - return absl::OkStatus(); - }; - + bool inserted_headers = false; + bool inserted_body = false; + bool inserted_trailers = false; InsertContextPtr inserter = cache()->makeInsertContext(std::move(lookup), encoder_callbacks_); absl::Cleanup destroy_inserter{[&inserter] { inserter->onDestroy(); }}; const ResponseMetadata metadata{time_system_.systemTime()}; bool headers_end_stream = body.empty() && !trailers.has_value(); - inserter->insertHeaders(headers, metadata, make_insert_callback(), headers_end_stream); - auto status = wait_for_insert("insertHeaders()"); - if (!status.ok()) { - return status; + inserter->insertHeaders( + headers, metadata, [&inserted_headers](bool result) { inserted_headers = result; }, + headers_end_stream); + pumpDispatcher(); + if (!inserted_headers) { + return absl::InternalError("headers were not inserted"); } if (headers_end_stream) { return absl::OkStatus(); } if (!body.empty()) { - inserter->insertBody(Buffer::OwnedImpl(body), make_insert_callback(), - /*end_stream=*/!trailers.has_value()); - auto status = wait_for_insert("insertBody()"); - if (!status.ok()) { - return status; + inserter->insertBody( + Buffer::OwnedImpl(body), [&inserted_body](bool result) { inserted_body = result; }, + /*end_stream=*/!trailers.has_value()); + pumpDispatcher(); + if (!inserted_body) { + return absl::InternalError("body was not inserted"); } } - - if (trailers.has_value()) { - inserter->insertTrailers(trailers.value(), make_insert_callback()); - auto status = wait_for_insert("insertTrailers()"); - if (!status.ok()) { - return status; - } + if (!trailers.has_value()) { + return absl::OkStatus(); + } + inserter->insertTrailers(trailers.value(), + [&inserted_trailers](bool result) { inserted_trailers = result; }); + pumpDispatcher(); + if (!inserted_trailers) { + return absl::InternalError("trailers were not inserted"); } return absl::OkStatus(); } absl::Status HttpCacheImplementationTest::insert(absl::string_view request_path, const Http::TestResponseHeaderMapImpl& headers, - const absl::string_view body, - std::chrono::milliseconds timeout) { - return insert(lookup(request_path), headers, body, timeout); + const absl::string_view body) { + return insert(lookup(request_path), headers, body); } std::pair HttpCacheImplementationTest::getHeaders(LookupContext& context) { - Http::ResponseHeaderMapPtr response_headers_ptr; - auto headers_promise = - std::make_shared>>(); - context.getHeaders([headers_promise](LookupResult&& lookup_result, bool end_stream) { + std::pair returned_pair; + bool seen_result = false; + context.getHeaders([&returned_pair, &seen_result](LookupResult&& lookup_result, bool end_stream) { EXPECT_NE(lookup_result.cache_entry_status_, CacheEntryStatus::Unusable); EXPECT_NE(lookup_result.headers_, nullptr); - headers_promise->set_value(std::make_pair(std::move(lookup_result.headers_), end_stream)); + returned_pair.first = std::move(lookup_result.headers_); + returned_pair.second = end_stream; + seen_result = true; }); - auto future = headers_promise->get_future(); - EXPECT_EQ(std::future_status::ready, future.wait_for(std::chrono::seconds(5))); - return future.get(); + pumpDispatcher(); + EXPECT_TRUE(seen_result); + return returned_pair; } std::pair HttpCacheImplementationTest::getBody(LookupContext& context, uint64_t start, uint64_t end) { AdjustedByteRange range(start, end); - auto body_promise = std::make_shared>>(); - context.getBody(range, [body_promise](Buffer::InstancePtr&& data, bool end_stream) { - EXPECT_NE(data, nullptr); - body_promise->set_value(data ? std::make_pair(data->toString(), end_stream) - : std::make_pair("", end_stream)); - }); - auto future = body_promise->get_future(); - EXPECT_EQ(std::future_status::ready, future.wait_for(std::chrono::seconds(5))); - return future.get(); + std::pair returned_pair; + bool seen_result = false; + context.getBody(range, + [&returned_pair, &seen_result](Buffer::InstancePtr&& data, bool end_stream) { + EXPECT_NE(data, nullptr); + returned_pair = std::make_pair(data->toString(), end_stream); + seen_result = true; + }); + pumpDispatcher(); + EXPECT_TRUE(seen_result); + return returned_pair; } Http::TestResponseTrailerMapImpl HttpCacheImplementationTest::getTrailers(LookupContext& context) { - auto trailers_promise = - std::make_shared>>(); - context.getTrailers([trailers_promise](Http::ResponseTrailerMapPtr&& data) { + Http::ResponseTrailerMapPtr trailers; + context.getTrailers([&trailers](Http::ResponseTrailerMapPtr&& data) { if (data) { - trailers_promise->set_value(std::move(data)); + trailers = std::move(data); } }); - auto future = trailers_promise->get_future(); - EXPECT_EQ(std::future_status::ready, future.wait_for(std::chrono::seconds(5))); - Http::TestResponseTrailerMapImpl trailers; - if (std::future_status::ready == future.wait_for(std::chrono::seconds(5))) { - trailers = *future.get(); - } - return trailers; + pumpDispatcher(); + EXPECT_THAT(trailers, testing::NotNull()); + return *trailers; } LookupRequest HttpCacheImplementationTest::makeLookupRequest(absl::string_view request_path) { @@ -423,30 +411,23 @@ TEST_P(HttpCacheImplementationTest, StreamingPut) { InsertContextPtr inserter = cache()->makeInsertContext(lookup(request_path), encoder_callbacks_); absl::Cleanup destroy_inserter{[&inserter] { inserter->onDestroy(); }}; ResponseMetadata metadata{time_system_.systemTime()}; - std::promise insert_headers_promise; - std::promise insert_body1_promise; - std::promise insert_body2_promise; + bool inserted_headers = false; + bool inserted_body1 = false; + bool inserted_body2 = false; inserter->insertHeaders( - response_headers, metadata, - [&insert_headers_promise](bool ready) { insert_headers_promise.set_value(ready); }, false); - auto insert_future = insert_headers_promise.get_future(); - ASSERT_EQ(std::future_status::ready, insert_future.wait_for(std::chrono::seconds(5))) - << "timed out waiting for inserts to complete"; - ASSERT_TRUE(insert_future.get()); + response_headers, metadata, [&inserted_headers](bool ready) { inserted_headers = ready; }, + false); + pumpDispatcher(); + ASSERT_TRUE(inserted_headers); inserter->insertBody( - Buffer::OwnedImpl("Hello, "), - [&insert_body1_promise](bool ready) { insert_body1_promise.set_value(ready); }, false); - insert_future = insert_body1_promise.get_future(); - ASSERT_EQ(std::future_status::ready, insert_future.wait_for(std::chrono::seconds(5))) - << "timed out waiting for inserts to complete"; - ASSERT_TRUE(insert_future.get()); + Buffer::OwnedImpl("Hello, "), [&inserted_body1](bool ready) { inserted_body1 = ready; }, + false); + pumpDispatcher(); + ASSERT_TRUE(inserted_body1); inserter->insertBody( - Buffer::OwnedImpl("World!"), - [&insert_body2_promise](bool ready) { insert_body2_promise.set_value(ready); }, true); - insert_future = insert_body2_promise.get_future(); - ASSERT_EQ(std::future_status::ready, insert_future.wait_for(std::chrono::seconds(5))) - << "timed out waiting for inserts to complete"; - ASSERT_TRUE(insert_future.get()); + Buffer::OwnedImpl("World!"), [&inserted_body2](bool ready) { inserted_body2 = ready; }, true); + pumpDispatcher(); + ASSERT_TRUE(inserted_body2); LookupContextPtr name_lookup = lookup(request_path); ASSERT_EQ(CacheEntryStatus::Ok, lookup_result_.cache_entry_status_); diff --git a/test/extensions/filters/http/cache/http_cache_implementation_test_common.h b/test/extensions/filters/http/cache/http_cache_implementation_test_common.h index d172f95fab..497bfcad81 100644 --- a/test/extensions/filters/http/cache/http_cache_implementation_test_common.h +++ b/test/extensions/filters/http/cache/http_cache_implementation_test_common.h @@ -28,8 +28,9 @@ class HttpCacheTestDelegate { public: virtual ~HttpCacheTestDelegate() = default; - virtual void setUp(Event::MockDispatcher& dispatcher) { dispatcher_ = &dispatcher; } + virtual void setUp() {} virtual void tearDown() {} + virtual std::shared_ptr cache() = 0; // Specifies whether or not the cache supports validating stale cache entries @@ -38,14 +39,21 @@ class HttpCacheTestDelegate { // RequiresValidation. virtual bool validationEnabled() const = 0; - Event::MockDispatcher& dispatcher() { return *dispatcher_; } + // May be overridden to, for example, also drain other threads into the dispatcher + // before draining the dispatcher. + virtual void beforePumpingDispatcher(){}; + void pumpDispatcher(); + + Event::Dispatcher& dispatcher() { return *dispatcher_; } private: - Event::MockDispatcher* dispatcher_ = nullptr; + Api::ApiPtr api_ = Api::createApiForTest(); + Event::DispatcherPtr dispatcher_ = api_->allocateDispatcher("test_thread"); }; class HttpCacheImplementationTest - : public testing::TestWithParam()>> { + : public Event::TestUsingSimulatedTime, + public testing::TestWithParam()>> { public: static constexpr absl::Duration kLastValidUpdateMinInterval = absl::Seconds(10); @@ -55,21 +63,19 @@ class HttpCacheImplementationTest std::shared_ptr cache() const { return delegate_->cache(); } bool validationEnabled() const { return delegate_->validationEnabled(); } + void pumpDispatcher() { delegate_->pumpDispatcher(); } LookupContextPtr lookup(absl::string_view request_path); absl::Status insert(LookupContextPtr lookup, const Http::TestResponseHeaderMapImpl& headers, - const absl::string_view body, - std::chrono::milliseconds timeout = std::chrono::seconds(1)); + const absl::string_view body); virtual absl::Status insert(LookupContextPtr lookup, const Http::TestResponseHeaderMapImpl& headers, const absl::string_view body, - const absl::optional trailers, - std::chrono::milliseconds timeout = std::chrono::seconds(1)); + const absl::optional trailers); absl::Status insert(absl::string_view request_path, - const Http::TestResponseHeaderMapImpl& headers, const absl::string_view body, - std::chrono::milliseconds timeout = std::chrono::seconds(1)); + const Http::TestResponseHeaderMapImpl& headers, const absl::string_view body); // Returns the headers and a bool for end_stream. std::pair getHeaders(LookupContext& context); @@ -99,7 +105,7 @@ class HttpCacheImplementationTest bool lookup_end_stream_after_headers_; Http::TestRequestHeaderMapImpl request_headers_; Event::SimulatedTimeSystem time_system_; - Event::MockDispatcher dispatcher_; + Event::Dispatcher& dispatcher() { return delegate_->dispatcher(); } DateFormatter formatter_{"%a, %d %b %Y %H:%M:%S GMT"}; NiceMock decoder_callbacks_; NiceMock encoder_callbacks_; diff --git a/test/extensions/filters/http/cors/cors_filter_test.cc b/test/extensions/filters/http/cors/cors_filter_test.cc index c699fe543e..17c1c5818d 100644 --- a/test/extensions/filters/http/cors/cors_filter_test.cc +++ b/test/extensions/filters/http/cors/cors_filter_test.cc @@ -87,8 +87,8 @@ TEST_F(CorsFilterTest, InitializeCorsPoliciesTest) { EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_.decodeHeaders(request_headers, true)); EXPECT_EQ(false, isCorsRequest()); EXPECT_EQ(2, filter_.policiesForTest().size()); - EXPECT_EQ(cors_policy_.get(), filter_.policiesForTest().at(0)); - EXPECT_EQ(cors_policy_.get(), filter_.policiesForTest().at(1)); + EXPECT_EQ(cors_policy_.get(), &filter_.policiesForTest().at(0).get()); + EXPECT_EQ(cors_policy_.get(), &filter_.policiesForTest().at(1).get()); } // Only 'typed_per_filter_config' of virtual host has cors policy. @@ -104,7 +104,7 @@ TEST_F(CorsFilterTest, InitializeCorsPoliciesTest) { EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_.decodeHeaders(request_headers, true)); EXPECT_EQ(false, isCorsRequest()); EXPECT_EQ(1, filter_.policiesForTest().size()); - EXPECT_EQ(cors_policy_.get(), filter_.policiesForTest().at(0)); + EXPECT_EQ(cors_policy_.get(), &filter_.policiesForTest().at(0).get()); } // No cors policy in the 'typed_per_filter_config'. @@ -121,9 +121,7 @@ TEST_F(CorsFilterTest, InitializeCorsPoliciesTest) { EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_.decodeHeaders(request_headers, true)); EXPECT_EQ(false, isCorsRequest()); - EXPECT_EQ(2, filter_.policiesForTest().size()); - EXPECT_EQ(nullptr, filter_.policiesForTest().at(0)); - EXPECT_EQ(nullptr, filter_.policiesForTest().at(1)); + EXPECT_EQ(0, filter_.policiesForTest().size()); } { filter_ = CorsFilter(config_); @@ -139,9 +137,8 @@ TEST_F(CorsFilterTest, InitializeCorsPoliciesTest) { EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_.decodeHeaders(request_headers, true)); EXPECT_EQ(false, isCorsRequest()); - EXPECT_EQ(2, filter_.policiesForTest().size()); - EXPECT_EQ(cors_policy_.get(), filter_.policiesForTest().at(0)); - EXPECT_EQ(nullptr, filter_.policiesForTest().at(1)); + EXPECT_EQ(1, filter_.policiesForTest().size()); + EXPECT_EQ(cors_policy_.get(), &filter_.policiesForTest().at(0).get()); } { filter_ = CorsFilter(config_); @@ -157,9 +154,8 @@ TEST_F(CorsFilterTest, InitializeCorsPoliciesTest) { EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_.decodeHeaders(request_headers, false)); EXPECT_EQ(false, isCorsRequest()); - EXPECT_EQ(2, filter_.policiesForTest().size()); - EXPECT_EQ(nullptr, filter_.policiesForTest().at(0)); - EXPECT_EQ(cors_policy_.get(), filter_.policiesForTest().at(1)); + EXPECT_EQ(1, filter_.policiesForTest().size()); + EXPECT_EQ(cors_policy_.get(), &filter_.policiesForTest().at(0).get()); } } diff --git a/test/extensions/filters/http/file_system_buffer/filter_test.cc b/test/extensions/filters/http/file_system_buffer/filter_test.cc index 78541b92b3..cf3bd20171 100644 --- a/test/extensions/filters/http/file_system_buffer/filter_test.cc +++ b/test/extensions/filters/http/file_system_buffer/filter_test.cc @@ -23,6 +23,7 @@ using Extensions::Common::AsyncFiles::MockAsyncFileManager; using Extensions::Common::AsyncFiles::MockAsyncFileManagerFactory; using ::testing::NiceMock; using ::testing::Return; +using ::testing::ReturnRef; class FileSystemBufferFilterTest : public testing::Test { public: @@ -42,8 +43,9 @@ class FileSystemBufferFilterTest : public testing::Test { protected: void expectAsyncFileCreated() { - EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _)); + EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _, _)); } + void pumpDispatcher() { dispatcher_->run(Event::Dispatcher::RunType::Block); } // A file is only opened by the filter if it's going to chain a write immediately afterwards, // so we combine completing the open and expecting the write, which allows for simplified control // of the handle. @@ -56,13 +58,13 @@ class FileSystemBufferFilterTest : public testing::Test { } void expectWriteWithPosition(MockAsyncFileHandle handle, absl::string_view content, off_t offset) { - EXPECT_CALL(*handle, write(BufferStringEqual(std::string(content)), offset, _)); + EXPECT_CALL(*handle, write(_, BufferStringEqual(std::string(content)), offset, _)); } void completeWriteOfSize(size_t length) { mock_async_file_manager_->nextActionCompletes(absl::StatusOr{length}); } void expectRead(MockAsyncFileHandle handle, off_t offset, size_t size) { - EXPECT_CALL(*handle, read(offset, size, _)); + EXPECT_CALL(*handle, read(_, offset, size, _)); } void completeRead(absl::string_view content) { mock_async_file_manager_->nextActionCompletes(absl::StatusOr>{ @@ -86,6 +88,10 @@ class FileSystemBufferFilterTest : public testing::Test { proto_config); } + void useDeferredDispatcher() { + EXPECT_CALL(decoder_callbacks_, dispatcher()).WillRepeatedly(ReturnRef(*dispatcher_)); + } + void createFilterFromYaml(absl::string_view yaml) { auto config = configFromYaml(yaml); filter_ = std::make_shared(config); @@ -105,14 +111,6 @@ class FileSystemBufferFilterTest : public testing::Test { ASSERT_FALSE(continued_encoding_); continued_encoding_ = true; }); - // Using EXPECT_CALL rather than ON_CALL because this one was set up in Envoy code and - // isn't a NiceMock. Using EXPECT reduces log noise. - // For simplicity's sake the dispatcher just runs the function immediately - // - since the mock is capturing callbacks, we're effectively triggering - // the dispatcher when we resolve the callback. - EXPECT_CALL(decoder_callbacks_.dispatcher_, post(_)).WillRepeatedly([](Event::PostCb fn) { - fn(); - }); filter_->setDecoderFilterCallbacks(decoder_callbacks_); filter_->setEncoderFilterCallbacks(encoder_callbacks_); EXPECT_CALL(decoder_callbacks_, onDecoderFilterAboveWriteBufferHighWatermark()) @@ -154,6 +152,8 @@ class FileSystemBufferFilterTest : public testing::Test { bool continued_encoding_ = false; int request_source_watermark_ = 0; int response_source_watermark_ = 0; + Api::ApiPtr api_ = Api::createApiForTest(); + Event::DispatcherPtr dispatcher_ = api_->allocateDispatcher("test_thread"); LogLevelSetter log_level_setter_ = LogLevelSetter(ENVOY_SPDLOG_LEVEL(debug)); }; @@ -738,26 +738,23 @@ TEST_F(FileSystemBufferFilterTest, FilterDestroyedWhileFileActionIsInDispatcherI behavior: fully_buffer: {} )"); + useDeferredDispatcher(); EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false)); + pumpDispatcher(); expectAsyncFileCreated(); Buffer::OwnedImpl request_body{"12345678901234567890"}; EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, filter_->decodeData(request_body, false)); + pumpDispatcher(); completeCreateFileAndExpectWrite("1234567890"); - Event::PostCb intercepted_dispatcher_callback; - // Our default mock dispatcher behavior calls the callback immediately - here we intercept - // one so we can call it after a 'realistic' delay during which something else happened to - // destroy the filter. - EXPECT_CALL(decoder_callbacks_.dispatcher_, post(_)) - .WillOnce([&intercepted_dispatcher_callback](Event::PostCb fn) { - intercepted_dispatcher_callback = std::move(fn); - }); + pumpDispatcher(); + // Action completes in file system manager. Don't consume the dispatched callback yet. completeWriteOfSize(10); + // Action is cancelled before callback; async file manager prevents the callback after this. + EXPECT_CALL(*mock_async_file_manager_, mockCancel()); destroyFilter(); - // Callback called from dispatcher after filter was destroyed and its pointer invalidated, - // should not cause a crash. - intercepted_dispatcher_callback(); + pumpDispatcher(); } TEST_F(FileSystemBufferFilterTest, MergesRouteConfig) { diff --git a/test/extensions/filters/http/file_system_buffer/fragment_test.cc b/test/extensions/filters/http/file_system_buffer/fragment_test.cc index 662173729c..59e1644fca 100644 --- a/test/extensions/filters/http/file_system_buffer/fragment_test.cc +++ b/test/extensions/filters/http/file_system_buffer/fragment_test.cc @@ -22,33 +22,33 @@ using StatusHelpers::HasStatusMessage; using ::testing::_; using ::testing::Eq; using ::testing::HasSubstr; +using ::testing::MockFunction; using ::testing::StrictMock; -std::function storageSuccessCallback() { - return [](absl::Status status) { ASSERT_OK(status); }; -} - -template -std::function storageFailureCallback(MatcherT matcher) { - return [matcher](absl::Status status) { EXPECT_THAT(status, matcher); }; -} - -void dispatchImmediately(std::function callback) { callback(); } - class FileSystemBufferFilterFragmentTest : public ::testing::Test { public: + void resolveFileActions() { dispatcher_->run(Event::Dispatcher::RunType::Block); } + protected: MockAsyncFileHandle handle_ = std::make_shared>(); void moveFragmentToStorage(Fragment* frag) { - EXPECT_CALL(*handle_, write(_, _, _)) - .WillOnce( - [frag](Buffer::Instance&, off_t, std::function)> callback) { - callback(frag->size()); - return []() {}; - }); - EXPECT_OK(frag->toStorage(handle_, 123, &dispatchImmediately, storageSuccessCallback())); + EXPECT_CALL(*handle_, write(_, _, _, _)) + .WillOnce([frag](Event::Dispatcher* dispatcher, Buffer::Instance&, off_t, + absl::AnyInvocable)> callback) { + dispatcher->post([frag, callback = std::move(callback)]() mutable { + std::move(callback)(frag->size()); + }); + return []() {}; + }); + MockFunction callback; + EXPECT_OK(frag->toStorage(handle_, 123, *dispatcher_, callback.AsStdFunction())); + EXPECT_CALL(callback, Call(absl::OkStatus())); + resolveFileActions(); } + + Api::ApiPtr api_ = Api::createApiForTest(); + Event::DispatcherPtr dispatcher_ = api_->allocateDispatcher("test_thread"); }; TEST_F(FileSystemBufferFilterFragmentTest, CreatesAndExtractsWithoutCopying) { @@ -77,39 +77,42 @@ TEST_F(FileSystemBufferFilterFragmentTest, CreatesFragmentFromPartialBufferAndCo TEST_F(FileSystemBufferFilterFragmentTest, WritesAndReadsBack) { Buffer::OwnedImpl input("hello"); Fragment frag(input); - std::function)> captured_write_callback; - EXPECT_CALL(*handle_, write(BufferStringEqual("hello"), 123, _)) - .WillOnce([&captured_write_callback](Buffer::Instance&, off_t, - std::function)> callback) { - captured_write_callback = std::move(callback); + EXPECT_CALL(*handle_, write(_, BufferStringEqual("hello"), 123, _)) + .WillOnce([](Event::Dispatcher* dispatcher, Buffer::Instance&, off_t, + absl::AnyInvocable)> callback) { + dispatcher->post([callback = std::move(callback)]() mutable { std::move(callback)(5); }); return []() {}; }); // Request the fragment be moved to storage. - EXPECT_OK(frag.toStorage(handle_, 123, &dispatchImmediately, storageSuccessCallback())); + MockFunction write_callback; + EXPECT_OK(frag.toStorage(handle_, 123, *dispatcher_, write_callback.AsStdFunction())); // Before the file confirms written, the state should be neither in memory nor in storage. EXPECT_FALSE(frag.isMemory()); EXPECT_FALSE(frag.isStorage()); // Fake the file thread confirming 5 bytes were written. - captured_write_callback(5); + EXPECT_CALL(write_callback, Call(absl::OkStatus())); + resolveFileActions(); // Now the fragment should be tagged as being in storage. EXPECT_TRUE(frag.isStorage()); EXPECT_FALSE(frag.isMemory()); - std::function>)> captured_read_callback; - EXPECT_CALL(*handle_, read(123, 5, _)) + EXPECT_CALL(*handle_, read(_, 123, 5, _)) .WillOnce( - [&captured_read_callback]( - off_t, size_t, - std::function>)> callback) { - captured_read_callback = std::move(callback); + [](Event::Dispatcher* dispatcher, off_t, size_t, + absl::AnyInvocable>)> callback) { + dispatcher->post([callback = std::move(callback)]() mutable { + std::move(callback)(std::make_unique("hello")); + }); return []() {}; }); // Request the fragment be moved from storage. - EXPECT_OK(frag.fromStorage(handle_, &dispatchImmediately, storageSuccessCallback())); + MockFunction read_callback; + EXPECT_OK(frag.fromStorage(handle_, *dispatcher_, read_callback.AsStdFunction())); // Before the file confirms read, the state should be neither in memory nor storage. EXPECT_FALSE(frag.isMemory()); EXPECT_FALSE(frag.isStorage()); // Fake the file thread completing read. - captured_read_callback(std::make_unique("hello")); + EXPECT_CALL(read_callback, Call(absl::OkStatus())); + resolveFileActions(); // Now the fragment should be tagged as being in memory. EXPECT_TRUE(frag.isMemory()); EXPECT_FALSE(frag.isStorage()); @@ -121,86 +124,81 @@ TEST_F(FileSystemBufferFilterFragmentTest, WritesAndReadsBack) { TEST_F(FileSystemBufferFilterFragmentTest, ReturnsErrorOnWriteError) { Buffer::OwnedImpl input("hello"); Fragment frag(input); - auto write_error = absl::UnknownError("write error"); - std::function)> captured_write_callback; - EXPECT_CALL(*handle_, write(BufferStringEqual("hello"), 123, _)) - .WillOnce([&captured_write_callback](Buffer::Instance&, off_t, - std::function)> callback) { - captured_write_callback = std::move(callback); + EXPECT_CALL(*handle_, write(_, BufferStringEqual("hello"), 123, _)) + .WillOnce([](Event::Dispatcher* dispatcher, Buffer::Instance&, off_t, + absl::AnyInvocable)> callback) { + dispatcher->post([callback = std::move(callback)]() mutable { + std::move(callback)(absl::UnknownError("write error")); + }); return []() {}; }); // Request the fragment be moved to storage. - EXPECT_OK( - frag.toStorage(handle_, 123, &dispatchImmediately, storageFailureCallback(Eq(write_error)))); - - // Fake file system declares a write error. This should - // provoke the expected error in the callback above. - captured_write_callback(write_error); + MockFunction callback; + EXPECT_OK(frag.toStorage(handle_, 123, *dispatcher_, callback.AsStdFunction())); + EXPECT_CALL(callback, Call(Eq(absl::UnknownError("write error")))); + resolveFileActions(); } TEST_F(FileSystemBufferFilterFragmentTest, ReturnsErrorOnWriteIncomplete) { Buffer::OwnedImpl input("hello"); Fragment frag(input); - std::function)> captured_write_callback; - EXPECT_CALL(*handle_, write(BufferStringEqual("hello"), 123, _)) - .WillOnce([&captured_write_callback](Buffer::Instance&, off_t, - std::function)> callback) { - captured_write_callback = std::move(callback); + EXPECT_CALL(*handle_, write(_, BufferStringEqual("hello"), 123, _)) + .WillOnce([](Event::Dispatcher* dispatcher, Buffer::Instance&, off_t, + absl::AnyInvocable)> callback) { + dispatcher->post([callback = std::move(callback)]() mutable { std::move(callback)(2); }); return []() {}; }); // Request the fragment be moved to storage. - EXPECT_OK(frag.toStorage( - handle_, 123, &dispatchImmediately, - storageFailureCallback(HasStatusMessage(HasSubstr("wrote 2 bytes, wanted 5"))))); - + MockFunction callback; + EXPECT_OK(frag.toStorage(handle_, 123, *dispatcher_, callback.AsStdFunction())); // Fake file says it wrote 2 bytes when the fragment was of size 5 - this should // provoke the expected error in the callback above. - captured_write_callback(2); + EXPECT_CALL(callback, Call(HasStatusMessage(HasSubstr("wrote 2 bytes, wanted 5")))); + resolveFileActions(); } TEST_F(FileSystemBufferFilterFragmentTest, ReturnsErrorOnReadError) { Buffer::OwnedImpl input("hello"); Fragment frag(input); moveFragmentToStorage(&frag); - auto read_error = absl::UnknownError("read error"); - std::function>)> captured_read_callback; - EXPECT_CALL(*handle_, read(123, 5, _)) + EXPECT_CALL(*handle_, read(_, 123, 5, _)) .WillOnce( - [&captured_read_callback]( - off_t, size_t, - std::function>)> callback) { - captured_read_callback = std::move(callback); + [](Event::Dispatcher* dispatcher, off_t, size_t, + absl::AnyInvocable>)> callback) { + dispatcher->post([callback = std::move(callback)]() mutable { + std::move(callback)(absl::UnknownError("read error")); + }); return []() {}; }); // Request the fragment be moved from storage. - EXPECT_OK( - frag.fromStorage(handle_, &dispatchImmediately, storageFailureCallback(Eq(read_error)))); - // Fake file system declares a read error. This should - // provoke the expected error in the callback above. - captured_read_callback(read_error); + MockFunction callback; + EXPECT_OK(frag.fromStorage(handle_, *dispatcher_, callback.AsStdFunction())); + EXPECT_CALL(callback, Call(Eq(absl::UnknownError("read error")))); + resolveFileActions(); } TEST_F(FileSystemBufferFilterFragmentTest, ReturnsErrorOnReadIncomplete) { Buffer::OwnedImpl input("hello"); Fragment frag(input); moveFragmentToStorage(&frag); - std::function>)> captured_read_callback; - EXPECT_CALL(*handle_, read(123, 5, _)) + absl::AnyInvocable>)> + captured_read_callback; + EXPECT_CALL(*handle_, read(_, 123, 5, _)) .WillOnce( - [&captured_read_callback]( - off_t, size_t, - std::function>)> callback) { - captured_read_callback = std::move(callback); + [](Event::Dispatcher* dispatcher, off_t, size_t, + absl::AnyInvocable>)> callback) { + dispatcher->post([callback = std::move(callback)]() mutable { + std::move(callback)(std::make_unique("he")); + }); return []() {}; }); + MockFunction callback; // Request the fragment be moved from storage. - EXPECT_OK(frag.fromStorage( - handle_, &dispatchImmediately, - storageFailureCallback(HasStatusMessage(HasSubstr("read got 2 bytes, wanted 5"))))); - - // Fake file system declares a read error. This should + EXPECT_OK(frag.fromStorage(handle_, *dispatcher_, callback.AsStdFunction())); + // Mock file system declares a read error. This should // provoke the expected error in the callback above. - captured_read_callback(std::make_unique("he")); + EXPECT_CALL(callback, Call(HasStatusMessage(HasSubstr("read got 2 bytes, wanted 5")))); + resolveFileActions(); } } // namespace FileSystemBuffer diff --git a/test/extensions/filters/http/rate_limit_quota/client_test.cc b/test/extensions/filters/http/rate_limit_quota/client_test.cc index 1fee6b6f46..336b47b7ff 100644 --- a/test/extensions/filters/http/rate_limit_quota/client_test.cc +++ b/test/extensions/filters/http/rate_limit_quota/client_test.cc @@ -18,7 +18,7 @@ class RateLimitClientTest : public testing::Test { }; TEST_F(RateLimitClientTest, OpenAndCloseStream) { - EXPECT_OK(test_client.client_->startStream(test_client.stream_info_)); + EXPECT_OK(test_client.client_->startStream(&test_client.stream_info_)); EXPECT_CALL(test_client.stream_, closeStream()); EXPECT_CALL(test_client.stream_, resetStream()); test_client.client_->closeStream(); @@ -27,7 +27,7 @@ TEST_F(RateLimitClientTest, OpenAndCloseStream) { TEST_F(RateLimitClientTest, SendUsageReport) { ::envoy::service::rate_limit_quota::v3::BucketId bucket_id; TestUtility::loadFromYaml(SingleBukcetId, bucket_id); - EXPECT_OK(test_client.client_->startStream(test_client.stream_info_)); + EXPECT_OK(test_client.client_->startStream(&test_client.stream_info_)); bool end_stream = false; // Send quota usage report and ensure that we get it. EXPECT_CALL(test_client.stream_, sendMessageRaw_(_, end_stream)); @@ -39,7 +39,7 @@ TEST_F(RateLimitClientTest, SendUsageReport) { } TEST_F(RateLimitClientTest, SendRequestAndReceiveResponse) { - EXPECT_OK(test_client.client_->startStream(test_client.stream_info_)); + EXPECT_OK(test_client.client_->startStream(&test_client.stream_info_)); ASSERT_NE(test_client.stream_callbacks_, nullptr); auto empty_request_headers = Http::RequestHeaderMapImpl::create(); @@ -66,6 +66,33 @@ TEST_F(RateLimitClientTest, SendRequestAndReceiveResponse) { test_client.client_->onRemoteClose(0, ""); } +TEST_F(RateLimitClientTest, RestartStreamWhileInUse) { + ::envoy::service::rate_limit_quota::v3::BucketId bucket_id; + TestUtility::loadFromYaml(SingleBukcetId, bucket_id); + EXPECT_OK(test_client.client_->startStream(&test_client.stream_info_)); + + bool end_stream = false; + // Send quota usage report and ensure that we get it. + EXPECT_CALL(test_client.stream_, sendMessageRaw_(_, end_stream)); + const size_t bucket_id_hash = MessageUtil::hash(bucket_id); + test_client.client_->sendUsageReport(bucket_id_hash); + EXPECT_CALL(test_client.stream_, closeStream()); + EXPECT_CALL(test_client.stream_, resetStream()); + test_client.client_->closeStream(); + + // Expect the stream to reopen while trying to send the next usage report. + EXPECT_CALL(test_client.stream_, sendMessageRaw_(_, end_stream)); + test_client.client_->sendUsageReport(bucket_id_hash); + EXPECT_CALL(test_client.stream_, closeStream()); + EXPECT_CALL(test_client.stream_, resetStream()); + test_client.client_->closeStream(); + + // Expect the client to handle a restart failure. + EXPECT_CALL(*test_client.async_client_, startRaw(_, _, _, _)).WillOnce(testing::Return(nullptr)); + WAIT_FOR_LOG_CONTAINS("error", "Failed to start the stream to send reports.", + { test_client.client_->sendUsageReport(bucket_id_hash); }); +} + } // namespace } // namespace RateLimitQuota } // namespace HttpFilters diff --git a/test/extensions/filters/http/rate_limit_quota/client_test_utils.h b/test/extensions/filters/http/rate_limit_quota/client_test_utils.h index 2f19f9b48b..e6624af9b4 100644 --- a/test/extensions/filters/http/rate_limit_quota/client_test_utils.h +++ b/test/extensions/filters/http/rate_limit_quota/client_test_utils.h @@ -70,12 +70,12 @@ class RateLimitTestClient { } Grpc::RawAsyncClientSharedPtr mockCreateAsyncClient(Unused, Unused, Unused) { - auto async_client = std::make_shared(); - EXPECT_CALL(*async_client, startRaw("envoy.service.rate_limit_quota.v3.RateLimitQuotaService", - "StreamRateLimitQuotas", _, _)) - .WillOnce(Invoke(this, &RateLimitTestClient::mockStartRaw)); + async_client_ = std::make_shared(); + EXPECT_CALL(*async_client_, startRaw("envoy.service.rate_limit_quota.v3.RateLimitQuotaService", + "StreamRateLimitQuotas", _, _)) + .WillRepeatedly(Invoke(this, &RateLimitTestClient::mockStartRaw)); - return async_client; + return async_client_; } Grpc::RawAsyncStream* mockStartRaw(Unused, Unused, Grpc::RawAsyncStreamCallbacks& callbacks, @@ -97,7 +97,7 @@ class RateLimitTestClient { Grpc::RawAsyncStreamCallbacks* stream_callbacks_; Grpc::Status::GrpcStatus grpc_status_ = Grpc::Status::WellKnownGrpcStatus::Ok; RateLimitClientPtr client_; - // std::unique_ptr client_; + std::shared_ptr async_client_ = nullptr; MockRateLimitQuotaCallbacks callbacks_; bool external_inited_ = false; bool start_failed_ = false; diff --git a/test/extensions/filters/http/rate_limit_quota/integration_test.cc b/test/extensions/filters/http/rate_limit_quota/integration_test.cc index dc37a9720b..37d4a97df6 100644 --- a/test/extensions/filters/http/rate_limit_quota/integration_test.cc +++ b/test/extensions/filters/http/rate_limit_quota/integration_test.cc @@ -787,42 +787,51 @@ TEST_P(RateLimitQuotaIntegrationTest, BasicFlowPeriodicalReportWithStreamClosed) EXPECT_TRUE(response_->complete()); EXPECT_EQ(response_->headers().getStatusValue(), "200"); + // ValidMatcherConfig. + int report_interval_sec = 60; // Trigger the report periodically. for (int i = 0; i < 6; ++i) { if (i == 2) { // Close the stream. - rlqs_stream_->finishGrpcStream(Grpc::Status::Ok); + WAIT_FOR_LOG_CONTAINS("debug", "gRPC stream closed remotely with status", + { rlqs_stream_->finishGrpcStream(Grpc::Status::Canceled); }); + ASSERT_TRUE(rlqs_stream_->waitForReset()); } // Advance the time by report_interval. simTime().advanceTimeWait(std::chrono::milliseconds(report_interval_sec * 1000)); - // Only perform rlqs server check and response before stream is remotely closed. - if (i < 2) { - // Checks that the rate limit server has received the periodical reports. - ASSERT_TRUE(rlqs_stream_->waitForGrpcMessage(*dispatcher_, reports)); + if (i == 2) { + // Stream should be restarted when next required for usage reporting. + ASSERT_TRUE(rlqs_connection_->waitForNewStream(*dispatcher_, rlqs_stream_)); + rlqs_stream_->startGrpcStream(); + } - // Verify the usage report content. - ASSERT_THAT(reports.bucket_quota_usages_size(), 1); - const auto& usage = reports.bucket_quota_usages(0); - // Report only represents the usage since last report. - // In the periodical report case here, the number of request allowed and denied is 0 since no - // new requests comes in. - EXPECT_EQ(usage.num_requests_allowed(), 0); - EXPECT_EQ(usage.num_requests_denied(), 0); - // time_elapsed equals to periodical reporting interval. - EXPECT_EQ(Protobuf::util::TimeUtil::DurationToSeconds(usage.time_elapsed()), - report_interval_sec); + // Only perform rlqs server check and response before stream is remotely + // closed. Checks that the rate limit server has received the periodical + // reports. + ASSERT_TRUE(rlqs_stream_->waitForGrpcMessage(*dispatcher_, reports)); + + // Verify the usage report content. + ASSERT_THAT(reports.bucket_quota_usages_size(), 1); + const auto& usage = reports.bucket_quota_usages(0); + // Report only represents the usage since last report. + // In the periodical report case here, the number of request allowed and + // denied is 0 since no new requests comes in. + EXPECT_EQ(usage.num_requests_allowed(), 0); + EXPECT_EQ(usage.num_requests_denied(), 0); + // time_elapsed equals to periodical reporting interval. + EXPECT_EQ(Protobuf::util::TimeUtil::DurationToSeconds(usage.time_elapsed()), + report_interval_sec); - // Build the rlqs server response. - envoy::service::rate_limit_quota::v3::RateLimitQuotaResponse rlqs_response2; - auto* bucket_action2 = rlqs_response2.add_bucket_action(); + // Build the rlqs server response. + envoy::service::rate_limit_quota::v3::RateLimitQuotaResponse rlqs_response2; + auto* bucket_action2 = rlqs_response2.add_bucket_action(); - for (const auto& [key, value] : custom_headers_cpy) { - (*bucket_action2->mutable_bucket_id()->mutable_bucket()).insert({key, value}); - } - rlqs_stream_->sendGrpcMessage(rlqs_response2); + for (const auto& [key, value] : custom_headers_cpy) { + (*bucket_action2->mutable_bucket_id()->mutable_bucket()).insert({key, value}); } + rlqs_stream_->sendGrpcMessage(rlqs_response2); } } diff --git a/test/extensions/filters/http/rate_limit_quota/mocks.h b/test/extensions/filters/http/rate_limit_quota/mocks.h index 01d374ec6c..aedd75e3e9 100644 --- a/test/extensions/filters/http/rate_limit_quota/mocks.h +++ b/test/extensions/filters/http/rate_limit_quota/mocks.h @@ -28,7 +28,7 @@ class MockRateLimitClient : public RateLimitClient { MockRateLimitClient() = default; ~MockRateLimitClient() override = default; - MOCK_METHOD(absl::Status, startStream, (const StreamInfo::StreamInfo&)); + MOCK_METHOD(absl::Status, startStream, (const StreamInfo::StreamInfo*)); MOCK_METHOD(void, closeStream, ()); MOCK_METHOD(void, sendUsageReport, (absl::optional)); diff --git a/test/extensions/filters/http/wasm/test_data/BUILD b/test/extensions/filters/http/wasm/test_data/BUILD index 3cebdbc4fc..dc89c15852 100644 --- a/test/extensions/filters/http/wasm/test_data/BUILD +++ b/test/extensions/filters/http/wasm/test_data/BUILD @@ -15,7 +15,7 @@ wasm_rust_binary( srcs = ["async_call_rust.rs"], deps = [ "@proxy_wasm_rust_sdk//:proxy_wasm", - "@proxy_wasm_rust_sdk//bazel/cargo:log", + "@proxy_wasm_rust_sdk//bazel/cargo/remote:log", ], ) @@ -24,7 +24,7 @@ wasm_rust_binary( srcs = ["body_rust.rs"], deps = [ "@proxy_wasm_rust_sdk//:proxy_wasm", - "@proxy_wasm_rust_sdk//bazel/cargo:log", + "@proxy_wasm_rust_sdk//bazel/cargo/remote:log", ], ) @@ -33,7 +33,7 @@ wasm_rust_binary( srcs = ["close_stream_rust.rs"], deps = [ "@proxy_wasm_rust_sdk//:proxy_wasm", - "@proxy_wasm_rust_sdk//bazel/cargo:log", + "@proxy_wasm_rust_sdk//bazel/cargo/remote:log", ], ) @@ -43,7 +43,7 @@ wasm_rust_binary( deps = [ "//bazel/external/cargo:protobuf", "@proxy_wasm_rust_sdk//:proxy_wasm", - "@proxy_wasm_rust_sdk//bazel/cargo:log", + "@proxy_wasm_rust_sdk//bazel/cargo/remote:log", ], ) @@ -53,7 +53,7 @@ wasm_rust_binary( deps = [ "//bazel/external/cargo:protobuf", "@proxy_wasm_rust_sdk//:proxy_wasm", - "@proxy_wasm_rust_sdk//bazel/cargo:log", + "@proxy_wasm_rust_sdk//bazel/cargo/remote:log", ], ) @@ -63,7 +63,7 @@ wasm_rust_binary( wasi = True, deps = [ "@proxy_wasm_rust_sdk//:proxy_wasm", - "@proxy_wasm_rust_sdk//bazel/cargo:log", + "@proxy_wasm_rust_sdk//bazel/cargo/remote:log", ], ) @@ -72,7 +72,7 @@ wasm_rust_binary( srcs = ["metadata_rust.rs"], deps = [ "@proxy_wasm_rust_sdk//:proxy_wasm", - "@proxy_wasm_rust_sdk//bazel/cargo:log", + "@proxy_wasm_rust_sdk//bazel/cargo/remote:log", ], ) @@ -81,7 +81,7 @@ wasm_rust_binary( srcs = ["panic_rust.rs"], deps = [ "@proxy_wasm_rust_sdk//:proxy_wasm", - "@proxy_wasm_rust_sdk//bazel/cargo:log", + "@proxy_wasm_rust_sdk//bazel/cargo/remote:log", ], ) @@ -90,7 +90,7 @@ wasm_rust_binary( srcs = ["resume_call_rust.rs"], deps = [ "@proxy_wasm_rust_sdk//:proxy_wasm", - "@proxy_wasm_rust_sdk//bazel/cargo:log", + "@proxy_wasm_rust_sdk//bazel/cargo/remote:log", ], ) @@ -99,7 +99,7 @@ wasm_rust_binary( srcs = ["shared_data_rust.rs"], deps = [ "@proxy_wasm_rust_sdk//:proxy_wasm", - "@proxy_wasm_rust_sdk//bazel/cargo:log", + "@proxy_wasm_rust_sdk//bazel/cargo/remote:log", ], ) @@ -108,7 +108,7 @@ wasm_rust_binary( srcs = ["shared_queue_rust.rs"], deps = [ "@proxy_wasm_rust_sdk//:proxy_wasm", - "@proxy_wasm_rust_sdk//bazel/cargo:log", + "@proxy_wasm_rust_sdk//bazel/cargo/remote:log", ], ) diff --git a/test/extensions/filters/network/generic_proxy/proxy_test.cc b/test/extensions/filters/network/generic_proxy/proxy_test.cc index 6f6121a64d..31900ba301 100644 --- a/test/extensions/filters/network/generic_proxy/proxy_test.cc +++ b/test/extensions/filters/network/generic_proxy/proxy_test.cc @@ -108,7 +108,7 @@ class FilterConfigTest : public testing::Test { envoy::config::core::v3::SubstitutionFormatString sff_config; sff_config.mutable_text_format_source()->set_inline_string(format); auto formatter = - Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig( + *Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig( sff_config, factory_context_); return std::make_shared( diff --git a/test/extensions/filters/network/wasm/test_data/BUILD b/test/extensions/filters/network/wasm/test_data/BUILD index 7095acc432..5767f8ba51 100644 --- a/test/extensions/filters/network/wasm/test_data/BUILD +++ b/test/extensions/filters/network/wasm/test_data/BUILD @@ -14,7 +14,7 @@ wasm_rust_binary( srcs = ["close_stream_rust.rs"], deps = [ "@proxy_wasm_rust_sdk//:proxy_wasm", - "@proxy_wasm_rust_sdk//bazel/cargo:log", + "@proxy_wasm_rust_sdk//bazel/cargo/remote:log", ], ) @@ -23,7 +23,7 @@ wasm_rust_binary( srcs = ["logging_rust.rs"], deps = [ "@proxy_wasm_rust_sdk//:proxy_wasm", - "@proxy_wasm_rust_sdk//bazel/cargo:log", + "@proxy_wasm_rust_sdk//bazel/cargo/remote:log", ], ) @@ -32,7 +32,7 @@ wasm_rust_binary( srcs = ["panic_rust.rs"], deps = [ "@proxy_wasm_rust_sdk//:proxy_wasm", - "@proxy_wasm_rust_sdk//bazel/cargo:log", + "@proxy_wasm_rust_sdk//bazel/cargo/remote:log", ], ) @@ -41,7 +41,7 @@ wasm_rust_binary( srcs = ["resume_call_rust.rs"], deps = [ "@proxy_wasm_rust_sdk//:proxy_wasm", - "@proxy_wasm_rust_sdk//bazel/cargo:log", + "@proxy_wasm_rust_sdk//bazel/cargo/remote:log", ], ) diff --git a/test/extensions/formatter/cel/cel_test.cc b/test/extensions/formatter/cel/cel_test.cc index 77f9d1b69d..ff4e4425cd 100644 --- a/test/extensions/formatter/cel/cel_test.cc +++ b/test/extensions/formatter/cel/cel_test.cc @@ -77,7 +77,7 @@ TEST_F(CELFormatterTest, TestRequestHeader) { TestUtility::loadFromYaml(yaml, config_); auto formatter = - Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); + *Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); EXPECT_EQ("GET", formatter->formatWithContext(formatter_context_, stream_info_)); } @@ -93,7 +93,7 @@ TEST_F(CELFormatterTest, TestMissingRequestHeader) { TestUtility::loadFromYaml(yaml, config_); auto formatter = - Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); + *Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); EXPECT_EQ("-", formatter->formatWithContext(formatter_context_, stream_info_)); } @@ -109,7 +109,7 @@ TEST_F(CELFormatterTest, TestWithoutMaxLength) { TestUtility::loadFromYaml(yaml, config_); auto formatter = - Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); + *Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); EXPECT_EQ("/original/path?secret=parameter", formatter->formatWithContext(formatter_context_, stream_info_)); } @@ -126,7 +126,7 @@ TEST_F(CELFormatterTest, TestMaxLength) { TestUtility::loadFromYaml(yaml, config_); auto formatter = - Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); + *Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); EXPECT_EQ("/original", formatter->formatWithContext(formatter_context_, stream_info_)); } @@ -142,7 +142,7 @@ TEST_F(CELFormatterTest, TestContains) { TestUtility::loadFromYaml(yaml, config_); auto formatter = - Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); + *Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); EXPECT_EQ("true", formatter->formatWithContext(formatter_context_, stream_info_)); } @@ -158,7 +158,7 @@ TEST_F(CELFormatterTest, TestComplexCelExpression) { TestUtility::loadFromYaml(yaml, config_); auto formatter = - Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); + *Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); EXPECT_EQ("true /original false", formatter->formatWithContext(formatter_context_, stream_info_)); } @@ -174,7 +174,7 @@ TEST_F(CELFormatterTest, TestInvalidExpression) { TestUtility::loadFromYaml(yaml, config_); EXPECT_THROW_WITH_REGEX( - Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_), + *Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_), EnvoyException, "Not able to parse filter expression: .*"); } #endif diff --git a/test/extensions/formatter/metadata/metadata_test.cc b/test/extensions/formatter/metadata/metadata_test.cc index acb03df661..f6d434036f 100644 --- a/test/extensions/formatter/metadata/metadata_test.cc +++ b/test/extensions/formatter/metadata/metadata_test.cc @@ -37,7 +37,9 @@ class MetadataFormatterTest : public ::testing::Test { )EOF", tag, type); TestUtility::loadFromYaml(yaml, config_); - return Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); + return THROW_OR_RETURN_VALUE( + Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_), + Envoy::Formatter::FormatterBasePtr); } Http::TestRequestHeaderMapImpl request_headers_; diff --git a/test/extensions/formatter/req_without_query/req_without_query_test.cc b/test/extensions/formatter/req_without_query/req_without_query_test.cc index bda43e2626..e019ef4e91 100644 --- a/test/extensions/formatter/req_without_query/req_without_query_test.cc +++ b/test/extensions/formatter/req_without_query/req_without_query_test.cc @@ -44,7 +44,7 @@ TEST_F(ReqWithoutQueryTest, TestStripQueryString) { TestUtility::loadFromYaml(yaml, config_); auto formatter = - Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); + *Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); EXPECT_EQ("/request/path", formatter->formatWithContext(formatter_context_, stream_info_)); } @@ -61,7 +61,7 @@ TEST_F(ReqWithoutQueryTest, TestSelectMainHeader) { TestUtility::loadFromYaml(yaml, config_); auto formatter = - Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); + *Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); EXPECT_EQ("/original/path", formatter->formatWithContext(formatter_context_, stream_info_)); } @@ -78,7 +78,7 @@ TEST_F(ReqWithoutQueryTest, TestSelectAlternativeHeader) { TestUtility::loadFromYaml(yaml, config_); auto formatter = - Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); + *Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); EXPECT_EQ("/request/path", formatter->formatWithContext(formatter_context_, stream_info_)); } @@ -95,7 +95,7 @@ TEST_F(ReqWithoutQueryTest, TestTruncateHeader) { TestUtility::loadFromYaml(yaml, config_); auto formatter = - Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); + *Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); EXPECT_EQ("/requ", formatter->formatWithContext(formatter_context_, stream_info_)); } @@ -112,7 +112,7 @@ TEST_F(ReqWithoutQueryTest, TestNonExistingHeader) { TestUtility::loadFromYaml(yaml, config_); auto formatter = - Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); + *Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); EXPECT_EQ("-", formatter->formatWithContext(formatter_context_, stream_info_)); } @@ -139,7 +139,7 @@ TEST_F(ReqWithoutQueryTest, TestFormatJson) { TestUtility::loadFromYaml(yaml, config_); auto formatter = - Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); + *Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_); const std::string actual = formatter->formatWithContext(formatter_context_, stream_info_); EXPECT_TRUE(TestUtility::jsonStringEqual(actual, expected)); } @@ -156,7 +156,8 @@ TEST_F(ReqWithoutQueryTest, TestParserNotRecognizingCommand) { )EOF"; TestUtility::loadFromYaml(yaml, config_); - EXPECT_THROW(Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_), + EXPECT_THROW(Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config_, context_) + .IgnoreError(), EnvoyException); } diff --git a/test/extensions/http/cache/file_system_http_cache/file_system_http_cache_test.cc b/test/extensions/http/cache/file_system_http_cache/file_system_http_cache_test.cc index 208163fb91..29c87ea43a 100644 --- a/test/extensions/http/cache/file_system_http_cache/file_system_http_cache_test.cc +++ b/test/extensions/http/cache/file_system_http_cache/file_system_http_cache_test.cc @@ -335,6 +335,8 @@ class FileSystemHttpCacheTestWithMockFiles : public FileSystemHttpCacheTest { return ret; } + void pumpDispatcher() { dispatcher_->run(Event::Dispatcher::RunType::Block); } + protected: ::testing::NiceMock mock_singleton_manager_; std::shared_ptr mock_async_file_manager_factory_ = @@ -364,6 +366,8 @@ class FileSystemHttpCacheTestWithMockFiles : public FileSystemHttpCacheTest { std::function expect_true_callback_; size_t headers_size_; size_t trailers_size_; + Api::ApiPtr api_ = Api::createApiForTest(); + Event::DispatcherPtr dispatcher_ = api_->allocateDispatcher("test_thread"); }; TEST_F(FileSystemHttpCacheTestWithMockFiles, WriteVaryNodeFailingToCreateFileJustAborts) { @@ -375,19 +379,21 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, WriteVaryNodeFailingToCreateFileJus {"cache-control", "public,max-age=3600"}, {"vary", "accept"}}; // one file created for the vary node, one for the actual write. - EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _)).Times(2); + EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _, _)).Times(2); inserter->insertHeaders( response_headers, metadata_, [&](bool result) { EXPECT_FALSE(result); }, true); // File handle for the vary node. // (This is the failure under test, we expect write to *not* be called.) mock_async_file_manager_->nextActionCompletes( absl::StatusOr{absl::UnknownError("create failure for vary node")}); + pumpDispatcher(); // Fail to create file for the cache entry node. // (This provokes the false callback to insertHeaders.) mock_async_file_manager_->nextActionCompletes( absl::StatusOr{absl::UnknownError("open failure")}); + pumpDispatcher(); // File handle was not used and is expected to be closed. - EXPECT_OK(mock_async_file_handle_->close([](absl::Status) {})); + EXPECT_OK(mock_async_file_handle_->close(nullptr, [](absl::Status) {})); } TEST_F(FileSystemHttpCacheTestWithMockFiles, WriteVaryNodeFailingToWriteJustClosesTheFile) { @@ -399,28 +405,31 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, WriteVaryNodeFailingToWriteJustClos {"cache-control", "public,max-age=3600"}, {"vary", "accept"}}; // one file created for the vary node, one for the actual write. - EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _)).Times(2); + EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _, _)).Times(2); inserter->insertHeaders( response_headers, metadata_, [&](bool result) { EXPECT_FALSE(result); }, true); - EXPECT_CALL(*mock_async_file_handle_, write(_, _, _)); + EXPECT_CALL(*mock_async_file_handle_, write(_, _, _, _)); // File handle for the vary node. // (This triggers the expected write call.) mock_async_file_manager_->nextActionCompletes( absl::StatusOr{mock_async_file_handle_}); + pumpDispatcher(); // Fail to create file for the cache entry node. // (This provokes the false callback to insertHeaders.) mock_async_file_manager_->nextActionCompletes( absl::StatusOr{absl::UnknownError("open failure")}); + pumpDispatcher(); // Fail to write for the vary node. mock_async_file_manager_->nextActionCompletes( absl::StatusOr(absl::UnknownError("write failure"))); + pumpDispatcher(); } TEST_F(FileSystemHttpCacheTestWithMockFiles, LookupDuringAnotherInsertPreventsInserts) { auto inserter = testInserter(); absl::Cleanup destroy_inserter{[&inserter]() { inserter->onDestroy(); }}; // First inserter will try to create a file. - EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _)); + EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _, _)); inserter->insertHeaders( response_headers_, metadata_, [&](bool result) { EXPECT_FALSE(result); }, false); @@ -429,12 +438,13 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, LookupDuringAnotherInsertPreventsIn // Allow the first inserter to complete after the second lookup was made. mock_async_file_manager_->nextActionCompletes( absl::StatusOr{absl::UnknownError("intentionally failed to open file")}); + pumpDispatcher(); inserter2->insertHeaders(response_headers_, metadata_, expect_false_callback_, false); inserter2->insertBody(Buffer::OwnedImpl("boop"), expect_false_callback_, false); inserter2->insertTrailers(response_trailers_, expect_false_callback_); EXPECT_EQ(false_callbacks_called_, 3); // The file handle didn't actually get used in this test, but is expected to be closed. - EXPECT_OK(mock_async_file_handle_->close([](absl::Status) {})); + EXPECT_OK(mock_async_file_handle_->close(nullptr, [](absl::Status) {})); } TEST_F(FileSystemHttpCacheTestWithMockFiles, DuplicateInsertWhileInsertInProgressIsPrevented) { @@ -443,29 +453,31 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, DuplicateInsertWhileInsertInProgres auto inserter2 = testInserter(); absl::Cleanup destroy_inserter2{[&inserter2]() { inserter2->onDestroy(); }}; // First inserter will try to create a file. - EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _)); + EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _, _)); inserter->insertHeaders(response_headers_, metadata_, expect_false_callback_, false); inserter2->insertHeaders(response_headers_, metadata_, expect_false_callback_, false); // Allow the first inserter to complete after the second insert was called. mock_async_file_manager_->nextActionCompletes( absl::StatusOr{absl::UnknownError("intentionally failed to open file")}); + pumpDispatcher(); inserter2->insertBody(Buffer::OwnedImpl("boop"), expect_false_callback_, false); inserter2->insertTrailers(response_trailers_, expect_false_callback_); EXPECT_EQ(false_callbacks_called_, 4); // The file handle didn't actually get used in this test, but is expected to be closed. - EXPECT_OK(mock_async_file_handle_->close([](absl::Status) {})); + EXPECT_OK(mock_async_file_handle_->close(nullptr, [](absl::Status) {})); } TEST_F(FileSystemHttpCacheTestWithMockFiles, FailedOpenForReadReturnsMiss) { auto lookup = testLookupContext(); absl::Cleanup destroy_lookup([&lookup]() { lookup->onDestroy(); }); LookupResult result; - EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _)); + EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _, _)); lookup->getHeaders([&](LookupResult&& r, bool /*end_stream*/) { result = std::move(r); }); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(absl::UnknownError("Intentionally failed to open file"))); + pumpDispatcher(); // File handle didn't get used but is expected to be closed. - EXPECT_OK(mock_async_file_handle_->close([](absl::Status) {})); + EXPECT_OK(mock_async_file_handle_->close(nullptr, [](absl::Status) {})); EXPECT_EQ(result.cache_entry_status_, CacheEntryStatus::Unusable); EXPECT_EQ(cache_->stats().cache_miss_.value(), 1); EXPECT_EQ(cache_->stats().cache_hit_.value(), 0); @@ -480,21 +492,25 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, FailedReadOfHeaderBlockInvalidatesT auto lookup = testLookupContext(); absl::Cleanup destroy_lookup([&lookup]() { lookup->onDestroy(); }); LookupResult result; - EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _)); - EXPECT_CALL(*mock_async_file_handle_, read(0, CacheFileFixedBlock::size(), _)); + EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _, _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, 0, CacheFileFixedBlock::size(), _)); lookup->getHeaders([&](LookupResult&& r, bool /*end_stream*/) { result = std::move(r); }); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(mock_async_file_handle_)); - EXPECT_CALL(*mock_async_file_manager_, stat(_, _)); - EXPECT_CALL(*mock_async_file_manager_, unlink(_, _)); + pumpDispatcher(); + EXPECT_CALL(*mock_async_file_manager_, stat(_, _, _)); + EXPECT_CALL(*mock_async_file_manager_, unlink(_, _, _)); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(absl::UnknownError("intentional failure to read"))); + pumpDispatcher(); struct stat stat_result = {}; stat_result.st_size = 12345; // stat mock_async_file_manager_->nextActionCompletes(absl::StatusOr{stat_result}); + pumpDispatcher(); // unlink mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); + pumpDispatcher(); EXPECT_EQ(result.cache_entry_status_, CacheEntryStatus::Unusable); waitForEvictionThreadIdle(); // Should have deducted the size of the file that got deleted. Since we started at 2 * 12345, @@ -519,19 +535,23 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, ReadWithInvalidHeaderBlockInvalidat auto lookup = testLookupContext(); absl::Cleanup destroy_lookup([&lookup]() { lookup->onDestroy(); }); LookupResult result; - EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _)); - EXPECT_CALL(*mock_async_file_handle_, read(0, CacheFileFixedBlock::size(), _)); + EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _, _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, 0, CacheFileFixedBlock::size(), _)); lookup->getHeaders([&](LookupResult&& r, bool /*end_stream*/) { result = std::move(r); }); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(mock_async_file_handle_)); - EXPECT_CALL(*mock_async_file_manager_, stat(_, _)); - EXPECT_CALL(*mock_async_file_manager_, unlink(_, _)); + pumpDispatcher(); + EXPECT_CALL(*mock_async_file_manager_, stat(_, _, _)); + EXPECT_CALL(*mock_async_file_manager_, unlink(_, _, _)); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(invalidHeaderBlock())); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::StatusOr{ absl::UnknownError("intentionally failed to stat, for coverage")}); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::UnknownError("intentionally failed to unlink, for coverage")); + pumpDispatcher(); EXPECT_EQ(result.cache_entry_status_, CacheEntryStatus::Unusable); } @@ -539,22 +559,27 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, FailedReadOfHeaderProtoInvalidatesT auto lookup = testLookupContext(); absl::Cleanup destroy_lookup([&lookup]() { lookup->onDestroy(); }); LookupResult result; - EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _)); - EXPECT_CALL(*mock_async_file_handle_, read(0, CacheFileFixedBlock::size(), _)); - EXPECT_CALL(*mock_async_file_handle_, read(CacheFileFixedBlock::size(), headers_size_, _)); + EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _, _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, 0, CacheFileFixedBlock::size(), _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, CacheFileFixedBlock::size(), headers_size_, _)); lookup->getHeaders([&](LookupResult&& r, bool /*end_stream*/) { result = std::move(r); }); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(mock_async_file_handle_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(testHeaderBlock(0))); - EXPECT_CALL(*mock_async_file_manager_, stat(_, _)); - EXPECT_CALL(*mock_async_file_manager_, unlink(_, _)); + pumpDispatcher(); + EXPECT_CALL(*mock_async_file_manager_, stat(_, _, _)); + EXPECT_CALL(*mock_async_file_manager_, unlink(_, _, _)); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(absl::UnknownError("intentional failure to read"))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::StatusOr{ absl::UnknownError("intentionally failed to stat, for coverage")}); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::UnknownError("intentionally failed to unlink, for coverage")); + pumpDispatcher(); EXPECT_EQ(result.cache_entry_status_, CacheEntryStatus::Unusable); } @@ -563,34 +588,40 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, FailedReadOfBodyInvalidatesTheCache absl::Cleanup destroy_lookup([&lookup]() { lookup->onDestroy(); }); LookupResult result; bool end_stream_after_headers = true; // initialized wrong to ensure it's set. - EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _)); - EXPECT_CALL(*mock_async_file_handle_, read(0, CacheFileFixedBlock::size(), _)); - EXPECT_CALL(*mock_async_file_handle_, read(CacheFileFixedBlock::size(), headers_size_, _)); + EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _, _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, 0, CacheFileFixedBlock::size(), _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, CacheFileFixedBlock::size(), headers_size_, _)); lookup->getHeaders([&](LookupResult&& r, bool es) { result = std::move(r); end_stream_after_headers = es; }); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(mock_async_file_handle_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(testHeaderBlock(0))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(testHeaderBuffer())); + pumpDispatcher(); // result should be populated. EXPECT_NE(result.cache_entry_status_, CacheEntryStatus::Unusable); EXPECT_FALSE(end_stream_after_headers); - EXPECT_CALL(*mock_async_file_handle_, read(_, _, _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, _, _, _)); lookup->getBody(AdjustedByteRange(0, 8), [&](Buffer::InstancePtr body, bool /*end_stream*/) { EXPECT_EQ(body.get(), nullptr); }); - EXPECT_CALL(*mock_async_file_manager_, stat(_, _)); - EXPECT_CALL(*mock_async_file_manager_, unlink(_, _)); + EXPECT_CALL(*mock_async_file_manager_, stat(_, _, _)); + EXPECT_CALL(*mock_async_file_manager_, unlink(_, _, _)); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(absl::UnknownError("intentional failure to read"))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::StatusOr{ absl::UnknownError("intentionally failed to stat, for coverage")}); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::UnknownError("intentionally failed to unlink, for coverage")); + pumpDispatcher(); } TEST_F(FileSystemHttpCacheTestWithMockFiles, FailedReadOfTrailersInvalidatesTheCacheEntry) { @@ -598,230 +629,268 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, FailedReadOfTrailersInvalidatesTheC absl::Cleanup destroy_lookup([&lookup]() { lookup->onDestroy(); }); LookupResult result; bool end_stream_after_headers = true; - EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _)); - EXPECT_CALL(*mock_async_file_handle_, read(0, CacheFileFixedBlock::size(), _)); - EXPECT_CALL(*mock_async_file_handle_, read(CacheFileFixedBlock::size(), headers_size_, _)); + EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _, _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, 0, CacheFileFixedBlock::size(), _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, CacheFileFixedBlock::size(), headers_size_, _)); lookup->getHeaders([&](LookupResult&& r, bool es) { result = std::move(r); end_stream_after_headers = es; }); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(mock_async_file_handle_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(testHeaderBlock(0))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(testHeaderBuffer())); + pumpDispatcher(); // result should be populated. EXPECT_NE(result.cache_entry_status_, CacheEntryStatus::Unusable); EXPECT_FALSE(end_stream_after_headers); - EXPECT_CALL(*mock_async_file_handle_, read(_, 8, _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, _, 8, _)); lookup->getBody(AdjustedByteRange(0, 8), [&](Buffer::InstancePtr body, bool end_stream) { EXPECT_EQ(body->toString(), "beepbeep"); EXPECT_FALSE(end_stream); }); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(std::make_unique("beepbeep"))); - EXPECT_CALL(*mock_async_file_handle_, read(_, _, _)); + pumpDispatcher(); + EXPECT_CALL(*mock_async_file_handle_, read(_, _, _, _)); // No point validating that the trailers are empty since that's not even particularly // desirable behavior - it's a quirk of the filter that we can't properly signify an error. lookup->getTrailers([&](Http::ResponseTrailerMapPtr) {}); - EXPECT_CALL(*mock_async_file_manager_, stat(_, _)); - EXPECT_CALL(*mock_async_file_manager_, unlink(_, _)); + EXPECT_CALL(*mock_async_file_manager_, stat(_, _, _)); + EXPECT_CALL(*mock_async_file_manager_, unlink(_, _, _)); mock_async_file_manager_->nextActionCompletes(absl::StatusOr( absl::UnknownError("intentional failure to read trailers"))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::StatusOr{ absl::UnknownError("intentionally failed to stat, for coverage")}); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::UnknownError("intentionally failed to unlink, for coverage")); + pumpDispatcher(); } TEST_F(FileSystemHttpCacheTestWithMockFiles, ReadWithMultipleBlocksWorksCorrectly) { trailers_size_ = 0; auto lookup = testLookupContext(); LookupResult result; - EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _)); - EXPECT_CALL(*mock_async_file_handle_, read(0, CacheFileFixedBlock::size(), _)); + EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _, _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, 0, CacheFileFixedBlock::size(), _)); EXPECT_CALL(*mock_async_file_handle_, - read(CacheFileFixedBlock::offsetToHeaders(), headers_size_, _)); + read(_, CacheFileFixedBlock::offsetToHeaders(), headers_size_, _)); lookup->getHeaders([&](LookupResult&& r, bool end_stream) { result = std::move(r); EXPECT_FALSE(end_stream) << "in headers"; }); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(mock_async_file_handle_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(testHeaderBlock(8))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(testHeaderBuffer())); + pumpDispatcher(); EXPECT_CALL(*mock_async_file_handle_, - read(CacheFileFixedBlock::offsetToHeaders() + headers_size_, 4, _)); + read(_, CacheFileFixedBlock::offsetToHeaders() + headers_size_, 4, _)); EXPECT_CALL(*mock_async_file_handle_, - read(CacheFileFixedBlock::offsetToHeaders() + headers_size_ + 4, 4, _)); + read(_, CacheFileFixedBlock::offsetToHeaders() + headers_size_ + 4, 4, _)); lookup->getBody(AdjustedByteRange(0, 4), [&](Buffer::InstancePtr body, bool end_stream) { EXPECT_EQ(body->toString(), "beep"); EXPECT_FALSE(end_stream) << "in body part 1"; }); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(std::make_unique("beep"))); + pumpDispatcher(); lookup->getBody(AdjustedByteRange(4, 8), [&](Buffer::InstancePtr body, bool end_stream) { EXPECT_EQ(body->toString(), "boop"); EXPECT_TRUE(end_stream) << "in body part 2"; }); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(std::make_unique("boop"))); + pumpDispatcher(); // While we're here, incidentally test the behavior of aborting a lookup in progress // while no file actions are in flight. lookup->onDestroy(); lookup.reset(); // There should be a file-close in the queue. mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); + pumpDispatcher(); } TEST_F(FileSystemHttpCacheTestWithMockFiles, DestroyingALookupWithFileActionInFlightCancelsAction) { auto lookup = testLookupContext(); absl::Cleanup destroy_lookup([&lookup]() { lookup->onDestroy(); }); LookupResult result; - EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _)); - EXPECT_CALL(*mock_async_file_manager_, mockCancel()).WillOnce([this]() { - mock_async_file_manager_->queue_.pop_front(); - }); + EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _, _)); + EXPECT_CALL(*mock_async_file_manager_, mockCancel()); lookup->getHeaders([&](LookupResult&& r, bool /*end_stream*/) { result = std::move(r); }); // File wasn't used in this test but is expected to be closed. - EXPECT_OK(mock_async_file_handle_->close([](absl::Status) {})); + EXPECT_OK(mock_async_file_handle_->close(nullptr, [](absl::Status) {})); } TEST_F(FileSystemHttpCacheTestWithMockFiles, DestroyingInsertContextWithFileActionInFlightCancelsAction) { auto inserter = testInserter(); absl::Cleanup destroy_inserter([&inserter]() { inserter->onDestroy(); }); - EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _)); - EXPECT_CALL(*mock_async_file_manager_, mockCancel()).WillOnce([this]() { - mock_async_file_manager_->queue_.pop_front(); - }); + EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _, _)); + EXPECT_CALL(*mock_async_file_manager_, mockCancel()); inserter->insertHeaders(response_headers_, metadata_, expect_false_callback_, false); // File wasn't used in this test but is expected to be closed. - EXPECT_OK(mock_async_file_handle_->close([](absl::Status) {})); + EXPECT_OK(mock_async_file_handle_->close(nullptr, [](absl::Status) {})); } TEST_F(FileSystemHttpCacheTestWithMockFiles, InsertAbortsOnFailureToWriteEmptyHeaderBlock) { auto inserter = testInserter(); absl::Cleanup destroy_inserter([&inserter]() { inserter->onDestroy(); }); - EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _)); - EXPECT_CALL(*mock_async_file_handle_, write(_, _, _)); + EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _, _)); + EXPECT_CALL(*mock_async_file_handle_, write(_, _, _, _)); inserter->insertHeaders(response_headers_, metadata_, expect_false_callback_, false); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(mock_async_file_handle_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::StatusOr( absl::UnknownError("intentionally failed write to empty header block"))); + pumpDispatcher(); EXPECT_EQ(false_callbacks_called_, 1); } TEST_F(FileSystemHttpCacheTestWithMockFiles, InsertAbortsOnFailureToWriteHeaderChunk) { auto inserter = testInserter(); absl::Cleanup destroy_inserter([&inserter]() { inserter->onDestroy(); }); - EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _)); - EXPECT_CALL(*mock_async_file_handle_, write(_, _, _)).Times(2); + EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _, _)); + EXPECT_CALL(*mock_async_file_handle_, write(_, _, _, _)).Times(2); inserter->insertHeaders(response_headers_, metadata_, expect_false_callback_, false); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(mock_async_file_handle_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(CacheFileFixedBlock::size())); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(absl::UnknownError("intentionally failed write of header chunk"))); + pumpDispatcher(); EXPECT_EQ(false_callbacks_called_, 1); } TEST_F(FileSystemHttpCacheTestWithMockFiles, InsertAbortsOnFailureToWriteBodyChunk) { auto inserter = testInserter(); absl::Cleanup destroy_inserter([&inserter]() { inserter->onDestroy(); }); - EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _)); - EXPECT_CALL(*mock_async_file_handle_, write(_, _, _)).Times(3); + EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _, _)); + EXPECT_CALL(*mock_async_file_handle_, write(_, _, _, _)).Times(3); inserter->insertHeaders(response_headers_, metadata_, expect_true_callback_, false); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(mock_async_file_handle_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(CacheFileFixedBlock::size())); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::StatusOr(headers_size_)); + pumpDispatcher(); EXPECT_EQ(true_callbacks_called_, 1); inserter->insertBody(Buffer::OwnedImpl("woop"), expect_false_callback_, false); // Intentionally undersized write of body chunk. mock_async_file_manager_->nextActionCompletes(absl::StatusOr(1)); + pumpDispatcher(); EXPECT_EQ(false_callbacks_called_, 1); } TEST_F(FileSystemHttpCacheTestWithMockFiles, InsertAbortsOnFailureToWriteTrailerChunk) { auto inserter = testInserter(); absl::Cleanup destroy_inserter([&inserter]() { inserter->onDestroy(); }); - EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _)); - EXPECT_CALL(*mock_async_file_handle_, write(_, _, _)).Times(4); + EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _, _)); + EXPECT_CALL(*mock_async_file_handle_, write(_, _, _, _)).Times(4); inserter->insertHeaders(response_headers_, metadata_, expect_true_callback_, false); const absl::string_view body = "woop"; mock_async_file_manager_->nextActionCompletes( absl::StatusOr(mock_async_file_handle_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(CacheFileFixedBlock::size())); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::StatusOr(headers_size_)); + pumpDispatcher(); EXPECT_EQ(true_callbacks_called_, 1); inserter->insertBody(Buffer::OwnedImpl(body), expect_true_callback_, false); mock_async_file_manager_->nextActionCompletes(absl::StatusOr(body.size())); + pumpDispatcher(); EXPECT_EQ(true_callbacks_called_, 2); inserter->insertTrailers(response_trailers_, expect_false_callback_); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(absl::UnknownError("intentionally failed write of trailer chunk"))); + pumpDispatcher(); EXPECT_EQ(false_callbacks_called_, 1); } TEST_F(FileSystemHttpCacheTestWithMockFiles, InsertAbortsOnFailureToWriteUpdatedHeaderBlock) { auto inserter = testInserter(); absl::Cleanup destroy_inserter([&inserter]() { inserter->onDestroy(); }); - EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _)); - EXPECT_CALL(*mock_async_file_handle_, write(_, _, _)).Times(5); + EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _, _)); + EXPECT_CALL(*mock_async_file_handle_, write(_, _, _, _)).Times(5); inserter->insertHeaders(response_headers_, metadata_, expect_true_callback_, false); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(mock_async_file_handle_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(CacheFileFixedBlock::size())); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::StatusOr(headers_size_)); + pumpDispatcher(); EXPECT_EQ(true_callbacks_called_, 1); const absl::string_view body = "woop"; inserter->insertBody(Buffer::OwnedImpl(body), expect_true_callback_, false); mock_async_file_manager_->nextActionCompletes(absl::StatusOr(body.size())); + pumpDispatcher(); EXPECT_EQ(true_callbacks_called_, 2); inserter->insertTrailers(response_trailers_, expect_false_callback_); mock_async_file_manager_->nextActionCompletes(absl::StatusOr(trailers_size_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::StatusOr( absl::UnknownError("intentionally failed write of updated header block"))); + pumpDispatcher(); EXPECT_EQ(false_callbacks_called_, 1); } TEST_F(FileSystemHttpCacheTestWithMockFiles, InsertAbortsOnFailureToLinkFile) { auto inserter = testInserter(); absl::Cleanup destroy_inserter([&inserter]() { inserter->onDestroy(); }); - EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _)); - EXPECT_CALL(*mock_async_file_handle_, write(_, _, _)).Times(5); - EXPECT_CALL(*mock_async_file_manager_, stat(_, _)); - EXPECT_CALL(*mock_async_file_manager_, unlink(_, _)); - EXPECT_CALL(*mock_async_file_handle_, createHardLink(_, _)); + EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _, _)); + EXPECT_CALL(*mock_async_file_handle_, write(_, _, _, _)).Times(5); + EXPECT_CALL(*mock_async_file_manager_, stat(_, _, _)); + EXPECT_CALL(*mock_async_file_manager_, unlink(_, _, _)); + EXPECT_CALL(*mock_async_file_handle_, createHardLink(_, _, _)); inserter->insertHeaders(response_headers_, metadata_, expect_true_callback_, false); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(mock_async_file_handle_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(CacheFileFixedBlock::size())); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::StatusOr(headers_size_)); + pumpDispatcher(); EXPECT_EQ(true_callbacks_called_, 1); const absl::string_view body = "woop"; inserter->insertBody(Buffer::OwnedImpl(body), expect_true_callback_, false); mock_async_file_manager_->nextActionCompletes(absl::StatusOr(body.size())); + pumpDispatcher(); EXPECT_EQ(true_callbacks_called_, 2); inserter->insertTrailers(response_trailers_, expect_false_callback_); mock_async_file_manager_->nextActionCompletes(absl::StatusOr(trailers_size_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(CacheFileFixedBlock::size())); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::StatusOr{ absl::UnknownError("intentionally failed to stat, for coverage")}); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::UnknownError("intentionally failed to link cache file")); + pumpDispatcher(); EXPECT_EQ(false_callbacks_called_, 1); } @@ -834,16 +903,17 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, UpdateHeadersAbortsIfFileOpenFailed {"cache-control", "public,max-age=3600"}, }; auto lookup_context = testLookupContext(); - EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _)); + EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _, _)); bool update_success; cache_->updateHeaders(*lookup_context, response_headers, {time_system_.systemTime()}, [&update_success](bool success) { update_success = success; }); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(absl::UnknownError("Intentionally failed to open file"))); + pumpDispatcher(); lookup_context->onDestroy(); EXPECT_FALSE(update_success); // File is not used in this test, but is expected to be closed. - EXPECT_OK(mock_async_file_handle_->close([](absl::Status) {})); + EXPECT_OK(mock_async_file_handle_->close(nullptr, [](absl::Status) {})); } TEST_F(FileSystemHttpCacheTestWithMockFiles, UpdateHeadersKeepsTryingIfUnlinkOriginalFails) { @@ -855,19 +925,23 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, UpdateHeadersKeepsTryingIfUnlinkOri {"cache-control", "public,max-age=3600"}, }; auto lookup_context = testLookupContext(); - EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _)); - EXPECT_CALL(*mock_async_file_manager_, unlink(_, _)); - EXPECT_CALL(*mock_async_file_handle_, read(0, CacheFileFixedBlock::size(), _)); + EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _, _)); + EXPECT_CALL(*mock_async_file_manager_, unlink(_, _, _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, 0, CacheFileFixedBlock::size(), _)); bool update_success; cache_->updateHeaders(*lookup_context, response_headers, {time_system_.systemTime()}, [&update_success](bool success) { update_success = success; }); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(mock_async_file_handle_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::UnknownError("Intentionally failed to unlink")); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::StatusOr( absl::UnknownError("Intentionally failed to read header block"))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); // close read handle + pumpDispatcher(); lookup_context->onDestroy(); EXPECT_FALSE(update_success); } @@ -881,21 +955,26 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, UpdateHeadersAbortsIfReadHeadersFai {"cache-control", "public,max-age=3600"}, }; auto lookup_context = testLookupContext(); - EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _)); - EXPECT_CALL(*mock_async_file_manager_, unlink(_, _)); - EXPECT_CALL(*mock_async_file_handle_, read(0, CacheFileFixedBlock::size(), _)); - EXPECT_CALL(*mock_async_file_handle_, read(CacheFileFixedBlock::size(), headers_size_, _)); + EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _, _)); + EXPECT_CALL(*mock_async_file_manager_, unlink(_, _, _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, 0, CacheFileFixedBlock::size(), _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, CacheFileFixedBlock::size(), headers_size_, _)); bool update_success; cache_->updateHeaders(*lookup_context, response_headers, {time_system_.systemTime()}, [&update_success](bool success) { update_success = success; }); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(mock_async_file_handle_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(testHeaderBlock(0))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::StatusOr( absl::UnknownError("Intentionally failed to read headers block"))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); // close read handle + pumpDispatcher(); lookup_context->onDestroy(); EXPECT_FALSE(update_success); } @@ -918,22 +997,27 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, UpdateHeadersAbortsIfReadHeadersFin {"cache-control", "public,max-age=3600"}, }; auto lookup_context = testLookupContext(); - EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _)); - EXPECT_CALL(*mock_async_file_manager_, unlink(_, _)); - EXPECT_CALL(*mock_async_file_handle_, read(0, CacheFileFixedBlock::size(), _)); + EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _, _)); + EXPECT_CALL(*mock_async_file_manager_, unlink(_, _, _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, 0, CacheFileFixedBlock::size(), _)); EXPECT_CALL(*mock_async_file_handle_, - read(CacheFileFixedBlock::size(), vary_headers_buffer->length(), _)); + read(_, CacheFileFixedBlock::size(), vary_headers_buffer->length(), _)); bool update_success; cache_->updateHeaders(*lookup_context, response_headers, {time_system_.systemTime()}, [&update_success](bool success) { update_success = success; }); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(mock_async_file_handle_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(std::move(vary_block_buffer))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(std::move(vary_headers_buffer))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); // close read handle + pumpDispatcher(); lookup_context->onDestroy(); EXPECT_FALSE(update_success); } @@ -947,24 +1031,30 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, UpdateHeadersAbortsIfOpenForWriteFa {"cache-control", "public,max-age=3600"}, }; auto lookup_context = testLookupContext(); - EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _)); - EXPECT_CALL(*mock_async_file_manager_, unlink(_, _)); - EXPECT_CALL(*mock_async_file_handle_, read(0, CacheFileFixedBlock::size(), _)); - EXPECT_CALL(*mock_async_file_handle_, read(CacheFileFixedBlock::size(), headers_size_, _)); - EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _)); + EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _, _)); + EXPECT_CALL(*mock_async_file_manager_, unlink(_, _, _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, 0, CacheFileFixedBlock::size(), _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, CacheFileFixedBlock::size(), headers_size_, _)); + EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _, _)); bool update_success; cache_->updateHeaders(*lookup_context, response_headers, {time_system_.systemTime()}, [&update_success](bool success) { update_success = success; }); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(mock_async_file_handle_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(testHeaderBlock(0))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(testHeaderBuffer())); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::StatusOr( absl::UnknownError("Intentionally failed to create file for write"))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); // close read handle + pumpDispatcher(); lookup_context->onDestroy(); EXPECT_FALSE(update_success); } @@ -980,27 +1070,35 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, UpdateHeadersAbortsIfWriteHeaderBlo auto lookup_context = testLookupContext(); MockAsyncFileHandle write_handle = std::make_shared>(mock_async_file_manager_); - EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _)); - EXPECT_CALL(*mock_async_file_manager_, unlink(_, _)); - EXPECT_CALL(*mock_async_file_handle_, read(0, CacheFileFixedBlock::size(), _)); - EXPECT_CALL(*mock_async_file_handle_, read(CacheFileFixedBlock::size(), headers_size_, _)); - EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _)); - EXPECT_CALL(*write_handle, write(_, 0, _)); + EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _, _)); + EXPECT_CALL(*mock_async_file_manager_, unlink(_, _, _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, 0, CacheFileFixedBlock::size(), _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, CacheFileFixedBlock::size(), headers_size_, _)); + EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _, _)); + EXPECT_CALL(*write_handle, write(_, _, 0, _)); bool update_success; cache_->updateHeaders(*lookup_context, response_headers, {time_system_.systemTime()}, [&update_success](bool success) { update_success = success; }); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(mock_async_file_handle_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(testHeaderBlock(0))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(testHeaderBuffer())); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::StatusOr(write_handle)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(absl::UnknownError("Intentionally failed to write header block"))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); // close read handle + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); // close write handle + pumpDispatcher(); lookup_context->onDestroy(); EXPECT_FALSE(update_success); } @@ -1019,31 +1117,40 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, UpdateHeadersAbortsIfReadBodyFails) auto lookup_context = testLookupContext(); MockAsyncFileHandle write_handle = std::make_shared>(mock_async_file_manager_); - EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _)); - EXPECT_CALL(*mock_async_file_manager_, unlink(_, _)); - EXPECT_CALL(*mock_async_file_handle_, read(0, CacheFileFixedBlock::size(), _)); - EXPECT_CALL(*mock_async_file_handle_, read(CacheFileFixedBlock::size(), headers_size_, _)); - EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _)); - EXPECT_CALL(*write_handle, write(_, 0, _)); + EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _, _)); + EXPECT_CALL(*mock_async_file_manager_, unlink(_, _, _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, 0, CacheFileFixedBlock::size(), _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, CacheFileFixedBlock::size(), headers_size_, _)); + EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _, _)); + EXPECT_CALL(*write_handle, write(_, _, 0, _)); EXPECT_CALL(*mock_async_file_handle_, - read(CacheFileFixedBlock::size() + headers_size_, body_size + trailers_size_, _)); + read(_, CacheFileFixedBlock::size() + headers_size_, body_size + trailers_size_, _)); bool update_success; cache_->updateHeaders(*lookup_context, response_headers, {time_system_.systemTime()}, [&update_success](bool success) { update_success = success; }); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(mock_async_file_handle_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(testHeaderBlock(body_size))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(testHeaderBuffer())); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::StatusOr(write_handle)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(updated_headers_size + CacheFileFixedBlock::size())); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(absl::UnknownError("intentionally failed body read"))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); // close read handle + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); // close write handle + pumpDispatcher(); lookup_context->onDestroy(); EXPECT_FALSE(update_success); } @@ -1062,36 +1169,46 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, UpdateHeadersAbortsIfWriteBodyFails auto lookup_context = testLookupContext(); MockAsyncFileHandle write_handle = std::make_shared>(mock_async_file_manager_); - EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _)); - EXPECT_CALL(*mock_async_file_manager_, unlink(_, _)); - EXPECT_CALL(*mock_async_file_handle_, read(0, CacheFileFixedBlock::size(), _)); - EXPECT_CALL(*mock_async_file_handle_, read(CacheFileFixedBlock::size(), headers_size_, _)); - EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _)); - EXPECT_CALL(*write_handle, write(_, 0, _)); + EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _, _)); + EXPECT_CALL(*mock_async_file_manager_, unlink(_, _, _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, 0, CacheFileFixedBlock::size(), _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, CacheFileFixedBlock::size(), headers_size_, _)); + EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _, _)); + EXPECT_CALL(*write_handle, write(_, _, 0, _)); EXPECT_CALL(*mock_async_file_handle_, - read(CacheFileFixedBlock::size() + headers_size_, body_size + trailers_size_, _)); - EXPECT_CALL(*write_handle, write(_, CacheFileFixedBlock::size() + updated_headers_size, _)); + read(_, CacheFileFixedBlock::size() + headers_size_, body_size + trailers_size_, _)); + EXPECT_CALL(*write_handle, write(_, _, CacheFileFixedBlock::size() + updated_headers_size, _)); bool update_success; cache_->updateHeaders(*lookup_context, response_headers, {time_system_.systemTime()}, [&update_success](bool success) { update_success = success; }); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(mock_async_file_handle_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(testHeaderBlock(body_size))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(testHeaderBuffer())); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::StatusOr(write_handle)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(updated_headers_size + CacheFileFixedBlock::size())); + pumpDispatcher(); std::string body_and_trailers; body_and_trailers.resize(body_size + trailers_size_); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(std::make_unique(body_and_trailers))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(absl::UnknownError("intentionally failed body write"))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); // close read handle + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); // close write handle + pumpDispatcher(); lookup_context->onDestroy(); EXPECT_FALSE(update_success); } @@ -1110,19 +1227,20 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, UpdateHeadersCopiesInChunksIfBodySi auto lookup_context = testLookupContext(); MockAsyncFileHandle write_handle = std::make_shared>(mock_async_file_manager_); - EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _)); - EXPECT_CALL(*mock_async_file_manager_, unlink(_, _)); - EXPECT_CALL(*mock_async_file_handle_, read(0, CacheFileFixedBlock::size(), _)); - EXPECT_CALL(*mock_async_file_handle_, read(CacheFileFixedBlock::size(), headers_size_, _)); - EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _)); - EXPECT_CALL(*write_handle, write(_, 0, _)); + EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _, _)); + EXPECT_CALL(*mock_async_file_manager_, unlink(_, _, _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, 0, CacheFileFixedBlock::size(), _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, CacheFileFixedBlock::size(), headers_size_, _)); + EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _, _)); + EXPECT_CALL(*write_handle, write(_, _, 0, _)); EXPECT_CALL(*mock_async_file_handle_, - read(CacheFileFixedBlock::size() + headers_size_, + read(_, CacheFileFixedBlock::size() + headers_size_, FileSystemHttpCache::max_update_headers_copy_chunk_size_, _)); - EXPECT_CALL(*write_handle, write(_, CacheFileFixedBlock::size() + updated_headers_size, _)); + EXPECT_CALL(*write_handle, write(_, _, CacheFileFixedBlock::size() + updated_headers_size, _)); EXPECT_CALL( *mock_async_file_handle_, - read(CacheFileFixedBlock::size() + headers_size_ + + read(_, + CacheFileFixedBlock::size() + headers_size_ + FileSystemHttpCache::max_update_headers_copy_chunk_size_, body_size + trailers_size_ - FileSystemHttpCache::max_update_headers_copy_chunk_size_, _)); @@ -1131,24 +1249,35 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, UpdateHeadersCopiesInChunksIfBodySi [&update_success](bool success) { update_success = success; }); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(mock_async_file_handle_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(testHeaderBlock(body_size))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(testHeaderBuffer())); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::StatusOr(write_handle)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(updated_headers_size + CacheFileFixedBlock::size())); + pumpDispatcher(); std::string body_chunk; body_chunk.resize(FileSystemHttpCache::max_update_headers_copy_chunk_size_); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(std::make_unique(body_chunk))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(FileSystemHttpCache::max_update_headers_copy_chunk_size_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::StatusOr( absl::UnknownError("intentionally failed second body read"))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); // close read handle + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); // close write handle + pumpDispatcher(); lookup_context->onDestroy(); EXPECT_FALSE(update_success); } @@ -1167,37 +1296,48 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, UpdateHeadersAbortsIfLinkFails) { auto lookup_context = testLookupContext(); MockAsyncFileHandle write_handle = std::make_shared>(mock_async_file_manager_); - EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _)); - EXPECT_CALL(*mock_async_file_manager_, unlink(_, _)); - EXPECT_CALL(*mock_async_file_handle_, read(0, CacheFileFixedBlock::size(), _)); - EXPECT_CALL(*mock_async_file_handle_, read(CacheFileFixedBlock::size(), headers_size_, _)); - EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _)); - EXPECT_CALL(*write_handle, write(_, 0, _)); + EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _, _)); + EXPECT_CALL(*mock_async_file_manager_, unlink(_, _, _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, 0, CacheFileFixedBlock::size(), _)); + EXPECT_CALL(*mock_async_file_handle_, read(_, CacheFileFixedBlock::size(), headers_size_, _)); + EXPECT_CALL(*mock_async_file_manager_, createAnonymousFile(_, _, _)); + EXPECT_CALL(*write_handle, write(_, _, 0, _)); EXPECT_CALL(*mock_async_file_handle_, - read(CacheFileFixedBlock::size() + headers_size_, body_size + trailers_size_, _)); - EXPECT_CALL(*write_handle, write(_, CacheFileFixedBlock::size() + updated_headers_size, _)); - EXPECT_CALL(*write_handle, createHardLink(_, _)); + read(_, CacheFileFixedBlock::size() + headers_size_, body_size + trailers_size_, _)); + EXPECT_CALL(*write_handle, write(_, _, CacheFileFixedBlock::size() + updated_headers_size, _)); + EXPECT_CALL(*write_handle, createHardLink(_, _, _)); bool update_success; cache_->updateHeaders(*lookup_context, response_headers, {time_system_.systemTime()}, [&update_success](bool success) { update_success = success; }); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(mock_async_file_handle_)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(testHeaderBlock(body_size))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(testHeaderBuffer())); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::StatusOr(write_handle)); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(updated_headers_size + CacheFileFixedBlock::size())); + pumpDispatcher(); std::string body_and_trailers; body_and_trailers.resize(body_size + trailers_size_); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(std::make_unique(body_and_trailers))); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::StatusOr(body_and_trailers.size())); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::UnknownError("intentionally failed to link")); + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); // close read handle + pumpDispatcher(); mock_async_file_manager_->nextActionCompletes(absl::OkStatus()); // close write handle + pumpDispatcher(); lookup_context->onDestroy(); EXPECT_FALSE(update_success); } @@ -1210,7 +1350,7 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, UpdateHeadersAbortsEarlyIfCacheEntr {"x-whatever", "updated"}, {"cache-control", "public,max-age=3600"}, }; - EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _)); + EXPECT_CALL(*mock_async_file_manager_, openExistingFile(_, _, _, _)); bool update_success; cache_->updateHeaders(*lookup_context, response_headers, {time_system_.systemTime()}, [&update_success](bool success) { update_success = success; }); @@ -1221,10 +1361,11 @@ TEST_F(FileSystemHttpCacheTestWithMockFiles, UpdateHeadersAbortsEarlyIfCacheEntr [&update_success](bool success) { update_success = success; }); mock_async_file_manager_->nextActionCompletes( absl::StatusOr(absl::UnknownError("intentionally failed to open file"))); + pumpDispatcher(); lookup_context->onDestroy(); lookup_context_2->onDestroy(); // The file handle didn't actually get used in this test, but is expected to be closed. - EXPECT_OK(mock_async_file_handle_->close([](absl::Status) {})); + EXPECT_OK(mock_async_file_handle_->close(nullptr, [](absl::Status) {})); } // For the standard cache tests from http_cache_implementation_test_common.cc @@ -1236,6 +1377,7 @@ class FileSystemHttpCacheTestDelegate : public HttpCacheTestDelegate, FileSystemHttpCacheTestDelegate() { initCache(); } std::shared_ptr cache() override { return cache_; } bool validationEnabled() const override { return true; } + void beforePumpingDispatcher() override { cache_->drainAsyncFileActionsForTest(); } }; // For the standard cache tests from http_cache_implementation_test_common.cc diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index b2dc7b4fa1..65251998c9 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -2380,6 +2380,22 @@ TEST_P(QuicHttpIntegrationTest, SendDisableActiveMigration) { ASSERT_TRUE(response->complete()); } +TEST_P(QuicHttpIntegrationTest, RejectTraffic) { + config_helper_.addConfigModifier([=](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + bootstrap.mutable_static_resources() + ->mutable_listeners(0) + ->mutable_udp_listener_config() + ->mutable_quic_options() + ->set_reject_new_connections(true); + }); + + initialize(); + codec_client_ = makeRawHttpConnection(makeClientConnection(lookupPort("http")), absl::nullopt); + EXPECT_TRUE(codec_client_->disconnected()); + EXPECT_EQ(quic::QUIC_INVALID_VERSION, + static_cast(codec_client_->connection())->error()); +} + // Validate that the transport parameter is not sent when `send_disable_active_migration` is // unset. TEST_P(QuicHttpIntegrationTest, UnsetSendDisableActiveMigration) { diff --git a/test/per_file_coverage.sh b/test/per_file_coverage.sh index eae1d69683..c5bc35cf78 100755 --- a/test/per_file_coverage.sh +++ b/test/per_file_coverage.sh @@ -4,8 +4,6 @@ # for existing directories with low coverage. declare -a KNOWN_LOW_COVERAGE=( "source/common:96.2" -"source/common/api:84.5" # flaky due to posix: be careful adjusting -"source/common/api/posix:83.8" # flaky (accept failover non-deterministic): be careful adjusting "source/common/common/posix:96.2" # flaky due to posix: be careful adjusting "source/common/config:96.1" "source/common/crypto:95.5" diff --git a/tools/code_format/config.yaml b/tools/code_format/config.yaml index 286208a3ba..5b1d73eda6 100644 --- a/tools/code_format/config.yaml +++ b/tools/code_format/config.yaml @@ -110,7 +110,6 @@ paths: - source/common/formatter/stream_info_formatter.h - source/common/formatter/stream_info_formatter.cc - source/common/formatter/substitution_formatter.h - - source/common/formatter/substitution_format_string.h - source/common/stats/tag_extractor_impl.cc - source/common/protobuf/yaml_utility.cc - source/common/protobuf/utility.cc @@ -161,36 +160,86 @@ paths: # Extensions can exempt entire directories but new extensions # points should ideally use StatusOr - source/extensions/access_loggers - - source/extensions/bootstrap - - source/extensions/clusters + - source/extensions/bootstrap/wasm/config.cc + - source/extensions/clusters/eds/ + - source/extensions/clusters/logical_dns + - source/extensions/clusters/original_dst + - source/extensions/clusters/redis + - source/extensions/clusters/static + - source/extensions/clusters/strict_dns - source/extensions/common - - source/extensions/config + - source/extensions/config/validators/minimum_clusters/minimum_clusters_validator.cc - source/extensions/config_subscription - - source/extensions/compression/zstd/common - - source/extensions/early_data - - source/extensions/filters + - source/extensions/compression/zstd/common/dictionary_manager.h + - source/extensions/filters/http/adaptive_concurrency/controller + - source/extensions/filters/http/admission_control + - source/extensions/filters/http/aws_lambda + - source/extensions/filters/http/aws_request_signing + - source/extensions/filters/http/bandwidth_limit + - source/extensions/filters/http/basic_auth + - source/extensions/filters/http/cache + - source/extensions/filters/http/cdn_loop + - source/extensions/filters/http/common + - source/extensions/filters/http/composite + - source/extensions/filters/http/ext_authz + - source/extensions/filters/http/ext_proc + - source/extensions/filters/http/file_system_buffer + - source/extensions/filters/http/gcp_authn + - source/extensions/filters/http/grpc_field_extraction + - source/extensions/filters/http/grpc_json_transcoder + - source/extensions/filters/http/grpc_stats + - source/extensions/filters/http/header_mutation + - source/extensions/filters/http/header_to_metadata + - source/extensions/filters/http/health_check + - source/extensions/filters/http/ip_tagging + - source/extensions/filters/http/json_to_metadata + - source/extensions/filters/http/jwt_authn + - source/extensions/filters/http/local_ratelimit + - source/extensions/filters/http/oauth2 + - source/extensions/filters/http/file_system_buffer + - source/extensions/filters/http/header_to_metadata + - source/extensions/filters/http/on_demand + - source/extensions/filters/http/json_to_metadata + - source/extensions/filters/http/json_to_metadata + - source/extensions/filters/http/thrift_to_metadata + - source/extensions/filters/http/lua + - source/extensions/filters/http/proto_message_extraction + - source/extensions/filters/http/rate_limit_quota + - source/extensions/filters/http/ratelimit + - source/extensions/filters/http/oauth2 + - source/extensions/filters/http/wasm + - source/extensions/filters/network + - source/extensions/filters/common + - source/extensions/filters/udp + - source/extensions/filters/listener - source/extensions/formatter - - source/extensions/geoip_providers + - source/extensions/geoip_providers/maxmind/geoip_provider.cc - source/extensions/grpc_credentials - - source/extensions/health_check + - source/extensions/health_check/event_sinks/file/file_sink_impl.h - source/extensions/health_checkers - - source/extensions/http - - source/extensions/io_socket + - source/extensions/http/cache/file_system_http_cache/config.cc + - source/extensions/http/custom_response + - source/extensions/http/early_header_mutation + - source/extensions/http/injected_credentials + - source/extensions/http/original_ip_detection + - source/extensions/http/stateful_session + - source/extensions/io_socket/user_space - source/extensions/key_value - - source/extensions/listener_managers - - source/extensions/load_balancing_policies + - source/extensions/load_balancing_policies/maglev + - source/extensions/load_balancing_policies/ring_hash + - source/extensions/load_balancing_policies/subset - source/extensions/matching - - source/extensions/network - - source/extensions/path + - source/extensions/network/dns_resolver/cares/ - source/extensions/quic/server_preferred_address - source/extensions/rate_limit_descriptors - source/extensions/resource_monitors - - source/extensions/retry - - source/extensions/router + - source/extensions/router/cluster_specifiers/lua/lua_cluster_specifier.cc - source/extensions/stat_sinks - - source/extensions/string_matcher + - source/extensions/string_matcher/lua/match.cc - source/extensions/tracers - - source/extensions/transport_sockets + - source/extensions/transport_sockets/internal_upstream + - source/extensions/transport_sockets/tls/cert_validator + - source/extensions/transport_sockets/tcp_stats/config.cc # Only one C++ file should instantiate grpc_init grpc_init: