Skip to content

Commit 4287010

Browse files
Fix concurrent search and index delete (elastic#42621)
Changed order of listener invocation so that we notify before registering search context and notify after unregistering same. This ensures that count up/down like what we do in ShardSearchStats works. Otherwise, we risk notifying onFreeScrollContext before notifying onNewScrollContext (same for onFreeContext/onNewContext, but we currently have no assertions failing in those). Closes elastic#28053
1 parent 04a7a19 commit 4287010

File tree

2 files changed

+53
-14
lines changed

2 files changed

+53
-14
lines changed

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

+31-10
Original file line numberDiff line numberDiff line change
@@ -594,19 +594,35 @@ final SearchContext createAndPutContext(ShardSearchRequest request) throws IOExc
594594
}
595595

596596
SearchContext context = createContext(request);
597+
onNewContext(context);
597598
boolean success = false;
598599
try {
599600
putContext(context);
600-
if (request.scroll() != null) {
601+
success = true;
602+
return context;
603+
} finally {
604+
if (success == false) {
605+
freeContext(context.id());
606+
}
607+
}
608+
}
609+
610+
private void onNewContext(SearchContext context) {
611+
boolean success = false;
612+
try {
613+
if (context.scrollContext() != null) {
601614
openScrollContexts.incrementAndGet();
602615
context.indexShard().getSearchOperationListener().onNewScrollContext(context);
603616
}
604617
context.indexShard().getSearchOperationListener().onNewContext(context);
605618
success = true;
606-
return context;
607619
} finally {
608-
if (!success) {
609-
freeContext(context.id());
620+
// currently, the concrete listener is CompositeListener, which swallows exceptions, but here we anyway try to do the
621+
// right thing by closing and notifying onFreeXXX in case one of the listeners fails with an exception in the future.
622+
if (success == false) {
623+
try (SearchContext dummy = context) {
624+
onFreeContext(context);
625+
}
610626
}
611627
}
612628
}
@@ -693,13 +709,8 @@ private void freeAllContextForIndex(Index index) {
693709
public boolean freeContext(long id) {
694710
final SearchContext context = removeContext(id);
695711
if (context != null) {
696-
assert context.refCount() > 0 : " refCount must be > 0: " + context.refCount();
697712
try {
698-
context.indexShard().getSearchOperationListener().onFreeContext(context);
699-
if (context.scrollContext() != null) {
700-
openScrollContexts.decrementAndGet();
701-
context.indexShard().getSearchOperationListener().onFreeScrollContext(context);
702-
}
713+
onFreeContext(context);
703714
} finally {
704715
context.close();
705716
}
@@ -708,6 +719,16 @@ public boolean freeContext(long id) {
708719
return false;
709720
}
710721

722+
private void onFreeContext(SearchContext context) {
723+
assert context.refCount() > 0 : " refCount must be > 0: " + context.refCount();
724+
assert activeContexts.containsKey(context.id()) == false;
725+
context.indexShard().getSearchOperationListener().onFreeContext(context);
726+
if (context.scrollContext() != null) {
727+
openScrollContexts.decrementAndGet();
728+
context.indexShard().getSearchOperationListener().onFreeScrollContext(context);
729+
}
730+
}
731+
711732
public void freeAllScrollContexts() {
712733
for (SearchContext searchContext : activeContexts.values()) {
713734
if (searchContext.scrollContext() != null) {

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

+22-4
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
package org.elasticsearch.search;
2020

2121
import com.carrotsearch.hppc.IntArrayList;
22-
2322
import org.apache.lucene.search.Query;
2423
import org.apache.lucene.store.AlreadyClosedException;
2524
import org.elasticsearch.ElasticsearchException;
@@ -52,6 +51,7 @@
5251
import org.elasticsearch.index.query.QueryRewriteContext;
5352
import org.elasticsearch.index.query.QueryShardContext;
5453
import org.elasticsearch.index.query.TermQueryBuilder;
54+
import org.elasticsearch.index.search.stats.SearchStats;
5555
import org.elasticsearch.index.shard.IndexShard;
5656
import org.elasticsearch.index.shard.SearchOperationListener;
5757
import org.elasticsearch.index.shard.ShardId;
@@ -229,6 +229,7 @@ public void testSearchWhileIndexDeleted() throws InterruptedException {
229229
AtomicBoolean running = new AtomicBoolean(true);
230230
CountDownLatch startGun = new CountDownLatch(1);
231231
Semaphore semaphore = new Semaphore(Integer.MAX_VALUE);
232+
232233
final Thread thread = new Thread() {
233234
@Override
234235
public void run() {
@@ -267,10 +268,17 @@ public void onFailure(Exception e) {
267268
try {
268269
try {
269270
PlainActionFuture<SearchPhaseResult> result = new PlainActionFuture<>();
270-
service.executeQueryPhase(
271-
new ShardSearchLocalRequest(indexShard.shardId(), 1, SearchType.DEFAULT,
271+
final boolean useScroll = randomBoolean();
272+
ShardSearchLocalRequest shardRequest;
273+
if (useScroll) {
274+
shardRequest = new ShardScrollRequestTest(indexShard.shardId());
275+
} else {
276+
shardRequest = new ShardSearchLocalRequest(indexShard.shardId(), 1, SearchType.DEFAULT,
272277
new SearchSourceBuilder(), new String[0], false, new AliasFilter(null, Strings.EMPTY_ARRAY), 1.0f,
273-
true, null, null),
278+
true, null, null);
279+
}
280+
service.executeQueryPhase(
281+
shardRequest,
274282
new SearchTask(123L, "", "", "", null, Collections.emptyMap()), result);
275283
SearchPhaseResult searchPhaseResult = result.get();
276284
IntArrayList intCursors = new IntArrayList(1);
@@ -279,6 +287,9 @@ public void onFailure(Exception e) {
279287
PlainActionFuture<FetchSearchResult> listener = new PlainActionFuture<>();
280288
service.executeFetchPhase(req, new SearchTask(123L, "", "", "", null, Collections.emptyMap()), listener);
281289
listener.get();
290+
if (useScroll) {
291+
service.freeContext(searchPhaseResult.getRequestId());
292+
}
282293
} catch (ExecutionException ex) {
283294
assertThat(ex.getCause(), instanceOf(RuntimeException.class));
284295
throw ((RuntimeException)ex.getCause());
@@ -296,6 +307,13 @@ public void onFailure(Exception e) {
296307
thread.join();
297308
semaphore.acquire(Integer.MAX_VALUE);
298309
}
310+
311+
assertEquals(0, service.getActiveContexts());
312+
313+
SearchStats.Stats totalStats = indexShard.searchStats().getTotal();
314+
assertEquals(0, totalStats.getQueryCurrent());
315+
assertEquals(0, totalStats.getScrollCurrent());
316+
assertEquals(0, totalStats.getFetchCurrent());
299317
}
300318

301319
public void testTimeout() throws IOException {

0 commit comments

Comments
 (0)