Skip to content

Commit 7768974

Browse files
authored
Always stop the timer when profiling the fetch phase (#132570) (#132585)
If setNextReader in any sub fetch phase throws we will not stop the profile timer we started. Subphases do various things in setNextReader from validations, throwing Exceptions outright, to reading Lucene specific things like doc values. This moves the timer.stop call in a finally block to make sure everything timer related is copacetic.
1 parent 706a25f commit 7768974

File tree

3 files changed

+94
-12
lines changed

3 files changed

+94
-12
lines changed

docs/changelog/132570.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 132570
2+
summary: Always stop the timer when profiling the fetch phase
3+
area: Search
4+
type: bug
5+
issues: []

server/src/main/java/org/elasticsearch/search/fetch/FetchPhase.java

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -161,17 +161,21 @@ private SearchHits buildSearchHits(SearchContext context, int[] docIdsToLoad, Pr
161161
@Override
162162
protected void setNextReader(LeafReaderContext ctx, int[] docsInLeaf) throws IOException {
163163
Timer timer = profiler.startNextReader();
164-
this.ctx = ctx;
165-
this.leafNestedDocuments = nestedDocuments.getLeafNestedDocuments(ctx);
166-
this.leafStoredFieldLoader = storedFieldLoader.getLoader(ctx, docsInLeaf);
167-
this.leafSourceLoader = sourceLoader.leaf(ctx.reader(), docsInLeaf);
168-
this.leafIdLoader = idLoader.leaf(leafStoredFieldLoader, ctx.reader(), docsInLeaf);
169-
fieldLookupProvider.setNextReader(ctx);
170-
for (FetchSubPhaseProcessor processor : processors) {
171-
processor.setNextReader(ctx);
172-
}
173-
if (timer != null) {
174-
timer.stop();
164+
try {
165+
this.ctx = ctx;
166+
this.leafNestedDocuments = nestedDocuments.getLeafNestedDocuments(ctx);
167+
this.leafStoredFieldLoader = storedFieldLoader.getLoader(ctx, docsInLeaf);
168+
this.leafSourceLoader = sourceLoader.leaf(ctx.reader(), docsInLeaf);
169+
this.leafIdLoader = idLoader.leaf(leafStoredFieldLoader, ctx.reader(), docsInLeaf);
170+
171+
fieldLookupProvider.setNextReader(ctx);
172+
for (FetchSubPhaseProcessor processor : processors) {
173+
processor.setNextReader(ctx);
174+
}
175+
} finally {
176+
if (timer != null) {
177+
timer.stop();
178+
}
175179
}
176180
}
177181

server/src/test/java/org/elasticsearch/action/search/FetchSearchPhaseTests.java

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import org.elasticsearch.search.SearchPhaseResult;
5151
import org.elasticsearch.search.SearchShardTarget;
5252
import org.elasticsearch.search.fetch.FetchPhase;
53+
import org.elasticsearch.search.fetch.FetchPhaseExecutionException;
5354
import org.elasticsearch.search.fetch.FetchSearchResult;
5455
import org.elasticsearch.search.fetch.FetchSubPhase;
5556
import org.elasticsearch.search.fetch.FetchSubPhaseProcessor;
@@ -63,6 +64,7 @@
6364
import org.elasticsearch.search.internal.ShardSearchRequest;
6465
import org.elasticsearch.search.lookup.Source;
6566
import org.elasticsearch.search.profile.ProfileResult;
67+
import org.elasticsearch.search.profile.Profilers;
6668
import org.elasticsearch.search.profile.SearchProfileQueryPhaseResult;
6769
import org.elasticsearch.search.profile.SearchProfileShardResult;
6870
import org.elasticsearch.search.query.QuerySearchResult;
@@ -873,6 +875,63 @@ public StoredFieldsSpec storedFieldsSpec() {
873875
}
874876
}
875877

878+
public void testTimerStoppedAndSubPhasesExceptionsPropagate() throws IOException {
879+
// if the timer is not stopped properly whilst profiling the fetch phase the exceptions
880+
// in sub phases#setNextReader will not propagate as the cause that failed the fetch phase (instead a timer illegal state exception
881+
// will propagate)
882+
// this tests ensures that exceptions in sub phases are propagated correctly as the cause of the fetch phase failure (which in turn
883+
// implies the timer was handled correctly)
884+
Directory dir = newDirectory();
885+
RandomIndexWriter w = new RandomIndexWriter(random(), dir);
886+
887+
String body = "{ \"thefield\": \" " + randomAlphaOfLength(48_000) + "\" }";
888+
for (int i = 0; i < 10; i++) {
889+
Document document = new Document();
890+
document.add(new StringField("id", Integer.toString(i), Field.Store.YES));
891+
w.addDocument(document);
892+
}
893+
if (randomBoolean()) {
894+
w.forceMerge(1);
895+
}
896+
IndexReader r = w.getReader();
897+
w.close();
898+
ContextIndexSearcher contextIndexSearcher = createSearcher(r);
899+
try (
900+
SearchContext searchContext = createSearchContext(
901+
contextIndexSearcher,
902+
true,
903+
new NoopCircuitBreaker(CircuitBreaker.REQUEST),
904+
true
905+
)
906+
) {
907+
FetchPhase fetchPhase = new FetchPhase(List.of(fetchContext -> new FetchSubPhaseProcessor() {
908+
@Override
909+
public void setNextReader(LeafReaderContext readerContext) throws IOException {
910+
throw new IOException("bad things");
911+
}
912+
913+
@Override
914+
public void process(FetchSubPhase.HitContext hitContext) throws IOException {
915+
Source source = hitContext.source();
916+
hitContext.hit().sourceRef(source.internalSourceRef());
917+
}
918+
919+
@Override
920+
public StoredFieldsSpec storedFieldsSpec() {
921+
return StoredFieldsSpec.NEEDS_SOURCE;
922+
}
923+
}));
924+
FetchPhaseExecutionException fetchPhaseExecutionException = assertThrows(
925+
FetchPhaseExecutionException.class,
926+
() -> fetchPhase.execute(searchContext, IntStream.range(0, 100).toArray(), null)
927+
);
928+
assertThat(fetchPhaseExecutionException.getCause().getMessage(), is("bad things"));
929+
} finally {
930+
r.close();
931+
dir.close();
932+
}
933+
}
934+
876935
private static ContextIndexSearcher createSearcher(IndexReader reader) throws IOException {
877936
return new ContextIndexSearcher(reader, null, null, new QueryCachingPolicy() {
878937
@Override
@@ -910,13 +969,22 @@ public StoredFieldsSpec storedFieldsSpec() {
910969
}
911970

912971
private static SearchContext createSearchContext(ContextIndexSearcher contextIndexSearcher, boolean allowPartialResults) {
913-
return createSearchContext(contextIndexSearcher, allowPartialResults, null);
972+
return createSearchContext(contextIndexSearcher, allowPartialResults, null, false);
914973
}
915974

916975
private static SearchContext createSearchContext(
917976
ContextIndexSearcher contextIndexSearcher,
918977
boolean allowPartialResults,
919978
@Nullable CircuitBreaker circuitBreaker
979+
) {
980+
return createSearchContext(contextIndexSearcher, allowPartialResults, circuitBreaker, false);
981+
}
982+
983+
private static SearchContext createSearchContext(
984+
ContextIndexSearcher contextIndexSearcher,
985+
boolean allowPartialResults,
986+
@Nullable CircuitBreaker circuitBreaker,
987+
boolean profileEnabled
920988
) {
921989
IndexSettings indexSettings = new IndexSettings(
922990
IndexMetadata.builder("index")
@@ -999,6 +1067,11 @@ public CircuitBreaker circuitBreaker() {
9991067
return super.circuitBreaker();
10001068
}
10011069
}
1070+
1071+
@Override
1072+
public Profilers getProfilers() {
1073+
return profileEnabled ? new Profilers(contextIndexSearcher) : null;
1074+
}
10021075
};
10031076
searchContext.addReleasable(searchContext.fetchResult()::decRef);
10041077
searchContext.setTask(new SearchShardTask(-1, "type", "action", "description", null, Collections.emptyMap()));

0 commit comments

Comments
 (0)