22
33#include " envoy/config/core/v3/base.pb.h"
44#include " envoy/config/endpoint/v3/endpoint_components.pb.h"
5+ #include " envoy/network/connection.h"
56#include " envoy/network/drain_decision.h"
67#include " envoy/upstream/locality.h"
78
1011#include " source/common/network/utility.h"
1112#include " source/common/protobuf/protobuf.h"
1213#include " source/common/protobuf/utility.h"
13- #include " source/common/runtime/runtime_features.h"
1414#include " source/common/upstream/upstream_impl.h"
1515#include " source/extensions/dynamic_modules/dynamic_modules.h"
1616
17- #include " absl/strings/str_cat.h"
18-
1917namespace Envoy {
2018namespace Extensions {
2119namespace Clusters {
@@ -57,10 +55,9 @@ struct DynamicModuleThreadAwareLoadBalancer : public Upstream::ThreadAwareLoadBa
5755
5856absl::StatusOr<std::shared_ptr<DynamicModuleClusterConfig>> DynamicModuleClusterConfig::create (
5957 const std::string& cluster_name, const std::string& cluster_config,
60- const std::string& metrics_namespace,
6158 Envoy::Extensions::DynamicModules::DynamicModulePtr module , Stats::Scope& stats_scope) {
62- auto config = std::shared_ptr<DynamicModuleClusterConfig>(new DynamicModuleClusterConfig (
63- cluster_name, cluster_config, metrics_namespace , std::move (module ), stats_scope));
59+ auto config = std::shared_ptr<DynamicModuleClusterConfig>(
60+ new DynamicModuleClusterConfig ( cluster_name, cluster_config, std::move (module ), stats_scope));
6461
6562 // Resolve all required function pointers from the dynamic module.
6663#define RESOLVE_SYMBOL (name, type, member ) \
@@ -142,9 +139,8 @@ absl::StatusOr<std::shared_ptr<DynamicModuleClusterConfig>> DynamicModuleCluster
142139
143140DynamicModuleClusterConfig::DynamicModuleClusterConfig (
144141 const std::string& cluster_name, const std::string& cluster_config,
145- const std::string& metrics_namespace,
146142 Envoy::Extensions::DynamicModules::DynamicModulePtr module , Stats::Scope& stats_scope)
147- : stats_scope_(stats_scope.createScope(absl::StrCat(metrics_namespace, " . " ) )),
143+ : stats_scope_(stats_scope.createScope(" dynamicmodulescustom. " )),
148144 stat_name_pool_ (stats_scope_->symbolTable ()), cluster_name_(cluster_name),
149145 cluster_config_(cluster_config), dynamic_module_(std::move(module )) {}
150146
@@ -161,6 +157,12 @@ DynamicModuleClusterConfig::~DynamicModuleClusterConfig() {
161157DynamicModuleClusterHandle::~DynamicModuleClusterHandle () {
162158 std::shared_ptr<DynamicModuleCluster> cluster = std::move (cluster_);
163159 cluster_.reset ();
160+ // Release lifecycle handles eagerly while the lifecycle notifier is still valid. When the
161+ // dispatcher destructor clears pending callbacks, the cluster destructor would otherwise try to
162+ // unregister from already-destroyed lifecycle notifier lists.
163+ cluster->server_initialized_handle_ .reset ();
164+ cluster->shutdown_handle_ .reset ();
165+ cluster->drain_handle_ .reset ();
164166 Event::Dispatcher& dispatcher = cluster->dispatcher_ ;
165167 dispatcher.post ([cluster = std::move (cluster)]() mutable { cluster.reset (); });
166168}
@@ -620,6 +622,14 @@ DynamicModuleLoadBalancer::chooseHost(Upstream::LoadBalancerContext* context) {
620622 return {nullptr };
621623 }
622624
625+ // Pre-capture the worker dispatcher and prepare the cancellation flag before calling into the
626+ // module. The module's choose_host may spawn a background thread that calls
627+ // async_host_selection_complete, which reads these fields. Setting them beforehand establishes
628+ // a happens-before relationship via the thread::spawn synchronization in the module.
629+ const auto * connection = context != nullptr ? context->downstreamConnection () : nullptr ;
630+ active_async_dispatcher_ = connection != nullptr ? &connection->dispatcher () : nullptr ;
631+ active_async_cancelled_ = std::make_shared<std::atomic<bool >>(false );
632+
623633 envoy_dynamic_module_type_cluster_host_envoy_ptr host_ptr = nullptr ;
624634 envoy_dynamic_module_type_cluster_lb_async_handle_module_ptr async_handle = nullptr ;
625635 handle_->cluster_ ->config ()->on_cluster_lb_choose_host_ (in_module_lb_, context, &host_ptr,
@@ -629,10 +639,14 @@ DynamicModuleLoadBalancer::chooseHost(Upstream::LoadBalancerContext* context) {
629639 // Async pending: the module will call the completion callback later.
630640 auto cancelable = std::make_unique<DynamicModuleAsyncHostSelectionHandle>(
631641 async_handle, in_module_lb_,
632- handle_->cluster_ ->config ()->on_cluster_lb_cancel_host_selection_ );
642+ handle_->cluster_ ->config ()->on_cluster_lb_cancel_host_selection_ , active_async_cancelled_ );
633643 return Upstream::HostSelectionResponse{nullptr , std::move (cancelable)};
634644 }
635645
646+ // Synchronous result or no host. Clear the async state.
647+ active_async_dispatcher_ = nullptr ;
648+ active_async_cancelled_ = nullptr ;
649+
636650 if (host_ptr == nullptr ) {
637651 return {nullptr };
638652 }
@@ -642,12 +656,19 @@ DynamicModuleLoadBalancer::chooseHost(Upstream::LoadBalancerContext* context) {
642656 return {host};
643657}
644658
645- void DynamicModuleAsyncHostSelectionHandle::cancel () {
646- if (cancel_fn_ != nullptr ) {
659+ DynamicModuleAsyncHostSelectionHandle::~DynamicModuleAsyncHostSelectionHandle () {
660+ // Free the module-side async handle. The cancel function takes ownership of the handle and
661+ // drops it, so this works for both cancellation and normal completion paths.
662+ if (async_handle_ != nullptr && cancel_fn_ != nullptr ) {
647663 cancel_fn_ (in_module_lb_, async_handle_);
664+ async_handle_ = nullptr ;
648665 }
649666}
650667
668+ void DynamicModuleAsyncHostSelectionHandle::cancel () {
669+ cancelled_->store (true , std::memory_order_release);
670+ }
671+
651672const Upstream::PrioritySet& DynamicModuleLoadBalancer::prioritySet () const {
652673 return handle_->cluster_ ->prioritySet ();
653674}
@@ -724,28 +745,14 @@ DynamicModuleClusterFactory::createClusterWithConfig(
724745 module_or_error.status ().message ()));
725746 }
726747
727- // Use configured metrics namespace or fall back to the default.
728- const std::string metrics_namespace = module_config.metrics_namespace ().empty ()
729- ? std::string (DefaultMetricsNamespace)
730- : module_config.metrics_namespace ();
731-
732748 // Create the cluster configuration.
733749 auto config_or_error = DynamicModuleClusterConfig::create (
734- proto_config.cluster_name (), cluster_config_bytes, metrics_namespace ,
735- std::move (module_or_error. value ()), context.serverFactoryContext ().serverScope ());
750+ proto_config.cluster_name (), cluster_config_bytes, std::move (module_or_error. value ()) ,
751+ context.serverFactoryContext ().serverScope ());
736752 if (!config_or_error.ok ()) {
737753 return config_or_error.status ();
738754 }
739755
740- // When the runtime guard is enabled, register the metrics namespace as a custom stat namespace.
741- // This causes the namespace prefix to be stripped from prometheus output and no envoy_ prefix
742- // is added. This is the legacy behavior for backward compatibility.
743- if (Runtime::runtimeFeatureEnabled (
744- " envoy.reloadable_features.dynamic_modules_strip_custom_stat_prefix" )) {
745- context.serverFactoryContext ().api ().customStatNamespaces ().registerStatNamespace (
746- metrics_namespace);
747- }
748-
749756 // Create the cluster.
750757 absl::Status creation_status = absl::OkStatus ();
751758 auto new_cluster = std::shared_ptr<DynamicModuleCluster>(new DynamicModuleCluster (
0 commit comments