From bc22e1e4d1587f61887bc4b2e309e300df341b07 Mon Sep 17 00:00:00 2001 From: Mikita Hradovich Date: Thu, 13 Aug 2026 12:24:58 +0200 Subject: [PATCH] fix: carry the ShardAwarenessTest trace retry into the patched tags 3.11.4.0 and 3.11.5.15 patch the test's dead assertThat(anyLocal) into a real assertTrue, which then fails whenever a query trace comes back incomplete. Regenerate both hunks so the patched result matches the scylla-3.x fix. Co-Authored-By: Claude Opus 5 (1M context) --- versions/scylla/3.11.4.0/patch | 120 ++++++++++++++++++++++++++++---- versions/scylla/3.11.5.15/patch | 120 ++++++++++++++++++++++++++++---- 2 files changed, 210 insertions(+), 30 deletions(-) diff --git a/versions/scylla/3.11.4.0/patch b/versions/scylla/3.11.4.0/patch index acd0e21..7ffc06d 100644 --- a/versions/scylla/3.11.4.0/patch +++ b/versions/scylla/3.11.4.0/patch @@ -760,30 +760,120 @@ index 777ea439f8..a63e169dac 100644 assertThat(table.getColumns().get(1)) .isNotNull() diff --git a/driver-core/src/test/java/com/datastax/driver/core/ShardAwarenessTest.java b/driver-core/src/test/java/com/datastax/driver/core/ShardAwarenessTest.java -index b1867a0562..8fa0a885b9 100644 +index b1867a0562..2825c44b12 100644 --- a/driver-core/src/test/java/com/datastax/driver/core/ShardAwarenessTest.java +++ b/driver-core/src/test/java/com/datastax/driver/core/ShardAwarenessTest.java -@@ -82,12 +82,17 @@ - event.getSource(), - event.getThreadName(), - event.getDescription()); +@@ -17,6 +17,7 @@ package com.datastax.driver.core; + + import static com.datastax.driver.core.Assertions.assertThat; + import static org.testng.Assert.assertTrue; ++import static org.testng.Assert.fail; + + import com.datastax.driver.core.QueryTrace.Event; + import com.datastax.driver.core.utils.ScyllaOnly; +@@ -36,6 +37,13 @@ import org.testng.annotations.Test; + }) + public class ShardAwarenessTest extends CCMTestsSupport { + private static final Logger logger = LoggerFactory.getLogger(ShardAwarenessTest.class); ++ ++ /** ++ * How many times a traced read is re-issued when its query trace comes back without the local ++ * read event. Guards against incomplete traces, not against a wrong shard. ++ */ ++ private static final int MAX_TRACE_ATTEMPTS = 3; ++ + private final boolean useAdvancedShardAwareness; + + @Factory(dataProvider = "dataProvider") +@@ -62,32 +70,63 @@ public class ShardAwarenessTest extends CCMTestsSupport { + private void verifyCorrectShardSingleRow(String pk, String ck, String v, String shard) { + PreparedStatement prepared = + session().prepare("SELECT pk, ck, v FROM shardawaretest.t WHERE pk=? AND ck=?"); +- ResultSet result = session().execute(prepared.bind(pk, ck).enableTracing()); +- +- Row row = result.one(); +- assertTrue(result.isExhausted()); +- assertThat(row).isNotNull(); +- assertThat(row.getString("pk")).isEqualTo(pk); +- assertThat(row.getString("ck")).isEqualTo(ck); +- assertThat(row.getString("v")).isEqualTo(v); +- +- ExecutionInfo executionInfo = result.getExecutionInfo(); +- +- QueryTrace trace = executionInfo.getQueryTrace(); +- boolean anyLocal = false; +- for (Event event : trace.getEvents()) { +- logger.info( +- " {} - {} - [{}] - {}", +- event.getSourceElapsedMicros(), +- event.getSource(), +- event.getThreadName(), +- event.getDescription()); - assertThat(event.getThreadName()).startsWith(shard); -+ // Only data-local events carry the owning shard. Scylla 2025.1 adds coordinator-side -+ // events that legitimately run on shard 0 regardless of the data shard. Also, since -+ // Scylla 2025.1 the thread name carries a service-level suffix ("shard N/sl:"), -+ // so strip it before comparing. - if (event.getDescription().contains("querying locally")) { - anyLocal = true; -+ String normalized = event.getThreadName().replaceFirst("/sl:[^/]*$", ""); -+ assertThat(normalized).startsWith(shard); +- if (event.getDescription().contains("querying locally")) { +- anyLocal = true; ++ ++ for (int attempt = 1; attempt <= MAX_TRACE_ATTEMPTS; attempt++) { ++ // Bind a fresh statement on every attempt. Reusing one BoundStatement across attempts ++ // would pin the coordinator through the paging optimization, which would silently void ++ // the shard assertion below. ++ ResultSet result = session().execute(prepared.bind(pk, ck).enableTracing()); ++ ++ Row row = result.one(); ++ assertTrue(result.isExhausted()); ++ assertThat(row).isNotNull(); ++ assertThat(row.getString("pk")).isEqualTo(pk); ++ assertThat(row.getString("ck")).isEqualTo(ck); ++ assertThat(row.getString("v")).isEqualTo(v); ++ ++ ExecutionInfo executionInfo = result.getExecutionInfo(); ++ ++ QueryTrace trace = executionInfo.getQueryTrace(); ++ boolean anyLocal = false; ++ for (Event event : trace.getEvents()) { ++ logger.info( ++ " {} - {} - [{}] - {}", ++ event.getSourceElapsedMicros(), ++ event.getSource(), ++ event.getThreadName(), ++ event.getDescription()); ++ // Only data-local events carry the owning shard. Scylla 2025.1 adds coordinator-side ++ // events that legitimately run on shard 0 regardless of the data shard. Also, since ++ // Scylla 2025.1 the thread name carries a service-level suffix ("shard N/sl:"), ++ // so strip it before comparing. ++ if (event.getDescription().contains("querying locally")) { ++ anyLocal = true; ++ String normalized = event.getThreadName().replaceFirst("/sl:[^/]*$", ""); ++ assertThat(normalized).startsWith(shard); ++ } ++ } ++ if (anyLocal) { ++ return; } ++ // QueryTrace waits only until the coordinator's session row reports a duration; the ++ // events fetched alongside it can still be incomplete, and QueryTrace.doFetchTrace ++ // documents that the trace "may not contain the log of replicas". An event list without ++ // the local read is therefore not a functional failure, so retry with a fresh trace. ++ // Re-executing the query is the only way to get one: once a trace has been fetched its ++ // events are cached and frozen, so re-reading the same trace can never return more. ++ logger.warn( ++ "Attempt {}/{} for pk={}: trace {} carried no 'querying locally' event ({} events);" ++ + " retrying with a fresh trace", ++ attempt, ++ MAX_TRACE_ATTEMPTS, ++ pk, ++ trace.getTraceId(), ++ trace.getEvents().size()); } - assertThat(anyLocal); -+ assertTrue(anyLocal, "No 'querying locally' trace event was observed for the query"); ++ fail( ++ "No 'querying locally' trace event was observed for the query after " ++ + MAX_TRACE_ATTEMPTS ++ + " attempts"); } @Test(groups = "short") -@@ -95,7 +100,7 @@ public class ShardAwarenessTest extends CCMTestsSupport { +@@ -95,7 +134,7 @@ public class ShardAwarenessTest extends CCMTestsSupport { session().execute("DROP KEYSPACE IF EXISTS shardawaretest"); session() .execute( diff --git a/versions/scylla/3.11.5.15/patch b/versions/scylla/3.11.5.15/patch index cb6e655..56aef27 100644 --- a/versions/scylla/3.11.5.15/patch +++ b/versions/scylla/3.11.5.15/patch @@ -99,30 +99,120 @@ index ea75f84544..ea70e9081a 100644 private static final Logger logger = LoggerFactory.getLogger(SessionStressTest.class); diff --git a/driver-core/src/test/java/com/datastax/driver/core/ShardAwarenessTest.java b/driver-core/src/test/java/com/datastax/driver/core/ShardAwarenessTest.java -index ac36feb518..8fa0a885b9 100644 +index ac36feb518..2825c44b12 100644 --- a/driver-core/src/test/java/com/datastax/driver/core/ShardAwarenessTest.java +++ b/driver-core/src/test/java/com/datastax/driver/core/ShardAwarenessTest.java -@@ -82,12 +82,17 @@ - event.getSource(), - event.getThreadName(), - event.getDescription()); +@@ -17,6 +17,7 @@ package com.datastax.driver.core; + + import static com.datastax.driver.core.Assertions.assertThat; + import static org.testng.Assert.assertTrue; ++import static org.testng.Assert.fail; + + import com.datastax.driver.core.QueryTrace.Event; + import com.datastax.driver.core.utils.ScyllaOnly; +@@ -36,6 +37,13 @@ import org.testng.annotations.Test; + }) + public class ShardAwarenessTest extends CCMTestsSupport { + private static final Logger logger = LoggerFactory.getLogger(ShardAwarenessTest.class); ++ ++ /** ++ * How many times a traced read is re-issued when its query trace comes back without the local ++ * read event. Guards against incomplete traces, not against a wrong shard. ++ */ ++ private static final int MAX_TRACE_ATTEMPTS = 3; ++ + private final boolean useAdvancedShardAwareness; + + @Factory(dataProvider = "dataProvider") +@@ -62,32 +70,63 @@ public class ShardAwarenessTest extends CCMTestsSupport { + private void verifyCorrectShardSingleRow(String pk, String ck, String v, String shard) { + PreparedStatement prepared = + session().prepare("SELECT pk, ck, v FROM shardawaretest.t WHERE pk=? AND ck=?"); +- ResultSet result = session().execute(prepared.bind(pk, ck).enableTracing()); +- +- Row row = result.one(); +- assertTrue(result.isExhausted()); +- assertThat(row).isNotNull(); +- assertThat(row.getString("pk")).isEqualTo(pk); +- assertThat(row.getString("ck")).isEqualTo(ck); +- assertThat(row.getString("v")).isEqualTo(v); +- +- ExecutionInfo executionInfo = result.getExecutionInfo(); +- +- QueryTrace trace = executionInfo.getQueryTrace(); +- boolean anyLocal = false; +- for (Event event : trace.getEvents()) { +- logger.info( +- " {} - {} - [{}] - {}", +- event.getSourceElapsedMicros(), +- event.getSource(), +- event.getThreadName(), +- event.getDescription()); - assertThat(event.getThreadName()).startsWith(shard); -+ // Only data-local events carry the owning shard. Scylla 2025.1 adds coordinator-side -+ // events that legitimately run on shard 0 regardless of the data shard. Also, since -+ // Scylla 2025.1 the thread name carries a service-level suffix ("shard N/sl:"), -+ // so strip it before comparing. - if (event.getDescription().contains("querying locally")) { - anyLocal = true; -+ String normalized = event.getThreadName().replaceFirst("/sl:[^/]*$", ""); -+ assertThat(normalized).startsWith(shard); +- if (event.getDescription().contains("querying locally")) { +- anyLocal = true; ++ ++ for (int attempt = 1; attempt <= MAX_TRACE_ATTEMPTS; attempt++) { ++ // Bind a fresh statement on every attempt. Reusing one BoundStatement across attempts ++ // would pin the coordinator through the paging optimization, which would silently void ++ // the shard assertion below. ++ ResultSet result = session().execute(prepared.bind(pk, ck).enableTracing()); ++ ++ Row row = result.one(); ++ assertTrue(result.isExhausted()); ++ assertThat(row).isNotNull(); ++ assertThat(row.getString("pk")).isEqualTo(pk); ++ assertThat(row.getString("ck")).isEqualTo(ck); ++ assertThat(row.getString("v")).isEqualTo(v); ++ ++ ExecutionInfo executionInfo = result.getExecutionInfo(); ++ ++ QueryTrace trace = executionInfo.getQueryTrace(); ++ boolean anyLocal = false; ++ for (Event event : trace.getEvents()) { ++ logger.info( ++ " {} - {} - [{}] - {}", ++ event.getSourceElapsedMicros(), ++ event.getSource(), ++ event.getThreadName(), ++ event.getDescription()); ++ // Only data-local events carry the owning shard. Scylla 2025.1 adds coordinator-side ++ // events that legitimately run on shard 0 regardless of the data shard. Also, since ++ // Scylla 2025.1 the thread name carries a service-level suffix ("shard N/sl:"), ++ // so strip it before comparing. ++ if (event.getDescription().contains("querying locally")) { ++ anyLocal = true; ++ String normalized = event.getThreadName().replaceFirst("/sl:[^/]*$", ""); ++ assertThat(normalized).startsWith(shard); ++ } ++ } ++ if (anyLocal) { ++ return; } ++ // QueryTrace waits only until the coordinator's session row reports a duration; the ++ // events fetched alongside it can still be incomplete, and QueryTrace.doFetchTrace ++ // documents that the trace "may not contain the log of replicas". An event list without ++ // the local read is therefore not a functional failure, so retry with a fresh trace. ++ // Re-executing the query is the only way to get one: once a trace has been fetched its ++ // events are cached and frozen, so re-reading the same trace can never return more. ++ logger.warn( ++ "Attempt {}/{} for pk={}: trace {} carried no 'querying locally' event ({} events);" ++ + " retrying with a fresh trace", ++ attempt, ++ MAX_TRACE_ATTEMPTS, ++ pk, ++ trace.getTraceId(), ++ trace.getEvents().size()); } - assertThat(anyLocal); -+ assertTrue(anyLocal, "No 'querying locally' trace event was observed for the query"); ++ fail( ++ "No 'querying locally' trace event was observed for the query after " ++ + MAX_TRACE_ATTEMPTS ++ + " attempts"); } @Test(groups = "short") -@@ -95,7 +100,7 @@ +@@ -95,7 +134,7 @@ public class ShardAwarenessTest extends CCMTestsSupport { session().execute("DROP KEYSPACE IF EXISTS shardawaretest"); session() .execute(