blob: ecedb8c94a5de8b54101f2d3a8407e82cfb99b9b [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 java.io.IOException;
import org.apache.lucene.index.FilterLeafReader.FilterTerms;
import org.apache.lucene.index.FilterLeafReader.FilterTermsEnum;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.automaton.CompiledAutomaton;
/**
* The {@link ExitableDirectoryReader} wraps a real index {@link DirectoryReader} and
* allows for a {@link QueryTimeout} implementation object to be checked periodically
* to see if the thread should exit or not. If {@link QueryTimeout#shouldExit()}
* returns true, an {@link ExitingReaderException} is thrown.
*
* @see org.apache.lucene.search.TimeLimitingCollector
*/
public class ExitableDirectoryReader extends FilterDirectoryReader {
private QueryTimeout queryTimeout;
/**
* Exception that is thrown to prematurely terminate a term enumeration.
*/
@SuppressWarnings("serial")
public static class ExitingReaderException extends RuntimeException {
/** Constructor **/
public ExitingReaderException(String msg) {
super(msg);
}
}
/**
* Wrapper class for a SubReaderWrapper that is used by the ExitableDirectoryReader.
*/
public static class ExitableSubReaderWrapper extends SubReaderWrapper {
private QueryTimeout queryTimeout;
/** Constructor **/
public ExitableSubReaderWrapper(QueryTimeout queryTimeout) {
this.queryTimeout = queryTimeout;
}
@Override
public LeafReader wrap(LeafReader reader) {
return new ExitableFilterAtomicReader(reader, queryTimeout);
}
}
/**
* Wrapper class for another FilterAtomicReader. This is used by ExitableSubReaderWrapper.
*/
public static class ExitableFilterAtomicReader extends FilterLeafReader {
final private QueryTimeout queryTimeout;
final static int DOCS_BETWEEN_TIMEOUT_CHECK = 1000;
/** Constructor **/
public ExitableFilterAtomicReader(LeafReader in, QueryTimeout queryTimeout) {
super(in);
this.queryTimeout = queryTimeout;
}
@Override
public PointValues getPointValues(String field) throws IOException {
final PointValues pointValues = in.getPointValues(field);
if (pointValues == null) {
return null;
}
return (queryTimeout.isTimeoutEnabled()) ? new ExitablePointValues(pointValues, queryTimeout) : pointValues;
}
@Override
public Terms terms(String field) throws IOException {
Terms terms = in.terms(field);
if (terms == null) {
return null;
}
return (queryTimeout.isTimeoutEnabled()) ? new ExitableTerms(terms, queryTimeout) : terms;
}
// this impl does not change deletes or data so we can delegate the
// CacheHelpers
@Override
public CacheHelper getReaderCacheHelper() {
return in.getReaderCacheHelper();
}
@Override
public CacheHelper getCoreCacheHelper() {
return in.getCoreCacheHelper();
}
@Override
public NumericDocValues getNumericDocValues(String field) throws IOException {
final NumericDocValues numericDocValues = super.getNumericDocValues(field);
if (numericDocValues == null) {
return null;
}
return (queryTimeout.isTimeoutEnabled()) ? new FilterNumericDocValues(numericDocValues) {
private int docToCheck = 0;
@Override
public int advance(int target) throws IOException {
final int advance = super.advance(target);
if (advance >= docToCheck) {
checkAndThrow(in);
docToCheck = advance + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return advance;
}
@Override
public boolean advanceExact(int target) throws IOException {
final boolean advanceExact = super.advanceExact(target);
if (target >= docToCheck) {
checkAndThrow(in);
docToCheck=target + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return advanceExact;
}
@Override
public int nextDoc() throws IOException {
final int nextDoc = super.nextDoc();
if (nextDoc >= docToCheck) {
checkAndThrow(in);
docToCheck = nextDoc + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return nextDoc;
}
}: numericDocValues;
}
@Override
public BinaryDocValues getBinaryDocValues(String field) throws IOException {
final BinaryDocValues binaryDocValues = super.getBinaryDocValues(field);
if (binaryDocValues == null) {
return null;
}
return (queryTimeout.isTimeoutEnabled()) ? new FilterBinaryDocValues(binaryDocValues) {
private int docToCheck = 0;
@Override
public int advance(int target) throws IOException {
final int advance = super.advance(target);
if (target >= docToCheck) {
checkAndThrow(in);
docToCheck = target + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return advance;
}
@Override
public boolean advanceExact(int target) throws IOException {
final boolean advanceExact = super.advanceExact(target);
if (target >= docToCheck) {
checkAndThrow(in);
docToCheck = target + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return advanceExact;
}
@Override
public int nextDoc() throws IOException {
final int nextDoc = super.nextDoc();
if (nextDoc >= docToCheck) {
checkAndThrow(in);
docToCheck = nextDoc + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return nextDoc;
}
}: binaryDocValues;
}
@Override
public SortedDocValues getSortedDocValues(String field) throws IOException {
final SortedDocValues sortedDocValues = super.getSortedDocValues(field);
if (sortedDocValues == null) {
return null;
}
return (queryTimeout.isTimeoutEnabled()) ? new FilterSortedDocValues(sortedDocValues) {
private int docToCheck = 0;
@Override
public int advance(int target) throws IOException {
final int advance = super.advance(target);
if (advance >= docToCheck) {
checkAndThrow(in);
docToCheck = advance + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return advance;
}
@Override
public boolean advanceExact(int target) throws IOException {
final boolean advanceExact = super.advanceExact(target);
if (target >= docToCheck) {
checkAndThrow(in);
docToCheck = target + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return advanceExact;
}
@Override
public int nextDoc() throws IOException {
final int nextDoc = super.nextDoc();
if (nextDoc >= docToCheck) {
checkAndThrow(in);
docToCheck = nextDoc + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return nextDoc;
}
}: sortedDocValues;
}
@Override
public SortedNumericDocValues getSortedNumericDocValues(String field) throws IOException {
final SortedNumericDocValues sortedNumericDocValues = super.getSortedNumericDocValues(field);
if (sortedNumericDocValues == null) {
return null;
}
return (queryTimeout.isTimeoutEnabled()) ? new FilterSortedNumericDocValues(sortedNumericDocValues) {
private int docToCheck = 0;
@Override
public int advance(int target) throws IOException {
final int advance = super.advance(target);
if (advance >= docToCheck) {
checkAndThrow(in);
docToCheck = advance + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return advance;
}
@Override
public boolean advanceExact(int target) throws IOException {
final boolean advanceExact = super.advanceExact(target);
if (target >= docToCheck) {
checkAndThrow(in);
docToCheck = target + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return advanceExact;
}
@Override
public int nextDoc() throws IOException {
final int nextDoc = super.nextDoc();
if (nextDoc >= docToCheck) {
checkAndThrow(in);
docToCheck = nextDoc + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return nextDoc;
}
}: sortedNumericDocValues;
}
@Override
public SortedSetDocValues getSortedSetDocValues(String field) throws IOException {
final SortedSetDocValues sortedSetDocValues = super.getSortedSetDocValues(field);
if (sortedSetDocValues == null) {
return null;
}
return (queryTimeout.isTimeoutEnabled()) ? new FilterSortedSetDocValues(sortedSetDocValues) {
private int docToCheck=0;
@Override
public int advance(int target) throws IOException {
final int advance = super.advance(target);
if (advance >= docToCheck) {
checkAndThrow(in);
docToCheck = advance + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return advance;
}
@Override
public boolean advanceExact(int target) throws IOException {
final boolean advanceExact = super.advanceExact(target);
if (target >= docToCheck) {
checkAndThrow(in);
docToCheck = target + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return advanceExact;
}
@Override
public int nextDoc() throws IOException {
final int nextDoc = super.nextDoc();
if (nextDoc >= docToCheck) {
checkAndThrow(in);
docToCheck = nextDoc + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return nextDoc;
}
}: sortedSetDocValues;
}
/**
* Throws {@link ExitingReaderException} if {@link QueryTimeout#shouldExit()} returns true,
* or if {@link Thread#interrupted()} returns true.
* @param in underneath docValues
*/
private void checkAndThrow(DocIdSetIterator in) {
if (queryTimeout.shouldExit()) {
throw new ExitingReaderException("The request took too long to iterate over doc values. Timeout: "
+ queryTimeout.toString() + ", DocValues=" + in
);
} else if (Thread.interrupted()) {
throw new ExitingReaderException("Interrupted while iterating over point values. PointValues=" + in);
}
}
}
/**
* Wrapper class for another PointValues implementation that is used by ExitableFields.
*/
private static class ExitablePointValues extends PointValues {
private final PointValues in;
private final QueryTimeout queryTimeout;
private ExitablePointValues(PointValues in, QueryTimeout queryTimeout) {
this.in = in;
this.queryTimeout = queryTimeout;
checkAndThrow();
}
/**
* Throws {@link ExitingReaderException} if {@link QueryTimeout#shouldExit()} returns true,
* or if {@link Thread#interrupted()} returns true.
*/
private void checkAndThrow() {
if (queryTimeout.shouldExit()) {
throw new ExitingReaderException("The request took too long to iterate over point values. Timeout: "
+ queryTimeout.toString()
+ ", PointValues=" + in
);
} else if (Thread.interrupted()) {
throw new ExitingReaderException("Interrupted while iterating over point values. PointValues=" + in);
}
}
@Override
public void intersect(IntersectVisitor visitor) throws IOException {
checkAndThrow();
in.intersect(new ExitableIntersectVisitor(visitor, queryTimeout));
}
@Override
public long estimatePointCount(IntersectVisitor visitor) {
checkAndThrow();
return in.estimatePointCount(visitor);
}
@Override
public byte[] getMinPackedValue() throws IOException {
checkAndThrow();
return in.getMinPackedValue();
}
@Override
public byte[] getMaxPackedValue() throws IOException {
checkAndThrow();
return in.getMaxPackedValue();
}
@Override
public int getNumDimensions() throws IOException {
checkAndThrow();
return in.getNumDimensions();
}
@Override
public int getNumIndexDimensions() throws IOException {
checkAndThrow();
return in.getNumIndexDimensions();
}
@Override
public int getBytesPerDimension() throws IOException {
checkAndThrow();
return in.getBytesPerDimension();
}
@Override
public long size() {
checkAndThrow();
return in.size();
}
@Override
public int getDocCount() {
checkAndThrow();
return in.getDocCount();
}
}
private static class ExitableIntersectVisitor implements PointValues.IntersectVisitor {
private static final int MAX_CALLS_BEFORE_QUERY_TIMEOUT_CHECK = 10;
private final PointValues.IntersectVisitor in;
private final QueryTimeout queryTimeout;
private int calls;
private ExitableIntersectVisitor(PointValues.IntersectVisitor in, QueryTimeout queryTimeout) {
this.in = in;
this.queryTimeout = queryTimeout;
}
/**
* Throws {@link ExitingReaderException} if {@link QueryTimeout#shouldExit()} returns true,
* or if {@link Thread#interrupted()} returns true.
*/
private void checkAndThrowWithSampling() {
if (calls++ % MAX_CALLS_BEFORE_QUERY_TIMEOUT_CHECK == 0) {
checkAndThrow();
}
}
private void checkAndThrow() {
if (queryTimeout.shouldExit()) {
throw new ExitingReaderException("The request took too long to intersect point values. Timeout: "
+ queryTimeout.toString()
+ ", PointValues=" + in
);
} else if (Thread.interrupted()) {
throw new ExitingReaderException("Interrupted while intersecting point values. PointValues=" + in);
}
}
@Override
public void visit(int docID) throws IOException {
checkAndThrowWithSampling();
in.visit(docID);
}
@Override
public void visit(int docID, byte[] packedValue) throws IOException {
checkAndThrowWithSampling();
in.visit(docID, packedValue);
}
@Override
public PointValues.Relation compare(byte[] minPackedValue, byte[] maxPackedValue) {
checkAndThrow();
return in.compare(minPackedValue, maxPackedValue);
}
@Override
public void grow(int count) {
checkAndThrow();
in.grow(count);
}
}
/**
* Wrapper class for another Terms implementation that is used by ExitableFields.
*/
public static class ExitableTerms extends FilterTerms {
private QueryTimeout queryTimeout;
/** Constructor **/
public ExitableTerms(Terms terms, QueryTimeout queryTimeout) {
super(terms);
this.queryTimeout = queryTimeout;
}
@Override
public TermsEnum intersect(CompiledAutomaton compiled, BytesRef startTerm) throws IOException {
return new ExitableTermsEnum(in.intersect(compiled, startTerm), queryTimeout);
}
@Override
public TermsEnum iterator() throws IOException {
return new ExitableTermsEnum(in.iterator(), queryTimeout);
}
}
/**
* Wrapper class for TermsEnum that is used by ExitableTerms for implementing an
* exitable enumeration of terms.
*/
public static class ExitableTermsEnum extends FilterTermsEnum {
// Create bit mask in the form of 0000 1111 for efficient checking
private static final int NUM_CALLS_PER_TIMEOUT_CHECK = (1 << 4) - 1; // 15
private int calls;
private QueryTimeout queryTimeout;
/** Constructor **/
public ExitableTermsEnum(TermsEnum termsEnum, QueryTimeout queryTimeout) {
super(termsEnum);
this.queryTimeout = queryTimeout;
checkTimeoutWithSampling();
}
/**
* Throws {@link ExitingReaderException} if {@link QueryTimeout#shouldExit()} returns true,
* or if {@link Thread#interrupted()} returns true.
*/
private void checkTimeoutWithSampling() {
if ((calls++ & NUM_CALLS_PER_TIMEOUT_CHECK) == 0) {
if (queryTimeout.shouldExit()) {
throw new ExitingReaderException("The request took too long to iterate over terms. Timeout: "
+ queryTimeout.toString()
+ ", TermsEnum=" + in
);
} else if (Thread.interrupted()) {
throw new ExitingReaderException("Interrupted while iterating over terms. TermsEnum=" + in);
}
}
}
@Override
public BytesRef next() throws IOException {
// Before every iteration, check if the iteration should exit
checkTimeoutWithSampling();
return in.next();
}
}
/**
* Constructor
* @param in DirectoryReader that this ExitableDirectoryReader wraps around to make it Exitable.
* @param queryTimeout The object to periodically check if the query should time out.
*/
public ExitableDirectoryReader(DirectoryReader in, QueryTimeout queryTimeout) throws IOException {
super(in, new ExitableSubReaderWrapper(queryTimeout));
this.queryTimeout = queryTimeout;
}
@Override
protected DirectoryReader doWrapDirectoryReader(DirectoryReader in) throws IOException {
return new ExitableDirectoryReader(in, queryTimeout);
}
/**
* Wraps a provided DirectoryReader. Note that for convenience, the returned reader
* can be used normally (e.g. passed to {@link DirectoryReader#openIfChanged(DirectoryReader)})
* and so on.
*/
public static DirectoryReader wrap(DirectoryReader in, QueryTimeout queryTimeout) throws IOException {
return new ExitableDirectoryReader(in, queryTimeout);
}
@Override
public CacheHelper getReaderCacheHelper() {
return in.getReaderCacheHelper();
}
@Override
public String toString() {
return "ExitableDirectoryReader(" + in.toString() + ")";
}
}