From 4787fe4f555b79ab8cdac6a14be43a02655f622e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 27 Apr 2026 18:36:13 +0000
Subject: [PATCH 1/9] Initial plan


From 7d8eb34255fbb0eb0821be37eba45784fbc5f613 Mon Sep 17 00:00:00 2001
From: phlax <phlax@users.noreply.github.com>
Date: Fri, 17 Apr 2026 13:11:59 +0100
Subject: [PATCH 2/9] test: Fix ws integration test flake (#44508)
 Co-authored-by: phlax <454682+phlax@users.noreply.github.com>

---
 test/integration/BUILD                         | 6 +-----
 test/integration/websocket_integration_test.cc | 4 ++--
 2 files changed, 3 insertions(+), 7 deletions(-)

diff --git a/test/integration/BUILD b/test/integration/BUILD
index 742ce73574537..12d00af12e98d 100644
--- a/test/integration/BUILD
+++ b/test/integration/BUILD
@@ -1608,12 +1608,8 @@ envoy_cc_test_library(
 
 envoy_cc_test(
     name = "websocket_integration_test",
-    size = "large",
     srcs = ["websocket_integration_test.cc"],
-    rbe_pool = "2core",
-    tags = [
-        "cpu:3",
-    ],
+    rbe_pool = "linux_x64_small",
     deps = [
         ":http_protocol_integration_lib",
         ":websocket_integration_test_lib",
diff --git a/test/integration/websocket_integration_test.cc b/test/integration/websocket_integration_test.cc
index c303ea2a23c98..0477ffc5104a0 100644
--- a/test/integration/websocket_integration_test.cc
+++ b/test/integration/websocket_integration_test.cc
@@ -922,7 +922,7 @@ TEST_P(WebsocketIntegrationTest, WebSocketUpgradeRouteTimeoutWithRetries) {
         auto* route_config = hcm.mutable_route_config();
         auto* virtual_host = route_config->mutable_virtual_hosts(0);
         auto* route = virtual_host->mutable_routes(0)->mutable_route();
-        route->mutable_timeout()->set_nanos(200 * 1000 * 1000); // 200ms route timeout
+        route->mutable_timeout()->set_seconds(2); // 2s route timeout
       });
   initialize();
 
@@ -936,7 +936,7 @@ TEST_P(WebsocketIntegrationTest, WebSocketUpgradeRouteTimeoutWithRetries) {
   ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_));
   ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_));
   ASSERT_TRUE(upstream_request_->waitForHeadersComplete());
-  upstream_request_->encodeHeaders(upgradeFailedResponseHeaders(), false);
+  upstream_request_->encodeHeaders(upgradeFailedResponseHeaders(), true);
 
   // Wait for the first request to be reset or disconnected
   ASSERT_TRUE(waitForUpstreamDisconnectOrReset());

From efaf031c9077fe12e661dbdd88b3d372487fa66d Mon Sep 17 00:00:00 2001
From: phlax <phlax@users.noreply.github.com>
Date: Fri, 17 Apr 2026 18:53:30 +0100
Subject: [PATCH 3/9] test/ws: Follow up fixes from #44508 (#44518)

