Skip to content

Commit 06e8b46

Browse files
Use a threadsafe map in SearchAsyncActionTests
Today `SearchAsyncActionTests#testFanOutAndCollect` uses a simple `HashMap` for the `nodeToContextMap` variable, which is then accessed from multiple threads without, apparently, explicit synchronisation. This provides an explanation for the test failure identified in #29242 in which `.toString()` returns `"[]"` just before `.isEmpty` returns `false`, without any concurrent modifications. This change converts `nodeToContextMap` to a `newConcurrentMap()` so that this cannot occur. It also fixes a race condition in the detection of double-calling the subsequent search phase. This is a backport of #33700 to 5.6. Relates #33700 Relates #29242 Relates #34506
1 parent 0ec872d commit 06e8b46

File tree

1 file changed

+14
-6
lines changed

1 file changed

+14
-6
lines changed

core/src/test/java/org/elasticsearch/action/search/SearchAsyncActionTests.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,13 @@
5353
import java.util.concurrent.CountDownLatch;
5454
import java.util.concurrent.ExecutorService;
5555
import java.util.concurrent.Executors;
56+
import java.util.concurrent.atomic.AtomicBoolean;
5657
import java.util.concurrent.atomic.AtomicInteger;
5758
import java.util.concurrent.atomic.AtomicReference;
5859

60+
import static org.elasticsearch.common.util.concurrent.ConcurrentCollections.newConcurrentMap;
61+
import static org.elasticsearch.common.util.concurrent.ConcurrentCollections.newConcurrentSet;
62+
5963
public class SearchAsyncActionTests extends ESTestCase {
6064

6165
public void testSkipSearchShards() throws InterruptedException {
@@ -137,7 +141,7 @@ protected void executePhaseOnShard(SearchShardIterator shardIt, ShardRouting sha
137141
protected SearchPhase getNextPhase(SearchPhaseResults<TestSearchPhaseResult> results, SearchPhaseContext context) {
138142
return new SearchPhase("test") {
139143
@Override
140-
public void run() throws IOException {
144+
public void run() {
141145
latch.countDown();
142146
}
143147
};
@@ -253,7 +257,6 @@ public void run() throws IOException {
253257
public void testFanOutAndCollect() throws InterruptedException {
254258
SearchRequest request = new SearchRequest();
255259
request.setMaxConcurrentShardRequests(randomIntBetween(1, 100));
256-
CountDownLatch latch = new CountDownLatch(1);
257260
AtomicReference<TestSearchResponse> response = new AtomicReference<>();
258261
ActionListener<SearchResponse> responseListener = new ActionListener<SearchResponse>() {
259262
@Override
@@ -270,7 +273,7 @@ public void onFailure(Exception e) {
270273
DiscoveryNode primaryNode = new DiscoveryNode("node_1", new LocalTransportAddress("foo"), Version.CURRENT);
271274
DiscoveryNode replicaNode = new DiscoveryNode("node_2", new LocalTransportAddress("bar"), Version.CURRENT);
272275

273-
Map<DiscoveryNode, Set<Long>> nodeToContextMap = new HashMap<>();
276+
Map<DiscoveryNode, Set<Long>> nodeToContextMap = newConcurrentMap();
274277
AtomicInteger contextIdGenerator = new AtomicInteger(0);
275278
GroupShardsIterator<SearchShardIterator> shardsIter = getShardsIter("idx",
276279
new OriginalIndices(new String[]{"idx"}, IndicesOptions.strictExpandOpenAndForbidClosed()),
@@ -289,7 +292,9 @@ public void sendFreeContext(Transport.Connection connection, long contextId, Ori
289292
lookup.put(replicaNode.getId(), new MockConnection(replicaNode));
290293
Map<String, AliasFilter> aliasFilters = Collections.singletonMap("_na_", new AliasFilter(null, Strings.EMPTY_ARRAY));
291294
final ExecutorService executor = Executors.newFixedThreadPool(randomIntBetween(1, Runtime.getRuntime().availableProcessors()));
292-
AbstractSearchAsyncAction asyncAction =
295+
final CountDownLatch latch = new CountDownLatch(1);
296+
final AtomicBoolean latchTriggered = new AtomicBoolean();
297+
AbstractSearchAsyncAction<TestSearchPhaseResult> asyncAction =
293298
new AbstractSearchAsyncAction<TestSearchPhaseResult>(
294299
"test",
295300
logger,
@@ -317,7 +322,7 @@ protected void executePhaseOnShard(SearchShardIterator shardIt, ShardRouting sha
317322
Transport.Connection connection = getConnection(null, shard.currentNodeId());
318323
TestSearchPhaseResult testSearchPhaseResult = new TestSearchPhaseResult(contextIdGenerator.incrementAndGet(),
319324
connection.getNode());
320-
Set<Long> ids = nodeToContextMap.computeIfAbsent(connection.getNode(), (n) -> new HashSet<>());
325+
Set<Long> ids = nodeToContextMap.computeIfAbsent(connection.getNode(), (n) -> newConcurrentSet());
321326
ids.add(testSearchPhaseResult.getRequestId());
322327
if (randomBoolean()) {
323328
listener.onResponse(testSearchPhaseResult);
@@ -330,13 +335,16 @@ protected void executePhaseOnShard(SearchShardIterator shardIt, ShardRouting sha
330335
protected SearchPhase getNextPhase(SearchPhaseResults<TestSearchPhaseResult> results, SearchPhaseContext context) {
331336
return new SearchPhase("test") {
332337
@Override
333-
public void run() throws IOException {
338+
public void run() {
334339
for (int i = 0; i < results.getNumShards(); i++) {
335340
TestSearchPhaseResult result = results.getAtomicArray().get(i);
336341
assertEquals(result.node.getId(), result.getSearchShardTarget().getNodeId());
337342
sendReleaseSearchContext(result.getRequestId(), new MockConnection(result.node), OriginalIndices.NONE);
338343
}
339344
responseListener.onResponse(response);
345+
if (latchTriggered.compareAndSet(false, true) == false) {
346+
throw new AssertionError("latch triggered twice");
347+
}
340348
latch.countDown();
341349
}
342350
};

0 commit comments

Comments
 (0)