Skip to content

Commit 01af65d

Browse files
committed
Fix concurrent requests race over scroll context limit (#53449)
Concurrent search scroll requests can lead to more scroll contexts than the limit.
1 parent e1094ac commit 01af65d

File tree

2 files changed

+58
-5
lines changed

2 files changed

+58
-5
lines changed

server/src/main/java/org/elasticsearch/search/SearchService.java

+12-5
Original file line numberDiff line numberDiff line change
@@ -585,15 +585,24 @@ final SearchContext createAndPutContext(ShardSearchRequest request) throws IOExc
585585
" not be allowed in the next major version by default. You can change the [" +
586586
MAX_OPEN_SCROLL_CONTEXT.getKey() + "] setting to use a greater default value or lower the number of" +
587587
" scrolls that you need to run in parallel.");
588-
} else if (openScrollContexts.get() >= maxOpenScrollContext) {
588+
}
589+
if (openScrollContexts.incrementAndGet() > maxOpenScrollContext) {
590+
openScrollContexts.decrementAndGet();
589591
throw new ElasticsearchException(
590592
"Trying to create too many scroll contexts. Must be less than or equal to: [" +
591593
maxOpenScrollContext + "]. " + "This limit can be set by changing the ["
592594
+ MAX_OPEN_SCROLL_CONTEXT.getKey() + "] setting.");
593595
}
594596
}
595-
596-
SearchContext context = createContext(request);
597+
SearchContext context = null;
598+
try {
599+
context = createContext(request);
600+
context.addReleasable(openScrollContexts::decrementAndGet, Lifetime.CONTEXT);
601+
} finally {
602+
if (context == null) {
603+
openScrollContexts.decrementAndGet();
604+
}
605+
}
597606
onNewContext(context);
598607
boolean success = false;
599608
try {
@@ -611,7 +620,6 @@ private void onNewContext(SearchContext context) {
611620
boolean success = false;
612621
try {
613622
if (context.scrollContext() != null) {
614-
openScrollContexts.incrementAndGet();
615623
context.indexShard().getSearchOperationListener().onNewScrollContext(context);
616624
}
617625
context.indexShard().getSearchOperationListener().onNewContext(context);
@@ -724,7 +732,6 @@ private void onFreeContext(SearchContext context) {
724732
assert activeContexts.containsKey(context.id()) == false;
725733
context.indexShard().getSearchOperationListener().onFreeContext(context);
726734
if (context.scrollContext() != null) {
727-
openScrollContexts.decrementAndGet();
728735
context.indexShard().getSearchOperationListener().onFreeScrollContext(context);
729736
}
730737
}

server/src/test/java/org/elasticsearch/search/SearchServiceTests.java

+46
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,52 @@ public void testMaxOpenScrollContexts() throws RuntimeException, IOException {
489489
}
490490
}
491491

492+
public void testOpenScrollContextsConcurrently() throws Exception {
493+
final int maxScrollContexts = randomIntBetween(50, 200);
494+
assertAcked(client().admin().cluster().prepareUpdateSettings()
495+
.setTransientSettings(Settings.builder().put(SearchService.MAX_OPEN_SCROLL_CONTEXT.getKey(), maxScrollContexts)));
496+
try {
497+
createIndex("index");
498+
final IndicesService indicesService = getInstanceFromNode(IndicesService.class);
499+
final IndexShard indexShard = indicesService.indexServiceSafe(resolveIndex("index")).getShard(0);
500+
501+
final SearchService searchService = getInstanceFromNode(SearchService.class);
502+
Thread[] threads = new Thread[randomIntBetween(2, 8)];
503+
CountDownLatch latch = new CountDownLatch(threads.length);
504+
for (int i = 0; i < threads.length; i++) {
505+
threads[i] = new Thread(() -> {
506+
latch.countDown();
507+
try {
508+
latch.await();
509+
for (; ; ) {
510+
try {
511+
searchService.createAndPutContext(new ShardScrollRequestTest(indexShard.shardId()));
512+
} catch (ElasticsearchException e) {
513+
assertThat(e.getMessage(), equalTo(
514+
"Trying to create too many scroll contexts. Must be less than or equal to: " +
515+
"[" + maxScrollContexts + "]. " +
516+
"This limit can be set by changing the [search.max_open_scroll_context] setting."));
517+
return;
518+
}
519+
}
520+
} catch (Exception e) {
521+
throw new AssertionError(e);
522+
}
523+
});
524+
threads[i].setName("elasticsearch[node_s_0][search]");
525+
threads[i].start();
526+
}
527+
for (Thread thread : threads) {
528+
thread.join();
529+
}
530+
assertThat(searchService.getActiveContexts(), equalTo(maxScrollContexts));
531+
searchService.freeAllScrollContexts();
532+
} finally {
533+
assertAcked(client().admin().cluster().prepareUpdateSettings()
534+
.setTransientSettings(Settings.builder().putNull(SearchService.MAX_OPEN_SCROLL_CONTEXT.getKey())));
535+
}
536+
}
537+
492538
public static class FailOnRewriteQueryPlugin extends Plugin implements SearchPlugin {
493539
@Override
494540
public List<QuerySpec<?>> getQueries() {

0 commit comments

Comments
 (0)