Signed-off-by: Ryan Northey <ryan@synca.io>
Co-authored-by: phlax <454682+phlax@users.noreply.github.com>
---
 test/integration/BUILD                         |  1 +
 test/integration/websocket_integration_test.cc | 10 +++++-----
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/test/integration/BUILD b/test/integration/BUILD
index 12d00af12e98d..f345db37d3881 100644
--- a/test/integration/BUILD
+++ b/test/integration/BUILD
@@ -1610,6 +1610,7 @@ envoy_cc_test(
     name = "websocket_integration_test",
     srcs = ["websocket_integration_test.cc"],
     rbe_pool = "linux_x64_small",
+    shard_count = 4,
     deps = [
         ":http_protocol_integration_lib",
         ":websocket_integration_test_lib",
diff --git a/test/integration/websocket_integration_test.cc b/test/integration/websocket_integration_test.cc
index 0477ffc5104a0..146e376718dde 100644
--- a/test/integration/websocket_integration_test.cc
+++ b/test/integration/websocket_integration_test.cc
@@ -262,8 +262,8 @@ TEST_P(WebsocketIntegrationTest, WebSocketConnectionIdleTimeout) {
         auto* route_config = hcm.mutable_route_config();
         auto* virtual_host = route_config->mutable_virtual_hosts(0);
         auto* route = virtual_host->mutable_routes(0)->mutable_route();
-        route->mutable_idle_timeout()->set_seconds(0);
-        route->mutable_idle_timeout()->set_nanos(200 * 1000 * 1000);
+        route->mutable_idle_timeout()->set_seconds(1);
+        route->mutable_idle_timeout()->set_nanos(0);
       });
   initialize();
 
@@ -850,8 +850,8 @@ TEST_P(WebsocketIntegrationTest, WebSocketUpgradePerTryTimeout) {
         auto* route_config = hcm.mutable_route_config();
         auto* virtual_host = route_config->mutable_virtual_hosts(0);
         auto* route = virtual_host->mutable_routes(0)->mutable_route();
-        route->mutable_retry_policy()->mutable_per_try_timeout()->set_nanos(
-            200 * 1000 * 1000); // 200ms per-try timeout
+        route->mutable_retry_policy()->mutable_per_try_timeout()->set_seconds(
+            2); // 2s per-try timeout (safe under msan/tsan)
       });
   initialize();
 
@@ -886,7 +886,7 @@ TEST_P(WebsocketIntegrationTest, WebSocketUpgradeRouteTimeout) {
         auto* route_config = hcm.mutable_route_config();
         auto* virtual_host = route_config->mutable_virtual_hosts(0);
         auto* route = virtual_host->mutable_routes(0)->mutable_route();
-        route->mutable_timeout()->set_nanos(200 * 1000 * 1000); // 200ms route timeout
+        route->mutable_timeout()->set_seconds(2); // 2s route timeout (safe under msan/tsan)
       });
   initialize();
 

