-
Notifications
You must be signed in to change notification settings - Fork 5.3k
runtime: simplifying absl-flags based implementation #20169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
df1754e
5b56ea7
c3deda3
bf65934
44d38d6
1ed7bb1
88b6212
a15fcae
c17ab29
b1ba0ad
d3feb84
d34f110
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,34 @@ | ||
| #include "source/common/runtime/runtime_features.h" | ||
|
|
||
| #include "absl/flags/commandlineflag.h" | ||
| #include "absl/flags/flag.h" | ||
| #include "absl/strings/match.h" | ||
| #include "absl/strings/str_replace.h" | ||
|
|
||
| /* To add a runtime guard to Envoy, add 2 lines to this file. | ||
| * | ||
| * RUNTIME_GUARD(envoy_reloadable_features_flag_name) | ||
| * | ||
| * to the sorted macro block below and | ||
| * | ||
| * &FLAGS_envoy_reloadable_features_flag_name | ||
| * | ||
| * to the runtime features constexpr. | ||
| * | ||
| * The runtime guard to use in source and release notes will then be of the form | ||
| * "envoy.reloadable_features.flag_name" due to the prior naming scheme and swapPrefix. | ||
| **/ | ||
|
|
||
| #define RUNTIME_GUARD(name) ABSL_FLAG(bool, name, true, ""); // NOLINT | ||
| #define FALSE_RUNTIME_GUARD(name) ABSL_FLAG(bool, name, false, ""); // NOLINT | ||
|
|
||
| // Add additional features here to enable the new code paths by default. | ||
| // | ||
| // Per documentation in CONTRIBUTING.md is expected that new high risk code paths be guarded | ||
| // by runtime feature guards. If you add a guard of the form | ||
| // RUNTIME_GUARD(envoy_reloadable_features_my_feature_name) | ||
| // here you can guard code checking against "envoy.reloadable_features.my_feature_name". | ||
| // Please note the swap of envoy_reloadable_features_ to envoy.reloadable_features.! | ||
| // | ||
| // if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.my_feature_name")) { | ||
| // [new code path] | ||
| // else { | ||
| // [old_code_path] | ||
| // } | ||
| // | ||
| // Runtime features are true by default, so the new code path is exercised. | ||
| // To make a runtime feature false by default, use FALSE_RUNTIME_GUARD, and add | ||
| // a TODO to change it to true. | ||
| // | ||
| // If issues are found that require a runtime feature to be disabled, it should be reported | ||
| // ASAP by filing a bug on github. Overriding non-buggy code is strongly discouraged to avoid the | ||
| // problem of the bugs being found after the old code path has been removed. | ||
alyssawilk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| RUNTIME_GUARD(envoy_reloadable_features_allow_upstream_inline_write); | ||
| RUNTIME_GUARD(envoy_reloadable_features_append_or_truncate); | ||
| RUNTIME_GUARD(envoy_reloadable_features_append_to_accept_content_encoding_only_once); | ||
|
|
@@ -81,18 +89,61 @@ ABSL_FLAG(uint64_t, re2_max_program_size_warn_level, // NOLINT | |
|
|
||
| namespace Envoy { | ||
| namespace Runtime { | ||
| namespace { | ||
|
|
||
| std::string swapPrefix(std::string name) { | ||
| return absl::StrReplaceAll(name, {{"envoy_", "envoy."}, {"features_", "features."}}); | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| // This is a singleton class to map Envoy style flag names to absl flags | ||
| class RuntimeFeatures { | ||
RyanTheOptimist marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| public: | ||
| RuntimeFeatures(); | ||
|
|
||
| // Get the command line flag corresponding to the Envoy style feature name, or | ||
| // nullptr if it is not a registered flag. | ||
| absl::CommandLineFlag* getFlag(absl::string_view feature) const { | ||
RyanTheOptimist marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| auto it = all_features_.find(feature); | ||
| if (it == all_features_.end()) { | ||
| return nullptr; | ||
| } | ||
| return it->second; | ||
| } | ||
|
|
||
| private: | ||
| absl::flat_hash_map<std::string, absl::CommandLineFlag*> all_features_; | ||
| }; | ||
|
|
||
| using RuntimeFeaturesDefaults = ConstSingleton<RuntimeFeatures>; | ||
|
|
||
| RuntimeFeatures::RuntimeFeatures() { | ||
| absl::flat_hash_map<absl::string_view, absl::CommandLineFlag*> flags = absl::GetAllFlags(); | ||
| for (auto& it : flags) { | ||
| absl::string_view name = it.second->Name(); | ||
| if ((!absl::StartsWith(name, "envoy_reloadable_features_") && | ||
| !absl::StartsWith(name, "envoy_restart_features_")) || | ||
|
Comment on lines
+125
to
+126
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just so I understand since I'm not that familiar with this code: why this filtering? How are flags accessed if not via this?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this largely helps us avoid pulling in erroneous flags in google 3, where we may end up with non-envoy command line flags. |
||
| !it.second->TryGet<bool>().has_value()) { | ||
| continue; | ||
| } | ||
| std::string envoy_name = swapPrefix(std::string(name)); | ||
| all_features_.emplace(envoy_name, it.second); | ||
| } | ||
| } | ||
|
|
||
| bool isRuntimeFeature(absl::string_view feature) { | ||
| return RuntimeFeaturesDefaults::get().getFlag(feature) != nullptr; | ||
| } | ||
|
|
||
| bool runtimeFeatureEnabled(absl::string_view feature) { | ||
| auto* flag = RuntimeFeaturesDefaults::get().getFlag(feature); | ||
| absl::CommandLineFlag* flag = RuntimeFeaturesDefaults::get().getFlag(feature); | ||
| if (flag == nullptr) { | ||
| IS_ENVOY_BUG(absl::StrCat("Unable to find runtime feature ", feature)); | ||
| return false; | ||
| } | ||
| return absl::GetFlag(*flag); | ||
| // We validate in map creation that the flag is a boolean. | ||
| return flag->TryGet<bool>().value(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OOC: What is the difference between TryGet().value() and GetFlag()?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. GetFlag takes the absl flag and returns a commandlineflag. Here we don't have the absl flag we only have the string name, so tryget allows us to get a coommandlineflag from that.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AH! I see. That makes sense. I think I got confused because link 138 didn't change so I assumed that the type of flag didn't change. I think this is an example of how auto can actually make code a bit less readable, but it's a tradeoff, for sure. For bonus points consider changing auto to absl::CommandLineFlag on line 138. But totally at your discretion. |
||
| } | ||
|
|
||
| uint64_t getInteger(absl::string_view feature, uint64_t default_value) { | ||
|
|
@@ -113,81 +164,14 @@ uint64_t getInteger(absl::string_view feature, uint64_t default_value) { | |
| return default_value; | ||
| } | ||
|
|
||
| // Add additional features here to enable the new code paths by default. | ||
| // | ||
| // Per documentation in CONTRIBUTING.md is expected that new high risk code paths be guarded | ||
| // by runtime feature guards, i.e | ||
| // | ||
| // if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.my_feature_name")) { | ||
| // [new code path] | ||
| // else { | ||
| // [old_code_path] | ||
| // } | ||
| // | ||
| // Runtime features are false by default, so the old code path is exercised. | ||
| // To make a runtime feature true by default, add it to the array below. | ||
| // New features should be true-by-default for an Envoy release cycle before the | ||
| // old code path is removed. | ||
| // | ||
| // If issues are found that require a runtime feature to be disabled, it should be reported | ||
| // ASAP by filing a bug on github. Overriding non-buggy code is strongly discouraged to avoid the | ||
| // problem of the bugs being found after the old code path has been removed. | ||
| // clang-format off | ||
| constexpr absl::Flag<bool>* runtime_features[] = { | ||
| // Test flags | ||
| &FLAGS_envoy_reloadable_features_test_feature_false, | ||
| &FLAGS_envoy_reloadable_features_test_feature_true, | ||
| // Begin alphabetically sorted section_ | ||
| &FLAGS_envoy_reloadable_features_allow_multiple_dns_addresses, | ||
| &FLAGS_envoy_reloadable_features_allow_upstream_inline_write, | ||
| &FLAGS_envoy_reloadable_features_append_or_truncate, | ||
| &FLAGS_envoy_reloadable_features_append_to_accept_content_encoding_only_once, | ||
| &FLAGS_envoy_reloadable_features_conn_pool_delete_when_idle, | ||
| &FLAGS_envoy_reloadable_features_conn_pool_new_stream_with_early_data_and_http3, | ||
| &FLAGS_envoy_reloadable_features_correct_scheme_and_xfp, | ||
| &FLAGS_envoy_reloadable_features_correctly_validate_alpn, | ||
| &FLAGS_envoy_reloadable_features_defer_processing_backedup_streams, | ||
| &FLAGS_envoy_reloadable_features_deprecate_global_ints, | ||
| &FLAGS_envoy_reloadable_features_disable_tls_inspector_injection, | ||
| &FLAGS_envoy_reloadable_features_do_not_await_headers_on_upstream_timeout_to_emit_stats, | ||
| &FLAGS_envoy_reloadable_features_enable_grpc_async_client_cache, | ||
| &FLAGS_envoy_reloadable_features_fix_added_trailers, | ||
| &FLAGS_envoy_reloadable_features_handle_stream_reset_during_hcm_encoding, | ||
| &FLAGS_envoy_reloadable_features_http1_lazy_read_disable, | ||
| &FLAGS_envoy_reloadable_features_http2_allow_capacity_increase_by_settings, | ||
| &FLAGS_envoy_reloadable_features_http2_new_codec_wrapper, | ||
| &FLAGS_envoy_reloadable_features_http_ext_authz_do_not_skip_direct_response_and_redirect, | ||
| &FLAGS_envoy_reloadable_features_http_reject_path_with_fragment, | ||
| &FLAGS_envoy_reloadable_features_http_strip_fragment_from_path_unsafe_if_disabled, | ||
| &FLAGS_envoy_reloadable_features_internal_address, | ||
| &FLAGS_envoy_reloadable_features_listener_wildcard_match_ip_family, | ||
| &FLAGS_envoy_reloadable_features_new_tcp_connection_pool, | ||
| &FLAGS_envoy_reloadable_features_postpone_h3_client_connect_to_next_loop, | ||
| &FLAGS_envoy_reloadable_features_proxy_102_103, | ||
| &FLAGS_envoy_reloadable_features_sanitize_http_header_referer, | ||
| &FLAGS_envoy_reloadable_features_skip_delay_close, | ||
| &FLAGS_envoy_reloadable_features_skip_dispatching_frames_for_closed_connection, | ||
| &FLAGS_envoy_reloadable_features_strict_check_on_ipv4_compat, | ||
| &FLAGS_envoy_reloadable_features_support_locality_update_on_eds_cluster_endpoints, | ||
| &FLAGS_envoy_reloadable_features_udp_listener_updates_filter_chain_in_place, | ||
| &FLAGS_envoy_reloadable_features_unified_mux, | ||
| &FLAGS_envoy_reloadable_features_update_expected_rq_timeout_on_retry, | ||
| &FLAGS_envoy_reloadable_features_update_grpc_response_error_tag, | ||
| &FLAGS_envoy_reloadable_features_use_dns_ttl, | ||
| &FLAGS_envoy_reloadable_features_validate_connect, | ||
| &FLAGS_envoy_restart_features_explicit_wildcard_resource, | ||
| &FLAGS_envoy_restart_features_use_apple_api_for_dns_lookups, | ||
| &FLAGS_envoy_restart_features_no_runtime_singleton, | ||
| }; | ||
| // clang-format on | ||
|
|
||
| void maybeSetRuntimeGuard(absl::string_view name, bool value) { | ||
| auto* flag = RuntimeFeaturesDefaults::get().getFlag(name); | ||
| absl::CommandLineFlag* flag = RuntimeFeaturesDefaults::get().getFlag(name); | ||
| if (flag == nullptr) { | ||
| IS_ENVOY_BUG(absl::StrCat("Unable to find runtime feature ", name)); | ||
| return; | ||
| } | ||
| absl::SetFlag(flag, value); | ||
| std::string err; | ||
| flag->ParseFrom(value ? "true" : "false", &err); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting. The old code looked simpler, but I suspect it was deficient in some way. Can you elaborate?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the code is cleaner using absl::Flags, but while you can get from string to CommandLineFlag there's no public registry from string to absl::Flags - the reflection only goes from string to CommandLineFlag, so I had to switch from the "I know my type" absl::Flag to "am I a boolean or not?" CommandLineFlag variant to get rid of the array.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense. Same point about auto confusing me. Same bonus points for changing auto to absl::CommandLineFlag :) |
||
| } | ||
|
|
||
| void maybeSetDeprecatedInts(absl::string_view name, uint32_t value) { | ||
|
|
@@ -211,36 +195,5 @@ void maybeSetDeprecatedInts(absl::string_view name, uint32_t value) { | |
| } | ||
| } | ||
|
|
||
| std::string swapPrefix(std::string name) { | ||
| return absl::StrReplaceAll(name, {{"envoy_", "envoy."}, {"features_", "features."}}); | ||
| } | ||
|
|
||
| RuntimeFeatures::RuntimeFeatures() { | ||
| for (auto& feature : runtime_features) { | ||
| auto& reflection = absl::GetFlagReflectionHandle(*feature); | ||
| std::string envoy_name = swapPrefix(std::string(reflection.Name())); | ||
| all_features_.emplace(envoy_name, feature); | ||
| absl::optional<bool> value = reflection.TryGet<bool>(); | ||
| ASSERT(value.has_value()); | ||
| if (value.value()) { | ||
| enabled_features_.emplace(envoy_name, feature); | ||
| } else { | ||
| disabled_features_.emplace(envoy_name, feature); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void RuntimeFeatures::restoreDefaults() const { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this stuff no longer necessary?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, it's replaced by the absl flagsaver infrastructure |
||
| for (const auto& feature : enabled_features_) { | ||
| absl::SetFlag(feature.second, true); | ||
| } | ||
| for (const auto& feature : disabled_features_) { | ||
| absl::SetFlag(feature.second, false); | ||
| } | ||
| absl::SetFlag(&FLAGS_envoy_headermap_lazy_map_min_size, 3); | ||
| absl::SetFlag(&FLAGS_re2_max_program_size_error_level, 100); | ||
| absl::SetFlag(&FLAGS_re2_max_program_size_warn_level, std::numeric_limits<uint32_t>::max()); | ||
| } | ||
|
|
||
| } // namespace Runtime | ||
| } // namespace Envoy | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if people mess this up? Will it error out? If not, can we do something to avoid this mismatch?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it'll work just fine actually, it just won't have standard envoy guard naming.