blob: f43ebfedfd3958a50a0292ce5e8a7a346a71e781 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.index;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.document.*;
import org.apache.lucene.index.ExitableDirectoryReader.ExitingReaderException;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.LuceneTestCase;
import java.io.IOException;
import java.util.Arrays;
/**
* Test that uses a default/lucene Implementation of {@link QueryTimeout}
* to exit out long running queries that take too long to iterate over Terms.
*/
public class TestExitableDirectoryReader extends LuceneTestCase {
private static class TestReader extends FilterLeafReader {
private static class TestTerms extends FilterTerms {
TestTerms(Terms in) {
super(in);
}
@Override
public TermsEnum iterator() throws IOException {
return new TestTermsEnum(super.iterator());
}
}
private static class TestTermsEnum extends FilterTermsEnum {
public TestTermsEnum(TermsEnum in) {
super(in);
}
/**
* Sleep between iterations to timeout things.
*/
@Override
public BytesRef next() throws IOException {
try {
// Sleep for 100ms before each .next() call.
Thread.sleep(100);
} catch (InterruptedException e) {
}
return in.next();
}
}
public TestReader(LeafReader reader) throws IOException {
super(reader);
}
@Override
public Terms terms(String field) throws IOException {
Terms terms = super.terms(field);
return terms==null ? null : new TestTerms(terms);
}
@Override
public CacheHelper getCoreCacheHelper() {
return in.getCoreCacheHelper();
}
@Override
public CacheHelper getReaderCacheHelper() {
return in.getReaderCacheHelper();
}
}
/**
* Tests timing out of TermsEnum iterations
* @throws Exception on error
*/
public void testExitableFilterTermsIndexReader() throws Exception {
Directory directory = newDirectory();
IndexWriter writer = new IndexWriter(directory, newIndexWriterConfig(new MockAnalyzer(random())));
Document d1 = new Document();
d1.add(newTextField("default", "one two", Field.Store.YES));
writer.addDocument(d1);
Document d2 = new Document();
d2.add(newTextField("default", "one three", Field.Store.YES));
writer.addDocument(d2);
Document d3 = new Document();
d3.add(newTextField("default", "ones two four", Field.Store.YES));
writer.addDocument(d3);
writer.forceMerge(1);
writer.commit();
writer.close();
DirectoryReader directoryReader;
DirectoryReader exitableDirectoryReader;
IndexReader reader;
IndexSearcher searcher;
Query query = new PrefixQuery(new Term("default", "o"));
// Set a fairly high timeout value (infinite) and expect the query to complete in that time frame.
// Not checking the validity of the result, all we are bothered about in this test is the timing out.
directoryReader = DirectoryReader.open(directory);
exitableDirectoryReader = new ExitableDirectoryReader(directoryReader, infiniteQueryTimeout());
reader = new TestReader(getOnlyLeafReader(exitableDirectoryReader));
searcher = new IndexSearcher(reader);
searcher.search(query, 10);
reader.close();
// Set a really low timeout value (immediate) and expect an Exception
directoryReader = DirectoryReader.open(directory);
exitableDirectoryReader = new ExitableDirectoryReader(directoryReader, immediateQueryTimeout());
reader = new TestReader(getOnlyLeafReader(exitableDirectoryReader));
IndexSearcher slowSearcher = new IndexSearcher(reader);
expectThrows(ExitingReaderException.class, () -> {
slowSearcher.search(query, 10);
});
reader.close();
// Set maximum time out and expect the query to complete.
// Not checking the validity of the result, all we are bothered about in this test is the timing out.
directoryReader = DirectoryReader.open(directory);
exitableDirectoryReader = new ExitableDirectoryReader(directoryReader, infiniteQueryTimeout());
reader = new TestReader(getOnlyLeafReader(exitableDirectoryReader));
searcher = new IndexSearcher(reader);
searcher.search(query, 10);
reader.close();
// Set a negative time allowed and expect the query to complete (should disable timeouts)
// Not checking the validity of the result, all we are bothered about in this test is the timing out.
directoryReader = DirectoryReader.open(directory);
exitableDirectoryReader = new ExitableDirectoryReader(directoryReader, disabledQueryTimeout());
reader = new TestReader(getOnlyLeafReader(exitableDirectoryReader));
searcher = new IndexSearcher(reader);
searcher.search(query, 10);
reader.close();
directory.close();
}
/**
* Tests time out check sampling of TermsEnum iterations
*
* @throws Exception on error
*/
public void testExitableTermsEnumSampleTimeoutCheck() throws Exception {
try (Directory directory = newDirectory()) {
try (IndexWriter writer = new IndexWriter(directory, newIndexWriterConfig(new MockAnalyzer(random())))) {
for (int i = 0; i < 50; i++) {
Document d1 = new Document();
d1.add(newTextField("default", "term" + i, Field.Store.YES));
writer.addDocument(d1);
}
writer.forceMerge(1);
writer.commit();
DirectoryReader directoryReader;
DirectoryReader exitableDirectoryReader;
IndexReader reader;
IndexSearcher searcher;
Query query = new PrefixQuery(new Term("default", "term"));
// Set a fairly high timeout value (infinite) and expect the query to complete in that time frame.
// Not checking the validity of the result, but checking the sampling kicks in to reduce the number of timeout check
CountingQueryTimeout queryTimeout = new CountingQueryTimeout();
directoryReader = DirectoryReader.open(directory);
exitableDirectoryReader = new ExitableDirectoryReader(directoryReader, queryTimeout);
reader = new TestReader(getOnlyLeafReader(exitableDirectoryReader));
searcher = new IndexSearcher(reader);
searcher.search(query, 300);
reader.close();
// The number of sampled query time out check here depends on two factors:
// 1. ExitableDirectoryReader.ExitableTermsEnum.NUM_CALLS_PER_TIMEOUT_CHECK
// 2. MultiTermQueryConstantScoreWrapper.BOOLEAN_REWRITE_TERM_COUNT_THRESHOLD
assertEquals(5, queryTimeout.getShouldExitCallCount());
}
}
}
/**
* Tests timing out of PointValues queries
*
* @throws Exception on error
*/
public void testExitablePointValuesIndexReader() throws Exception {
Directory directory = newDirectory();
IndexWriter writer = new IndexWriter(directory, newIndexWriterConfig(new MockAnalyzer(random())));
Document d1 = new Document();
d1.add(new IntPoint("default", 10));
writer.addDocument(d1);
Document d2 = new Document();
d2.add(new IntPoint("default", 100));
writer.addDocument(d2);
Document d3 = new Document();
d3.add(new IntPoint("default", 1000));
writer.addDocument(d3);
writer.forceMerge(1);
writer.commit();
writer.close();
DirectoryReader directoryReader;
DirectoryReader exitableDirectoryReader;
IndexReader reader;
IndexSearcher searcher;
Query query = IntPoint.newRangeQuery("default", 10, 20);
// Set a fairly high timeout value (infinite) and expect the query to complete in that time frame.
// Not checking the validity of the result, all we are bothered about in this test is the timing out.
directoryReader = DirectoryReader.open(directory);
exitableDirectoryReader = new ExitableDirectoryReader(directoryReader, infiniteQueryTimeout());
reader = new TestReader(getOnlyLeafReader(exitableDirectoryReader));
searcher = new IndexSearcher(reader);
searcher.search(query, 10);
reader.close();
// Set a really low timeout value (immediate) and expect an Exception
directoryReader = DirectoryReader.open(directory);
exitableDirectoryReader = new ExitableDirectoryReader(directoryReader, immediateQueryTimeout());
reader = new TestReader(getOnlyLeafReader(exitableDirectoryReader));
IndexSearcher slowSearcher = new IndexSearcher(reader);
expectThrows(ExitingReaderException.class, () -> {
slowSearcher.search(query, 10);
});
reader.close();
// Set maximum time out and expect the query to complete.
// Not checking the validity of the result, all we are bothered about in this test is the timing out.
directoryReader = DirectoryReader.open(directory);
exitableDirectoryReader = new ExitableDirectoryReader(directoryReader, infiniteQueryTimeout());
reader = new TestReader(getOnlyLeafReader(exitableDirectoryReader));
searcher = new IndexSearcher(reader);
searcher.search(query, 10);
reader.close();
// Set a negative time allowed and expect the query to complete (should disable timeouts)
// Not checking the validity of the result, all we are bothered about in this test is the timing out.
directoryReader = DirectoryReader.open(directory);
exitableDirectoryReader = new ExitableDirectoryReader(directoryReader, disabledQueryTimeout());
reader = new TestReader(getOnlyLeafReader(exitableDirectoryReader));
searcher = new IndexSearcher(reader);
searcher.search(query, 10);
reader.close();
directory.close();
}
private static QueryTimeout disabledQueryTimeout() {
return new QueryTimeout() {
@Override
public boolean shouldExit() {
return false;
}
@Override
public boolean isTimeoutEnabled() {
return false;
}
};
}
private static QueryTimeout infiniteQueryTimeout() {
return new QueryTimeout() {
@Override
public boolean shouldExit() {
return false;
}
@Override
public boolean isTimeoutEnabled() {
return true;
}
};
}
private static class CountingQueryTimeout implements QueryTimeout {
private int counter = 0;
@Override
public boolean shouldExit() {
counter++;
return false;
}
@Override
public boolean isTimeoutEnabled() {
return true;
}
public int getShouldExitCallCount() {
return counter;
}
}
private static QueryTimeout immediateQueryTimeout() {
return new QueryTimeout() {
@Override
public boolean shouldExit() {
return true;
}
@Override
public boolean isTimeoutEnabled() {
return true;
}
};
}
@FunctionalInterface
interface DvFactory {
DocValuesIterator create(LeafReader leaf) throws IOException;
}
public void testDocValues() throws IOException {
Directory directory = newDirectory();
IndexWriter writer = new IndexWriter(directory, newIndexWriterConfig(new MockAnalyzer(random())));
Document d1 = new Document();
addDVs(d1, 10);
writer.addDocument(d1);
Document d2 = new Document();
addDVs(d2, 100);
writer.addDocument(d2);
Document d3 = new Document();
addDVs(d3, 1000);
writer.addDocument(d3);
writer.forceMerge(1);
writer.commit();
writer.close();
DirectoryReader directoryReader;
DirectoryReader exitableDirectoryReader;
for (DvFactory dvFactory : Arrays.<DvFactory>asList(
(r) -> r.getSortedDocValues("sorted"),
(r) -> r.getSortedSetDocValues("sortedset"),
(r) -> r.getSortedNumericDocValues("sortednumeric"),
(r) -> r.getNumericDocValues("numeric"),
(r) -> r.getBinaryDocValues("binary")
))
{
directoryReader = DirectoryReader.open(directory);
exitableDirectoryReader = new ExitableDirectoryReader(directoryReader, immediateQueryTimeout());
{
IndexReader reader = new TestReader(getOnlyLeafReader(exitableDirectoryReader));
expectThrows(ExitingReaderException.class, () -> {
LeafReader leaf = reader.leaves().get(0).reader();
DocValuesIterator iter = dvFactory.create(leaf);
scan(leaf, iter);
});
reader.close();
}
directoryReader = DirectoryReader.open(directory);
exitableDirectoryReader = new ExitableDirectoryReader(directoryReader, random().nextBoolean()?
infiniteQueryTimeout() : disabledQueryTimeout());
{
IndexReader reader = new TestReader(getOnlyLeafReader(exitableDirectoryReader));
final LeafReader leaf = reader.leaves().get(0).reader();
scan(leaf, dvFactory.create(leaf));
assertNull(leaf.getNumericDocValues("absent"));
assertNull(leaf.getBinaryDocValues("absent"));
assertNull(leaf.getSortedDocValues("absent"));
assertNull(leaf.getSortedNumericDocValues("absent"));
assertNull(leaf.getSortedSetDocValues("absent"));
reader.close();
}
}
directory.close();
}
static private void scan(LeafReader leaf, DocValuesIterator iter ) throws IOException {
for (iter.nextDoc(); iter.docID()!=DocIdSetIterator.NO_MORE_DOCS
&& iter.docID()<leaf.maxDoc();) {
final int nextDocId = iter.docID()+1;
if (random().nextBoolean() && nextDocId<leaf.maxDoc()) {
if(random().nextBoolean()) {
iter.advance(nextDocId);
} else {
iter.advanceExact(nextDocId);
}
} else {
iter.nextDoc();
}
}
}
private void addDVs(Document d1, int i) {
d1.add(new NumericDocValuesField("numeric", i));
d1.add(new BinaryDocValuesField("binary", new BytesRef(""+i)));
d1.add(new SortedDocValuesField("sorted", new BytesRef(""+i)));
d1.add(new SortedNumericDocValuesField("sortednumeric", i));
d1.add(new SortedSetDocValuesField("sortedset", new BytesRef(""+i)));
}
}