Skip to content

Commit f9a9dcb

Browse files
authored
Close query cache on index service creation failure (elastic#48230)
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 f9227da commit f9a9dcb

File tree

3 files changed

+136
-33
lines changed

3 files changed

+136
-33
lines changed

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

+28-13
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@
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;
45+
import org.elasticsearch.index.analysis.IndexAnalyzers;
4446
import org.elasticsearch.index.cache.query.DisabledQueryCache;
4547
import org.elasticsearch.index.cache.query.IndexQueryCache;
4648
import org.elasticsearch.index.cache.query.QueryCache;
@@ -399,22 +401,35 @@ public IndexService newIndexService(
399401
indexReaderWrapper.get() == null ? (shard) -> null : indexReaderWrapper.get();
400402
eventListener.beforeIndexCreated(indexSettings.getIndex(), indexSettings.getSettings());
401403
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);
404+
QueryCache queryCache = null;
405+
IndexAnalyzers indexAnalyzers = null;
406+
boolean success = false;
407+
try {
408+
if (indexSettings.getValue(INDEX_QUERY_CACHE_ENABLED_SETTING)) {
409+
BiFunction<IndexSettings, IndicesQueryCache, QueryCache> queryCacheProvider = forceQueryCacheProvider.get();
410+
if (queryCacheProvider == null) {
411+
queryCache = new IndexQueryCache(indexSettings, indicesQueryCache);
412+
} else {
413+
queryCache = queryCacheProvider.apply(indexSettings, indicesQueryCache);
414+
}
407415
} else {
408-
queryCache = queryCacheProvider.apply(indexSettings, indicesQueryCache);
416+
queryCache = new DisabledQueryCache(indexSettings);
417+
}
418+
if (IndexService.needsMapperService(indexSettings, indexCreationContext)) {
419+
indexAnalyzers = analysisRegistry.build(indexSettings);
420+
}
421+
final IndexService indexService = new IndexService(indexSettings, indexCreationContext, environment, xContentRegistry,
422+
new SimilarityService(indexSettings, scriptService, similarities), shardStoreDeleter, indexAnalyzers,
423+
engineFactory, circuitBreakerService, bigArrays, threadPool, scriptService, clusterService, client, queryCache,
424+
directoryFactory, eventListener, readerWrapperFactory, mapperRegistry, indicesFieldDataCache, searchOperationListeners,
425+
indexOperationListeners, namedWriteableRegistry);
426+
success = true;
427+
return indexService;
428+
} finally {
429+
if (success == false) {
430+
IOUtils.closeWhileHandlingException(queryCache, indexAnalyzers);
409431
}
410-
} else {
411-
queryCache = new DisabledQueryCache(indexSettings);
412432
}
413-
return new IndexService(indexSettings, indexCreationContext, environment, xContentRegistry,
414-
new SimilarityService(indexSettings, scriptService, similarities),
415-
shardStoreDeleter, analysisRegistry, engineFactory, circuitBreakerService, bigArrays, threadPool, scriptService,
416-
clusterService, client, queryCache, directoryFactory, eventListener, readerWrapperFactory, mapperRegistry,
417-
indicesFieldDataCache, searchOperationListeners, indexOperationListeners, namedWriteableRegistry);
418433
}
419434

