@@ -74,11 +74,7 @@ RedisCluster::RedisCluster(
74
74
context.serverFactoryContext().mainThreadDispatcher(),
75
75
context.serverFactoryContext().clusterManager(),
76
76
context.serverFactoryContext().api().timeSource())),
77
- registration_handle_(refresh_manager_->registerCluster (
78
- cluster_name_, redirect_refresh_interval_, redirect_refresh_threshold_,
79
- failure_refresh_threshold_, host_degraded_refresh_threshold_, [&]() {
80
- redis_discovery_session_->resolve_timer_ ->enableTimer (std::chrono::milliseconds (0 ));
81
- })) {
77
+ registration_handle_(nullptr ) {
82
78
const auto & locality_lb_endpoints = load_assignment_.endpoints ();
83
79
for (const auto & locality_lb_endpoint : locality_lb_endpoints) {
84
80
for (const auto & lb_endpoint : locality_lb_endpoint.lb_endpoints ()) {
@@ -87,6 +83,32 @@ RedisCluster::RedisCluster(
87
83
*this , host.socket_address ().address (), host.socket_address ().port_value ()));
88
84
}
89
85
}
86
+
87
+ // Register the cluster callback using weak_ptr to avoid use-after-free
88
+ std::weak_ptr<RedisDiscoverySession> weak_session = redis_discovery_session_;
89
+ registration_handle_ = refresh_manager_->registerCluster (
90
+ cluster_name_, redirect_refresh_interval_, redirect_refresh_threshold_,
91
+ failure_refresh_threshold_, host_degraded_refresh_threshold_, [weak_session]() {
92
+ // Try to lock the weak pointer to ensure the session is still alive
93
+ auto session = weak_session.lock ();
94
+ if (session && session->resolve_timer_ ) {
95
+ session->resolve_timer_ ->enableTimer (std::chrono::milliseconds (0 ));
96
+ }
97
+ });
98
+ }
99
+
100
+ RedisCluster::~RedisCluster () {
101
+ // Set flag to prevent any callbacks from executing during destruction
102
+ is_destroying_.store (true );
103
+
104
+ // Reset redis_discovery_session_ before other members are destroyed
105
+ // to ensure any pending callbacks from refresh_manager_ don't access it.
106
+ // This matches the approach in PR #39625.
107
+ redis_discovery_session_.reset ();
108
+
109
+ // Also clear DNS discovery targets to prevent their callbacks from
110
+ // accessing the destroyed cluster.
111
+ dns_discovery_resolve_targets_.clear ();
90
112
}
91
113
92
114
void RedisCluster::startPreInit () {
@@ -198,7 +220,7 @@ RedisCluster::DnsDiscoveryResolveTarget::~DnsDiscoveryResolveTarget() {
198
220
active_query_->cancel (Network::ActiveDnsQuery::CancelReason::QueryAbandoned);
199
221
}
200
222
// Disable timer for mock tests.
201
- if (resolve_timer_) {
223
+ if (resolve_timer_ && resolve_timer_-> enabled () ) {
202
224
resolve_timer_->disableTimer ();
203
225
}
204
226
}
@@ -220,8 +242,13 @@ void RedisCluster::DnsDiscoveryResolveTarget::startResolveDns() {
220
242
}
221
243
222
244
if (!resolve_timer_) {
223
- resolve_timer_ =
224
- parent_.dispatcher_ .createTimer ([this ]() -> void { startResolveDns (); });
245
+ resolve_timer_ = parent_.dispatcher_ .createTimer ([this ]() -> void {
246
+ // Check if the parent cluster is being destroyed
247
+ if (parent_.is_destroying_ .load ()) {
248
+ return ;
249
+ }
250
+ startResolveDns ();
251
+ });
225
252
}
226
253
// if the initial dns resolved to empty, we'll skip the redis discovery phase and
227
254
// treat it as an empty cluster.
@@ -244,7 +271,13 @@ RedisCluster::RedisDiscoverySession::RedisDiscoverySession(
244
271
Envoy::Extensions::Clusters::Redis::RedisCluster& parent,
245
272
NetworkFilters::Common::Redis::Client::ClientFactory& client_factory)
246
273
: parent_(parent), dispatcher_(parent.dispatcher_),
247
- resolve_timer_(parent.dispatcher_.createTimer([this ]() -> void { startResolveRedis (); })),
274
+ resolve_timer_(parent.dispatcher_.createTimer([this ]() -> void {
275
+ // Check if the parent cluster is being destroyed
276
+ if (parent_.is_destroying_ .load ()) {
277
+ return ;
278
+ }
279
+ startResolveRedis ();
280
+ })),
248
281
client_factory_(client_factory), buffer_timeout_(0 ),
249
282
redis_command_stats_(
250
283
NetworkFilters::Common::Redis::RedisCommandStats::createRedisCommandStats (
0 commit comments