From bc47ba8c3486930a3d3e12ae48b164a1061e8f60 Mon Sep 17 00:00:00 2001
From: phlax <phlax@users.noreply.github.com>
Date: Mon, 20 Apr 2026 15:25:10 +0100
Subject: [PATCH 4/9] test/ws: Increase sharding further (#44529)

as this can still flake in msan

Signed-off-by: Ryan Northey <ryan@synca.io>
Co-authored-by: phlax <454682+phlax@users.noreply.github.com>
---
 test/integration/BUILD | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/integration/BUILD b/test/integration/BUILD
index f345db37d3881..1e34440db1901 100644
--- a/test/integration/BUILD
+++ b/test/integration/BUILD
@@ -1610,7 +1610,7 @@ envoy_cc_test(
     name = "websocket_integration_test",
     srcs = ["websocket_integration_test.cc"],
     rbe_pool = "linux_x64_small",
-    shard_count = 4,
+    shard_count = 8,
     deps = [
         ":http_protocol_integration_lib",
         ":websocket_integration_test_lib",

From 781709909b7c905c58bd4e7463779090512424bc Mon Sep 17 00:00:00 2001
From: phlax <phlax@users.noreply.github.com>
Date: Wed, 22 Apr 2026 15:07:08 +0100
Subject: [PATCH 5/9] test/idle_timeout: Fix flake (#44584)

Scale RequestTimeoutMs by TIMEOUT_FACTOR to match IdleTimeoutMs. The
hard-coded 200ms races test setup on loaded/sanitizer runs, causing the
request timer to fire before the test body executes.

fix #26979

Signed-off-by: Ryan Northey <ryan@synca.io>
Co-authored-by: phlax <454682+phlax@users.noreply.github.com>
---
 test/integration/idle_timeout_integration_test.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/integration/idle_timeout_integration_test.cc b/test/integration/idle_timeout_integration_test.cc
index 92eebb3dc82fa..4df41441c4212 100644
--- a/test/integration/idle_timeout_integration_test.cc
+++ b/test/integration/idle_timeout_integration_test.cc
@@ -117,7 +117,7 @@ class IdleTimeoutIntegrationTest : public HttpProtocolIntegrationTest {
   }
 
   static constexpr uint64_t IdleTimeoutMs = 300 * TIMEOUT_FACTOR;
-  static constexpr uint64_t RequestTimeoutMs = 200;
+  static constexpr uint64_t RequestTimeoutMs = 200 * TIMEOUT_FACTOR;
   bool enable_global_idle_timeout_{false};
   bool enable_per_stream_idle_timeout_{false};
   bool enable_request_timeout_{false};

From 2074c2c8cbddbf51383fefb041cbdffccf6f430b Mon Sep 17 00:00:00 2001
From: phlax <phlax@users.noreply.github.com>
Date: Fri, 17 Apr 2026 22:10:30 +0100
Subject: [PATCH 6/9] quic: Remove flaky ASSERT in
 `ShouldCreateOutgoingBidirectionalStream` (#44500)

QuicSpdyClientSession::ShouldCreateOutgoingBidirectionalStream() can
legitimately return false, but we return true unconditionally to avoid a
nullptr deref in QuicHttpClientConnectionImpl::newStream.

The existing ASSERT on the parent's return value was therefore incorrect
and triggered flakes (e.g. in buffer_accounting_integration_test on
MSAN). Drop the ASSERT and update the comment.

Fixes #41526

---------

Signed-off-by: Ryan Northey <ryan@synca.io>
Co-authored-by: phlax <454682+phlax@users.noreply.github.com>
---
 source/common/quic/envoy_quic_client_session.h | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/source/common/quic/envoy_quic_client_session.h b/source/common/quic/envoy_quic_client_session.h
index 111d060bfd023..3db7136dd5ff8 100644
--- a/source/common/quic/envoy_quic_client_session.h
+++ b/source/common/quic/envoy_quic_client_session.h
@@ -105,9 +105,10 @@ class EnvoyQuicClientSession : public QuicFilterManagerConnectionImpl,
   quic::QuicSpdyStream* CreateIncomingStream(quic::PendingStream* pending) override;
   std::unique_ptr<quic::QuicCryptoClientStreamBase> CreateQuicCryptoStream() override;
   bool ShouldCreateOutgoingBidirectionalStream() override {
-    ASSERT(quic::QuicSpdyClientSession::ShouldCreateOutgoingBidirectionalStream());
-    // Prefer creating an "invalid" stream outside of current stream bounds to
-    // crashing when dereferencing a nullptr in QuicHttpClientConnectionImpl::newStream
+    // quic::QuicSpdyClientSession::ShouldCreateOutgoingBidirectionalStream()
+    // might return false, but we want to create the stream anyway
+    // because otherwise we crash dereferencing a nullptr, so we
+    // don't even ask it, and just return true.
     return true;
   }
   // QuicFilterManagerConnectionImpl

From cf1f4460eb2b916665b5a0695587198027e2cf5e Mon Sep 17 00:00:00 2001
From: phlax <phlax@users.noreply.github.com>
Date: Fri, 17 Apr 2026 21:06:54 +0100
Subject: [PATCH 7/9] test: Attempt to fix flake in grpc transcoder test
 (#44510)

by separating the tests

Hopefully fix: #44427

Signed-off-by: Ryan Northey <ryan@synca.io>
Co-authored-by: phlax <454682+phlax@users.noreply.github.com>
---
 .../grpc_json_transcoder_integration_test.cc              | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc b/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc
index 5a9990bbce34f..d13393560390c 100644
--- a/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc
+++ b/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc
@@ -1299,7 +1299,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, UTF8) {
       false);
 }
 
-TEST_P(GrpcJsonTranscoderIntegrationTest, DisableRequestValidation) {
+TEST_P(GrpcJsonTranscoderIntegrationTest, DisableRequestValidationGrpcContentType) {
   HttpIntegrationTest::initialize();
 
   // Transcoding does not occur from a request with the gRPC content type.
@@ -1315,7 +1315,10 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, DisableRequestValidation) {
       Http::TestResponseHeaderMapImpl{
           {":status", "200"}, {"grpc-status", "5"}, {"grpc-message", "Shelf 9999 Not Found"}},
       "", true, false, R"({ "theme" : "Children")");
+}
 
+TEST_P(GrpcJsonTranscoderIntegrationTest, DisableRequestValidationUnknownPath) {
+  HttpIntegrationTest::initialize();
   // Transcoding does not occur when unknown path is called.
   // HTTP Request to is passed directly to gRPC backend.
   // gRPC response is passed directly to HTTP client.
@@ -1328,7 +1331,10 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, DisableRequestValidation) {
       Http::TestResponseHeaderMapImpl{
           {":status", "200"}, {"grpc-status", "5"}, {"grpc-message", "Shelf 9999 Not Found"}},
       "", true, false, R"({ "theme" : "Children")");
+}
 
+TEST_P(GrpcJsonTranscoderIntegrationTest, DisableRequestValidationUnknownQueryParam) {
+  HttpIntegrationTest::initialize();
   // Transcoding does not occur when unknown query param is included.
   // HTTP Request to is passed directly to gRPC backend.
   // gRPC response is passed directly to HTTP client.

From fcfc7bccd51f37bf778550558a53a055e8591909 Mon Sep 17 00:00:00 2001
From: "Adi (Suissa) Peleg" <adip@google.com>
Date: Fri, 30 Jan 2026 10:17:42 -0500
Subject: [PATCH 8/9] lrs: proper shutdown of the LRS stream (#43083)

Signed-off-by: Adi Suissa-Peleg <adip@google.com>

Co-authored-by: phlax <454682+phlax@users.noreply.github.com>
---
 changelogs/current.yaml                          |  3 +++
 source/common/upstream/cluster_manager_impl.h    |  1 +
 source/common/upstream/load_stats_reporter.cc    | 11 +++++++++++
 source/common/upstream/load_stats_reporter.h     |  1 +
 test/common/upstream/load_stats_reporter_test.cc | 13 ++++++++++++-
 5 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/changelogs/current.yaml b/changelogs/current.yaml
index 9ecf0d6e48ce5..d2050924e28d4 100644
--- a/changelogs/current.yaml
+++ b/changelogs/current.yaml
@@ -8,6 +8,9 @@ minor_behavior_changes:
 
 bug_fixes:
 # *Changes expected to improve the state of the world and are unlikely to have negative effects*
+- area: load_report
+  change: |
+    Fixed an issue upon load-report shutdown race with ADS stream. Introduced proper cleanup of the gRPC stream.
 
 removed_config_or_runtime:
 # *Normally occurs at the end of the* :ref:`deprecation period <deprecated>`
diff --git a/source/common/upstream/cluster_manager_impl.h b/source/common/upstream/cluster_manager_impl.h
index fd4adb4d330e5..da43a4b0be6f4 100644
--- a/source/common/upstream/cluster_manager_impl.h
+++ b/source/common/upstream/cluster_manager_impl.h
@@ -312,6 +312,7 @@ class ClusterManagerImpl : public ClusterManager,
     // Make sure we destroy all potential outgoing connections before this returns.
     cds_api_.reset();
     xds_manager_.shutdown();
+    load_stats_reporter_.reset();
     active_clusters_.clear();
     warming_clusters_.clear();
     updateClusterCounts();
diff --git a/source/common/upstream/load_stats_reporter.cc b/source/common/upstream/load_stats_reporter.cc
index 6cc62f14709ff..cce389102dba2 100644
--- a/source/common/upstream/load_stats_reporter.cc
+++ b/source/common/upstream/load_stats_reporter.cc
@@ -29,6 +29,17 @@ LoadStatsReporter::LoadStatsReporter(const LocalInfo::LocalInfo& local_info,
   establishNewStream();
 }
 
+LoadStatsReporter::~LoadStatsReporter() {
+  // Disable the timer.
+  ENVOY_LOG_MISC(info, "Destroying LoadStatsReporter");
+  retry_timer_->disableTimer();
+  response_timer_->disableTimer();
+  if (stream_ != nullptr) {
+    stream_->resetStream();
+    stream_ = nullptr;
+  }
+}
+
 void LoadStatsReporter::setRetryTimer() {
   ENVOY_LOG(info, "Load reporter stats stream/connection will retry in {} ms.", RETRY_DELAY_MS);
   retry_timer_->enableTimer(std::chrono::milliseconds(RETRY_DELAY_MS));
diff --git a/source/common/upstream/load_stats_reporter.h b/source/common/upstream/load_stats_reporter.h
index eeae2b6050c39..c0485b5cdec9a 100644
--- a/source/common/upstream/load_stats_reporter.h
+++ b/source/common/upstream/load_stats_reporter.h
@@ -36,6 +36,7 @@ class LoadStatsReporter
   LoadStatsReporter(const LocalInfo::LocalInfo& local_info, ClusterManager& cluster_manager,
                     Stats::Scope& scope, Grpc::RawAsyncClientSharedPtr&& async_client,
                     Event::Dispatcher& dispatcher);
+  virtual ~LoadStatsReporter();
 
   // Grpc::AsyncStreamCallbacks
   void onCreateInitialMetadata(Http::RequestHeaderMap& metadata) override;
diff --git a/test/common/upstream/load_stats_reporter_test.cc b/test/common/upstream/load_stats_reporter_test.cc
index 949b42a00e426..f7a488da8c567 100644
--- a/test/common/upstream/load_stats_reporter_test.cc
+++ b/test/common/upstream/load_stats_reporter_test.cc
@@ -37,6 +37,17 @@ class LoadStatsReporterTest : public testing::Test {
       : retry_timer_(new Event::MockTimer()), response_timer_(new Event::MockTimer()),
         async_client_(new Grpc::MockAsyncClient()) {}
 
+  void TearDown() override {
+    if (load_stats_reporter_ != nullptr) {
+      // Validate that LoadStatsReporter correctly shuts down by disabling
+      // timers and resetting the stream.
+      EXPECT_CALL(*retry_timer_, disableTimer());
+      EXPECT_CALL(*response_timer_, disableTimer());
+      EXPECT_CALL(async_stream_, resetStream());
+      load_stats_reporter_.reset();
+    }
+  }
+
   void createLoadStatsReporter() {
     InSequence s;
     EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Invoke([this](Event::TimerCb timer_cb) {
@@ -97,7 +108,6 @@ class LoadStatsReporterTest : public testing::Test {
   NiceMock<Upstream::MockClusterManager> cm_;
   Event::MockDispatcher dispatcher_;
   Stats::IsolatedStoreImpl stats_store_;
-  std::unique_ptr<LoadStatsReporter> load_stats_reporter_;
   Event::MockTimer* retry_timer_;
   Event::TimerCb retry_timer_cb_;
   Event::MockTimer* response_timer_;
@@ -105,6 +115,7 @@ class LoadStatsReporterTest : public testing::Test {
   Grpc::MockAsyncStream async_stream_;
   Grpc::MockAsyncClient* async_client_;
   NiceMock<LocalInfo::MockLocalInfo> local_info_;
+  std::unique_ptr<LoadStatsReporter> load_stats_reporter_;
 };
 
 // Validate that stream creation results in a timer based retry.

From 76f893ca0cfd8698d630919da445cb1e7f0b660f Mon Sep 17 00:00:00 2001
From: botengyao <boteng@google.com>
Date: Mon, 2 Mar 2026 16:37:24 -0500
Subject: [PATCH 9/9] dns test: improve tests by teardown the server and
 upstream (#43711)

Co-authored-by: phlax <454682+phlax@users.noreply.github.com>
---
 .../filters/udp/dns_filter/dns_filter_integration_test.cc  | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/test/extensions/filters/udp/dns_filter/dns_filter_integration_test.cc b/test/extensions/filters/udp/dns_filter/dns_filter_integration_test.cc
index fde6bf587350f..b9b6d740ae65e 100644
--- a/test/extensions/filters/udp/dns_filter/dns_filter_integration_test.cc
+++ b/test/extensions/filters/udp/dns_filter/dns_filter_integration_test.cc
@@ -88,6 +88,13 @@ class DnsFilterIntegrationTest : public testing::TestWithParam<Network::Address:
 
   void setupResponseParser() { histogram_.unit_ = Stats::Histogram::Unit::Milliseconds; }
 
+  void TearDown() override {
+    if (test_server_) {
+      test_server_.reset();
+    }
+    fake_upstreams_.clear();
+  }
+
   static std::string configToUse() {
     return fmt::format(R"EOF(
 admin:
