Skip to content

Commit a5cb0bc

Browse files
committed
Merge pull request #14084 from s1monw/close_the_wrapper_only
Streamline top level reader close listeners and forbid general usage
1 parent a1bed55 commit a5cb0bc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+719
-153
lines changed

core/src/main/java/org/elasticsearch/common/lucene/index/ElasticsearchDirectoryReader.java

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,8 @@
1818
*/
1919
package org.elasticsearch.common.lucene.index;
2020

21-
import org.apache.lucene.index.DirectoryReader;
22-
import org.apache.lucene.index.FilterDirectoryReader;
23-
import org.apache.lucene.index.FilterLeafReader;
24-
import org.apache.lucene.index.LeafReader;
21+
import org.apache.lucene.index.*;
22+
import org.elasticsearch.common.SuppressForbidden;
2523
import org.elasticsearch.index.shard.ShardId;
2624

2725
import java.io.IOException;
@@ -76,4 +74,38 @@ public LeafReader wrap(LeafReader reader) {
7674
}
7775
}
7876

77+
/**
78+
* Adds the given listener to the provided directory reader. The reader must contain an {@link ElasticsearchDirectoryReader} in it's hierarchy
79+
* otherwise we can't safely install the listener.
80+
*
81+
* @throws IllegalArgumentException if the reader doesn't contain an {@link ElasticsearchDirectoryReader} in it's hierarchy
82+
*/
83+
@SuppressForbidden(reason = "This is the only sane way to add a ReaderClosedListener")
84+
public static void addReaderCloseListener(DirectoryReader reader, IndexReader.ReaderClosedListener listener) {
85+
ElasticsearchDirectoryReader elasticsearchDirectoryReader = getElasticsearchDirectoryReader(reader);
86+
if (elasticsearchDirectoryReader != null) {
87+
assert reader.getCoreCacheKey() == elasticsearchDirectoryReader.getCoreCacheKey();
88+
elasticsearchDirectoryReader.addReaderClosedListener(listener);
89+
return;
90+
}
91+
throw new IllegalArgumentException("Can't install close listener reader is not an ElasticsearchDirectoryReader/ElasticsearchLeafReader");
92+
}
93+
94+
/**
95+
* Tries to unwrap the given reader until the first {@link ElasticsearchDirectoryReader} instance is found or <code>null</code> if no instance is found;
96+
*/
97+
public static ElasticsearchDirectoryReader getElasticsearchDirectoryReader(DirectoryReader reader) {
98+
if (reader instanceof FilterDirectoryReader) {
99+
if (reader instanceof ElasticsearchDirectoryReader) {
100+
return (ElasticsearchDirectoryReader) reader;
101+
} else {
102+
// We need to use FilterDirectoryReader#getDelegate and not FilterDirectoryReader#unwrap, because
103+
// If there are multiple levels of filtered leaf readers then with the unwrap() method it immediately
104+
// returns the most inner leaf reader and thus skipping of over any other filtered leaf reader that
105+
// may be instance of ElasticsearchLeafReader. This can cause us to miss the shardId.
106+
return getElasticsearchDirectoryReader(((FilterDirectoryReader) reader).getDelegate());
107+
}
108+
}
109+
return null;
110+
}
79111
}

core/src/main/java/org/elasticsearch/common/lucene/index/ElasticsearchLeafReader.java

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,7 @@
1818
*/
1919
package org.elasticsearch.common.lucene.index;
2020

21-
import org.apache.lucene.index.DirectoryReader;
22-
import org.apache.lucene.index.FilterDirectoryReader;
23-
import org.apache.lucene.index.FilterLeafReader;
24-
import org.apache.lucene.index.LeafReader;
21+
import org.apache.lucene.index.*;
2522
import org.elasticsearch.index.shard.ShardId;
2623

2724
/**
@@ -38,7 +35,7 @@ public final class ElasticsearchLeafReader extends FilterLeafReader {
3835
*
3936
* @param in specified base reader.
4037
*/
41-
ElasticsearchLeafReader(LeafReader in, ShardId shardId) {
38+
public ElasticsearchLeafReader(LeafReader in, ShardId shardId) {
4239
super(in);
4340
this.shardId = shardId;
4441
}
@@ -55,8 +52,18 @@ public Object getCoreCacheKey() {
5552
return in.getCoreCacheKey();
5653
}
5754

58-
@Override
59-
public Object getCombinedCoreAndDeletesKey() {
60-
return in.getCombinedCoreAndDeletesKey();
55+
public static ElasticsearchLeafReader getElasticsearchLeafReader(LeafReader reader) {
56+
if (reader instanceof FilterLeafReader) {
57+
if (reader instanceof ElasticsearchLeafReader) {
58+
return (ElasticsearchLeafReader) reader;
59+
} else {
60+
// We need to use FilterLeafReader#getDelegate and not FilterLeafReader#unwrap, because
61+
// If there are multiple levels of filtered leaf readers then with the unwrap() method it immediately
62+
// returns the most inner leaf reader and thus skipping of over any other filtered leaf reader that
63+
// may be instance of ElasticsearchLeafReader. This can cause us to miss the shardId.
64+
return getElasticsearchLeafReader(((FilterLeafReader) reader).getDelegate());
65+
}
66+
}
67+
return null;
6168
}
6269
}

core/src/main/java/org/elasticsearch/common/lucene/uid/Versions.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.common.lucene.uid;
2121

22+
import org.apache.lucene.index.DirectoryReader;
2223
import org.apache.lucene.index.IndexReader;
2324
import org.apache.lucene.index.LeafReader;
2425
import org.apache.lucene.index.LeafReader.CoreClosedListener;

core/src/main/java/org/elasticsearch/index/engine/Engine.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,13 @@ public IndexReader reader() {
600600
return searcher.getIndexReader();
601601
}
602602

603+
public DirectoryReader getDirectoryReader() {
604+
if (reader() instanceof DirectoryReader) {
605+
return (DirectoryReader) reader();
606+
}
607+
throw new IllegalStateException("Can't use " + reader().getClass() + " as a directory reader");
608+
}
609+
603610
public IndexSearcher searcher() {
604611
return searcher;
605612
}

core/src/main/java/org/elasticsearch/index/engine/IndexSearcherWrapper.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import org.apache.lucene.index.DirectoryReader;
2323
import org.apache.lucene.search.IndexSearcher;
2424

25+
import java.io.IOException;
26+
2527
/**
2628
* Extension point to add custom functionality at request time to the {@link DirectoryReader}
2729
* and {@link IndexSearcher} managed by the {@link Engine}.
@@ -33,7 +35,7 @@ public interface IndexSearcherWrapper {
3335
* @return a new directory reader wrapping the provided directory reader or if no wrapping was performed
3436
* the provided directory reader
3537
*/
36-
DirectoryReader wrap(DirectoryReader reader);
38+
DirectoryReader wrap(DirectoryReader reader) throws IOException;
3739

3840
/**
3941
* @param engineConfig The engine config which can be used to get the query cache and query cache policy from

core/src/main/java/org/elasticsearch/index/engine/IndexSearcherWrappingService.java

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,15 @@
2020
package org.elasticsearch.index.engine;
2121

2222
import org.apache.lucene.index.DirectoryReader;
23+
import org.apache.lucene.index.FilterDirectoryReader;
24+
import org.apache.lucene.index.LeafReader;
2325
import org.apache.lucene.search.IndexSearcher;
2426
import org.elasticsearch.ElasticsearchException;
2527
import org.elasticsearch.common.inject.Inject;
28+
import org.elasticsearch.common.lucene.index.ElasticsearchDirectoryReader;
2629
import org.elasticsearch.index.engine.Engine.Searcher;
2730

31+
import java.io.IOException;
2832
import java.util.Set;
2933

3034
/**
@@ -60,35 +64,86 @@ public IndexSearcherWrappingService(Set<IndexSearcherWrapper> wrappers) {
6064

6165
/**
6266
* If there are configured {@link IndexSearcherWrapper} instances, the {@link IndexSearcher} of the provided engine searcher
63-
* gets wrapped and a new {@link Searcher} instances is returned, otherwise the provided {@link Searcher} is returned.
67+
* gets wrapped and a new {@link Engine.Searcher} instances is returned, otherwise the provided {@link Engine.Searcher} is returned.
6468
*
65-
* This is invoked each time a {@link Searcher} is requested to do an operation. (for example search)
69+
* This is invoked each time a {@link Engine.Searcher} is requested to do an operation. (for example search)
6670
*/
67-
public Searcher wrap(EngineConfig engineConfig, final Searcher engineSearcher) throws EngineException {
71+
public final Engine.Searcher wrap(EngineConfig engineConfig, final Engine.Searcher engineSearcher) throws IOException {
72+
final ElasticsearchDirectoryReader elasticsearchDirectoryReader = ElasticsearchDirectoryReader.getElasticsearchDirectoryReader(engineSearcher.getDirectoryReader());
73+
if (elasticsearchDirectoryReader == null) {
74+
throw new IllegalStateException("Can't wrap non elasticsearch directory reader");
75+
}
6876
if (wrapper == null) {
6977
return engineSearcher;
7078
}
79+
NonClosingReaderWrapper nonClosingReaderWrapper = new NonClosingReaderWrapper(engineSearcher.getDirectoryReader());
80+
DirectoryReader reader = wrapper.wrap(nonClosingReaderWrapper);
81+
if (reader != nonClosingReaderWrapper) {
82+
if (reader.getCoreCacheKey() != elasticsearchDirectoryReader.getCoreCacheKey()) {
83+
throw new IllegalStateException("wrapped directory reader doesn't delegate IndexReader#getCoreCacheKey, wrappers must override this method and delegate" +
84+
" to the original readers core cache key. Wrapped readers can't be used as cache keys since their are used only per request which would lead to subtle bugs");
85+
}
86+
if (ElasticsearchDirectoryReader.getElasticsearchDirectoryReader(reader) != elasticsearchDirectoryReader) {
87+
// prevent that somebody wraps with a non-filter reader
88+
throw new IllegalStateException("wrapped directory reader hides actual ElasticsearchDirectoryReader but shouldn't");
89+
}
90+
}
7191

72-
DirectoryReader reader = wrapper.wrap((DirectoryReader) engineSearcher.reader());
73-
IndexSearcher innerIndexSearcher = new IndexSearcher(reader);
92+
final IndexSearcher innerIndexSearcher = new IndexSearcher(reader);
7493
innerIndexSearcher.setQueryCache(engineConfig.getQueryCache());
7594
innerIndexSearcher.setQueryCachingPolicy(engineConfig.getQueryCachingPolicy());
7695
innerIndexSearcher.setSimilarity(engineConfig.getSimilarity());
7796
// TODO: Right now IndexSearcher isn't wrapper friendly, when it becomes wrapper friendly we should revise this extension point
7897
// For example if IndexSearcher#rewrite() is overwritten than also IndexSearcher#createNormalizedWeight needs to be overwritten
7998
// This needs to be fixed before we can allow the IndexSearcher from Engine to be wrapped multiple times
80-
IndexSearcher indexSearcher = wrapper.wrap(engineConfig, innerIndexSearcher);
81-
if (reader == engineSearcher.reader() && indexSearcher == innerIndexSearcher) {
99+
final IndexSearcher indexSearcher = wrapper.wrap(engineConfig, innerIndexSearcher);
100+
if (reader == nonClosingReaderWrapper && indexSearcher == innerIndexSearcher) {
82101
return engineSearcher;
83102
} else {
84103
return new Engine.Searcher(engineSearcher.source(), indexSearcher) {
85-
86104
@Override
87105
public void close() throws ElasticsearchException {
88-
engineSearcher.close();
106+
try {
107+
reader().close();
108+
// we close the reader to make sure wrappers can release resources if needed....
109+
// our NonClosingReaderWrapper makes sure that our reader is not closed
110+
} catch (IOException e) {
111+
throw new ElasticsearchException("failed to close reader", e);
112+
} finally {
113+
engineSearcher.close();
114+
}
115+
89116
}
90117
};
91118
}
92119
}
93120

121+
private static final class NonClosingReaderWrapper extends FilterDirectoryReader {
122+
123+
private NonClosingReaderWrapper(DirectoryReader in) throws IOException {
124+
super(in, new SubReaderWrapper() {
125+
@Override
126+
public LeafReader wrap(LeafReader reader) {
127+
return reader;
128+
}
129+
});
130+
}
131+
132+
@Override
133+
protected DirectoryReader doWrapDirectoryReader(DirectoryReader in) throws IOException {
134+
return new NonClosingReaderWrapper(in);
135+
}
136+
137+
@Override
138+
protected void doClose() throws IOException {
139+
// don't close here - mimic the MultiReader#doClose = false behavior that FilterDirectoryReader doesn't have
140+
}
141+
142+
@Override
143+
public Object getCoreCacheKey() {
144+
return in.getCoreCacheKey();
145+
}
146+
}
147+
148+
94149
}

core/src/main/java/org/elasticsearch/index/engine/InternalEngine.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.elasticsearch.common.lucene.LoggerInfoStream;
4141
import org.elasticsearch.common.lucene.Lucene;
4242
import org.elasticsearch.common.lucene.index.ElasticsearchDirectoryReader;
43+
import org.elasticsearch.common.lucene.index.ElasticsearchLeafReader;
4344
import org.elasticsearch.common.lucene.uid.Versions;
4445
import org.elasticsearch.common.math.MathUtils;
4546
import org.elasticsearch.common.settings.Settings;
@@ -1038,9 +1039,10 @@ private IndexWriter createWriter(boolean create) throws IOException {
10381039
@Override
10391040
public void warm(LeafReader reader) throws IOException {
10401041
try {
1041-
assert isMergedSegment(reader);
1042+
LeafReader esLeafReader = new ElasticsearchLeafReader(reader, shardId);
1043+
assert isMergedSegment(esLeafReader);
10421044
if (warmer != null) {
1043-
final Engine.Searcher searcher = new Searcher("warmer", searcherFactory.newSearcher(reader, null));
1045+
final Engine.Searcher searcher = new Searcher("warmer", searcherFactory.newSearcher(esLeafReader, null));
10441046
final IndicesWarmer.WarmerContext context = new IndicesWarmer.WarmerContext(shardId, searcher);
10451047
warmer.warmNewReaders(context);
10461048
}
@@ -1081,6 +1083,12 @@ final static class SearchFactory extends EngineSearcherFactory {
10811083
@Override
10821084
public IndexSearcher newSearcher(IndexReader reader, IndexReader previousReader) throws IOException {
10831085
IndexSearcher searcher = super.newSearcher(reader, previousReader);
1086+
if (reader instanceof LeafReader && isMergedSegment((LeafReader)reader)) {
1087+
// we call newSearcher from the IndexReaderWarmer which warms segments during merging
1088+
// in that case the reader is a LeafReader and all we need to do is to build a new Searcher
1089+
// and return it since it does it's own warming for that particular reader.
1090+
return searcher;
1091+
}
10841092
if (warmer != null) {
10851093
// we need to pass a custom searcher that does not release anything on Engine.Search Release,
10861094
// we will release explicitly
@@ -1118,10 +1126,11 @@ public IndexSearcher newSearcher(IndexReader reader, IndexReader previousReader)
11181126
}
11191127

11201128
if (newSearcher != null) {
1121-
IndicesWarmer.WarmerContext context = new IndicesWarmer.WarmerContext(shardId, new Searcher("warmer", newSearcher));
1129+
IndicesWarmer.WarmerContext context = new IndicesWarmer.WarmerContext(shardId, new Searcher("new_reader_warming", newSearcher));
11221130
warmer.warmNewReaders(context);
11231131
}
1124-
warmer.warmTopReader(new IndicesWarmer.WarmerContext(shardId, new Searcher("warmer", searcher)));
1132+
assert searcher.getIndexReader() instanceof ElasticsearchDirectoryReader : "this class needs an ElasticsearchDirectoryReader but got: " + searcher.getIndexReader().getClass();
1133+
warmer.warmTopReader(new IndicesWarmer.WarmerContext(shardId, new Searcher("top_reader_warming", searcher)));
11251134
} catch (Throwable e) {
11261135
if (isEngineClosed.get() == false) {
11271136
logger.warn("failed to prepare/warm", e);

core/src/main/java/org/elasticsearch/index/fielddata/IndexFieldData.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919

2020
package org.elasticsearch.index.fielddata;
2121

22+
import org.apache.lucene.index.DirectoryReader;
2223
import org.apache.lucene.index.LeafReaderContext;
23-
import org.apache.lucene.index.IndexReader;
2424
import org.apache.lucene.search.*;
2525
import org.apache.lucene.search.join.BitDocIdSetFilter;
2626
import org.apache.lucene.util.BitDocIdSet;
@@ -231,11 +231,11 @@ IndexFieldData<?> build(Index index, @IndexSettings Settings indexSettings, Mapp
231231
CircuitBreakerService breakerService, MapperService mapperService);
232232
}
233233

234-
public static interface Global<FD extends AtomicFieldData> extends IndexFieldData<FD> {
234+
interface Global<FD extends AtomicFieldData> extends IndexFieldData<FD> {
235235

236-
IndexFieldData<FD> loadGlobal(IndexReader indexReader);
236+
IndexFieldData<FD> loadGlobal(DirectoryReader indexReader);
237237

238-
IndexFieldData<FD> localGlobalDirect(IndexReader indexReader) throws Exception;
238+
IndexFieldData<FD> localGlobalDirect(DirectoryReader indexReader) throws Exception;
239239

240240
}
241241

core/src/main/java/org/elasticsearch/index/fielddata/IndexFieldDataCache.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.index.fielddata;
2121

22+
import org.apache.lucene.index.DirectoryReader;
2223
import org.apache.lucene.index.LeafReaderContext;
2324
import org.apache.lucene.index.IndexReader;
2425
import org.apache.lucene.util.Accountable;
@@ -31,7 +32,7 @@ public interface IndexFieldDataCache {
3132

3233
<FD extends AtomicFieldData, IFD extends IndexFieldData<FD>> FD load(LeafReaderContext context, IFD indexFieldData) throws Exception;
3334

34-
<FD extends AtomicFieldData, IFD extends IndexFieldData.Global<FD>> IFD load(final IndexReader indexReader, final IFD indexFieldData) throws Exception;
35+
<FD extends AtomicFieldData, IFD extends IndexFieldData.Global<FD>> IFD load(final DirectoryReader indexReader, final IFD indexFieldData) throws Exception;
3536

3637
/**
3738
* Clears all the field data stored cached in on this index.
@@ -61,7 +62,7 @@ public <FD extends AtomicFieldData, IFD extends IndexFieldData<FD>> FD load(Leaf
6162

6263
@Override
6364
@SuppressWarnings("unchecked")
64-
public <FD extends AtomicFieldData, IFD extends IndexFieldData.Global<FD>> IFD load(IndexReader indexReader, IFD indexFieldData) throws Exception {
65+
public <FD extends AtomicFieldData, IFD extends IndexFieldData.Global<FD>> IFD load(DirectoryReader indexReader, IFD indexFieldData) throws Exception {
6566
return (IFD) indexFieldData.localGlobalDirect(indexReader);
6667
}
6768

core/src/main/java/org/elasticsearch/index/fielddata/IndexOrdinalsFieldData.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.index.fielddata;
2121

22+
import org.apache.lucene.index.DirectoryReader;
2223
import org.apache.lucene.index.IndexReader;
2324

2425

@@ -33,12 +34,12 @@ public interface IndexOrdinalsFieldData extends IndexFieldData.Global<AtomicOrdi
3334
* potentially from a cache.
3435
*/
3536
@Override
36-
IndexOrdinalsFieldData loadGlobal(IndexReader indexReader);
37+
IndexOrdinalsFieldData loadGlobal(DirectoryReader indexReader);
3738

3839
/**
3940
* Load a global view of the ordinals for the given {@link IndexReader}.
4041
*/
4142
@Override
42-
IndexOrdinalsFieldData localGlobalDirect(IndexReader indexReader) throws Exception;
43+
IndexOrdinalsFieldData localGlobalDirect(DirectoryReader indexReader) throws Exception;
4344

4445
}

core/src/main/java/org/elasticsearch/index/fielddata/IndexParentChildFieldData.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.index.fielddata;
2121

22+
import org.apache.lucene.index.DirectoryReader;
2223
import org.apache.lucene.index.IndexReader;
2324

2425

@@ -34,12 +35,12 @@ public interface IndexParentChildFieldData extends IndexFieldData.Global<AtomicP
3435
* potentially from a cache.
3536
*/
3637
@Override
37-
IndexParentChildFieldData loadGlobal(IndexReader indexReader);
38+
IndexParentChildFieldData loadGlobal(DirectoryReader indexReader);
3839

3940
/**
4041
* Load a global view of the ordinals for the given {@link IndexReader}.
4142
*/
4243
@Override
43-
IndexParentChildFieldData localGlobalDirect(IndexReader indexReader) throws Exception;
44+
IndexParentChildFieldData localGlobalDirect(DirectoryReader indexReader) throws Exception;
4445

4546
}

0 commit comments

Comments
 (0)