Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix leaking searcher when shards are removed or relocated #52099

Merged
merged 4 commits into from
Feb 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions server/src/main/java/org/elasticsearch/search/SearchService.java
Original file line number Diff line number Diff line change
Expand Up @@ -694,22 +694,23 @@ public DefaultSearchContext createSearchContext(ShardSearchRequest request, Time
}

private DefaultSearchContext createSearchContext(SearchRewriteContext rewriteContext, TimeValue timeout) {
final ShardSearchRequest request = rewriteContext.request;
final Engine.Searcher searcher = rewriteContext.searcher;
IndexService indexService = indicesService.indexServiceSafe(request.shardId().getIndex());
IndexShard indexShard = indexService.getShard(request.shardId().getId());
SearchShardTarget shardTarget = new SearchShardTarget(clusterService.localNode().getId(),
indexShard.shardId(), request.getClusterAlias(), OriginalIndices.NONE);
boolean success = false;
try {
final ShardSearchRequest request = rewriteContext.request;
final Engine.Searcher searcher = rewriteContext.searcher;
IndexService indexService = indicesService.indexServiceSafe(request.shardId().getIndex());
IndexShard indexShard = indexService.getShard(request.shardId().getId());
SearchShardTarget shardTarget = new SearchShardTarget(clusterService.localNode().getId(),
indexShard.shardId(), request.getClusterAlias(), OriginalIndices.NONE);
DefaultSearchContext searchContext = new DefaultSearchContext(idGenerator.incrementAndGet(), request, shardTarget,
searcher, clusterService, indexService, indexShard, bigArrays, threadPool::relativeTimeInMillis, timeout, fetchPhase);
success = true;
return searchContext;
} finally {
if (success == false) {
// we handle the case where the DefaultSearchContext constructor throws an exception since we would otherwise
// leak a searcher and this can have severe implications (unable to obtain shard lock exceptions).
// we handle the case where `IndicesService#indexServiceSafe`or `IndexService#getShard`, or the DefaultSearchContext
// constructor throws an exception since we would otherwise leak a searcher and this can have severe implications
// (unable to obtain shard lock exceptions).
IOUtils.closeWhileHandlingException(rewriteContext.searcher);
}
}
Expand Down
22 changes: 20 additions & 2 deletions server/src/test/java/org/elasticsearch/recovery/RelocationIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.index.IndexService;
import org.elasticsearch.index.IndexSettings;
import org.elasticsearch.index.seqno.ReplicationTracker;
import org.elasticsearch.index.seqno.RetentionLease;
import org.elasticsearch.index.shard.IndexEventListener;
Expand Down Expand Up @@ -86,6 +87,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -456,7 +458,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
}
}

public void testIndexAndRelocateConcurrently() throws Exception {
public void testIndexSearchAndRelocateConcurrently() throws Exception {
int halfNodes = randomIntBetween(1, 3);
Settings[] nodeSettings = Stream.concat(
Stream.generate(() -> Settings.builder().put("node.attr.color", "blue").build()).limit(halfNodes),
Expand All @@ -473,8 +475,21 @@ public void testIndexAndRelocateConcurrently() throws Exception {
.put("index.routing.allocation.exclude.color", "blue")
.put(indexSettings())
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, randomInt(halfNodes - 1));
if (randomBoolean()) {
settings.put(IndexSettings.INDEX_REFRESH_INTERVAL_SETTING.getKey(), randomIntBetween(1, 10) + "s");
}
assertAcked(prepareCreate("test", settings));
assertAllShardsOnNodes("test", redNodes);
AtomicBoolean stopped = new AtomicBoolean(false);
Thread[] searchThreads = randomBoolean() ? new Thread[0] : new Thread[randomIntBetween(1, 4)];
for (int i = 0; i < searchThreads.length; i++) {
searchThreads[i] = new Thread(() -> {
while (stopped.get() == false) {
assertNoFailures(client().prepareSearch("test").setRequestCache(false).get());
}
});
searchThreads[i].start();
}
int numDocs = randomIntBetween(100, 150);
ArrayList<String> ids = new ArrayList<>();
logger.info(" --> indexing [{}] docs", numDocs);
Expand Down Expand Up @@ -512,7 +527,10 @@ public void testIndexAndRelocateConcurrently() throws Exception {
assertNoFailures(afterRelocation);
assertSearchHits(afterRelocation, ids.toArray(new String[ids.size()]));
}

stopped.set(true);
for (Thread searchThread : searchThreads) {
searchThread.join();
}
}

public void testRelocateWhileWaitingForRefresh() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -898,4 +898,35 @@ public void onFailure(Exception e) {
latch.await();
}
}

public void testDeleteIndexWhileSearch() throws Exception {
createIndex("test");
int numDocs = randomIntBetween(1, 20);
for (int i = 0; i < numDocs; i++) {
client().prepareIndex("test").setSource("f", "v").get();
}
client().admin().indices().prepareRefresh("test").get();
AtomicBoolean stopped = new AtomicBoolean(false);
Thread[] searchers = new Thread[randomIntBetween(1, 4)];
CountDownLatch latch = new CountDownLatch(searchers.length);
for (int i = 0; i < searchers.length; i++) {
searchers[i] = new Thread(() -> {
latch.countDown();
while (stopped.get() == false) {
try {
client().prepareSearch("test").setRequestCache(false).get();
} catch (Exception ignored) {
return;
}
}
});
searchers[i].start();
}
latch.await();
client().admin().indices().prepareDelete("test").get();
stopped.set(true);
for (Thread searcher : searchers) {
searcher.join();
}
}
}