Skip to content

Commit fe2f6b3

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 2789fe4 commit fe2f6b3

File tree

2 files changed

+48
-8
lines changed

2 files changed

+48
-8
lines changed

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

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,6 @@ private void onNewContext(SearchContext context) {
642642
boolean success = false;
643643
try {
644644
if (context.scrollContext() != null) {
645-
openScrollContexts.incrementAndGet();
646645
context.indexShard().getSearchOperationListener().onNewScrollContext(context);
647646
}
648647
context.indexShard().getSearchOperationListener().onNewContext(context);
@@ -661,14 +660,15 @@ private void onNewContext(SearchContext context) {
661660
final SearchContext createContext(SearchRewriteContext rewriteContext) throws IOException {
662661
final DefaultSearchContext context = createSearchContext(rewriteContext, defaultSearchTimeout);
663662
try {
664-
if (rewriteContext.request != null && openScrollContexts.get() >= maxOpenScrollContext) {
665-
throw new ElasticsearchException(
666-
"Trying to create too many scroll contexts. Must be less than or equal to: [" +
667-
maxOpenScrollContext + "]. " + "This limit can be set by changing the ["
668-
+ MAX_OPEN_SCROLL_CONTEXT.getKey() + "] setting.");
669-
}
670663
final ShardSearchRequest request = rewriteContext.request;
671664
if (request.scroll() != null) {
665+
context.addReleasable(openScrollContexts::decrementAndGet, Lifetime.CONTEXT);
666+
if (openScrollContexts.incrementAndGet() > maxOpenScrollContext) {
667+
throw new ElasticsearchException(
668+
"Trying to create too many scroll contexts. Must be less than or equal to: [" +
669+
maxOpenScrollContext + "]. " + "This limit can be set by changing the ["
670+
+ MAX_OPEN_SCROLL_CONTEXT.getKey() + "] setting.");
671+
}
672672
context.scrollContext(new ScrollContext());
673673
context.scrollContext().scroll = request.scroll();
674674
}
@@ -768,7 +768,6 @@ private void onFreeContext(SearchContext context) {
768768
assert activeContexts.containsKey(context.id().getId()) == false;
769769
context.indexShard().getSearchOperationListener().onFreeContext(context);
770770
if (context.scrollContext() != null) {
771-
openScrollContexts.decrementAndGet();
772771
context.indexShard().getSearchOperationListener().onFreeScrollContext(context);
773772
}
774773
}

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,47 @@ public void testMaxOpenScrollContexts() throws RuntimeException, IOException {
539539
ex.getMessage());
540540
}
541541

542+
public void testOpenScrollContextsConcurrently() throws Exception {
543+
createIndex("index");
544+
final IndicesService indicesService = getInstanceFromNode(IndicesService.class);
545+
final IndexShard indexShard = indicesService.indexServiceSafe(resolveIndex("index")).getShard(0);
546+
547+
final int maxScrollContexts = SearchService.MAX_OPEN_SCROLL_CONTEXT.get(Settings.EMPTY);
548+
final SearchService searchService = getInstanceFromNode(SearchService.class);
549+
Thread[] threads = new Thread[randomIntBetween(2, 8)];
550+
CountDownLatch latch = new CountDownLatch(threads.length);
551+
for (int i = 0; i < threads.length; i++) {
552+
threads[i] = new Thread(() -> {
553+
latch.countDown();
554+
try {
555+
latch.await();
556+
for (; ; ) {
557+
SearchService.SearchRewriteContext rewriteContext =
558+
searchService.acquireSearcherAndRewrite(new ShardScrollRequestTest(indexShard.shardId()), indexShard);
559+
try {
560+
searchService.createAndPutContext(rewriteContext);
561+
} catch (ElasticsearchException e) {
562+
assertThat(e.getMessage(), equalTo(
563+
"Trying to create too many scroll contexts. Must be less than or equal to: " +
564+
"[" + maxScrollContexts + "]. " +
565+
"This limit can be set by changing the [search.max_open_scroll_context] setting."));
566+
return;
567+
}
568+
}
569+
} catch (Exception e) {
570+
throw new AssertionError(e);
571+
}
572+
});
573+
threads[i].setName("elasticsearch[node_s_0][search]");
574+
threads[i].start();
575+
}
576+
for (Thread thread : threads) {
577+
thread.join();
578+
}
579+
assertThat(searchService.getActiveContexts(), equalTo(maxScrollContexts));
580+
searchService.freeAllScrollContexts();
581+
}
582+
542583
public static class FailOnRewriteQueryPlugin extends Plugin implements SearchPlugin {
543584
@Override
544585
public List<QuerySpec<?>> getQueries() {

0 commit comments

Comments
 (0)