Skip to content

Commit f14e5f4

Browse files
authored
Fix scroll contexts counter in SearchService (#71354)
The scroll context counter can be negative even become Integer.MAX_VALUE after handling many search requests. This bug causes two issues: - Disable the limit of open scroll contexts when the counter is negative - Prevent opening new scroll contexts when the counter is greater than the limit of open scroll contexts Relates #53449 Closes #56202
1 parent 5d0321c commit f14e5f4

File tree

5 files changed

+50
-14
lines changed

5 files changed

+50
-14
lines changed

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.elasticsearch.common.component.AbstractLifecycleComponent;
3434
import org.elasticsearch.common.io.stream.StreamInput;
3535
import org.elasticsearch.common.io.stream.StreamOutput;
36+
import org.elasticsearch.common.lease.Releasable;
3637
import org.elasticsearch.common.logging.DeprecationLogger;
3738
import org.elasticsearch.common.lucene.Lucene;
3839
import org.elasticsearch.common.settings.Setting;
@@ -574,6 +575,7 @@ private SearchContext findContext(long id, TransportRequest request) throws Sear
574575
}
575576

576577
final SearchContext createAndPutContext(ShardSearchRequest request) throws IOException {
578+
final Releasable decreaseScrollContexts;
577579
if (request.scroll() != null) {
578580
if (maxOpenScrollContext == Integer.MAX_VALUE && openScrollContexts.get() > 500) {
579581
/**
@@ -593,14 +595,17 @@ final SearchContext createAndPutContext(ShardSearchRequest request) throws IOExc
593595
maxOpenScrollContext + "]. " + "This limit can be set by changing the ["
594596
+ MAX_OPEN_SCROLL_CONTEXT.getKey() + "] setting.");
595597
}
598+
decreaseScrollContexts = openScrollContexts::decrementAndGet;
599+
} else {
600+
decreaseScrollContexts = () -> {};
596601
}
597602
SearchContext context = null;
598603
try {
599604
context = createContext(request);
600-
context.addReleasable(openScrollContexts::decrementAndGet, Lifetime.CONTEXT);
605+
context.addReleasable(decreaseScrollContexts, Lifetime.CONTEXT);
601606
} finally {
602607
if (context == null) {
603-
openScrollContexts.decrementAndGet();
608+
decreaseScrollContexts.close();
604609
}
605610
}
606611
onNewContext(context);
@@ -1023,6 +1028,13 @@ public int getActiveContexts() {
10231028
return this.activeContexts.size();
10241029
}
10251030

1031+
/**
1032+
* Returns the number of scroll contexts opened on the node
1033+
*/
1034+
public int getOpenScrollContexts() {
1035+
return openScrollContexts.get();
1036+
}
1037+
10261038
public ResponseCollectorService getResponseCollectorService() {
10271039
return this.responseCollectorService;
10281040
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,5 +796,6 @@ public void testCreateSearchContext() throws IOException {
796796
assertSame(searchShardTarget, searchContext.dfsResult().getSearchShardTarget());
797797
assertSame(searchShardTarget, searchContext.queryResult().getSearchShardTarget());
798798
assertSame(searchShardTarget, searchContext.fetchResult().getSearchShardTarget());
799+
assertThat(service.getOpenScrollContexts(), equalTo(0));
799800
}
800801
}

test/framework/src/main/java/org/elasticsearch/test/ESSingleNodeTestCase.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import org.elasticsearch.node.NodeValidationException;
5151
import org.elasticsearch.plugins.Plugin;
5252
import org.elasticsearch.script.ScriptService;
53+
import org.elasticsearch.search.SearchService;
5354
import org.elasticsearch.search.internal.SearchContext;
5455
import org.elasticsearch.test.discovery.TestZenDiscovery;
5556
import org.elasticsearch.threadpool.ThreadPool;
@@ -121,6 +122,9 @@ public void setUp() throws Exception {
121122
@Override
122123
public void tearDown() throws Exception {
123124
logger.info("[{}#{}]: cleaning up after test", getTestClass().getSimpleName(), getTestName());
125+
SearchService searchService = getInstanceFromNode(SearchService.class);
126+
assertThat(searchService.getActiveContexts(), equalTo(0));
127+
assertThat(searchService.getOpenScrollContexts(), equalTo(0));
124128
super.tearDown();
125129
assertAcked(client().admin().indices().prepareDelete("*").get());
126130
MetaData metaData = client().admin().cluster().prepareState().get().getState().getMetaData();

test/framework/src/main/java/org/elasticsearch/test/InternalTestCluster.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2355,6 +2355,7 @@ public void ensureEstimatedStats() {
23552355
public void assertAfterTest() throws IOException {
23562356
super.assertAfterTest();
23572357
assertRequestsFinished();
2358+
assertSearchContextsReleased();
23582359
for (NodeAndClient nodeAndClient : nodes.values()) {
23592360
NodeEnvironment env = nodeAndClient.node().getNodeEnvironment();
23602361
Set<ShardId> shardIds = env.lockedShards();
@@ -2388,4 +2389,18 @@ private void assertRequestsFinished() {
23882389
}
23892390
}
23902391
}
2392+
2393+
private void assertSearchContextsReleased() {
2394+
for (NodeAndClient nodeAndClient : nodes.values()) {
2395+
SearchService searchService = getInstance(SearchService.class, nodeAndClient.name);
2396+
try {
2397+
assertBusy(() -> {
2398+
assertThat(searchService.getActiveContexts(), equalTo(0));
2399+
assertThat(searchService.getOpenScrollContexts(), equalTo(0));
2400+
});
2401+
} catch (Exception e) {
2402+
throw new AssertionError("Failed to verify search contexts", e);
2403+
}
2404+
}
2405+
}
23912406
}

x-pack/plugin/core/src/test/java/org/elasticsearch/index/engine/FrozenIndexTests.java

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -121,18 +121,22 @@ public void testCloseFreezeAndOpen() throws ExecutionException, InterruptedExcep
121121
// now scroll
122122
SearchResponse searchResponse = client().prepareSearch().setIndicesOptions(IndicesOptions.STRICT_EXPAND_OPEN_FORBID_CLOSED)
123123
.setScroll(TimeValue.timeValueMinutes(1)).setSize(1).get();
124-
do {
125-
assertHitCount(searchResponse, 3);
126-
assertEquals(1, searchResponse.getHits().getHits().length);
127-
SearchService searchService = getInstanceFromNode(SearchService.class);
128-
assertThat(searchService.getActiveContexts(), Matchers.greaterThanOrEqualTo(1));
129-
for (int i = 0; i < 2; i++) {
130-
shard = indexService.getShard(i);
131-
engine = IndexShardTestCase.getEngine(shard);
132-
assertFalse(((FrozenEngine) engine).isReaderOpen());
133-
}
134-
searchResponse = client().prepareSearchScroll(searchResponse.getScrollId()).setScroll(TimeValue.timeValueMinutes(1)).get();
135-
} while (searchResponse.getHits().getHits().length > 0);
124+
try {
125+
do {
126+
assertHitCount(searchResponse, 3);
127+
assertEquals(1, searchResponse.getHits().getHits().length);
128+
SearchService searchService = getInstanceFromNode(SearchService.class);
129+
assertThat(searchService.getActiveContexts(), Matchers.greaterThanOrEqualTo(1));
130+
for (int i = 0; i < 2; i++) {
131+
shard = indexService.getShard(i);
132+
engine = IndexShardTestCase.getEngine(shard);
133+
assertFalse(((FrozenEngine) engine).isReaderOpen());
134+
}
135+
searchResponse = client().prepareSearchScroll(searchResponse.getScrollId()).setScroll(TimeValue.timeValueMinutes(1)).get();
136+
} while (searchResponse.getHits().getHits().length > 0);
137+
} finally {
138+
client().prepareClearScroll().addScrollId(searchResponse.getScrollId()).get();
139+
}
136140
}
137141

138142
public void testSearchAndGetAPIsAreThrottled() throws InterruptedException, IOException, ExecutionException {

0 commit comments

Comments
 (0)