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 concurrency bug in AbstractStringScriptFieldAutomatonQuery #106678

Merged
merged 6 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/changelog/106678.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 106678
summary: Fix concurrency bug in `AbstractStringScriptFieldAutomatonQuery`
area: Search
type: bug
issues:
- 105911
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ abstract class AbstractBooleanScriptFieldQuery extends AbstractScriptFieldQuery<
}

@Override
protected boolean matches(BooleanFieldScript scriptContext, int docId) {
protected final boolean matches(BooleanFieldScript scriptContext, int docId) {
scriptContext.runForDoc(docId);
return matches(scriptContext.trues(), scriptContext.falses());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ abstract class AbstractDoubleScriptFieldQuery extends AbstractScriptFieldQuery<D
}

@Override
protected boolean matches(DoubleFieldScript scriptContext, int docId) {
protected final boolean matches(DoubleFieldScript scriptContext, int docId) {
scriptContext.runForDoc(docId);
return matches(scriptContext.values(), scriptContext.count());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ abstract class AbstractGeoPointScriptFieldQuery extends AbstractScriptFieldQuery
}

@Override
protected boolean matches(GeoPointFieldScript scriptContext, int docId) {
protected final boolean matches(GeoPointFieldScript scriptContext, int docId) {
scriptContext.runForDoc(docId);
return matches(scriptContext.lats(), scriptContext.lons(), scriptContext.count());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ abstract class AbstractIpScriptFieldQuery extends AbstractScriptFieldQuery<IpFie
}

@Override
protected boolean matches(IpFieldScript scriptContext, int docId) {
protected final boolean matches(IpFieldScript scriptContext, int docId) {
scriptContext.runForDoc(docId);
return matches(scriptContext.values(), scriptContext.count());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ abstract class AbstractLongScriptFieldQuery extends AbstractScriptFieldQuery<Abs
}

@Override
protected boolean matches(AbstractLongFieldScript scriptContext, int docId) {
protected final boolean matches(AbstractLongFieldScript scriptContext, int docId) {
scriptContext.runForDoc(docId);
return AbstractLongScriptFieldQuery.this.matches(scriptContext.values(), scriptContext.count());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,7 @@ public boolean isCacheable(LeafReaderContext ctx) {
public Scorer scorer(LeafReaderContext ctx) {
S scriptContext = scriptContextFunction.apply(ctx);
DocIdSetIterator approximation = DocIdSetIterator.all(ctx.reader().maxDoc());
TwoPhaseIterator twoPhase = new TwoPhaseIterator(approximation) {
@Override
public boolean matches() {
return AbstractScriptFieldQuery.this.matches(scriptContext, approximation.docID());
}

@Override
public float matchCost() {
return MATCH_COST;
}
};
return new ConstantScoreScorer(this, score(), scoreMode, twoPhase);
return new ConstantScoreScorer(this, score(), scoreMode, createTwoPhaseIterator(scriptContext, approximation));
}

@Override
Expand All @@ -96,6 +85,20 @@ public Explanation explain(LeafReaderContext context, int doc) throws IOExceptio
};
}

protected TwoPhaseIterator createTwoPhaseIterator(S scriptContext, DocIdSetIterator approximation) {
return new TwoPhaseIterator(approximation) {
@Override
public boolean matches() {
return AbstractScriptFieldQuery.this.matches(scriptContext, approximation.docID());
}

@Override
public float matchCost() {
return MATCH_COST;
}
};
}

protected abstract boolean matches(S scriptContext, int docId);

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

package org.elasticsearch.search.runtime;

import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.QueryVisitor;
import org.apache.lucene.search.TwoPhaseIterator;
import org.apache.lucene.util.BytesRefBuilder;
import org.apache.lucene.util.automaton.ByteRunAutomaton;
import org.elasticsearch.script.Script;
Expand All @@ -17,7 +19,6 @@
import java.util.List;

public abstract class AbstractStringScriptFieldAutomatonQuery extends AbstractStringScriptFieldQuery {
private final BytesRefBuilder scratch = new BytesRefBuilder();
private final ByteRunAutomaton automaton;

public AbstractStringScriptFieldAutomatonQuery(
Expand All @@ -31,7 +32,23 @@ public AbstractStringScriptFieldAutomatonQuery(
}

@Override
protected final boolean matches(List<String> values) {
protected TwoPhaseIterator createTwoPhaseIterator(StringFieldScript scriptContext, DocIdSetIterator approximation) {
BytesRefBuilder scratch = new BytesRefBuilder();
return new TwoPhaseIterator(approximation) {
@Override
public boolean matches() {
scriptContext.runForDoc(approximation.docID());
return AbstractStringScriptFieldAutomatonQuery.this.matches(scriptContext.getValues(), scratch);
}

@Override
public float matchCost() {
return MATCH_COST;
}
};
}

protected final boolean matches(List<String> values, BytesRefBuilder scratch) {
for (String value : values) {
scratch.copyChars(value);
if (automaton.run(scratch.bytes(), 0, scratch.length())) {
Expand All @@ -41,6 +58,11 @@ protected final boolean matches(List<String> values) {
return false;
}

@Override
protected final boolean matches(List<String> values) {
throw new UnsupportedOperationException();
}

@Override
public final void visit(QueryVisitor visitor) {
if (visitor.acceptField(fieldName())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@

import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;

Expand All @@ -73,8 +74,8 @@ protected ScriptFactory dummyScript() {
@Override
public void testDocValues() throws IOException {
try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) {
iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [true]}"))));
iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [true, false]}"))));
addDocument(iw, List.of(new StoredField("_source", new BytesRef("{\"foo\": [true]}"))));
addDocument(iw, List.of(new StoredField("_source", new BytesRef("{\"foo\": [true, false]}"))));
List<Long> results = new ArrayList<>();
try (DirectoryReader reader = iw.getReader()) {
IndexSearcher searcher = newSearcher(reader);
Expand Down Expand Up @@ -104,16 +105,16 @@ public void collect(int doc) throws IOException {
};
}
});
assertThat(results, equalTo(List.of(1L, 0L, 1L)));
assertThat(results, containsInAnyOrder(1L, 0L, 1L));
}
}
}

@Override
public void testSort() throws IOException {
try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) {
iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [true]}"))));
iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [false]}"))));
addDocument(iw, List.of(new StoredField("_source", new BytesRef("{\"foo\": [true]}"))));
addDocument(iw, List.of(new StoredField("_source", new BytesRef("{\"foo\": [false]}"))));
try (DirectoryReader reader = iw.getReader()) {
IndexSearcher searcher = newSearcher(reader);
BooleanScriptFieldData ifd = simpleMappedFieldType().fielddataBuilder(mockFielddataContext()).build(null, null);
Expand All @@ -128,8 +129,8 @@ public void testSort() throws IOException {
@Override
public void testUsedInScript() throws IOException {
try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) {
iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [true]}"))));
iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [false]}"))));
addDocument(iw, List.of(new StoredField("_source", new BytesRef("{\"foo\": [true]}"))));
addDocument(iw, List.of(new StoredField("_source", new BytesRef("{\"foo\": [false]}"))));
try (DirectoryReader reader = iw.getReader()) {
IndexSearcher searcher = newSearcher(reader);
{
Expand Down Expand Up @@ -185,10 +186,10 @@ public double execute(ExplanationHolder explanation) {
@Override
public void testExistsQuery() throws IOException {
try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) {
iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [false]}"))));
iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [true]}"))));
iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [true, false]}"))));
iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": []}"))));
addDocument(iw, List.of(new StoredField("_source", new BytesRef("{\"foo\": [false]}"))));
addDocument(iw, List.of(new StoredField("_source", new BytesRef("{\"foo\": [true]}"))));
addDocument(iw, List.of(new StoredField("_source", new BytesRef("{\"foo\": [true, false]}"))));
addDocument(iw, List.of(new StoredField("_source", new BytesRef("{\"foo\": []}"))));
try (DirectoryReader reader = iw.getReader()) {
IndexSearcher searcher = newSearcher(reader);
assertThat(searcher.count(simpleMappedFieldType().existsQuery(mockContext())), equalTo(3));
Expand All @@ -199,7 +200,7 @@ public void testExistsQuery() throws IOException {
@Override
public void testRangeQuery() throws IOException {
try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) {
iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [true]}"))));
addDocument(iw, List.of(new StoredField("_source", new BytesRef("{\"foo\": [true]}"))));
try (DirectoryReader reader = iw.getReader()) {
IndexSearcher searcher = newSearcher(reader);
MappedFieldType ft = simpleMappedFieldType();
Expand All @@ -210,7 +211,7 @@ public void testRangeQuery() throws IOException {
}
}
try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) {
iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [false]}"))));
addDocument(iw, List.of(new StoredField("_source", new BytesRef("{\"foo\": [false]}"))));
try (DirectoryReader reader = iw.getReader()) {
IndexSearcher searcher = newSearcher(reader);
MappedFieldType ft = simpleMappedFieldType();
Expand All @@ -221,8 +222,8 @@ public void testRangeQuery() throws IOException {
}
}
try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) {
iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [false]}"))));
iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [true]}"))));
addDocument(iw, List.of(new StoredField("_source", new BytesRef("{\"foo\": [false]}"))));
addDocument(iw, List.of(new StoredField("_source", new BytesRef("{\"foo\": [true]}"))));
try (DirectoryReader reader = iw.getReader()) {
IndexSearcher searcher = newSearcher(reader);
MappedFieldType ft = simpleMappedFieldType();
Expand Down Expand Up @@ -269,7 +270,7 @@ protected Query randomRangeQuery(MappedFieldType ft, SearchExecutionContext ctx)
@Override
public void testTermQuery() throws IOException {
try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) {
iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [true]}"))));
addDocument(iw, List.of(new StoredField("_source", new BytesRef("{\"foo\": [true]}"))));
try (DirectoryReader reader = iw.getReader()) {
IndexSearcher searcher = newSearcher(reader);
assertThat(searcher.count(simpleMappedFieldType().termQuery(true, mockContext())), equalTo(1));
Expand All @@ -282,7 +283,7 @@ public void testTermQuery() throws IOException {
}
}
try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) {
iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [false]}"))));
addDocument(iw, List.of(new StoredField("_source", new BytesRef("{\"foo\": [false]}"))));
try (DirectoryReader reader = iw.getReader()) {
IndexSearcher searcher = newSearcher(reader);
assertThat(searcher.count(simpleMappedFieldType().termQuery(false, mockContext())), equalTo(1));
Expand All @@ -305,7 +306,7 @@ protected Query randomTermQuery(MappedFieldType ft, SearchExecutionContext ctx)
@Override
public void testTermsQuery() throws IOException {
try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) {
iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [true]}"))));
addDocument(iw, List.of(new StoredField("_source", new BytesRef("{\"foo\": [true]}"))));
try (DirectoryReader reader = iw.getReader()) {
IndexSearcher searcher = newSearcher(reader);
assertThat(searcher.count(simpleMappedFieldType().termsQuery(List.of(true, true), mockContext())), equalTo(1));
Expand All @@ -315,7 +316,7 @@ public void testTermsQuery() throws IOException {
}
}
try (Directory directory = newDirectory(); RandomIndexWriter iw = new RandomIndexWriter(random(), directory)) {
iw.addDocument(List.of(new StoredField("_source", new BytesRef("{\"foo\": [false]}"))));
addDocument(iw, List.of(new StoredField("_source", new BytesRef("{\"foo\": [false]}"))));
try (DirectoryReader reader = iw.getReader()) {
IndexSearcher searcher = newSearcher(reader);
assertThat(searcher.count(simpleMappedFieldType().termsQuery(List.of(false, false), mockContext())), equalTo(1));
Expand Down Expand Up @@ -364,7 +365,7 @@ public XContentParser parser() {
while (ctx.parser().nextToken() != Token.END_ARRAY) {
ootb.parse(ctx);
}
iw.addDocument(ctx.doc());
addDocument(iw, ctx.doc());
try (DirectoryReader reader = iw.getReader()) {
IndexSearcher searcher = newSearcher(reader);
assertSameCount(
Expand Down