From 8dc85bc951930359f9a8c468d0d23726ead1b9f9 Mon Sep 17 00:00:00 2001 From: "jueyin.hsl" Date: Tue, 2 Sep 2025 17:45:51 +0800 Subject: [PATCH 1/8] add hash cache to protobuf::message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在protobuf::message中使用递归哈希以避免转为字符串的开销,加入哈希缓存机制以减少envoy listener manager多次hash时重复计算,该优化对大量filter chain的场景非常有效 Change-Id: I1eeea1121a11a91478750290a573c569e2a25727 --- bazel/protobuf_hash_cache.patch | 462 ++++++++++ bazel/repositories.bzl | 3 +- source/common/protobuf/utility.h | 15 + .../rds/route_config_update_receiver_impl.h | 5 +- .../filter_chain_manager_impl.cc | 6 + .../filter_chain_manager_impl.h | 6 + .../listener_manager/listener_impl.cc | 7 +- .../listener_manager/listener_manager_impl.cc | 5 +- .../http/conn_manager_impl_fuzz_test.cc | 13 +- test/common/protobuf/utility_test.cc | 805 ++++++++++++++++++ .../stream_info/stream_info_impl_test.cc | 7 + 11 files changed, 1327 insertions(+), 7 deletions(-) create mode 100644 bazel/protobuf_hash_cache.patch diff --git a/bazel/protobuf_hash_cache.patch b/bazel/protobuf_hash_cache.patch new file mode 100644 index 0000000000000..13cef2cbfa644 --- /dev/null +++ b/bazel/protobuf_hash_cache.patch @@ -0,0 +1,462 @@ +diff --git a/src/google/protobuf/BUILD.bazel b/src/google/protobuf/BUILD.bazel +index 77ed2309f..825189ca5 100644 +--- a/src/google/protobuf/BUILD.bazel ++++ b/src/google/protobuf/BUILD.bazel +@@ -504,6 +504,7 @@ cc_library( + "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/time", + "@utf8_range//:utf8_validity", ++ "@com_github_cyan4973_xxhash//:xxhash", + ], + ) + +diff --git a/src/google/protobuf/message.cc b/src/google/protobuf/message.cc +index fc474dd7c..4db68a09d 100644 +--- a/src/google/protobuf/message.cc ++++ b/src/google/protobuf/message.cc +@@ -34,6 +34,7 @@ + + #include "google/protobuf/message.h" + ++#include + #include + #include + +@@ -60,7 +61,8 @@ + #include "google/protobuf/unknown_field_set.h" + #include "google/protobuf/wire_format.h" + #include "google/protobuf/wire_format_lite.h" +- ++#include "google/protobuf/dynamic_message.h" ++#include "xxhash.h" + + // Must be included last. + #include "google/protobuf/port_def.inc" +@@ -74,6 +76,93 @@ namespace internal { + // defined in generated_message_reflection.cc + void RegisterFileLevelMetadata(const DescriptorTable* descriptor_table); + ++// Helper function to extract type name from Any type_url ++std::string ExtractTypeNameFromUrl(const std::string& type_url) { ++ size_t last_slash = type_url.find_last_of('/'); ++ if (last_slash != std::string::npos && last_slash + 1 < type_url.length()) { ++ return type_url.substr(last_slash + 1); ++ } ++ return type_url; // Fallback to full URL if parsing fails ++} ++ ++// Helper function to check if map value is message type ++bool IsMapValueMessageTyped(const FieldDescriptor* map_field) { ++ return map_field->message_type()->field(1)->cpp_type() == ++ FieldDescriptor::CPPTYPE_MESSAGE; ++} ++ ++// Helper function to hash a single field value ++uint64_t HashFieldValue(const Reflection* reflection, const Message& message, ++ const FieldDescriptor* field, int index = -1) { ++ switch (field->cpp_type()) { ++ case FieldDescriptor::CPPTYPE_MESSAGE: ++ if (index >= 0) { ++ const Message& sub_message = reflection->GetRepeatedMessage(message, field, index); ++ return sub_message.GetCachedHashValue(); ++ } else if (reflection->HasField(message, field)) { ++ const Message& sub_message = reflection->GetMessage(message, field); ++ return sub_message.GetCachedHashValue(); ++ } ++ return 0; ++ case FieldDescriptor::CPPTYPE_INT32:{ ++ int32_t val = index >= 0 ? reflection->GetRepeatedInt32(message, field, index) ++ : reflection->GetInt32(message, field); ++ return XXH64(&val, sizeof(val), 0); ++ } ++ case FieldDescriptor::CPPTYPE_INT64:{ ++ int64_t val = index >= 0 ? reflection->GetRepeatedInt64(message, field, index) ++ : reflection->GetInt64(message, field); ++ return XXH64(&val, sizeof(val), 0); ++ } ++ case FieldDescriptor::CPPTYPE_UINT32:{ ++ uint32_t val = index >= 0 ? reflection->GetRepeatedUInt32(message, field, index) ++ : reflection->GetUInt32(message, field); ++ return XXH64(&val, sizeof(val), 0); ++ } ++ case FieldDescriptor::CPPTYPE_UINT64:{ ++ uint64_t val = index >= 0 ? reflection->GetRepeatedUInt64(message, field, index) ++ : reflection->GetUInt64(message, field); ++ return XXH64(&val, sizeof(val), 0); ++ } ++ case FieldDescriptor::CPPTYPE_DOUBLE:{ ++ double val = index >= 0 ? reflection->GetRepeatedDouble(message, field, index) ++ : reflection->GetDouble(message, field); ++ return XXH64(&val, sizeof(val), 0); ++ } ++ case FieldDescriptor::CPPTYPE_FLOAT:{ ++ float val = index >= 0 ? reflection->GetRepeatedFloat(message, field, index) ++ : reflection->GetFloat(message, field); ++ return XXH64(&val, sizeof(val), 0); ++ } ++ case FieldDescriptor::CPPTYPE_BOOL:{ ++ bool val = index >= 0 ? reflection->GetRepeatedBool(message, field, index) ++ : reflection->GetBool(message, field); ++ return XXH64(&val, sizeof(val), 0); ++ } ++ case FieldDescriptor::CPPTYPE_ENUM:{ ++ int32_t val = index >= 0 ? reflection->GetRepeatedEnumValue(message, field, index) ++ : reflection->GetEnumValue(message, field); ++ return XXH64(&val, sizeof(val), 0); ++ } ++ case FieldDescriptor::CPPTYPE_STRING:{ ++ std::string val = index >= 0 ? reflection->GetRepeatedString(message, field, index) ++ : reflection->GetString(message, field); ++ return XXH64(val.data(), val.size(), 0); ++ } ++ default:{ ++ if(index >= 0){ ++ fprintf(stderr, "Message::HashFieldValue: Unexpected repeated field type: %d\n", field->cpp_type()); ++ const Message& sub_message = reflection->GetRepeatedMessage(message, field, index); ++ return sub_message.GetCachedHashValue(); ++ } else if (reflection->HasField(message, field)){ ++ fprintf(stderr, "Message::HashFieldValue: Unexpected field type: %d\n", field->cpp_type()); ++ const Message& sub_message = reflection->GetMessage(message, field); ++ return sub_message.GetCachedHashValue(); ++ } ++ return 0; ++ } ++ } ++} + } // namespace internal + + using internal::DownCast; +@@ -215,6 +304,296 @@ uint64_t Message::GetInvariantPerBuild(uint64_t salt) { + return salt; + } + ++// Hash computation methods implementation ++uint64_t Message::ComputeHashValue() const { ++ ++ const Reflection* reflection = GetReflection(); ++ const Descriptor* descriptor = GetDescriptor(); ++ ++ // Use a stable hash seed that's consistent across runs ++ // This ensures deterministic hashing regardless of memory layout ++ uint64_t hash = 0x9e3779b97f4a7c15; // xxhash seed ++ ++ // Hash the descriptor type ++ hash = XXH64(descriptor->full_name().data(), descriptor->full_name().size(), hash); ++ ++ // Special handling for google.protobuf.Any type ++ if (descriptor->full_name() == "google.protobuf.Any") { ++ // For Any types, we need to hash the unpacked content to ensure consistency ++ // This mimics TextFormat's approach of expanding Any messages ++ const Reflection* reflection = GetReflection(); ++ const FieldDescriptor* type_url_field = descriptor->FindFieldByNumber(1); ++ const FieldDescriptor* value_field = descriptor->FindFieldByNumber(2); ++ ++ if (type_url_field && value_field && ++ reflection->HasField(*this, type_url_field) && ++ reflection->HasField(*this, value_field)) { ++ ++ std::string type_url = reflection->GetString(*this, type_url_field); ++ std::string serialized_value = reflection->GetString(*this, value_field); ++ ++ // Hash the type URL ++ hash = XXH64(type_url.data(), type_url.size(), hash); ++ /* ++ // Try to parse and hash the unpacked message for consistency ++ // This ensures that Any messages with same content produce same hash ++ // regardless of serialization order in the value field ++ try { ++ // Create a temporary message from the serialized value ++ DynamicMessageFactory factory; ++ const Descriptor* value_descriptor = ++ factory.GetPrototype(descriptor)->GetDescriptor()->file()->pool() ++ ->FindMessageTypeByName(internal::ExtractTypeNameFromUrl(type_url)); ++ ++ if (value_descriptor) { ++ std::unique_ptr unpacked_message( ++ factory.GetPrototype(value_descriptor)->New()); ++ if (unpacked_message->ParseFromString(serialized_value)) { ++ // Hash the unpacked message content ++ uint64_t unpacked_message_hash = unpacked_message->GetCachedHashValue(); ++ hash = XXH64(&unpacked_message_hash, sizeof(unpacked_message_hash), hash); ++ } else { ++ fprintf(stderr, "Message::ComputeHashValue: Parsing failed for Any message: %s\n", serialized_value.c_str()); ++ // If parsing fails, hash the raw serialized value ++ hash = XXH64(serialized_value.data(), serialized_value.size(), hash); ++ } ++ } else { ++ fprintf(stderr, "Message::ComputeHashValue: Type not found: %s\n", type_url.c_str()); ++ // If type not found, hash the raw serialized value ++ hash = XXH64(serialized_value.data(), serialized_value.size(), hash); ++ } ++ } catch (e) { ++ fprintf(stderr, "Message::ComputeHashValue: Error parsing Any message: %s\n", e.what()); ++ // If any error occurs, fall back to hashing the raw value ++ hash = XXH64(serialized_value.data(), serialized_value.size(), hash); ++ } ++ */ ++ ++ // Skip the any parsing and just hash the serialized value ++ hash = XXH64(serialized_value.data(), serialized_value.size(), hash); ++ ++ // Skip normal field processing for Any types since we've handled them specially ++ return hash; ++ } ++ } ++ ++ // Iterate through all fields and hash their values recursively ++ std::vector fields; ++ reflection->ListFields(*this, &fields); ++ ++ // Sort fields by field number to ensure consistent order ++ // Use stable_sort for deterministic ordering across runs ++ std::stable_sort(fields.begin(), fields.end(), ++ [](const FieldDescriptor* a, const FieldDescriptor* b) { ++ if (a->number() != b->number()) { ++ return a->number() < b->number(); // Primary: field number ++ } ++ // Secondary: field name for stability when field numbers are equal ++ return a->name() < b->name(); ++ }); ++ ++ for (const FieldDescriptor* field : fields) { ++ // Hash field number and type ++ uint32_t field_number = field->number(); ++ uint32_t field_type = field->type(); ++ hash = XXH64(&field_number, sizeof(field_number), hash); ++ hash = XXH64(&field_type, sizeof(field_type), hash); ++ ++ if (field->is_repeated()) { ++ // Handle repeated fields using RepeatedFieldAccessor for consistent access ++ const internal::RepeatedFieldAccessor* accessor = reflection->RepeatedFieldAccessor(field); ++ void* repeated_field_data = reflection->RepeatedFieldData(const_cast(this), field, ++ field->cpp_type(), ++ field->message_type()); ++ int size = accessor->Size(repeated_field_data); ++ hash = XXH64(&size, sizeof(size), hash); ++ ++ if (field->is_map()) { ++ // For map fields, use MapField to access the underlying map data ++ // This provides better performance and guarantees consistent ordering ++ ++ // Get key and value field descriptors ++ const Descriptor* map_entry_desc = field->message_type(); ++ const FieldDescriptor* key_field = map_entry_desc->field(0); // key field ++ const FieldDescriptor* value_field = map_entry_desc->field(1); // value field ++ ++ // Check if map value is message type ++ bool is_value_message = internal::IsMapValueMessageTyped(field); ++ ++ std::vector> map_entries; ++ ++ // Use MapIterator to iterate through the map ++ for (MapIterator iter = reflection->MapBegin(const_cast(this), field); ++ iter != reflection->MapEnd(const_cast(this), field); ++ ++iter) { ++ ++ const MapKey& key = iter.GetKey(); ++ const MapValueRef& value = iter.GetValueRef(); ++ ++ uint64_t key_hash = 0; ++ uint64_t value_hash = 0; ++ ++ // Hash key based on its type ++ switch (key_field->cpp_type()) { ++ case FieldDescriptor::CPPTYPE_STRING: { ++ std::string key_str = key.GetStringValue(); ++ key_hash = XXH64(key_str.data(), key_str.size(), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_INT64: { ++ int64_t key_int = key.GetInt64Value(); ++ key_hash = XXH64(&key_int, sizeof(key_int), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_INT32: { ++ int32_t key_int = key.GetInt32Value(); ++ key_hash = XXH64(&key_int, sizeof(key_int), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_UINT64: { ++ uint64_t key_int = key.GetUInt64Value(); ++ key_hash = XXH64(&key_int, sizeof(key_int), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_UINT32: { ++ uint32_t key_int = key.GetUInt32Value(); ++ key_hash = XXH64(&key_int, sizeof(key_int), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_BOOL: { ++ bool key_bool = key.GetBoolValue(); ++ key_hash = XXH64(&key_bool, sizeof(key_bool), 0); ++ break; ++ } ++ default: ++ // Should not reach here for valid map key types ++ fprintf(stderr, "Message::ComputeHashValue: Unexpected map key type: %d\n", key_field->cpp_type()); ++ break; ++ } ++ ++ // Hash value based on its type ++ if (is_value_message) { ++ // For message values, use GetCachedHashValue ++ const Message& value_msg = value.GetMessageValue(); ++ value_hash = value_msg.GetCachedHashValue(); ++ } else { ++ // For primitive values, hash directly ++ switch (value_field->cpp_type()) { ++ case FieldDescriptor::CPPTYPE_STRING: { ++ std::string value_str = value.GetStringValue(); ++ value_hash = XXH64(value_str.data(), value_str.size(), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_INT64: { ++ int64_t value_int = value.GetInt64Value(); ++ value_hash = XXH64(&value_int, sizeof(value_int), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_INT32: { ++ int32_t value_int = value.GetInt32Value(); ++ value_hash = XXH64(&value_int, sizeof(value_int), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_UINT64: { ++ uint64_t value_int = value.GetUInt64Value(); ++ value_hash = XXH64(&value_int, sizeof(value_int), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_UINT32: { ++ uint32_t value_int = value.GetUInt32Value(); ++ value_hash = XXH64(&value_int, sizeof(value_int), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_DOUBLE: { ++ double value_double = value.GetDoubleValue(); ++ value_hash = XXH64(&value_double, sizeof(value_double), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_FLOAT: { ++ float value_float = value.GetFloatValue(); ++ value_hash = XXH64(&value_float, sizeof(value_float), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_BOOL: { ++ bool value_bool = value.GetBoolValue(); ++ value_hash = XXH64(&value_bool, sizeof(value_bool), 0); ++ break; ++ } ++ case FieldDescriptor::CPPTYPE_ENUM: { ++ int32_t value_enum = value.GetEnumValue(); ++ value_hash = XXH64(&value_enum, sizeof(value_enum), 0); ++ break; ++ } ++ default: ++ // Should not reach here for valid map value types ++ fprintf(stderr, "Message::ComputeHashValue: Unexpected map value type: %d\n", value_field->cpp_type()); ++ break; ++ } ++ } ++ ++ map_entries.emplace_back(key_hash, value_hash); ++ } ++ ++ // Sort map entries by key hash for consistent ordering ++ // MapField provides consistent iteration order, but we still sort for extra safety ++ std::stable_sort(map_entries.begin(), map_entries.end(), ++ [](const auto& a, const auto& b) { ++ if (a.first != b.first) { ++ return a.first < b.first; // Primary: key hash ++ } ++ return a.second < b.second; // Secondary: value hash ++ }); ++ ++ // Hash sorted map entries ++ for (const auto& entry : map_entries) { ++ hash = XXH64(&entry.first, sizeof(entry.first), hash); ++ hash = XXH64(&entry.second, sizeof(entry.second), hash); ++ } ++ } else { ++ // Handle regular repeated fields (non-map) using RepeatedFieldAccessor ++ for (int i = 0; i < size; ++i) { ++ // Use a simplified approach: directly use HashFieldValue with index ++ uint64_t hash_value = internal::HashFieldValue(reflection, *this, field, i); ++ hash = XXH64(&hash_value, sizeof(hash_value), hash); ++ } ++ } ++ } else { ++ // Handle singular fields ++ uint64_t field_value = internal::HashFieldValue(reflection, *this, field); ++ hash = XXH64(&field_value, sizeof(field_value), hash); ++ } ++ } ++ ++ // Hash unknown fields if present ++ if (_internal_metadata_.have_unknown_fields()) { ++ const UnknownFieldSet& unknown_fields = reflection->GetUnknownFields(*this); ++ // Use field count and space used for unknown fields hash ++ uint32_t field_count = unknown_fields.field_count(); ++ uint64_t space_used = unknown_fields.SpaceUsedLong(); ++ hash = XXH64(&field_count, sizeof(field_count), hash); ++ hash = XXH64(&space_used, sizeof(space_used), hash); ++ } ++ ++ return hash; ++} ++ ++uint64_t Message::GetCachedHashValue() const { ++ if (!hash_cached_) { ++ cached_hash_value_ = ComputeHashValue(); ++ hash_cached_ = true; ++ } ++ return cached_hash_value_; ++} ++ ++bool Message::HasCachedHashValue() const { ++ return hash_cached_; ++} ++ ++void Message::SetCachedHashValue(uint64_t hash_value) const { ++ cached_hash_value_ = hash_value; ++ hash_cached_ = true; ++} ++ + namespace internal { + void* CreateSplitMessageGeneric(Arena* arena, const void* default_split, + size_t size, const void* message, +diff --git a/src/google/protobuf/message.h b/src/google/protobuf/message.h +index 6c5e24f9d..b9078785c 100644 +--- a/src/google/protobuf/message.h ++++ b/src/google/protobuf/message.h +@@ -362,6 +362,22 @@ class PROTOBUF_EXPORT Message : public MessageLite { + uint8_t* _InternalSerialize(uint8_t* target, + io::EpsCopyOutputStream* stream) const override; + ++ // Hash computation methods ---------------------------------------- ++ // Optimized hash computation with caching support ++ ++ // Compute hash value for this message using recursive hashing ++ // This avoids serialization and provides better performance ++ uint64_t ComputeHashValue() const; ++ ++ // Get cached hash value if available, otherwise compute and cache it ++ uint64_t GetCachedHashValue() const; ++ ++ // Set cached hash value ++ void SetCachedHashValue(uint64_t hash_value) const; ++ ++ // Check if hash value is cached ++ bool HasCachedHashValue() const; ++ + private: + // This is called only by the default implementation of ByteSize(), to + // update the cached size. If you override ByteSize(), you do not need +@@ -418,6 +434,9 @@ class PROTOBUF_EXPORT Message : public MessageLite { + size_t MaybeComputeUnknownFieldsSize(size_t total_size, + internal::CachedSize* cached_size) const; + ++ // Hash caching support ++ mutable uint64_t cached_hash_value_ = 0; ++ mutable bool hash_cached_ = false; + + protected: + static uint64_t GetInvariantPerBuild(uint64_t salt); diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 540c70fb16ab8..e1bb52062301d 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -902,7 +902,8 @@ def _com_google_protobuf(): external_http_archive( "com_google_protobuf", - patches = ["@envoy//bazel:protobuf.patch"], + patches = ["@envoy//bazel:protobuf.patch", + "@envoy//bazel:protobuf_hash_cache.patch"], patch_args = ["-p1"], ) diff --git a/source/common/protobuf/utility.h b/source/common/protobuf/utility.h index 8012a84f5fc80..b8caf7326aa65 100644 --- a/source/common/protobuf/utility.h +++ b/source/common/protobuf/utility.h @@ -580,6 +580,21 @@ class MessageUtil { static std::string sanitizeUtf8String(absl::string_view str); }; +#if defined(ALIMESH) && defined(ENVOY_ENABLE_FULL_PROTOS) +class HashCachedMessageUtil : public MessageUtil { +public: + bool operator()(const Protobuf::Message& message) const { return message.GetCachedHashValue(); } + + bool operator()(const Protobuf::Message& lhs, const Protobuf::Message& rhs) const { + return lhs.GetCachedHashValue() == rhs.GetCachedHashValue(); + } + + static std::size_t hash(const Protobuf::Message& message) { + return message.GetCachedHashValue(); + } +}; +#endif + class ValueUtil { public: static std::size_t hash(const ProtobufWkt::Value& value) { return MessageUtil::hash(value); } diff --git a/source/common/rds/route_config_update_receiver_impl.h b/source/common/rds/route_config_update_receiver_impl.h index 153dab4d491bf..b98f88f76b619 100644 --- a/source/common/rds/route_config_update_receiver_impl.h +++ b/source/common/rds/route_config_update_receiver_impl.h @@ -13,8 +13,11 @@ class RouteConfigUpdateReceiverImpl : public RouteConfigUpdateReceiver { public: RouteConfigUpdateReceiverImpl(ConfigTraits& config_traits, ProtoTraits& proto_traits, Server::Configuration::ServerFactoryContext& factory_context); - + #if defined(ALIMESH) && defined(ENVOY_ENABLE_FULL_PROTOS) + uint64_t getHash(const Protobuf::Message& rc) const { return HashCachedMessageUtil::hash(rc); } + #else uint64_t getHash(const Protobuf::Message& rc) const { return MessageUtil::hash(rc); } + #endif bool checkHash(uint64_t new_hash) const { return (new_hash != last_config_hash_); } void updateHash(uint64_t hash) { last_config_hash_ = hash; } void updateConfig(std::unique_ptr&& route_config_proto); diff --git a/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.cc b/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.cc index 356072181bb04..f5da350f90dd1 100644 --- a/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.cc +++ b/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.cc @@ -211,9 +211,15 @@ void FilterChainManagerImpl::addFilterChains( FilterChainFactoryBuilder& filter_chain_factory_builder, FilterChainFactoryContextCreator& context_creator) { Cleanup cleanup([this]() { origin_ = absl::nullopt; }); +#if defined(ALIMESH) && defined(ENVOY_ENABLE_FULL_PROTOS) + absl::node_hash_map + filter_chains; +#else absl::node_hash_map filter_chains; +#endif uint32_t new_filter_chain_size = 0; FilterChainsByName filter_chains_by_name; diff --git a/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.h b/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.h index 7addd4f710ff3..96befc09abbe6 100644 --- a/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.h +++ b/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.h @@ -151,8 +151,14 @@ class FilterChainManagerImpl : public Network::FilterChainManager, Logger::Loggable { public: using FcContextMap = + #if defined(ALIMESH) && defined(ENVOY_ENABLE_FULL_PROTOS) + absl::flat_hash_map; + #else absl::flat_hash_map; + #endif + FilterChainManagerImpl(const std::vector& addresses, Configuration::FactoryContext& factory_context, Init::Manager& init_manager) diff --git a/source/extensions/listener_managers/listener_manager/listener_impl.cc b/source/extensions/listener_managers/listener_manager/listener_impl.cc index 51b42225f556d..cc03b3116b745 100644 --- a/source/extensions/listener_managers/listener_manager/listener_impl.cc +++ b/source/extensions/listener_managers/listener_manager/listener_impl.cc @@ -1055,7 +1055,12 @@ void ListenerImpl::diffFilterChain(const ListenerImpl& another_listener, } // Filter chain manager maintains an optional default filter chain besides the filter chains // indexed by message. - if (auto eq = MessageUtil(); + if (auto + #if defined(ALIMESH) && defined(ENVOY_ENABLE_FULL_PROTOS) + eq = HashCachedMessageUtil(); + #else + eq = MessageUtil(); + #endif filter_chain_manager_->defaultFilterChainMessage().has_value() && (!another_listener.filter_chain_manager_->defaultFilterChainMessage().has_value() || !eq(*another_listener.filter_chain_manager_->defaultFilterChainMessage(), diff --git a/source/extensions/listener_managers/listener_manager/listener_manager_impl.cc b/source/extensions/listener_managers/listener_manager/listener_manager_impl.cc index 53182c0978626..61476a4537134 100644 --- a/source/extensions/listener_managers/listener_manager/listener_manager_impl.cc +++ b/source/extensions/listener_managers/listener_manager/listener_manager_impl.cc @@ -481,8 +481,11 @@ bool ListenerManagerImpl::addOrUpdateListenerInternal( name, envoy::config::core::v3::TrafficDirection_Name(config.traffic_direction())); return false; } - +#if defined(ALIMESH) && defined(ENVOY_ENABLE_FULL_PROTOS) + const uint64_t hash = HashCachedMessageUtil::hash(config); +#else const uint64_t hash = MessageUtil::hash(config); +#endif ENVOY_LOG(debug, "begin add/update listener: name={} hash={}", name, hash); auto existing_active_listener = getListenerByName(active_listeners_, name); diff --git a/test/common/http/conn_manager_impl_fuzz_test.cc b/test/common/http/conn_manager_impl_fuzz_test.cc index d7ae59ec6877d..dc2a7887bcd36 100644 --- a/test/common/http/conn_manager_impl_fuzz_test.cc +++ b/test/common/http/conn_manager_impl_fuzz_test.cc @@ -53,9 +53,16 @@ class FuzzConfig : public ConnectionManagerConfig { public: FuzzConfig(envoy::extensions::filters::network::http_connection_manager::v3:: HttpConnectionManager::ForwardClientCertDetails forward_client_cert) - : stats_({ALL_HTTP_CONN_MAN_STATS(POOL_COUNTER(*fake_stats_.rootScope()), - POOL_GAUGE(fake_stats_), - POOL_HISTOGRAM(*fake_stats_.rootScope()))}, + : stats_({ConnectionManagerNamedStats{ + ALL_HTTP_CONN_MAN_STATS(POOL_COUNTER(*fake_stats_.rootScope()), + POOL_GAUGE(fake_stats_), + POOL_HISTOGRAM(*fake_stats_.rootScope())) +#if defined(ALIMESH) + HIGRESS_EXT_HTTP_CONN_MAN_STATS(POOL_COUNTER(*fake_stats_.rootScope()), + POOL_GAUGE(fake_stats_), + POOL_HISTOGRAM(*fake_stats_.rootScope())) +#endif + }}, "", *fake_stats_.rootScope()), tracing_stats_{CONN_MAN_TRACING_STATS(POOL_COUNTER(fake_stats_))}, listener_stats_{CONN_MAN_LISTENER_STATS(POOL_COUNTER(fake_stats_))}, diff --git a/test/common/protobuf/utility_test.cc b/test/common/protobuf/utility_test.cc index 5d89275dc343d..5a9a2db5a16e6 100644 --- a/test/common/protobuf/utility_test.cc +++ b/test/common/protobuf/utility_test.cc @@ -171,6 +171,807 @@ TEST_F(ProtobufUtilityTest, EvaluateFractionalPercent) { } // namespace ProtobufPercentHelper +#if defined(ALIMESH) && defined(ENVOY_ENABLE_FULL_PROTOS) +TEST_F(ProtobufUtilityTest, HashCache) { + ProtobufWkt::StringValue str1, str2, str3; + TestUtility::loadFromJson("\"hello world\"", str1); + TestUtility::loadFromJson("\"hello world\"", str2); + TestUtility::loadFromJson("\"hello world!\"", str3); + + ProtobufWkt::Struct struct1, struct2, struct3; + (*struct1.mutable_fields())["field"].mutable_string_value()->assign(str1.value()); + (*struct2.mutable_fields())["field"].mutable_string_value()->assign(str2.value()); + (*struct3.mutable_fields())["field"].mutable_string_value()->assign(str3.value()); + + EXPECT_EQ(HashCachedMessageUtil::hash(struct1), HashCachedMessageUtil::hash(struct2)); + EXPECT_NE(HashCachedMessageUtil::hash(struct1), HashCachedMessageUtil::hash(struct3)); + + EXPECT_TRUE(struct1.fields().at("field").HasCachedHashValue()); + EXPECT_TRUE(struct2.fields().at("field").HasCachedHashValue()); + EXPECT_TRUE(struct3.fields().at("field").HasCachedHashValue()); + + ProtobufWkt::ListValue list1, list2, list3; + auto* v1 = list1.add_values(); + v1->set_string_value("hello"); + auto* v2 = list1.add_values(); + v2->set_string_value("world"); + + auto* v3 = list2.add_values(); + v3->set_string_value("hello"); + auto* v4 = list2.add_values(); + v4->set_string_value("world"); + + auto* v5 = list3.add_values(); + v5->set_string_value("hello"); + auto* v6 = list3.add_values(); + v6->set_string_value("world!"); + + EXPECT_EQ(HashCachedMessageUtil::hash(list1), HashCachedMessageUtil::hash(list2)); + EXPECT_NE(HashCachedMessageUtil::hash(list1), HashCachedMessageUtil::hash(list3)); + + EXPECT_TRUE(v1->HasCachedHashValue()); + EXPECT_TRUE(v2->HasCachedHashValue()); + EXPECT_TRUE(v3->HasCachedHashValue()); + EXPECT_TRUE(v4->HasCachedHashValue()); + EXPECT_TRUE(v5->HasCachedHashValue()); + EXPECT_TRUE(v6->HasCachedHashValue()); + + // Test direct message nesting (not map) - using Value with struct_value + ProtobufWkt::Value nested_value1, nested_value2, nested_value3; + + // Create nested structure: Value -> Struct -> Value -> StringValue + auto* nested_struct1 = nested_value1.mutable_struct_value(); + (*nested_struct1->mutable_fields())["nested_field"].set_string_value("nested hello world"); + + auto* nested_struct2 = nested_value2.mutable_struct_value(); + (*nested_struct2->mutable_fields())["nested_field"].set_string_value("nested hello world"); + + auto* nested_struct3 = nested_value3.mutable_struct_value(); + (*nested_struct3->mutable_fields())["nested_field"].set_string_value("nested hello world!"); + + EXPECT_EQ(HashCachedMessageUtil::hash(nested_value1), HashCachedMessageUtil::hash(nested_value2)); + EXPECT_NE(HashCachedMessageUtil::hash(nested_value1), HashCachedMessageUtil::hash(nested_value3)); + + // Check that all nested messages have cached hash values + EXPECT_TRUE(nested_value1.HasCachedHashValue()); + EXPECT_TRUE(nested_value2.HasCachedHashValue()); + EXPECT_TRUE(nested_value3.HasCachedHashValue()); + + // Check nested struct messages + EXPECT_TRUE(nested_value1.struct_value().HasCachedHashValue()); + EXPECT_TRUE(nested_value2.struct_value().HasCachedHashValue()); + EXPECT_TRUE(nested_value3.struct_value().HasCachedHashValue()); + + // Check the nested Value objects inside struct + EXPECT_TRUE(nested_value1.struct_value().fields().at("nested_field").HasCachedHashValue()); + EXPECT_TRUE(nested_value2.struct_value().fields().at("nested_field").HasCachedHashValue()); + EXPECT_TRUE(nested_value3.struct_value().fields().at("nested_field").HasCachedHashValue()); + + // Test deeper nesting: Value -> Struct -> Value -> Struct -> Value -> StringValue + ProtobufWkt::Value deep_nested_value1, deep_nested_value2; + + auto* deep_struct1 = deep_nested_value1.mutable_struct_value(); + auto* deep_inner_struct1 = (*deep_struct1->mutable_fields())["deep_field"].mutable_struct_value(); + (*deep_inner_struct1->mutable_fields())["inner_field"].set_string_value("deep nested value"); + + auto* deep_struct2 = deep_nested_value2.mutable_struct_value(); + auto* deep_inner_struct2 = (*deep_struct2->mutable_fields())["deep_field"].mutable_struct_value(); + (*deep_inner_struct2->mutable_fields())["inner_field"].set_string_value("deep nested value"); + + EXPECT_EQ(HashCachedMessageUtil::hash(deep_nested_value1), HashCachedMessageUtil::hash(deep_nested_value2)); + + // Check that all levels of nesting have cached hash values + EXPECT_TRUE(deep_nested_value1.HasCachedHashValue()); + EXPECT_TRUE(deep_nested_value2.HasCachedHashValue()); + + EXPECT_TRUE(deep_nested_value1.struct_value().HasCachedHashValue()); + EXPECT_TRUE(deep_nested_value2.struct_value().HasCachedHashValue()); + + EXPECT_TRUE(deep_nested_value1.struct_value().fields().at("deep_field").HasCachedHashValue()); + EXPECT_TRUE(deep_nested_value2.struct_value().fields().at("deep_field").HasCachedHashValue()); + + EXPECT_TRUE(deep_nested_value1.struct_value().fields().at("deep_field").struct_value().HasCachedHashValue()); + EXPECT_TRUE(deep_nested_value2.struct_value().fields().at("deep_field").struct_value().HasCachedHashValue()); + + EXPECT_TRUE(deep_nested_value1.struct_value().fields().at("deep_field").struct_value().fields().at("inner_field").HasCachedHashValue()); + EXPECT_TRUE(deep_nested_value2.struct_value().fields().at("deep_field").struct_value().fields().at("inner_field").HasCachedHashValue()); +} + +TEST_F(ProtobufUtilityTest, MessageUtilRecursiveHash) { + // Test string hashing using JSON to Proto message conversion + ProtobufWkt::StringValue str1, str2, str3; + + // Convert JSON strings to Proto messages + TestUtility::loadFromJson("\"hello world\"", str1); + TestUtility::loadFromJson("\"hello world\"", str2); + TestUtility::loadFromJson("\"hello world!\"", str3); + + // Test that identical strings produce same hash + EXPECT_EQ(HashCachedMessageUtil::hash(str1), HashCachedMessageUtil::hash(str2)); + + // Test that different strings produce different hashes + EXPECT_NE(HashCachedMessageUtil::hash(str1), HashCachedMessageUtil::hash(str3)); + + // Test that the hash is cached + EXPECT_EQ(str1.HasCachedHashValue(), true); + + // Test that hash is not zero + EXPECT_NE(0, HashCachedMessageUtil::hash(str1)); + EXPECT_NE(0, HashCachedMessageUtil::hash(str2)); + EXPECT_NE(0, HashCachedMessageUtil::hash(str3)); + + // Test hash consistency + uint64_t hash1 = HashCachedMessageUtil::hash(str1); + uint64_t hash2 = HashCachedMessageUtil::hash(str1); + EXPECT_EQ(hash1, hash2); // Same string should always produce same hash + + // Test with different string types + ProtobufWkt::BytesValue bytes1, bytes2; + // BytesValue expects base64 encoded strings + TestUtility::loadFromJson("\"aGVsbG8gd29ybGQ=\"", bytes1); // "hello world" in base64 + TestUtility::loadFromJson("\"aGVsbG8gd29ybGQ=\"", bytes2); // "hello world" in base64 + + // BytesValue should also produce consistent hashes + EXPECT_EQ(HashCachedMessageUtil::hash(bytes1), HashCachedMessageUtil::hash(bytes2)); + EXPECT_NE(0, HashCachedMessageUtil::hash(bytes1)); + + // Test with different base64 strings + ProtobufWkt::BytesValue bytes3; + TestUtility::loadFromJson("\"aGVsbG8gd29ybGQh\"", bytes3); // "hello world!" in base64 + EXPECT_NE(HashCachedMessageUtil::hash(bytes1), HashCachedMessageUtil::hash(bytes3)); +} + +TEST_F(ProtobufUtilityTest, MessageUtilHashComprehensive) { + // Test 1: Basic primitive types + { + // StringValue + ProtobufWkt::StringValue str1, str2, str3; + TestUtility::loadFromJson("\"test string\"", str1); + TestUtility::loadFromJson("\"test string\"", str2); + TestUtility::loadFromJson("\"different string\"", str3); + + EXPECT_EQ(HashCachedMessageUtil::hash(str1), HashCachedMessageUtil::hash(str2)); + EXPECT_NE(HashCachedMessageUtil::hash(str1), HashCachedMessageUtil::hash(str3)); + EXPECT_NE(0, HashCachedMessageUtil::hash(str1)); + + // BytesValue + ProtobufWkt::BytesValue bytes1, bytes2; + TestUtility::loadFromJson("\"dGVzdCBieXRlcw==\"", bytes1); // "test bytes" in base64 + TestUtility::loadFromJson("\"dGVzdCBieXRlcw==\"", bytes2); // "test bytes" in base64 + + EXPECT_EQ(HashCachedMessageUtil::hash(bytes1), HashCachedMessageUtil::hash(bytes2)); + EXPECT_NE(0, HashCachedMessageUtil::hash(bytes1)); + } + + // Test 2: Numeric types + { + // Int32Value + ProtobufWkt::Int32Value int1, int2, int3; + int1.set_value(42); + int2.set_value(42); + int3.set_value(100); + + EXPECT_EQ(HashCachedMessageUtil::hash(int1), HashCachedMessageUtil::hash(int2)); + EXPECT_NE(HashCachedMessageUtil::hash(int1), HashCachedMessageUtil::hash(int3)); + EXPECT_NE(0, HashCachedMessageUtil::hash(int1)); + + // UInt64Value + ProtobufWkt::UInt64Value uint1, uint2, uint3; + uint1.set_value(123456789); + uint2.set_value(123456789); + uint3.set_value(987654321); + + EXPECT_EQ(HashCachedMessageUtil::hash(uint1), HashCachedMessageUtil::hash(uint2)); + EXPECT_NE(HashCachedMessageUtil::hash(uint1), HashCachedMessageUtil::hash(uint3)); + EXPECT_NE(0, HashCachedMessageUtil::hash(uint1)); + + // DoubleValue + ProtobufWkt::DoubleValue double1, double2, double3; + double1.set_value(3.14159); + double2.set_value(3.14159); + double3.set_value(2.71828); + + EXPECT_EQ(HashCachedMessageUtil::hash(double1), HashCachedMessageUtil::hash(double2)); + EXPECT_NE(HashCachedMessageUtil::hash(double1), HashCachedMessageUtil::hash(double3)); + EXPECT_NE(0, HashCachedMessageUtil::hash(double1)); + + // BoolValue + ProtobufWkt::BoolValue bool1, bool2, bool3; + bool1.set_value(true); + bool2.set_value(true); + bool3.set_value(false); + + EXPECT_EQ(HashCachedMessageUtil::hash(bool1), HashCachedMessageUtil::hash(bool2)); + EXPECT_NE(HashCachedMessageUtil::hash(bool1), HashCachedMessageUtil::hash(bool3)); + EXPECT_NE(0, HashCachedMessageUtil::hash(bool1)); + } + + // Test 3: Complex types with nested messages + { + // Struct with nested fields + ProtobufWkt::Struct struct1, struct2, struct3; + + // Build struct1 + (*struct1.mutable_fields())["string_field"].set_string_value("hello"); + (*struct1.mutable_fields())["number_field"].set_number_value(42.5); + (*struct1.mutable_fields())["bool_field"].set_bool_value(true); + + // Build struct2 (identical to struct1) + (*struct2.mutable_fields())["string_field"].set_string_value("hello"); + (*struct2.mutable_fields())["number_field"].set_number_value(42.5); + (*struct2.mutable_fields())["bool_field"].set_bool_value(true); + + // Build struct3 (different) + (*struct3.mutable_fields())["string_field"].set_string_value("world"); + (*struct3.mutable_fields())["number_field"].set_number_value(42.5); + (*struct3.mutable_fields())["bool_field"].set_bool_value(true); + + EXPECT_EQ(HashCachedMessageUtil::hash(struct1), HashCachedMessageUtil::hash(struct2)); + EXPECT_NE(HashCachedMessageUtil::hash(struct1), HashCachedMessageUtil::hash(struct3)); + EXPECT_NE(0, HashCachedMessageUtil::hash(struct1)); + + // Test field order independence (should produce same hash) + ProtobufWkt::Struct struct4; + (*struct4.mutable_fields())["bool_field"].set_bool_value(true); + (*struct4.mutable_fields())["number_field"].set_number_value(42.5); + (*struct4.mutable_fields())["string_field"].set_string_value("hello"); + + EXPECT_EQ(HashCachedMessageUtil::hash(struct1), HashCachedMessageUtil::hash(struct4)); + } + + // Test 4: Repeated fields + { + // ListValue with repeated elements + ProtobufWkt::ListValue list1, list2, list3, list4; + + // Build list1: [1, 2, 3] + list1.add_values()->set_number_value(1); + list1.add_values()->set_number_value(2); + list1.add_values()->set_number_value(3); + + // Build list2: [1, 2, 3] (identical) + list2.add_values()->set_number_value(1); + list2.add_values()->set_number_value(2); + list2.add_values()->set_number_value(3); + + // Build list3: [1, 2, 4] (different) + list3.add_values()->set_number_value(1); + list3.add_values()->set_number_value(2); + list3.add_values()->set_number_value(4); + + // Build list4: [3, 2, 1] (different order) + list4.add_values()->set_number_value(3); + list4.add_values()->set_number_value(2); + list4.add_values()->set_number_value(1); + + EXPECT_EQ(HashCachedMessageUtil::hash(list1), HashCachedMessageUtil::hash(list2)); + EXPECT_NE(HashCachedMessageUtil::hash(list1), HashCachedMessageUtil::hash(list3)); + EXPECT_NE(HashCachedMessageUtil::hash(list1), HashCachedMessageUtil::hash(list4)); // Order matters + EXPECT_NE(0, HashCachedMessageUtil::hash(list1)); + + // Test empty list + ProtobufWkt::ListValue empty_list; + EXPECT_NE(0, HashCachedMessageUtil::hash(empty_list)); + EXPECT_NE(HashCachedMessageUtil::hash(empty_list), HashCachedMessageUtil::hash(list1)); + } + + // Test 5: Any type with packed messages + { + // Pack Struct into Any + ProtobufWkt::Struct original_struct; + (*original_struct.mutable_fields())["key1"].set_string_value("value2"); + (*original_struct.mutable_fields())["key2"].set_number_value(123); + + ProtobufWkt::Any any1, any2, any3; + any1.PackFrom(original_struct); + any2.PackFrom(original_struct); + + // Create different struct for any3 + ProtobufWkt::Struct different_struct; + (*different_struct.mutable_fields())["key1"].set_string_value("value1"); + (*different_struct.mutable_fields())["key2"].set_number_value(456); // Different value + any3.PackFrom(different_struct); + + EXPECT_EQ(HashCachedMessageUtil::hash(any1), HashCachedMessageUtil::hash(any2)); + EXPECT_NE(HashCachedMessageUtil::hash(any1), HashCachedMessageUtil::hash(any3)); + EXPECT_NE(0, HashCachedMessageUtil::hash(any1)); + + // Test that Any hash is different from original struct hash + EXPECT_NE(HashCachedMessageUtil::hash(any1), HashCachedMessageUtil::hash(original_struct)); + } + + // Test 6: Timestamp and Duration + { + // Timestamp + ProtobufWkt::Timestamp ts1, ts2, ts3; + ts1.set_seconds(1234567890); + ts1.set_nanos(123456789); + ts2.set_seconds(1234567890); + ts2.set_nanos(123456789); + ts3.set_seconds(1234567890); + ts3.set_nanos(987654321); + + EXPECT_EQ(HashCachedMessageUtil::hash(ts1), HashCachedMessageUtil::hash(ts2)); + EXPECT_NE(HashCachedMessageUtil::hash(ts1), HashCachedMessageUtil::hash(ts3)); + EXPECT_NE(0, HashCachedMessageUtil::hash(ts1)); + + // Duration + ProtobufWkt::Duration dur1, dur2, dur3; + dur1.set_seconds(3600); + dur1.set_nanos(500000000); + dur2.set_seconds(3600); + dur2.set_nanos(500000000); + dur3.set_seconds(7200); + dur3.set_nanos(500000000); + + EXPECT_EQ(HashCachedMessageUtil::hash(dur1), HashCachedMessageUtil::hash(dur2)); + EXPECT_NE(HashCachedMessageUtil::hash(dur1), HashCachedMessageUtil::hash(dur3)); + EXPECT_NE(0, HashCachedMessageUtil::hash(dur1)); + } + + // Test 7: Empty vs non-empty messages + { + ProtobufWkt::StringValue empty_str, non_empty_str; + TestUtility::loadFromJson("\"\"", empty_str); + TestUtility::loadFromJson("\"non-empty\"", non_empty_str); + + EXPECT_NE(HashCachedMessageUtil::hash(empty_str), HashCachedMessageUtil::hash(non_empty_str)); + EXPECT_NE(0, HashCachedMessageUtil::hash(empty_str)); + EXPECT_NE(0, HashCachedMessageUtil::hash(non_empty_str)); + + // Empty Struct + ProtobufWkt::Struct empty_struct; + EXPECT_NE(0, HashCachedMessageUtil::hash(empty_struct)); + EXPECT_NE(HashCachedMessageUtil::hash(empty_struct), HashCachedMessageUtil::hash(empty_str)); + } + + // Test 8: Hash consistency across multiple calls + { + ProtobufWkt::StringValue test_str; + TestUtility::loadFromJson("\"consistency test\"", test_str); + + uint64_t hash1 = HashCachedMessageUtil::hash(test_str); + uint64_t hash2 = HashCachedMessageUtil::hash(test_str); + uint64_t hash3 = HashCachedMessageUtil::hash(test_str); + + EXPECT_EQ(hash1, hash2); + EXPECT_EQ(hash2, hash3); + EXPECT_EQ(hash1, hash3); + EXPECT_NE(0, hash1); + } + + // Test 9: Large messages + { + // Create a large struct with many fields + ProtobufWkt::Struct large_struct; + for (int i = 0; i < 100; ++i) { + std::string field_name = "field_" + std::to_string(i); + std::string field_value = "value_" + std::to_string(i); + (*large_struct.mutable_fields())[field_name].set_string_value(field_value); + } + + EXPECT_NE(0, HashCachedMessageUtil::hash(large_struct)); + + // Create identical large struct + ProtobufWkt::Struct large_struct2; + for (int i = 0; i < 100; ++i) { + std::string field_name = "field_" + std::to_string(i); + std::string field_value = "value_" + std::to_string(i); + (*large_struct2.mutable_fields())[field_name].set_string_value(field_value); + } + + EXPECT_EQ(HashCachedMessageUtil::hash(large_struct), HashCachedMessageUtil::hash(large_struct2)); + } + + // Test 10: Edge cases + { + // Very long string + std::string long_string(10000, 'a'); + ProtobufWkt::StringValue long_str; + long_str.set_value(long_string); + + EXPECT_NE(0, HashCachedMessageUtil::hash(long_str)); + + // String with special characters + ProtobufWkt::StringValue special_str; + special_str.set_value("!@#$%^&*()_+-=[]{}|;':\",./<>?"); + + EXPECT_NE(0, HashCachedMessageUtil::hash(special_str)); + EXPECT_NE(HashCachedMessageUtil::hash(long_str), HashCachedMessageUtil::hash(special_str)); + + // Unicode string + ProtobufWkt::StringValue unicode_str; + unicode_str.set_value("Hello 世界 🌍"); + + EXPECT_NE(0, HashCachedMessageUtil::hash(unicode_str)); + EXPECT_NE(HashCachedMessageUtil::hash(unicode_str), HashCachedMessageUtil::hash(special_str)); + } +} + +TEST_F(ProtobufUtilityTest, MessageUtilRecursiveHashComplex) { + // Test recursive hashing with deeply nested structures + + // Create a complex nested structure + ProtobufWkt::Struct root_struct; + + // Level 1: Basic fields + (*root_struct.mutable_fields())["name"].set_string_value("root"); + (*root_struct.mutable_fields())["id"].set_number_value(1); + + // Level 2: Nested struct + ProtobufWkt::Struct* nested1 = (*root_struct.mutable_fields())["nested"].mutable_struct_value(); + (*nested1->mutable_fields())["level"].set_string_value("level2"); + (*nested1->mutable_fields())["count"].set_number_value(2); + + // Level 3: Another nested struct + ProtobufWkt::Struct* nested2 = (*nested1->mutable_fields())["deeper"].mutable_struct_value(); + (*nested2->mutable_fields())["level"].set_string_value("level3"); + (*nested2->mutable_fields())["final"].set_bool_value(true); + + // Level 4: List in nested struct + ProtobufWkt::ListValue* list = (*nested2->mutable_fields())["items"].mutable_list_value(); + list->add_values()->set_string_value("item1"); + list->add_values()->set_string_value("item2"); + list->add_values()->set_number_value(42); + + // Create identical structure + ProtobufWkt::Struct root_struct2; + (*root_struct2.mutable_fields())["name"].set_string_value("root"); + (*root_struct2.mutable_fields())["id"].set_number_value(1); + + ProtobufWkt::Struct* nested1_2 = + (*root_struct2.mutable_fields())["nested"].mutable_struct_value(); + (*nested1_2->mutable_fields())["level"].set_string_value("level2"); + (*nested1_2->mutable_fields())["count"].set_number_value(2); + + ProtobufWkt::Struct* nested2_2 = (*nested1_2->mutable_fields())["deeper"].mutable_struct_value(); + (*nested2_2->mutable_fields())["level"].set_string_value("level3"); + (*nested2_2->mutable_fields())["final"].set_bool_value(true); + + ProtobufWkt::ListValue* list2 = (*nested2_2->mutable_fields())["items"].mutable_list_value(); + list2->add_values()->set_string_value("item1"); + list2->add_values()->set_string_value("item2"); + list2->add_values()->set_number_value(42); + + // Test that identical nested structures produce same hash + EXPECT_EQ(HashCachedMessageUtil::hash(root_struct), HashCachedMessageUtil::hash(root_struct2)); + EXPECT_NE(0, HashCachedMessageUtil::hash(root_struct)); + + // Test that modifying any level changes the hash + ProtobufWkt::Struct modified_struct = root_struct; + (*modified_struct.mutable_fields())["name"].set_string_value("modified"); + + EXPECT_NE(HashCachedMessageUtil::hash(root_struct), HashCachedMessageUtil::hash(modified_struct)); + + // Test modifying nested level + ProtobufWkt::Struct modified_nested = root_struct; + ProtobufWkt::Struct* nested_mod = + (*modified_nested.mutable_fields())["nested"].mutable_struct_value(); + (*nested_mod->mutable_fields())["level"].set_string_value("modified_level2"); + + EXPECT_NE(HashCachedMessageUtil::hash(root_struct), HashCachedMessageUtil::hash(modified_nested)); + + // Test modifying deepest level + ProtobufWkt::Struct modified_deep = root_struct; + ProtobufWkt::Struct* nested_deep = + (*modified_deep.mutable_fields())["nested"].mutable_struct_value(); + ProtobufWkt::Struct* deeper_deep = + (*nested_deep->mutable_fields())["deeper"].mutable_struct_value(); + (*deeper_deep->mutable_fields())["final"].set_bool_value(false); + + EXPECT_NE(HashCachedMessageUtil::hash(root_struct), HashCachedMessageUtil::hash(modified_deep)); +} + +TEST_F(ProtobufUtilityTest, MessageUtilHashFieldTypes) { + // Test all field types supported by Protobuf + + // String fields + ProtobufWkt::StringValue str_msg; + str_msg.set_value("test string"); + EXPECT_NE(0, HashCachedMessageUtil::hash(str_msg)); + + // Integer fields + ProtobufWkt::Int32Value int32_msg; + int32_msg.set_value(-42); + EXPECT_NE(0, HashCachedMessageUtil::hash(int32_msg)); + + ProtobufWkt::UInt32Value uint32_msg; + uint32_msg.set_value(42); + EXPECT_NE(0, HashCachedMessageUtil::hash(uint32_msg)); + + ProtobufWkt::Int64Value int64_msg; + int64_msg.set_value(-1234567890123456789LL); + EXPECT_NE(0, HashCachedMessageUtil::hash(int64_msg)); + + ProtobufWkt::UInt64Value uint64_msg; + uint64_msg.set_value(1234567890123456789ULL); + EXPECT_NE(0, HashCachedMessageUtil::hash(uint64_msg)); + + // Floating point fields + ProtobufWkt::FloatValue float_msg; + float_msg.set_value(3.14159f); + EXPECT_NE(0, HashCachedMessageUtil::hash(float_msg)); + + ProtobufWkt::DoubleValue double_msg; + double_msg.set_value(2.718281828459045); + EXPECT_NE(0, HashCachedMessageUtil::hash(double_msg)); + + // Boolean fields + ProtobufWkt::BoolValue bool_msg; + bool_msg.set_value(true); + EXPECT_NE(0, HashCachedMessageUtil::hash(bool_msg)); + + // Enum fields (using well-known types) + // Note: NullValue is not a Message, so we can't hash it directly + // Instead test with a Struct containing null value + ProtobufWkt::Struct null_struct; + (*null_struct.mutable_fields())["null_field"].set_null_value(ProtobufWkt::NullValue::NULL_VALUE); + EXPECT_NE(0, HashCachedMessageUtil::hash(null_struct)); + + // Test that different types produce different hashes + std::vector hashes = { + HashCachedMessageUtil::hash(str_msg), HashCachedMessageUtil::hash(int32_msg), HashCachedMessageUtil::hash(uint32_msg), + HashCachedMessageUtil::hash(int64_msg), HashCachedMessageUtil::hash(uint64_msg), HashCachedMessageUtil::hash(float_msg), + HashCachedMessageUtil::hash(double_msg), HashCachedMessageUtil::hash(bool_msg), HashCachedMessageUtil::hash(null_struct)}; + + // All hashes should be different (very unlikely to have collisions) + for (size_t i = 0; i < hashes.size(); ++i) { + for (size_t j = i + 1; j < hashes.size(); ++j) { + EXPECT_NE(hashes[i], hashes[j]) << "Hash collision between types " << i << " and " << j; + } + } +} + + +TEST_F(ProtobufUtilityTest, MessageUtilRecursiveHashEdgeCases) { + // Test edge cases for recursive hashing + + // Test 1: Empty messages + ProtobufWkt::Struct empty_struct; + ProtobufWkt::StringValue empty_string; + empty_string.set_value(""); + + EXPECT_NE(0, HashCachedMessageUtil::hash(empty_struct)); + EXPECT_NE(0, HashCachedMessageUtil::hash(empty_string)); + EXPECT_NE(HashCachedMessageUtil::hash(empty_struct), HashCachedMessageUtil::hash(empty_string)); + + // Test 2: Messages with only default values + ProtobufWkt::Int32Value default_int; + ProtobufWkt::BoolValue default_bool; + ProtobufWkt::StringValue default_string; + + EXPECT_NE(0, HashCachedMessageUtil::hash(default_int)); + EXPECT_NE(0, HashCachedMessageUtil::hash(default_bool)); + EXPECT_NE(0, HashCachedMessageUtil::hash(default_string)); + + // Test 3: Messages with zero values + ProtobufWkt::Int32Value zero_int; + zero_int.set_value(0); + ProtobufWkt::UInt64Value zero_uint; + zero_uint.set_value(0); + ProtobufWkt::DoubleValue zero_double; + zero_double.set_value(0.0); + + EXPECT_NE(0, HashCachedMessageUtil::hash(zero_int)); + EXPECT_NE(0, HashCachedMessageUtil::hash(zero_uint)); + EXPECT_NE(0, HashCachedMessageUtil::hash(zero_double)); + + // Test 4: Messages with extreme values + ProtobufWkt::Int64Value max_int64; + max_int64.set_value(INT64_MAX); + ProtobufWkt::Int64Value min_int64; + min_int64.set_value(INT64_MIN); + ProtobufWkt::UInt64Value max_uint64; + max_uint64.set_value(UINT64_MAX); + + EXPECT_NE(0, HashCachedMessageUtil::hash(max_int64)); + EXPECT_NE(0, HashCachedMessageUtil::hash(min_int64)); + EXPECT_NE(0, HashCachedMessageUtil::hash(max_uint64)); + + // Test 5: Messages with special floating point values + ProtobufWkt::DoubleValue inf_double; + inf_double.set_value(std::numeric_limits::infinity()); + ProtobufWkt::DoubleValue neg_inf_double; + neg_inf_double.set_value(-std::numeric_limits::infinity()); + ProtobufWkt::DoubleValue nan_double; + nan_double.set_value(std::numeric_limits::quiet_NaN()); + + EXPECT_NE(0, HashCachedMessageUtil::hash(inf_double)); + EXPECT_NE(0, HashCachedMessageUtil::hash(neg_inf_double)); + EXPECT_NE(0, HashCachedMessageUtil::hash(nan_double)); + + // Test 6: Messages with very long strings + std::string very_long_string(100000, 'x'); + ProtobufWkt::StringValue long_str; + long_str.set_value(very_long_string); + + EXPECT_NE(0, HashCachedMessageUtil::hash(long_str)); + + // Test 7: Messages with binary data + std::string binary_data; + for (int i = 0; i < 256; ++i) { + binary_data.push_back(static_cast(i)); + } + ProtobufWkt::BytesValue binary_msg; + // Use Base64::encode with correct parameters + std::string encoded_data = Base64::encode(binary_data.data(), binary_data.length()); + TestUtility::loadFromJson("\"" + encoded_data + "\"", binary_msg); + + EXPECT_NE(0, HashCachedMessageUtil::hash(binary_msg)); + + // Test 8: Messages with mixed content types + ProtobufWkt::Struct mixed_struct; + (*mixed_struct.mutable_fields())["string"].set_string_value("mixed"); + (*mixed_struct.mutable_fields())["number"].set_number_value(42.5); + (*mixed_struct.mutable_fields())["boolean"].set_bool_value(true); + (*mixed_struct.mutable_fields())["null"].set_null_value(ProtobufWkt::NullValue::NULL_VALUE); + + EXPECT_NE(0, HashCachedMessageUtil::hash(mixed_struct)); + + // Test 9: Circular reference prevention (should not crash) + // This tests that the hash function can handle complex structures + ProtobufWkt::Struct complex_struct; + (*complex_struct.mutable_fields())["self"].mutable_struct_value(); + // Note: We don't create actual circular references as they would cause issues + + EXPECT_NE(0, HashCachedMessageUtil::hash(complex_struct)); +} + +TEST_F(ProtobufUtilityTest, MessageUtilHashCollisionDetection) { + // Test for potential hash collisions and hash quality + + // Test 1: Birthday paradox simulation + // Create many different messages and check for collisions + std::unordered_set hashes; + std::vector messages; + + // Generate 1000 different messages + for (int i = 0; i < 1000; ++i) { + ProtobufWkt::StringValue msg; + msg.set_value("unique_message_" + std::to_string(i) + "_" + std::to_string(i * 12345)); + messages.push_back(msg); + + uint64_t hash = HashCachedMessageUtil::hash(msg); + hashes.insert(hash); + } + + // Check collision rate (should be very low for good hash function) + double collision_rate = 1.0 - (static_cast(hashes.size()) / messages.size()); + EXPECT_LT(collision_rate, 0.001); // Expect less than 0.1% collision rate + + // Test 2: Similar input collision detection + // Test strings that differ by only one character + std::vector similar_strings = { + "hello world", "hello world!", "hello world!!", "hello world!!!", + "hello world!!!!", "hello world!!!!!", "hello world!!!!!!", "hello world!!!!!!!", + "hello world!!!!!!!!", "hello world!!!!!!!!!"}; + + std::unordered_set similar_hashes; + for (const auto& str : similar_strings) { + ProtobufWkt::StringValue msg; + msg.set_value(str); + similar_hashes.insert(HashCachedMessageUtil::hash(msg)); + } + + // Similar strings should produce different hashes + EXPECT_EQ(similar_hashes.size(), similar_strings.size()); + + // Test 3: Numeric proximity collision detection + // Test numbers that are very close to each other + std::vector close_numbers = {1.0, 1.0000001, 1.0000002, 1.0000003, 1.0000004, + 1.0000005, 1.0000006, 1.0000007, 1.0000008, 1.0000009}; + + std::unordered_set numeric_hashes; + for (double num : close_numbers) { + ProtobufWkt::DoubleValue msg; + msg.set_value(num); + numeric_hashes.insert(HashCachedMessageUtil::hash(msg)); + } + + // Close numbers should produce different hashes + EXPECT_EQ(numeric_hashes.size(), close_numbers.size()); + + // Test 4: Structure similarity collision detection + // Test structs with similar field names but different values + std::vector similar_structs; + + for (int i = 0; i < 10; ++i) { + ProtobufWkt::Struct msg; + (*msg.mutable_fields())["field_a"].set_string_value("value_" + std::to_string(i)); + (*msg.mutable_fields())["field_b"].set_number_value(i); + (*msg.mutable_fields())["field_c"].set_bool_value(i % 2 == 0); + similar_structs.push_back(msg); + } + + std::unordered_set struct_hashes; + for (const auto& msg : similar_structs) { + struct_hashes.insert(HashCachedMessageUtil::hash(msg)); + } + + // Similar structures should produce different hashes + EXPECT_EQ(struct_hashes.size(), similar_structs.size()); + + // Test 5: Hash avalanche effect + // Small changes should produce significantly different hashes + + // Test single character changes + std::vector avalanche_tests = { + "base message for avalanche test", // Original + "base message for avalanche test!", // Add exclamation + "base message for avalanche test?", // Change to question + "base message for avalanche test.", // Change to period + "base message for avalanche testx", // Change last character + "xbase message for avalanche test", // Change first character + "base message for avalanche test ", // Add space at end + " base message for avalanche test", // Add space at beginning + "Base message for avalanche test", // Capitalize first letter + "base Message for avalanche test" // Capitalize middle word + }; + + std::unordered_set avalanche_hashes; + for (const auto& str : avalanche_tests) { + ProtobufWkt::StringValue msg; + msg.set_value(str); + avalanche_hashes.insert(HashCachedMessageUtil::hash(msg)); + } + + // All avalanche tests should produce different hashes + EXPECT_EQ(avalanche_hashes.size(), avalanche_tests.size()); + + // Test 6: Hash distribution quality + // Check that hashes are well distributed across the hash space + std::vector all_hashes; + all_hashes.insert(all_hashes.end(), hashes.begin(), hashes.end()); + all_hashes.insert(all_hashes.end(), similar_hashes.begin(), similar_hashes.end()); + all_hashes.insert(all_hashes.end(), numeric_hashes.begin(), numeric_hashes.end()); + all_hashes.insert(all_hashes.end(), struct_hashes.begin(), struct_hashes.end()); + all_hashes.insert(all_hashes.end(), avalanche_hashes.begin(), avalanche_hashes.end()); + + // Calculate hash distribution statistics + if (all_hashes.size() > 1) { + uint64_t min_hash = *std::min_element(all_hashes.begin(), all_hashes.end()); + uint64_t max_hash = *std::max_element(all_hashes.begin(), all_hashes.end()); + uint64_t hash_range = max_hash - min_hash; + + // Hash range should be substantial (not all hashes clustered together) + EXPECT_GT(hash_range, UINT64_MAX / 100); // Should use at least 1% of hash space + } + + // Test 7: Deterministic hash behavior + // Same input should always produce same hash + ProtobufWkt::StringValue test_msg; + test_msg.set_value("deterministic test message"); + + uint64_t hash1 = HashCachedMessageUtil::hash(test_msg); + uint64_t hash2 = HashCachedMessageUtil::hash(test_msg); + uint64_t hash3 = HashCachedMessageUtil::hash(test_msg); + + EXPECT_EQ(hash1, hash2); + EXPECT_EQ(hash2, hash3); + EXPECT_EQ(hash1, hash3); + + // Test 8: Hash uniqueness across different types + // Different message types should produce different hashes + ProtobufWkt::StringValue str_msg; + str_msg.set_value("test"); + + ProtobufWkt::Int32Value int_msg; + int_msg.set_value(42); + + ProtobufWkt::BoolValue bool_msg; + bool_msg.set_value(true); + + uint64_t str_hash = HashCachedMessageUtil::hash(str_msg); + uint64_t int_hash = HashCachedMessageUtil::hash(int_msg); + uint64_t bool_hash = HashCachedMessageUtil::hash(bool_msg); + + // All should be different + EXPECT_NE(str_hash, int_hash); + EXPECT_NE(int_hash, bool_hash); + EXPECT_NE(str_hash, bool_hash); +} +#endif // ALIMESH + TEST_F(ProtobufUtilityTest, MessageUtilHash) { ProtobufWkt::Struct s; (*s.mutable_fields())["ab"].set_string_value("fgh"); @@ -185,8 +986,12 @@ TEST_F(ProtobufUtilityTest, MessageUtilHash) { ProtobufWkt::Any a3 = a1; a3.set_value(Base64::decode("CgsKAmFiEgUaA2ZnaAoLCgNjZGUSBBoCaWo=")); +#if defined(ALIMESH) && defined(ENVOY_ENABLE_FULL_PROTOS) + // the message hash skip the any type parse, it cause unordered map in any to be different +#else EXPECT_EQ(MessageUtil::hash(a1), MessageUtil::hash(a2)); EXPECT_EQ(MessageUtil::hash(a2), MessageUtil::hash(a3)); +#endif EXPECT_NE(0, MessageUtil::hash(a1)); EXPECT_NE(MessageUtil::hash(s), MessageUtil::hash(a1)); } diff --git a/test/common/stream_info/stream_info_impl_test.cc b/test/common/stream_info/stream_info_impl_test.cc index 46459efd069ea..c8ad33bdc043f 100644 --- a/test/common/stream_info/stream_info_impl_test.cc +++ b/test/common/stream_info/stream_info_impl_test.cc @@ -39,6 +39,13 @@ class StreamInfoImplTest : public testing::Test { void assertStreamInfoSize(StreamInfoImpl stream_info) { ASSERT_TRUE(sizeof(stream_info) == 840 || sizeof(stream_info) == 856 || sizeof(stream_info) == 888 || sizeof(stream_info) == 776 || +#if defined(ALIMESH) + sizeof(stream_info) == 816 || sizeof(stream_info) == 768 || + + // add hash cache to protobuf message + // detail: bazel/protobuf_hash_cache.patch + sizeof(stream_info) == 784 || +#endif sizeof(stream_info) == 728 || sizeof(stream_info) == 744) << "If adding fields to StreamInfoImpl, please check to see if you " "need to add them to setFromForRecreateStream or setFrom! Current size " From 354715238bb6f03738eab5ffbc842538e069201c Mon Sep 17 00:00:00 2001 From: zty98751 Date: Tue, 11 Nov 2025 13:53:28 +0800 Subject: [PATCH 2/8] fix typo Change-Id: Iff5ca5cccb4f63a15a499e1f5d8a7f98ab0131b5 --- source/common/http/conn_manager_config.h | 4 +- source/common/http/conn_manager_impl.cc | 2 +- source/common/protobuf/utility.h | 6 +- .../rds/route_config_update_receiver_impl.h | 6 +- .../filter_chain_manager_impl.cc | 2 +- .../filter_chain_manager_impl.h | 9 ++- .../listener_manager/listener_impl.cc | 10 +-- .../listener_manager/listener_manager_impl.cc | 4 +- .../http/conn_manager_impl_fuzz_test.cc | 2 +- .../http/conn_manager_impl_test_base.cc | 2 +- test/common/protobuf/utility_test.cc | 74 ++++++++++++------- .../stream_info/stream_info_impl_test.cc | 2 +- 12 files changed, 73 insertions(+), 50 deletions(-) diff --git a/source/common/http/conn_manager_config.h b/source/common/http/conn_manager_config.h index 69e6c48a03c26..670e321867a11 100644 --- a/source/common/http/conn_manager_config.h +++ b/source/common/http/conn_manager_config.h @@ -22,7 +22,7 @@ namespace Envoy { namespace Http { -#if defined(ALIMESH) +#if defined(HIGRESS) #define HIGRESS_EXT_HTTP_CONN_MAN_STATS(COUNTER, GAUGE, HISTOGRAM) \ COUNTER(downstream_rq_retry_scope_found_total) \ COUNTER(downstream_rq_retry_scope_not_found_total) @@ -98,7 +98,7 @@ namespace Http { */ struct ConnectionManagerNamedStats { ALL_HTTP_CONN_MAN_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT, GENERATE_HISTOGRAM_STRUCT) -#if defined(ALIMESH) +#if defined(HIGRESS) HIGRESS_EXT_HTTP_CONN_MAN_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT, GENERATE_HISTOGRAM_STRUCT) #endif diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 87ca935ceb8c8..a748843a14eaf 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -80,7 +80,7 @@ bool requestWasConnect(const RequestHeaderMapSharedPtr& headers, Protocol protoc ConnectionManagerStats ConnectionManagerImpl::generateStats(const std::string& prefix, Stats::Scope& scope) { return ConnectionManagerStats( -#if defined(ALIMESH) +#if defined(HIGRESS) {ALL_HTTP_CONN_MAN_STATS(POOL_COUNTER_PREFIX(scope, prefix), POOL_GAUGE_PREFIX(scope, prefix), POOL_HISTOGRAM_PREFIX(scope, prefix)) HIGRESS_EXT_HTTP_CONN_MAN_STATS(POOL_COUNTER_PREFIX(scope, prefix), diff --git a/source/common/protobuf/utility.h b/source/common/protobuf/utility.h index b8caf7326aa65..18402565f371d 100644 --- a/source/common/protobuf/utility.h +++ b/source/common/protobuf/utility.h @@ -580,7 +580,7 @@ class MessageUtil { static std::string sanitizeUtf8String(absl::string_view str); }; -#if defined(ALIMESH) && defined(ENVOY_ENABLE_FULL_PROTOS) +#if defined(HIGRESS) && defined(ENVOY_ENABLE_FULL_PROTOS) class HashCachedMessageUtil : public MessageUtil { public: bool operator()(const Protobuf::Message& message) const { return message.GetCachedHashValue(); } @@ -589,9 +589,7 @@ class HashCachedMessageUtil : public MessageUtil { return lhs.GetCachedHashValue() == rhs.GetCachedHashValue(); } - static std::size_t hash(const Protobuf::Message& message) { - return message.GetCachedHashValue(); - } + static std::size_t hash(const Protobuf::Message& message) { return message.GetCachedHashValue(); } }; #endif diff --git a/source/common/rds/route_config_update_receiver_impl.h b/source/common/rds/route_config_update_receiver_impl.h index b98f88f76b619..d708ffc310602 100644 --- a/source/common/rds/route_config_update_receiver_impl.h +++ b/source/common/rds/route_config_update_receiver_impl.h @@ -13,11 +13,11 @@ class RouteConfigUpdateReceiverImpl : public RouteConfigUpdateReceiver { public: RouteConfigUpdateReceiverImpl(ConfigTraits& config_traits, ProtoTraits& proto_traits, Server::Configuration::ServerFactoryContext& factory_context); - #if defined(ALIMESH) && defined(ENVOY_ENABLE_FULL_PROTOS) +#if defined(HIGRESS) && defined(ENVOY_ENABLE_FULL_PROTOS) uint64_t getHash(const Protobuf::Message& rc) const { return HashCachedMessageUtil::hash(rc); } - #else +#else uint64_t getHash(const Protobuf::Message& rc) const { return MessageUtil::hash(rc); } - #endif +#endif bool checkHash(uint64_t new_hash) const { return (new_hash != last_config_hash_); } void updateHash(uint64_t hash) { last_config_hash_ = hash; } void updateConfig(std::unique_ptr&& route_config_proto); diff --git a/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.cc b/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.cc index f5da350f90dd1..593537aaf249c 100644 --- a/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.cc +++ b/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.cc @@ -211,7 +211,7 @@ void FilterChainManagerImpl::addFilterChains( FilterChainFactoryBuilder& filter_chain_factory_builder, FilterChainFactoryContextCreator& context_creator) { Cleanup cleanup([this]() { origin_ = absl::nullopt; }); -#if defined(ALIMESH) && defined(ENVOY_ENABLE_FULL_PROTOS) +#if defined(HIGRESS) && defined(ENVOY_ENABLE_FULL_PROTOS) absl::node_hash_map filter_chains; diff --git a/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.h b/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.h index 96befc09abbe6..ad2d3756e3d60 100644 --- a/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.h +++ b/source/extensions/listener_managers/listener_manager/filter_chain_manager_impl.h @@ -151,13 +151,14 @@ class FilterChainManagerImpl : public Network::FilterChainManager, Logger::Loggable { public: using FcContextMap = - #if defined(ALIMESH) && defined(ENVOY_ENABLE_FULL_PROTOS) +#if defined(HIGRESS) && defined(ENVOY_ENABLE_FULL_PROTOS) absl::flat_hash_map; - #else + Network::DrainableFilterChainSharedPtr, HashCachedMessageUtil, + HashCachedMessageUtil>; +#else absl::flat_hash_map; - #endif +#endif FilterChainManagerImpl(const std::vector& addresses, Configuration::FactoryContext& factory_context, diff --git a/source/extensions/listener_managers/listener_manager/listener_impl.cc b/source/extensions/listener_managers/listener_manager/listener_impl.cc index cc03b3116b745..10184501e87c7 100644 --- a/source/extensions/listener_managers/listener_manager/listener_impl.cc +++ b/source/extensions/listener_managers/listener_manager/listener_impl.cc @@ -1056,11 +1056,11 @@ void ListenerImpl::diffFilterChain(const ListenerImpl& another_listener, // Filter chain manager maintains an optional default filter chain besides the filter chains // indexed by message. if (auto - #if defined(ALIMESH) && defined(ENVOY_ENABLE_FULL_PROTOS) - eq = HashCachedMessageUtil(); - #else - eq = MessageUtil(); - #endif +#if defined(HIGRESS) && defined(ENVOY_ENABLE_FULL_PROTOS) + eq = HashCachedMessageUtil(); +#else + eq = MessageUtil(); +#endif filter_chain_manager_->defaultFilterChainMessage().has_value() && (!another_listener.filter_chain_manager_->defaultFilterChainMessage().has_value() || !eq(*another_listener.filter_chain_manager_->defaultFilterChainMessage(), diff --git a/source/extensions/listener_managers/listener_manager/listener_manager_impl.cc b/source/extensions/listener_managers/listener_manager/listener_manager_impl.cc index 61476a4537134..f07290466db47 100644 --- a/source/extensions/listener_managers/listener_manager/listener_manager_impl.cc +++ b/source/extensions/listener_managers/listener_manager/listener_manager_impl.cc @@ -481,9 +481,9 @@ bool ListenerManagerImpl::addOrUpdateListenerInternal( name, envoy::config::core::v3::TrafficDirection_Name(config.traffic_direction())); return false; } -#if defined(ALIMESH) && defined(ENVOY_ENABLE_FULL_PROTOS) +#if defined(HIGRESS) && defined(ENVOY_ENABLE_FULL_PROTOS) const uint64_t hash = HashCachedMessageUtil::hash(config); -#else +#else const uint64_t hash = MessageUtil::hash(config); #endif ENVOY_LOG(debug, "begin add/update listener: name={} hash={}", name, hash); diff --git a/test/common/http/conn_manager_impl_fuzz_test.cc b/test/common/http/conn_manager_impl_fuzz_test.cc index dc2a7887bcd36..5589662f3edf1 100644 --- a/test/common/http/conn_manager_impl_fuzz_test.cc +++ b/test/common/http/conn_manager_impl_fuzz_test.cc @@ -57,7 +57,7 @@ class FuzzConfig : public ConnectionManagerConfig { ALL_HTTP_CONN_MAN_STATS(POOL_COUNTER(*fake_stats_.rootScope()), POOL_GAUGE(fake_stats_), POOL_HISTOGRAM(*fake_stats_.rootScope())) -#if defined(ALIMESH) +#if defined(HIGRESS) HIGRESS_EXT_HTTP_CONN_MAN_STATS(POOL_COUNTER(*fake_stats_.rootScope()), POOL_GAUGE(fake_stats_), POOL_HISTOGRAM(*fake_stats_.rootScope())) diff --git a/test/common/http/conn_manager_impl_test_base.cc b/test/common/http/conn_manager_impl_test_base.cc index 47a99dbfcdc54..883c60643dc48 100644 --- a/test/common/http/conn_manager_impl_test_base.cc +++ b/test/common/http/conn_manager_impl_test_base.cc @@ -19,7 +19,7 @@ HttpConnectionManagerImplMixin::HttpConnectionManagerImplMixin() Filesystem::FilePathAndType{Filesystem::DestinationType::File, access_log_path_}, {}, Formatter::SubstitutionFormatUtils::defaultSubstitutionFormatter(), log_manager_)}}, codec_(new NiceMock()), -#if defined(ALIMESH) +#if defined(HIGRESS) stats_({ALL_HTTP_CONN_MAN_STATS(POOL_COUNTER(*fake_stats_.rootScope()), POOL_GAUGE(*fake_stats_.rootScope()), POOL_HISTOGRAM(*fake_stats_.rootScope())) diff --git a/test/common/protobuf/utility_test.cc b/test/common/protobuf/utility_test.cc index 5a9a2db5a16e6..a2f635f10fe6a 100644 --- a/test/common/protobuf/utility_test.cc +++ b/test/common/protobuf/utility_test.cc @@ -171,7 +171,7 @@ TEST_F(ProtobufUtilityTest, EvaluateFractionalPercent) { } // namespace ProtobufPercentHelper -#if defined(ALIMESH) && defined(ENVOY_ENABLE_FULL_PROTOS) +#if defined(HIGRESS) && defined(ENVOY_ENABLE_FULL_PROTOS) TEST_F(ProtobufUtilityTest, HashCache) { ProtobufWkt::StringValue str1, str2, str3; TestUtility::loadFromJson("\"hello world\"", str1); @@ -218,14 +218,14 @@ TEST_F(ProtobufUtilityTest, HashCache) { // Test direct message nesting (not map) - using Value with struct_value ProtobufWkt::Value nested_value1, nested_value2, nested_value3; - + // Create nested structure: Value -> Struct -> Value -> StringValue auto* nested_struct1 = nested_value1.mutable_struct_value(); (*nested_struct1->mutable_fields())["nested_field"].set_string_value("nested hello world"); - + auto* nested_struct2 = nested_value2.mutable_struct_value(); (*nested_struct2->mutable_fields())["nested_field"].set_string_value("nested hello world"); - + auto* nested_struct3 = nested_value3.mutable_struct_value(); (*nested_struct3->mutable_fields())["nested_field"].set_string_value("nested hello world!"); @@ -236,12 +236,12 @@ TEST_F(ProtobufUtilityTest, HashCache) { EXPECT_TRUE(nested_value1.HasCachedHashValue()); EXPECT_TRUE(nested_value2.HasCachedHashValue()); EXPECT_TRUE(nested_value3.HasCachedHashValue()); - + // Check nested struct messages EXPECT_TRUE(nested_value1.struct_value().HasCachedHashValue()); EXPECT_TRUE(nested_value2.struct_value().HasCachedHashValue()); EXPECT_TRUE(nested_value3.struct_value().HasCachedHashValue()); - + // Check the nested Value objects inside struct EXPECT_TRUE(nested_value1.struct_value().fields().at("nested_field").HasCachedHashValue()); EXPECT_TRUE(nested_value2.struct_value().fields().at("nested_field").HasCachedHashValue()); @@ -249,32 +249,53 @@ TEST_F(ProtobufUtilityTest, HashCache) { // Test deeper nesting: Value -> Struct -> Value -> Struct -> Value -> StringValue ProtobufWkt::Value deep_nested_value1, deep_nested_value2; - + auto* deep_struct1 = deep_nested_value1.mutable_struct_value(); auto* deep_inner_struct1 = (*deep_struct1->mutable_fields())["deep_field"].mutable_struct_value(); (*deep_inner_struct1->mutable_fields())["inner_field"].set_string_value("deep nested value"); - + auto* deep_struct2 = deep_nested_value2.mutable_struct_value(); auto* deep_inner_struct2 = (*deep_struct2->mutable_fields())["deep_field"].mutable_struct_value(); (*deep_inner_struct2->mutable_fields())["inner_field"].set_string_value("deep nested value"); - EXPECT_EQ(HashCachedMessageUtil::hash(deep_nested_value1), HashCachedMessageUtil::hash(deep_nested_value2)); + EXPECT_EQ(HashCachedMessageUtil::hash(deep_nested_value1), + HashCachedMessageUtil::hash(deep_nested_value2)); // Check that all levels of nesting have cached hash values EXPECT_TRUE(deep_nested_value1.HasCachedHashValue()); EXPECT_TRUE(deep_nested_value2.HasCachedHashValue()); - + EXPECT_TRUE(deep_nested_value1.struct_value().HasCachedHashValue()); EXPECT_TRUE(deep_nested_value2.struct_value().HasCachedHashValue()); - + EXPECT_TRUE(deep_nested_value1.struct_value().fields().at("deep_field").HasCachedHashValue()); EXPECT_TRUE(deep_nested_value2.struct_value().fields().at("deep_field").HasCachedHashValue()); - - EXPECT_TRUE(deep_nested_value1.struct_value().fields().at("deep_field").struct_value().HasCachedHashValue()); - EXPECT_TRUE(deep_nested_value2.struct_value().fields().at("deep_field").struct_value().HasCachedHashValue()); - - EXPECT_TRUE(deep_nested_value1.struct_value().fields().at("deep_field").struct_value().fields().at("inner_field").HasCachedHashValue()); - EXPECT_TRUE(deep_nested_value2.struct_value().fields().at("deep_field").struct_value().fields().at("inner_field").HasCachedHashValue()); + + EXPECT_TRUE(deep_nested_value1.struct_value() + .fields() + .at("deep_field") + .struct_value() + .HasCachedHashValue()); + EXPECT_TRUE(deep_nested_value2.struct_value() + .fields() + .at("deep_field") + .struct_value() + .HasCachedHashValue()); + + EXPECT_TRUE(deep_nested_value1.struct_value() + .fields() + .at("deep_field") + .struct_value() + .fields() + .at("inner_field") + .HasCachedHashValue()); + EXPECT_TRUE(deep_nested_value2.struct_value() + .fields() + .at("deep_field") + .struct_value() + .fields() + .at("inner_field") + .HasCachedHashValue()); } TEST_F(ProtobufUtilityTest, MessageUtilRecursiveHash) { @@ -446,7 +467,8 @@ TEST_F(ProtobufUtilityTest, MessageUtilHashComprehensive) { EXPECT_EQ(HashCachedMessageUtil::hash(list1), HashCachedMessageUtil::hash(list2)); EXPECT_NE(HashCachedMessageUtil::hash(list1), HashCachedMessageUtil::hash(list3)); - EXPECT_NE(HashCachedMessageUtil::hash(list1), HashCachedMessageUtil::hash(list4)); // Order matters + EXPECT_NE(HashCachedMessageUtil::hash(list1), + HashCachedMessageUtil::hash(list4)); // Order matters EXPECT_NE(0, HashCachedMessageUtil::hash(list1)); // Test empty list @@ -560,7 +582,8 @@ TEST_F(ProtobufUtilityTest, MessageUtilHashComprehensive) { (*large_struct2.mutable_fields())[field_name].set_string_value(field_value); } - EXPECT_EQ(HashCachedMessageUtil::hash(large_struct), HashCachedMessageUtil::hash(large_struct2)); + EXPECT_EQ(HashCachedMessageUtil::hash(large_struct), + HashCachedMessageUtil::hash(large_struct2)); } // Test 10: Edge cases @@ -710,9 +733,11 @@ TEST_F(ProtobufUtilityTest, MessageUtilHashFieldTypes) { // Test that different types produce different hashes std::vector hashes = { - HashCachedMessageUtil::hash(str_msg), HashCachedMessageUtil::hash(int32_msg), HashCachedMessageUtil::hash(uint32_msg), - HashCachedMessageUtil::hash(int64_msg), HashCachedMessageUtil::hash(uint64_msg), HashCachedMessageUtil::hash(float_msg), - HashCachedMessageUtil::hash(double_msg), HashCachedMessageUtil::hash(bool_msg), HashCachedMessageUtil::hash(null_struct)}; + HashCachedMessageUtil::hash(str_msg), HashCachedMessageUtil::hash(int32_msg), + HashCachedMessageUtil::hash(uint32_msg), HashCachedMessageUtil::hash(int64_msg), + HashCachedMessageUtil::hash(uint64_msg), HashCachedMessageUtil::hash(float_msg), + HashCachedMessageUtil::hash(double_msg), HashCachedMessageUtil::hash(bool_msg), + HashCachedMessageUtil::hash(null_struct)}; // All hashes should be different (very unlikely to have collisions) for (size_t i = 0; i < hashes.size(); ++i) { @@ -722,7 +747,6 @@ TEST_F(ProtobufUtilityTest, MessageUtilHashFieldTypes) { } } - TEST_F(ProtobufUtilityTest, MessageUtilRecursiveHashEdgeCases) { // Test edge cases for recursive hashing @@ -970,7 +994,7 @@ TEST_F(ProtobufUtilityTest, MessageUtilHashCollisionDetection) { EXPECT_NE(int_hash, bool_hash); EXPECT_NE(str_hash, bool_hash); } -#endif // ALIMESH +#endif // HIGRESS TEST_F(ProtobufUtilityTest, MessageUtilHash) { ProtobufWkt::Struct s; @@ -986,7 +1010,7 @@ TEST_F(ProtobufUtilityTest, MessageUtilHash) { ProtobufWkt::Any a3 = a1; a3.set_value(Base64::decode("CgsKAmFiEgUaA2ZnaAoLCgNjZGUSBBoCaWo=")); -#if defined(ALIMESH) && defined(ENVOY_ENABLE_FULL_PROTOS) +#if defined(HIGRESS) && defined(ENVOY_ENABLE_FULL_PROTOS) // the message hash skip the any type parse, it cause unordered map in any to be different #else EXPECT_EQ(MessageUtil::hash(a1), MessageUtil::hash(a2)); diff --git a/test/common/stream_info/stream_info_impl_test.cc b/test/common/stream_info/stream_info_impl_test.cc index c8ad33bdc043f..77918a2ad3393 100644 --- a/test/common/stream_info/stream_info_impl_test.cc +++ b/test/common/stream_info/stream_info_impl_test.cc @@ -39,7 +39,7 @@ class StreamInfoImplTest : public testing::Test { void assertStreamInfoSize(StreamInfoImpl stream_info) { ASSERT_TRUE(sizeof(stream_info) == 840 || sizeof(stream_info) == 856 || sizeof(stream_info) == 888 || sizeof(stream_info) == 776 || -#if defined(ALIMESH) +#if defined(HIGRESS) sizeof(stream_info) == 816 || sizeof(stream_info) == 768 || // add hash cache to protobuf message From 1ddda52f3954be2e6a7fedbcae4a97ef3d30ec14 Mon Sep 17 00:00:00 2001 From: zty98751 Date: Tue, 11 Nov 2025 13:55:22 +0800 Subject: [PATCH 3/8] wasm: support proactive rebuild mechanism Change-Id: Id81142e2f0a14e4d884fbaf0222f56255d87d907 --- source/extensions/common/wasm/context.cc | 9 ++- source/extensions/common/wasm/stats_handler.h | 1 + source/extensions/common/wasm/wasm.cc | 18 +++-- source/extensions/common/wasm/wasm.h | 2 +- .../filters/http/wasm/wasm_filter.h | 14 +++- .../filters/network/wasm/wasm_filter.h | 2 +- .../filters/http/wasm/test_data/test_cpp.cc | 5 ++ .../filters/http/wasm/wasm_filter_test.cc | 65 +++++++++++++++++++ test/test_common/wasm_base.h | 12 +++- 9 files changed, 117 insertions(+), 11 deletions(-) diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index 756be7b6b5467..7ca19f512da7c 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -67,6 +67,7 @@ constexpr std::string_view ClearRouteCacheKey = "clear_route_cache"; constexpr std::string_view DisableClearRouteCache = "off"; constexpr std::string_view SetDecoderBufferLimit = "set_decoder_buffer_limit"; constexpr std::string_view SetEncoderBufferLimit = "set_encoder_buffer_limit"; +constexpr std::string_view WasmRebuildKey = "wasm_rebuild"; bool stringViewToUint32(std::string_view str, uint32_t& out_value) { try { @@ -1354,7 +1355,13 @@ WasmResult Context::setProperty(std::string_view path, std::string_view value) { prototype.life_span_); } #if defined(HIGRESS) - if (path == ClearRouteCacheKey) { + if (path == WasmRebuildKey) { + if (wasm_) { + wasm_->setShouldRebuild(true); + ENVOY_LOG(debug, "Wasm rebuild flag set by plugin"); + } + return WasmResult::Ok; + } else if (path == ClearRouteCacheKey) { disable_clear_route_cache_ = value == DisableClearRouteCache; } else if (path == SetDecoderBufferLimit && decoder_callbacks_) { uint32_t buffer_limit; diff --git a/source/extensions/common/wasm/stats_handler.h b/source/extensions/common/wasm/stats_handler.h index 2f492cc9c77e6..8f3d7a8b1346f 100644 --- a/source/extensions/common/wasm/stats_handler.h +++ b/source/extensions/common/wasm/stats_handler.h @@ -36,6 +36,7 @@ struct CreateWasmStats { COUNTER(created) \ GAUGE(active, NeverImport) \ PLUGIN_COUNTER(recover_total) \ + PLUGIN_COUNTER(rebuild_total) \ PLUGIN_COUNTER(crash_total) \ PLUGIN_COUNTER(recover_error) \ PLUGIN_GAUGE(crash, NeverImport) diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc index ab96bed40594a..24e4a38efa06c 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -195,7 +195,7 @@ Wasm::~Wasm() { } #if defined(HIGRESS) -bool PluginHandleSharedPtrThreadLocal::recover() { +bool PluginHandleSharedPtrThreadLocal::rebuild(bool is_fail_recovery) { if (handle_ == nullptr || handle_->wasmHandle() == nullptr || handle_->wasmHandle()->wasm() == nullptr) { ENVOY_LOG(warn, "wasm has not been initialized"); @@ -204,16 +204,22 @@ bool PluginHandleSharedPtrThreadLocal::recover() { auto& dispatcher = handle_->wasmHandle()->wasm()->dispatcher(); auto now = dispatcher.timeSource().monotonicTime() + cache_time_offset_for_testing; if (now - last_recover_time_ < std::chrono::seconds(MIN_RECOVER_INTERVAL_SECONDS)) { - ENVOY_LOG(debug, "recover interval has not been reached"); + ENVOY_LOG(debug, "rebuild interval has not been reached"); return false; } - // Even if recovery fails, it will be retried after the interval + // Even if rebuild fails, it will be retried after the interval last_recover_time_ = now; std::shared_ptr new_handle; - if (handle_->doRecover(new_handle)) { + if (handle_->rebuild(new_handle)) { handle_ = std::static_pointer_cast(new_handle); - handle_->wasmHandle()->wasm()->lifecycleStats().recover_total_.inc(); - ENVOY_LOG(info, "wasm vm recover from crash success"); + // Increment appropriate metrics based on rebuild type + if (is_fail_recovery) { + handle_->wasmHandle()->wasm()->lifecycleStats().recover_total_.inc(); + ENVOY_LOG(info, "wasm vm recover from crash success"); + } else { + handle_->wasmHandle()->wasm()->lifecycleStats().rebuild_total_.inc(); + ENVOY_LOG(info, "wasm vm rebuild success"); + } return true; } return false; diff --git a/source/extensions/common/wasm/wasm.h b/source/extensions/common/wasm/wasm.h index e6974a97c4d4f..cf5658e6d33f8 100644 --- a/source/extensions/common/wasm/wasm.h +++ b/source/extensions/common/wasm/wasm.h @@ -162,7 +162,7 @@ class PluginHandleSharedPtrThreadLocal : public ThreadLocal::ThreadLocalObject, public Logger::Loggable { public: PluginHandleSharedPtrThreadLocal(PluginHandleSharedPtr handle) : handle_(handle){}; - bool recover(); + bool rebuild(bool is_fail_recovery = false); #else class PluginHandleSharedPtrThreadLocal : public ThreadLocal::ThreadLocalObject { public: diff --git a/source/extensions/filters/http/wasm/wasm_filter.h b/source/extensions/filters/http/wasm/wasm_filter.h index 49ae1f1a84ae9..d083903490a80 100644 --- a/source/extensions/filters/http/wasm/wasm_filter.h +++ b/source/extensions/filters/http/wasm/wasm_filter.h @@ -48,7 +48,7 @@ class FilterConfig : Logger::Loggable { failed = true; } else if (wasm->isFailed()) { ENVOY_LOG(info, "wasm vm is crashed, try to recover"); - if (opt_ref->recover()) { + if (opt_ref->rebuild(true)) { ENVOY_LOG(info, "wasm vm recover success"); wasm = opt_ref->handle()->wasmHandle()->wasm().get(); handle = opt_ref->handle(); @@ -56,6 +56,18 @@ class FilterConfig : Logger::Loggable { ENVOY_LOG(info, "wasm vm recover failed"); failed = true; } + } else if (wasm->shouldRebuild()) { + ENVOY_LOG(info, "wasm vm requested rebuild, try to rebuild"); + if (opt_ref->rebuild(false)) { + ENVOY_LOG(info, "wasm vm rebuild success"); + wasm = opt_ref->handle()->wasmHandle()->wasm().get(); + handle = opt_ref->handle(); + // Reset rebuild state + wasm->setShouldRebuild(false); + } else { + ENVOY_LOG(info, "wasm vm rebuild failed"); + failed = true; + } } if (failed) { if (handle->plugin()->fail_open_) { diff --git a/source/extensions/filters/network/wasm/wasm_filter.h b/source/extensions/filters/network/wasm/wasm_filter.h index 9a6e21eb81268..d21e76faed09f 100644 --- a/source/extensions/filters/network/wasm/wasm_filter.h +++ b/source/extensions/filters/network/wasm/wasm_filter.h @@ -48,7 +48,7 @@ class FilterConfig : Logger::Loggable { failed = true; } else if (wasm->isFailed()) { ENVOY_LOG(info, "wasm vm is crashed, try to recover"); - if (opt_ref->recover()) { + if (opt_ref->rebuild(true)) { ENVOY_LOG(info, "wasm vm recover success"); wasm = opt_ref->handle()->wasmHandle()->wasm().get(); } else { diff --git a/test/extensions/filters/http/wasm/test_data/test_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_cpp.cc index 9ea317568fc00..27a1e2b3a9fe3 100644 --- a/test/extensions/filters/http/wasm/test_data/test_cpp.cc +++ b/test/extensions/filters/http/wasm/test_data/test_cpp.cc @@ -290,6 +290,11 @@ FilterHeadersStatus TestContext::onRequestHeaders(uint32_t, bool) { if (!getRequestHeader("crash")->toString().empty()) { abort(); } + } else if (test == "RebuildTest") { + if (!getRequestHeader("rebuild")->toString().empty()) { + logInfo("Setting rebuild flag"); + setFilterState("wasm_rebuild", "true"); + } } else if (test == "DisableClearRouteCache") { setFilterState("clear_route_cache", "off"); logDebug(std::string("onRequestHeaders ") + std::to_string(id()) + std::string(" ") + test); diff --git a/test/extensions/filters/http/wasm/wasm_filter_test.cc b/test/extensions/filters/http/wasm/wasm_filter_test.cc index f05b1716a8801..845304f9ecd80 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_test.cc @@ -1949,6 +1949,71 @@ TEST_P(WasmHttpFilterTest, RecoverFromCrash) { filter().onDestroy(); } + +TEST_P(WasmHttpFilterTest, ProactiveRebuild) { + auto runtime = std::get<0>(GetParam()); + if (runtime == "null") { + return; + } + if (std::get<1>(GetParam()) != "cpp") { + return; + } + setupTest("", "RebuildTest"); + setupFilter(); + EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(request_stream_info_)); + auto& rebuild_total = scope_->counterFromString("wasm.envoy.wasm.runtime." + runtime + + ".plugin.plugin_name.rebuild_total"); + auto& recover_total = scope_->counterFromString("wasm.envoy.wasm.runtime." + runtime + + ".plugin.plugin_name.recover_total"); + Http::MockStreamDecoderFilterCallbacks decoder_callbacks; + filter().setDecoderFilterCallbacks(decoder_callbacks); + EXPECT_EQ(0U, rebuild_total.value()); + EXPECT_EQ(0U, recover_total.value()); + + // First request: normal processing + Http::TestRequestHeaderMapImpl request_headers{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + EXPECT_EQ(0U, rebuild_total.value()); + EXPECT_EQ(0U, recover_total.value()); + + // Second request: set rebuild state by sending rebuild header + request_headers = Http::TestRequestHeaderMapImpl{{"rebuild", "true"}}; + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("Setting rebuild flag"))); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + EXPECT_EQ(0U, rebuild_total.value()); // No rebuild yet, just set the flag + EXPECT_EQ(0U, recover_total.value()); + + // Now trigger the actual rebuild using doRebuild() + doRebuild(); + EXPECT_EQ(1U, rebuild_total.value()); + EXPECT_EQ(0U, recover_total.value()); + + // Verify new instance is working + request_headers = {}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + EXPECT_EQ(1U, rebuild_total.value()); + EXPECT_EQ(0U, recover_total.value()); + + // Set rebuild state again + request_headers = Http::TestRequestHeaderMapImpl{{"rebuild", "true"}}; + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("Setting rebuild flag"))); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + EXPECT_EQ(1U, rebuild_total.value()); // Still 1, just set the flag again + EXPECT_EQ(0U, recover_total.value()); + + // Trigger second rebuild using doRebuild() + doRebuild(); + EXPECT_EQ(2U, rebuild_total.value()); + EXPECT_EQ(0U, recover_total.value()); + + // Verify new instance is still working after second rebuild + request_headers = {}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + EXPECT_EQ(2U, rebuild_total.value()); + EXPECT_EQ(0U, recover_total.value()); + + filter().onDestroy(); +} #endif // Test metadata access including CEL expressions. diff --git a/test/test_common/wasm_base.h b/test/test_common/wasm_base.h index 1cf6d2d1b9482..7fb66a3dc143b 100644 --- a/test/test_common/wasm_base.h +++ b/test/test_common/wasm_base.h @@ -150,13 +150,23 @@ template class WasmHttpFilterTestBase : public W #if defined(HIGRESS) template void doRecover() { std::shared_ptr new_handle; - if (WasmTestBase::plugin_handle_->doRecover(new_handle)) { + if (WasmTestBase::plugin_handle_->rebuild(new_handle)) { WasmTestBase::plugin_handle_ = std::static_pointer_cast(new_handle); WasmTestBase::wasm_ = WasmTestBase::plugin_handle_->wasmHandle(); WasmTestBase::wasm_->wasm()->lifecycleStats().recover_total_.inc(); setupFilterBase(); } } + + template void doRebuild() { + std::shared_ptr new_handle; + if (WasmTestBase::plugin_handle_->rebuild(new_handle)) { + WasmTestBase::plugin_handle_ = std::static_pointer_cast(new_handle); + WasmTestBase::wasm_ = WasmTestBase::plugin_handle_->wasmHandle(); + WasmTestBase::wasm_->wasm()->lifecycleStats().rebuild_total_.inc(); + setupFilterBase(); + } + } #endif std::unique_ptr context_; From 0ac0bd5d8466f80750aed938db9c5ce10b2434e7 Mon Sep 17 00:00:00 2001 From: zty98751 Date: Tue, 11 Nov 2025 13:56:34 +0800 Subject: [PATCH 4/8] optimize: When optimizing AI fallback, for the original request, set the initial status code and target service information. Change-Id: I3e21b8a555f15adb85285006bfca643202ed67f7 --- source/common/http/conn_manager_impl.cc | 7 +++++++ .../redirect_policy/redirect_policy.cc | 11 +++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index a748843a14eaf..d08dd83510457 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -2228,6 +2228,8 @@ void ConnectionManagerImpl::ActiveStream::recreateStream( // Prevent the stream from being used through the commonContinue process of // ActiveStreamDecoderFilter or ActiveStreamEncoderFilter. filter_manager_.interruptContinue(); + const auto& original_remote_address = + filter_manager_.streamInfo().downstreamAddressProvider().remoteAddress(); #else const auto& buffered_request_data = filter_manager_.bufferedRequestData(); const bool proxy_body = buffered_request_data != nullptr && buffered_request_data->length() > 0; @@ -2246,6 +2248,11 @@ void ConnectionManagerImpl::ActiveStream::recreateStream( RequestDecoder& new_stream = connection_manager_.newStream(*response_encoder, true); +#if defined(HIGRESS) + auto& active_stream = static_cast(new_stream); + active_stream.filter_manager_.setDownstreamRemoteAddress(original_remote_address); +#endif + // Set the new RequestDecoder on the ResponseEncoder. Even though all of the decoder callbacks // have already been called at this point, the encoder still needs the new decoder for deferred // logging in some cases. diff --git a/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc b/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc index b1a71a7692edf..d1066111a2d25 100644 --- a/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc +++ b/source/extensions/http/custom_response/redirect_policy/redirect_policy.cc @@ -280,6 +280,7 @@ ::Envoy::Http::FilterHeadersStatus RedirectPolicy::encodeHeaders( downstream_headers->setPath(path_and_query); #if defined(HIGRESS) } + auto original_upstream_cluster = encoder_callbacks->streamInfo().upstreamClusterInfo(); #endif if (decoder_callbacks->downstreamCallbacks()) { decoder_callbacks->downstreamCallbacks()->clearRouteCache(); @@ -307,9 +308,15 @@ ::Envoy::Http::FilterHeadersStatus RedirectPolicy::encodeHeaders( // Cache the original response code. absl::optional<::Envoy::Http::Code> original_response_code; #if defined(HIGRESS) + if (original_upstream_cluster.has_value()) { + encoder_callbacks->streamInfo().setUpstreamClusterInfo(*original_upstream_cluster); + } + absl::optional current_code = + ::Envoy::Http::Utility::getResponseStatusOrNullopt(headers); + if (current_code.has_value()) { + encoder_callbacks->streamInfo().setResponseCode(static_cast(*current_code)); + } if (keep_original_response_code_) { - absl::optional current_code = - ::Envoy::Http::Utility::getResponseStatusOrNullopt(headers); if (current_code.has_value()) { original_response_code = static_cast<::Envoy::Http::Code>(*current_code); } From 658c9bf62568bd097e758aae63c18304a90032cb Mon Sep 17 00:00:00 2001 From: zty98751 Date: Tue, 11 Nov 2025 14:20:02 +0800 Subject: [PATCH 5/8] fix: support xDS last update success stats Change-Id: I18431153fdc4d7b5b40ac1d7e10fe73240e2fa64 --- envoy/config/subscription.h | 1 + .../grpc/grpc_subscription_impl.cc | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/envoy/config/subscription.h b/envoy/config/subscription.h index 0c4d61a924c8e..f29fff44ad88a 100644 --- a/envoy/config/subscription.h +++ b/envoy/config/subscription.h @@ -243,6 +243,7 @@ using SubscriptionPtr = std::unique_ptr; COUNTER(update_failure) \ COUNTER(update_rejected) \ COUNTER(update_success) \ + GAUGE(last_update_success, NeverImport) \ GAUGE(update_time, NeverImport) \ GAUGE(version, NeverImport) \ HISTOGRAM(update_duration, Milliseconds) \ diff --git a/source/extensions/config_subscription/grpc/grpc_subscription_impl.cc b/source/extensions/config_subscription/grpc/grpc_subscription_impl.cc index d32adfa13160e..fe5c3b5bf2657 100644 --- a/source/extensions/config_subscription/grpc/grpc_subscription_impl.cc +++ b/source/extensions/config_subscription/grpc/grpc_subscription_impl.cc @@ -76,6 +76,9 @@ void GrpcSubscriptionImpl::onConfigUpdate(const std::vector( dispatcher_.timeSource().monotonicTime() - start); stats_.update_success_.inc(); +#ifdef ALIMESH + stats_.last_update_success_.set(true); +#endif stats_.update_attempt_.inc(); stats_.update_time_.set(DateUtil::nowToMilliseconds(dispatcher_.timeSource())); stats_.version_.set(HashUtil::xxHash64(version_info)); @@ -101,6 +104,9 @@ void GrpcSubscriptionImpl::onConfigUpdate( std::chrono::milliseconds update_duration = std::chrono::duration_cast( dispatcher_.timeSource().monotonicTime() - start); stats_.update_success_.inc(); +#ifdef ALIMESH + stats_.last_update_success_.set(true); +#endif stats_.update_time_.set(DateUtil::nowToMilliseconds(dispatcher_.timeSource())); stats_.version_.set(HashUtil::xxHash64(system_version_info)); stats_.version_text_.set(system_version_info); @@ -112,10 +118,16 @@ void GrpcSubscriptionImpl::onConfigUpdateFailed(ConfigUpdateFailureReason reason switch (reason) { case Envoy::Config::ConfigUpdateFailureReason::ConnectionFailure: stats_.update_failure_.inc(); +#ifdef ALIMESH + stats_.last_update_success_.set(false); +#endif ENVOY_LOG(debug, "gRPC update for {} failed", type_url_); break; case Envoy::Config::ConfigUpdateFailureReason::FetchTimedout: stats_.init_fetch_timeout_.inc(); +#ifdef ALIMESH + stats_.last_update_success_.set(false); +#endif disableInitFetchTimeoutTimer(); ENVOY_LOG(warn, "gRPC config: initial fetch timed out for {}", type_url_); callbacks_.onConfigUpdateFailed(reason, e); @@ -125,6 +137,9 @@ void GrpcSubscriptionImpl::onConfigUpdateFailed(ConfigUpdateFailureReason reason ASSERT(e != nullptr); disableInitFetchTimeoutTimer(); stats_.update_rejected_.inc(); +#ifdef ALIMESH + stats_.last_update_success_.set(false); +#endif ENVOY_LOG(warn, "gRPC config for {} rejected: {}", type_url_, e->what()); callbacks_.onConfigUpdateFailed(reason, e); break; From 900f21680204796dd225f192b050a1fc544b4eae Mon Sep 17 00:00:00 2001 From: zty98751 Date: Tue, 11 Nov 2025 15:02:30 +0800 Subject: [PATCH 6/8] optimize rebuild logic Change-Id: I247d12cc12c31f9e8d9e027d0f04af2fc37bf785 --- source/extensions/common/wasm/context.cc | 2 +- source/extensions/common/wasm/wasm.cc | 4 ++-- source/extensions/filters/http/wasm/wasm_filter.h | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index 7ca19f512da7c..4a0eced7b1526 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -67,7 +67,7 @@ constexpr std::string_view ClearRouteCacheKey = "clear_route_cache"; constexpr std::string_view DisableClearRouteCache = "off"; constexpr std::string_view SetDecoderBufferLimit = "set_decoder_buffer_limit"; constexpr std::string_view SetEncoderBufferLimit = "set_encoder_buffer_limit"; -constexpr std::string_view WasmRebuildKey = "wasm_rebuild"; +constexpr std::string_view WasmRebuildKey = "wasm_need_rebuild"; bool stringViewToUint32(std::string_view str, uint32_t& out_value) { try { diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc index 24e4a38efa06c..524e111617757 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -85,7 +85,7 @@ WasmEvent failStateToWasmEvent(FailState state) { PANIC("corrupt enum"); } -const int MIN_RECOVER_INTERVAL_SECONDS = 5; +const int MIN_RECOVER_INTERVAL_SECONDS = 1; #endif } // namespace @@ -204,7 +204,7 @@ bool PluginHandleSharedPtrThreadLocal::rebuild(bool is_fail_recovery) { auto& dispatcher = handle_->wasmHandle()->wasm()->dispatcher(); auto now = dispatcher.timeSource().monotonicTime() + cache_time_offset_for_testing; if (now - last_recover_time_ < std::chrono::seconds(MIN_RECOVER_INTERVAL_SECONDS)) { - ENVOY_LOG(debug, "rebuild interval has not been reached"); + ENVOY_LOG(info, "rebuild interval has not been reached"); return false; } // Even if rebuild fails, it will be retried after the interval diff --git a/source/extensions/filters/http/wasm/wasm_filter.h b/source/extensions/filters/http/wasm/wasm_filter.h index d083903490a80..3552bc7d3300f 100644 --- a/source/extensions/filters/http/wasm/wasm_filter.h +++ b/source/extensions/filters/http/wasm/wasm_filter.h @@ -65,8 +65,7 @@ class FilterConfig : Logger::Loggable { // Reset rebuild state wasm->setShouldRebuild(false); } else { - ENVOY_LOG(info, "wasm vm rebuild failed"); - failed = true; + ENVOY_LOG(info, "wasm vm rebuild failed, still using the stale one"); } } if (failed) { From d65cb3e01215c8c3ed0771ec76cf052e757dacf3 Mon Sep 17 00:00:00 2001 From: zty98751 Date: Tue, 11 Nov 2025 15:18:10 +0800 Subject: [PATCH 7/8] fix: Preserve end_stream state in filter chain during inject data operations Change-Id: Ie0930f4f1de7501447c3a420b476379b8787bb1e --- source/common/http/filter_manager.cc | 20 ++++++++++++++++++++ source/extensions/common/wasm/context.cc | 18 ++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/source/common/http/filter_manager.cc b/source/common/http/filter_manager.cc index 155f31f107a9b..85fe480b72872 100644 --- a/source/common/http/filter_manager.cc +++ b/source/common/http/filter_manager.cc @@ -420,6 +420,16 @@ void ActiveStreamDecoderFilter::injectDecodedDataToFilterChain(Buffer::Instance& headers_continued_ = true; doHeaders(false); } +#if defined(HIGRESS) + // Fix: When injecting data with end_stream=true, we must set remote_decode_complete_ flag + // to ensure subsequent filter chain iterations (e.g., via commonContinue) correctly recognize + // the stream is complete. Without this, if a downstream filter returns StopIteration and later + // resumes via continueDecoding()->commonContinue()->doData(), the complete() check would + // incorrectly return false, causing end_stream state inconsistency across the filter chain. + if (end_stream) { + parent_.state_.remote_decode_complete_ = true; + } +#endif parent_.decodeData(this, data, end_stream, FilterManager::FilterIterationStartState::CanStartFromCurrent); } @@ -1679,6 +1689,16 @@ void ActiveStreamEncoderFilter::injectEncodedDataToFilterChain(Buffer::Instance& headers_continued_ = true; doHeaders(false); } +#if defined(HIGRESS) + // Fix: When injecting data with end_stream=true, we must set local_complete_ flag to ensure + // subsequent filter chain iterations (e.g., via commonContinue) correctly recognize the stream + // is complete. Without this, if a downstream filter returns StopIteration and later resumes + // via continueEncoding()->commonContinue()->doData(), the complete() check would incorrectly + // return false, causing end_stream state inconsistency across the filter chain. + if (end_stream) { + parent_.state_.local_complete_ = true; + } +#endif parent_.encodeData(this, data, end_stream, FilterManager::FilterIterationStartState::CanStartFromCurrent); } diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index 4a0eced7b1526..fb3b9c522bc7a 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -721,12 +721,30 @@ Http::HeaderMap* Context::getMap(WasmHeaderMapType type) { } const Http::HeaderMap* Context::getConstMap(WasmHeaderMapType type) { +#if defined(HIGRESS) + const StreamInfo::StreamInfo* request_stream_info = getConstRequestStreamInfo(); +#endif switch (type) { case WasmHeaderMapType::RequestHeaders: if (access_log_phase_) { return access_log_request_headers_; } +#if defined(HIGRESS) + // Fallback mechanism for retrieving request headers: + // 1. First try the cached request_headers_ pointer (most common case) + // 2. If null, attempt to retrieve from StreamInfo (e.g., after internal redirects or + // when headers are stored in stream info but not directly cached) + // 3. Return nullptr if both sources are unavailable + if (request_headers_ != nullptr) { + return request_headers_; + } + if (request_stream_info == nullptr) { + return nullptr; + } + return request_stream_info->getRequestHeaders(); +#else return request_headers_; +#endif case WasmHeaderMapType::RequestTrailers: if (access_log_phase_) { return nullptr; From 45d3e7fdd44337329d50b9b8b22b9b4f26a22702 Mon Sep 17 00:00:00 2001 From: zty98751 Date: Tue, 11 Nov 2025 18:41:12 +0800 Subject: [PATCH 8/8] feat: add PLUGIN_VM_MEMORY property to query Wasm VM memory usage Change-Id: I787f124bb7b1b35d7b9fecfa8fe6261fd52f413f --- source/extensions/common/wasm/context.cc | 14 ++++++++++++++ .../filters/http/wasm/test_data/test_cpp.cc | 15 +++++++++++++++ .../filters/http/wasm/wasm_filter_test.cc | 19 +++++++++++++++++-- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index fb3b9c522bc7a..39c2831f4e61a 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -456,10 +456,17 @@ WasmResult serializeValue(Filters::Common::Expr::CelValue value, std::string* re return WasmResult::SerializationFailure; } +#if defined(HIGRESS) +#define PROPERTY_TOKENS(_f) \ + _f(NODE) _f(LISTENER_DIRECTION) _f(LISTENER_METADATA) _f(CLUSTER_NAME) _f(CLUSTER_METADATA) \ + _f(ROUTE_NAME) _f(ROUTE_METADATA) _f(PLUGIN_NAME) _f(UPSTREAM_HOST_METADATA) \ + _f(PLUGIN_ROOT_ID) _f(PLUGIN_VM_ID) _f(PLUGIN_VM_MEMORY) _f(CONNECTION_ID) +#else #define PROPERTY_TOKENS(_f) \ _f(NODE) _f(LISTENER_DIRECTION) _f(LISTENER_METADATA) _f(CLUSTER_NAME) _f(CLUSTER_METADATA) \ _f(ROUTE_NAME) _f(ROUTE_METADATA) _f(PLUGIN_NAME) _f(UPSTREAM_HOST_METADATA) \ _f(PLUGIN_ROOT_ID) _f(PLUGIN_VM_ID) _f(CONNECTION_ID) +#endif static inline std::string downCase(std::string s) { std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); }); @@ -614,6 +621,13 @@ Context::findValue(absl::string_view name, Protobuf::Arena* arena, bool last) co return CelValue::CreateStringView(toAbslStringView(root_id())); case PropertyToken::PLUGIN_VM_ID: return CelValue::CreateStringView(toAbslStringView(wasm()->vm_id())); +#if defined(HIGRESS) + case PropertyToken::PLUGIN_VM_MEMORY: + if (wasm() && wasm()->wasm_vm()) { + return CelValue::CreateUint64(wasm()->wasm_vm()->getMemorySize()); + } + break; +#endif } return {}; } diff --git a/test/extensions/filters/http/wasm/test_data/test_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_cpp.cc index 27a1e2b3a9fe3..16b2dd580ce24 100644 --- a/test/extensions/filters/http/wasm/test_data/test_cpp.cc +++ b/test/extensions/filters/http/wasm/test_data/test_cpp.cc @@ -286,6 +286,21 @@ FilterHeadersStatus TestContext::onRequestHeaders(uint32_t, bool) { logError("get route name failed"); } return FilterHeadersStatus::Continue; + } else if (test == "GetVMMemorySize") { + std::string value; + if (getValue({"plugin_vm_memory"}, &value)) { + // The value is stored as binary uint64_t, convert to string for logging + if (value.size() == sizeof(uint64_t)) { + uint64_t memory_size; + memcpy(&memory_size, value.data(), sizeof(uint64_t)); + logInfo("vm memory size is " + std::to_string(memory_size)); + } else { + logError("invalid memory size format"); + } + } else { + logError("get vm memory size failed"); + } + return FilterHeadersStatus::Continue; } else if (test == "CrashRecover") { if (!getRequestHeader("crash")->toString().empty()) { abort(); diff --git a/test/extensions/filters/http/wasm/wasm_filter_test.cc b/test/extensions/filters/http/wasm/wasm_filter_test.cc index 845304f9ecd80..a985af3113d36 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_test.cc @@ -1850,6 +1850,21 @@ TEST_P(WasmHttpFilterTest, GetRouteName) { EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); filter().onDestroy(); } +TEST_P(WasmHttpFilterTest, GetVMMemorySize) { + auto runtime = std::get<0>(GetParam()); + if (runtime == "null") { + return; + } + if (std::get<1>(GetParam()) != "cpp") { + return; + } + setupTest("", "GetVMMemorySize"); + setupFilter(); + EXPECT_CALL(filter(), log_(spdlog::level::info, testing::StartsWith("vm memory size is "))); + Http::TestRequestHeaderMapImpl request_headers{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); + filter().onDestroy(); +} TEST_P(WasmHttpFilterTest, RecoverFromCrash) { auto runtime = std::get<0>(GetParam()); if (runtime == "null") { @@ -1980,7 +1995,7 @@ TEST_P(WasmHttpFilterTest, ProactiveRebuild) { request_headers = Http::TestRequestHeaderMapImpl{{"rebuild", "true"}}; EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("Setting rebuild flag"))); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - EXPECT_EQ(0U, rebuild_total.value()); // No rebuild yet, just set the flag + EXPECT_EQ(0U, rebuild_total.value()); // No rebuild yet, just set the flag EXPECT_EQ(0U, recover_total.value()); // Now trigger the actual rebuild using doRebuild() @@ -1998,7 +2013,7 @@ TEST_P(WasmHttpFilterTest, ProactiveRebuild) { request_headers = Http::TestRequestHeaderMapImpl{{"rebuild", "true"}}; EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("Setting rebuild flag"))); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); - EXPECT_EQ(1U, rebuild_total.value()); // Still 1, just set the flag again + EXPECT_EQ(1U, rebuild_total.value()); // Still 1, just set the flag again EXPECT_EQ(0U, recover_total.value()); // Trigger second rebuild using doRebuild()