Skip to content

Commit 36431bf

Browse files
committed
Close query cache on index service creation failure
Today it is possible that we create the `QueryCache` and then fail to create the owning `IndexService` and this means we do not close the `QueryCache` again. This commit addresses that leak. Fixes elastic#48186
1 parent 1bc1a73 commit 36431bf

File tree

2 files changed

+60
-14
lines changed

2 files changed

+60
-14
lines changed

server/src/main/java/org/elasticsearch/index/IndexModule.java

+20-10
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.elasticsearch.common.settings.Settings;
4040
import org.elasticsearch.common.util.BigArrays;
4141
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
42+
import org.elasticsearch.core.internal.io.IOUtils;
4243
import org.elasticsearch.env.NodeEnvironment;
4344
import org.elasticsearch.index.analysis.AnalysisRegistry;
4445
import org.elasticsearch.index.cache.query.DisabledQueryCache;
@@ -399,22 +400,31 @@ public IndexService newIndexService(
399400
indexReaderWrapper.get() == null ? (shard) -> null : indexReaderWrapper.get();
400401
eventListener.beforeIndexCreated(indexSettings.getIndex(), indexSettings.getSettings());
401402
final IndexStorePlugin.DirectoryFactory directoryFactory = getDirectoryFactory(indexSettings, directoryFactories);
402-
final QueryCache queryCache;
403-
if (indexSettings.getValue(INDEX_QUERY_CACHE_ENABLED_SETTING)) {
404-
BiFunction<IndexSettings, IndicesQueryCache, QueryCache> queryCacheProvider = forceQueryCacheProvider.get();
405-
if (queryCacheProvider == null) {
406-
queryCache = new IndexQueryCache(indexSettings, indicesQueryCache);
403+
QueryCache queryCache = null;
404+
boolean success = false;
405+
try {
406+
if (indexSettings.getValue(INDEX_QUERY_CACHE_ENABLED_SETTING)) {
407+
BiFunction<IndexSettings, IndicesQueryCache, QueryCache> queryCacheProvider = forceQueryCacheProvider.get();
408+
if (queryCacheProvider == null) {
409+
queryCache = new IndexQueryCache(indexSettings, indicesQueryCache);
410+
} else {
411+
queryCache = queryCacheProvider.apply(indexSettings, indicesQueryCache);
412+
}
407413
} else {
408-
queryCache = queryCacheProvider.apply(indexSettings, indicesQueryCache);
414+
queryCache = new DisabledQueryCache(indexSettings);
409415
}
410-
} else {
411-
queryCache = new DisabledQueryCache(indexSettings);
412-
}
413-
return new IndexService(indexSettings, indexCreationContext, environment, xContentRegistry,
416+
final IndexService indexService = new IndexService(indexSettings, indexCreationContext, environment, xContentRegistry,
414417
new SimilarityService(indexSettings, scriptService, similarities),
415418
shardStoreDeleter, analysisRegistry, engineFactory, circuitBreakerService, bigArrays, threadPool, scriptService,
416419
clusterService, client, queryCache, directoryFactory, eventListener, readerWrapperFactory, mapperRegistry,
417420
indicesFieldDataCache, searchOperationListeners, indexOperationListeners, namedWriteableRegistry);
421+
success = true;
422+
return indexService;
423+
} finally {
424+
if (success == false) {
425+
IOUtils.closeWhileHandlingException(queryCache);
426+
}
427+
}
418428
}
419429

420430
private static IndexStorePlugin.DirectoryFactory getDirectoryFactory(

server/src/test/java/org/elasticsearch/index/IndexModuleTests.java

+40-4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.elasticsearch.common.settings.Settings;
4141
import org.elasticsearch.common.util.BigArrays;
4242
import org.elasticsearch.common.util.PageCacheRecycler;
43+
import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException;
4344
import org.elasticsearch.core.internal.io.IOUtils;
4445
import org.elasticsearch.env.Environment;
4546
import org.elasticsearch.env.NodeEnvironment;
@@ -84,13 +85,16 @@
8485

8586
import java.io.IOException;
8687
import java.util.Collections;
88+
import java.util.HashSet;
8789
import java.util.Map;
90+
import java.util.Set;
8891
import java.util.concurrent.TimeUnit;
8992
import java.util.concurrent.atomic.AtomicBoolean;
9093

9194
import static java.util.Collections.emptyMap;
9295
import static org.elasticsearch.index.IndexService.IndexCreationContext.CREATE_INDEX;
9396
import static org.hamcrest.Matchers.containsString;
97+
import static org.hamcrest.Matchers.empty;
9498
import static org.hamcrest.Matchers.hasToString;
9599
import static org.hamcrest.Matchers.instanceOf;
96100

@@ -354,11 +358,19 @@ public void testForceCustomQueryCache() throws IOException {
354358
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).build();
355359
final IndexSettings indexSettings = IndexSettingsModule.newIndexSettings("foo", settings);
356360
IndexModule module = new IndexModule(indexSettings, emptyAnalysisRegistry, new InternalEngineFactory(), Collections.emptyMap());
357-
module.forceQueryCacheProvider((a, b) -> new CustomQueryCache());
358-
expectThrows(AlreadySetException.class, () -> module.forceQueryCacheProvider((a, b) -> new CustomQueryCache()));
361+
final Set<CustomQueryCache> liveQueryCaches = new HashSet<>();
362+
module.forceQueryCacheProvider((a, b) -> {
363+
final CustomQueryCache customQueryCache = new CustomQueryCache(liveQueryCaches);
364+
liveQueryCaches.add(customQueryCache);
365+
return customQueryCache;
366+
});
367+
expectThrows(AlreadySetException.class, () -> module.forceQueryCacheProvider((a, b) -> {
368+
throw new AssertionError("never called");
369+
}));
359370
IndexService indexService = newIndexService(module);
360371
assertTrue(indexService.cache().query() instanceof CustomQueryCache);
361372
indexService.close("simon says", false);
373+
assertThat(liveQueryCaches, empty());
362374
}
363375

364376
public void testDefaultQueryCacheImplIsSelected() throws IOException {
@@ -379,12 +391,29 @@ public void testDisableQueryCacheHasPrecedenceOverForceQueryCache() throws IOExc
379391
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).build();
380392
final IndexSettings indexSettings = IndexSettingsModule.newIndexSettings("foo", settings);
381393
IndexModule module = new IndexModule(indexSettings, emptyAnalysisRegistry, new InternalEngineFactory(), Collections.emptyMap());
382-
module.forceQueryCacheProvider((a, b) -> new CustomQueryCache());
394+
module.forceQueryCacheProvider((a, b) -> new CustomQueryCache(null));
383395
IndexService indexService = newIndexService(module);
384396
assertTrue(indexService.cache().query() instanceof DisabledQueryCache);
385397
indexService.close("simon says", false);
386398
}
387399

400+
public void testCustomQueryCacheCleanedUpIfIndexServiceCreationFails() {
401+
Settings settings = Settings.builder()
402+
.put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString())
403+
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).build();
404+
final IndexSettings indexSettings = IndexSettingsModule.newIndexSettings("foo", settings);
405+
IndexModule module = new IndexModule(indexSettings, emptyAnalysisRegistry, new InternalEngineFactory(), Collections.emptyMap());
406+
final Set<CustomQueryCache> liveQueryCaches = new HashSet<>();
407+
module.forceQueryCacheProvider((a, b) -> {
408+
final CustomQueryCache customQueryCache = new CustomQueryCache(liveQueryCaches);
409+
liveQueryCaches.add(customQueryCache);
410+
return customQueryCache;
411+
});
412+
threadPool.shutdown(); // causes index service creation to fail
413+
expectThrows(EsRejectedExecutionException.class, () -> newIndexService(module));
414+
assertThat(liveQueryCaches, empty());
415+
}
416+
388417
public void testMmapNotAllowed() {
389418
String storeType = randomFrom(IndexModule.Type.HYBRIDFS.getSettingsKey(), IndexModule.Type.MMAPFS.getSettingsKey());
390419
final Settings settings = Settings.builder()
@@ -403,12 +432,19 @@ public void testMmapNotAllowed() {
403432

404433
class CustomQueryCache implements QueryCache {
405434

435+
private final Set<CustomQueryCache> liveQueryCaches;
436+
437+
CustomQueryCache(Set<CustomQueryCache> liveQueryCaches) {
438+
this.liveQueryCaches = liveQueryCaches;
439+
}
440+
406441
@Override
407442
public void clear(String reason) {
408443
}
409444

410445
@Override
411-
public void close() throws IOException {
446+
public void close() {
447+
assertTrue(liveQueryCaches == null || liveQueryCaches.remove(this));
412448
}
413449

414450
@Override

0 commit comments

Comments
 (0)