420435
private static IndexStorePlugin.DirectoryFactory getDirectoryFactory(

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

+18-15
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
import org.elasticsearch.env.NodeEnvironment;
4949
import org.elasticsearch.env.ShardLock;
5050
import org.elasticsearch.env.ShardLockObtainFailedException;
51-
import org.elasticsearch.index.analysis.AnalysisRegistry;
5251
import org.elasticsearch.index.analysis.IndexAnalyzers;
5352
import org.elasticsearch.index.cache.IndexCache;
5453
import org.elasticsearch.index.cache.bitset.BitsetFilterCache;
@@ -147,8 +146,7 @@ public IndexService(
147146
NamedXContentRegistry xContentRegistry,
148147
SimilarityService similarityService,
149148
ShardStoreDeleter shardStoreDeleter,
150-
AnalysisRegistry registry,
151-
EngineFactory engineFactory,
149+
IndexAnalyzers indexAnalyzers, EngineFactory engineFactory,
152150
CircuitBreakerService circuitBreakerService,
153151
BigArrays bigArrays,
154152
ThreadPool threadPool,
@@ -163,24 +161,16 @@ public IndexService(
163161
IndicesFieldDataCache indicesFieldDataCache,
164162
List<SearchOperationListener> searchOperationListeners,
165163
List<IndexingOperationListener> indexingOperationListeners,
166-
NamedWriteableRegistry namedWriteableRegistry) throws IOException {
164+
NamedWriteableRegistry namedWriteableRegistry) {
167165
super(indexSettings);
168166
this.indexSettings = indexSettings;
169167
this.xContentRegistry = xContentRegistry;
170168
this.similarityService = similarityService;
171169
this.namedWriteableRegistry = namedWriteableRegistry;
172170
this.circuitBreakerService = circuitBreakerService;
173-
if (indexSettings.getIndexMetaData().getState() == IndexMetaData.State.CLOSE &&
174-
indexCreationContext == IndexCreationContext.CREATE_INDEX) { // metadata verification needs a mapper service
175-
this.mapperService = null;
176-
this.indexFieldData = null;
177-
this.indexSortSupplier = () -> null;
178-
this.bitsetFilterCache = null;
179-
this.warmer = null;
180-
this.indexCache = null;
181-
} else {
182-
this.mapperService = new MapperService(indexSettings, registry.build(indexSettings), xContentRegistry, similarityService,
183-
mapperRegistry,
171+
if (needsMapperService(indexSettings, indexCreationContext)) {
172+
assert indexAnalyzers != null;
173+
this.mapperService = new MapperService(indexSettings, indexAnalyzers, xContentRegistry, similarityService, mapperRegistry,
184174
// we parse all percolator queries as they would be parsed on shard 0
185175
() -> newQueryShardContext(0, null, System::currentTimeMillis, null));
186176
this.indexFieldData = new IndexFieldDataService(indexSettings, indicesFieldDataCache, circuitBreakerService, mapperService);
@@ -198,6 +188,14 @@ public IndexService(
198188
this.bitsetFilterCache = new BitsetFilterCache(indexSettings, new BitsetCacheListener(this));
199189
this.warmer = new IndexWarmer(threadPool, indexFieldData, bitsetFilterCache.createListener(threadPool));
200190
this.indexCache = new IndexCache(indexSettings, queryCache, bitsetFilterCache);
191+
} else {
192+
assert indexAnalyzers == null;
193+
this.mapperService = null;
194+
this.indexFieldData = null;
195+
this.indexSortSupplier = () -> null;
196+
this.bitsetFilterCache = null;
197+
this.warmer = null;
198+
this.indexCache = null;
201199
}
202200

203201
this.shardStoreDeleter = shardStoreDeleter;
@@ -222,6 +220,11 @@ public IndexService(
222220
updateFsyncTaskIfNecessary();
223221
}
224222

223+
static boolean needsMapperService(IndexSettings indexSettings, IndexCreationContext indexCreationContext) {
224+
return false == (indexSettings.getIndexMetaData().getState() == IndexMetaData.State.CLOSE &&
225+
indexCreationContext == IndexCreationContext.CREATE_INDEX); // metadata verification needs a mapper service
226+
}
227+
225228
public enum IndexCreationContext {
226229
CREATE_INDEX,
227230
META_DATA_VERIFICATION

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

+90-5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package org.elasticsearch.index;
2020

21+
import org.apache.lucene.analysis.Analyzer;
2122
import org.apache.lucene.index.AssertingDirectoryReader;
2223
import org.apache.lucene.index.DirectoryReader;
2324
import org.apache.lucene.index.FieldInvertState;
@@ -40,12 +41,15 @@
4041
import org.elasticsearch.common.settings.Settings;
4142
import org.elasticsearch.common.util.BigArrays;
4243
import org.elasticsearch.common.util.PageCacheRecycler;
44+
import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException;
4345
import org.elasticsearch.core.internal.io.IOUtils;
4446
import org.elasticsearch.env.Environment;
4547
import org.elasticsearch.env.NodeEnvironment;
4648
import org.elasticsearch.env.ShardLock;
4749
import org.elasticsearch.env.TestEnvironment;
4850
import org.elasticsearch.index.analysis.AnalysisRegistry;
51+
import org.elasticsearch.index.analysis.AnalyzerProvider;
52+
import org.elasticsearch.index.analysis.AnalyzerScope;
4953
import org.elasticsearch.index.cache.query.DisabledQueryCache;
5054
import org.elasticsearch.index.cache.query.IndexQueryCache;
5155
import org.elasticsearch.index.cache.query.QueryCache;
@@ -65,6 +69,7 @@
6569
import org.elasticsearch.index.store.FsDirectoryFactory;
6670
import org.elasticsearch.indices.IndicesModule;
6771
import org.elasticsearch.indices.IndicesQueryCache;
72+
import org.elasticsearch.indices.analysis.AnalysisModule;
6873
import org.elasticsearch.indices.breaker.CircuitBreakerService;
6974
import org.elasticsearch.indices.breaker.NoneCircuitBreakerService;
7075
import org.elasticsearch.indices.cluster.IndicesClusterStateService.AllocatedIndices.IndexRemovalReason;
@@ -84,13 +89,17 @@
8489

8590
import java.io.IOException;
8691
import java.util.Collections;
92+
import java.util.HashSet;
8793
import java.util.Map;
94+
import java.util.Set;
8895
import java.util.concurrent.TimeUnit;
8996
import java.util.concurrent.atomic.AtomicBoolean;
9097

9198
import static java.util.Collections.emptyMap;
99+
import static java.util.Collections.singletonMap;
92100
import static org.elasticsearch.index.IndexService.IndexCreationContext.CREATE_INDEX;
93101
import static org.hamcrest.Matchers.containsString;
102+
import static org.hamcrest.Matchers.empty;
94103
import static org.hamcrest.Matchers.hasToString;
95104
import static org.hamcrest.Matchers.instanceOf;
96105

@@ -174,7 +183,7 @@ public void testRegisterIndexStore() throws IOException {
174183
.put(IndexModule.INDEX_STORE_TYPE_SETTING.getKey(), "foo_store")
175184
.build();
176185
final IndexSettings indexSettings = IndexSettingsModule.newIndexSettings(index, settings);
177-
final Map<String, IndexStorePlugin.DirectoryFactory> indexStoreFactories = Collections.singletonMap(
186+
final Map<String, IndexStorePlugin.DirectoryFactory> indexStoreFactories = singletonMap(
178187
"foo_store", new FooFunction());
179188
final IndexModule module = new IndexModule(indexSettings, emptyAnalysisRegistry, new InternalEngineFactory(), indexStoreFactories);
180189

@@ -354,11 +363,19 @@ public void testForceCustomQueryCache() throws IOException {
354363
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).build();
355364
final IndexSettings indexSettings = IndexSettingsModule.newIndexSettings("foo", settings);
356365
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()));
366+
final Set<CustomQueryCache> liveQueryCaches = new HashSet<>();
367+
module.forceQueryCacheProvider((a, b) -> {
368+
final CustomQueryCache customQueryCache = new CustomQueryCache(liveQueryCaches);
369+
liveQueryCaches.add(customQueryCache);
370+
return customQueryCache;
371+
});
372+
expectThrows(AlreadySetException.class, () -> module.forceQueryCacheProvider((a, b) -> {
373+
throw new AssertionError("never called");
374+
}));
359375
IndexService indexService = newIndexService(module);
360376
assertTrue(indexService.cache().query() instanceof CustomQueryCache);
361377
indexService.close("simon says", false);
378+
assertThat(liveQueryCaches, empty());
362379
}
363380

364381
public void testDefaultQueryCacheImplIsSelected() throws IOException {
@@ -379,12 +396,73 @@ public void testDisableQueryCacheHasPrecedenceOverForceQueryCache() throws IOExc
379396
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).build();
380397
final IndexSettings indexSettings = IndexSettingsModule.newIndexSettings("foo", settings);
381398
IndexModule module = new IndexModule(indexSettings, emptyAnalysisRegistry, new InternalEngineFactory(), Collections.emptyMap());
382-
module.forceQueryCacheProvider((a, b) -> new CustomQueryCache());
399+
module.forceQueryCacheProvider((a, b) -> new CustomQueryCache(null));
383400
IndexService indexService = newIndexService(module);
384401
assertTrue(indexService.cache().query() instanceof DisabledQueryCache);
385402
indexService.close("simon says", false);
386403
}
387404

405+
public void testCustomQueryCacheCleanedUpIfIndexServiceCreationFails() {
406+
Settings settings = Settings.builder()
407+
.put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString())
408+
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).build();
409+
final IndexSettings indexSettings = IndexSettingsModule.newIndexSettings("foo", settings);
410+
IndexModule module = new IndexModule(indexSettings, emptyAnalysisRegistry, new InternalEngineFactory(), Collections.emptyMap());
411+
final Set<CustomQueryCache> liveQueryCaches = new HashSet<>();
412+
module.forceQueryCacheProvider((a, b) -> {
413+
final CustomQueryCache customQueryCache = new CustomQueryCache(liveQueryCaches);
414+
liveQueryCaches.add(customQueryCache);
415+
return customQueryCache;
416+
});
417+
threadPool.shutdown(); // causes index service creation to fail
418+
expectThrows(EsRejectedExecutionException.class, () -> newIndexService(module));
419+
assertThat(liveQueryCaches, empty());
420+
}
421+
422+
public void testIndexAnalyzersCleanedUpIfIndexServiceCreationFails() {
423+
Settings settings = Settings.builder()
424+
.put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString())
425+
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT).build();
426+
final IndexSettings indexSettings = IndexSettingsModule.newIndexSettings("foo", settings);
427+
428+
final HashSet<Analyzer> openAnalyzers = new HashSet<>();
429+
final AnalysisModule.AnalysisProvider<AnalyzerProvider<?>> analysisProvider = (i,e,n,s) -> new AnalyzerProvider<>() {
430+
@Override
431+
public String name() {
432+
return "test";
433+
}
434+
435+
@Override
436+
public AnalyzerScope scope() {
437+
return AnalyzerScope.INDEX;
438+
}
439+
440+
@Override
441+
public Analyzer get() {
442+
final Analyzer analyzer = new Analyzer() {
443+
@Override
444+
protected TokenStreamComponents createComponents(String fieldName) {
445+
throw new AssertionError("should not be here");
446+
}
447+
448+
@Override
449+
public void close() {
450+
super.close();
451+
openAnalyzers.remove(this);
452+
}
453+
};
454+
openAnalyzers.add(analyzer);
455+
return analyzer;
456+
}
457+
};
458+
final AnalysisRegistry analysisRegistry = new AnalysisRegistry(environment, emptyMap(), emptyMap(), emptyMap(),
459+
singletonMap("test", analysisProvider), emptyMap(), emptyMap(), emptyMap(), emptyMap(), emptyMap());
460+
IndexModule module = new IndexModule(indexSettings, analysisRegistry, new InternalEngineFactory(), Collections.emptyMap());
461+
threadPool.shutdown(); // causes index service creation to fail
462+
expectThrows(EsRejectedExecutionException.class, () -> newIndexService(module));
463+
assertThat(openAnalyzers, empty());
464+
}
465+
388466
public void testMmapNotAllowed() {
389467
String storeType = randomFrom(IndexModule.Type.HYBRIDFS.getSettingsKey(), IndexModule.Type.MMAPFS.getSettingsKey());
390468
final Settings settings = Settings.builder()
@@ -403,12 +481,19 @@ public void testMmapNotAllowed() {
403481

404482
class CustomQueryCache implements QueryCache {
405483

484+
private final Set<CustomQueryCache> liveQueryCaches;
485+
486+
CustomQueryCache(Set<CustomQueryCache> liveQueryCaches) {
487+
this.liveQueryCaches = liveQueryCaches;
488+
}
489+
406490
@Override
407491
public void clear(String reason) {
408492
}
409493

410494
@Override
411-
public void close() throws IOException {
495+
public void close() {
496+
assertTrue(liveQueryCaches == null || liveQueryCaches.remove(this));
412497
}
413498

414499
@Override

0 commit comments

Comments
 (0)