1 package org.apache.lucene.search;
4 * Licensed to the Apache Software Foundation (ASF) under one or more
5 * contributor license agreements. See the NOTICE file distributed with
6 * this work for additional information regarding copyright ownership.
7 * The ASF licenses this file to You under the Apache License, Version 2.0
8 * (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
20 import java.io.IOException;
22 import org.apache.lucene.index.IndexReader;
23 import org.apache.lucene.util.ThreadInterruptedException;
26 * The {@link TimeLimitingCollector} is used to timeout search requests that
27 * take longer than the maximum allowed search time limit. After this time is
28 * exceeded, the search thread is stopped by throwing a
29 * {@link TimeExceededException}.
31 public class TimeLimitingCollector extends Collector {
34 * Default timer resolution.
35 * @see #setResolution(long)
37 public static final int DEFAULT_RESOLUTION = 20;
40 * Default for {@link #isGreedy()}.
43 public boolean DEFAULT_GREEDY = false;
45 private static long resolution = DEFAULT_RESOLUTION;
47 private boolean greedy = DEFAULT_GREEDY ;
49 private static final class TimerThread extends Thread {
51 // NOTE: we can avoid explicit synchronization here for several reasons:
52 // * updates to volatile long variables are atomic
53 // * only single thread modifies this value
54 // * use of volatile keyword ensures that it does not reside in
55 // a register, but in main memory (so that changes are visible to
57 // * visibility of changes does not need to be instantaneous, we can
58 // afford losing a tick or two.
60 // See section 17 of the Java Language Specification for details.
61 private volatile long time = 0;
64 * TimerThread provides a pseudo-clock service to all searching
65 * threads, so that they can count elapsed time with less overhead
66 * than repeatedly calling System.currentTimeMillis. A single
67 * thread should be created to be used for all searches.
69 private TimerThread() {
70 super("TimeLimitedCollector timer thread");
71 this.setDaemon( true );
77 // TODO: Use System.nanoTime() when Lucene moves to Java SE 5.
80 Thread.sleep( resolution );
81 } catch (InterruptedException ie) {
82 throw new ThreadInterruptedException(ie);
88 * Get the timer value in milliseconds.
90 public long getMilliseconds() {
95 /** Thrown when elapsed search time exceeds allowed search time. */
96 public static class TimeExceededException extends RuntimeException {
97 private long timeAllowed;
98 private long timeElapsed;
99 private int lastDocCollected;
100 private TimeExceededException(long timeAllowed, long timeElapsed, int lastDocCollected) {
101 super("Elapsed time: " + timeElapsed + "Exceeded allowed search time: " + timeAllowed + " ms.");
102 this.timeAllowed = timeAllowed;
103 this.timeElapsed = timeElapsed;
104 this.lastDocCollected = lastDocCollected;
106 /** Returns allowed time (milliseconds). */
107 public long getTimeAllowed() {
110 /** Returns elapsed time (milliseconds). */
111 public long getTimeElapsed() {
114 /** Returns last doc (absolute doc id) that was collected when the search time exceeded. */
115 public int getLastDocCollected() {
116 return lastDocCollected;
120 // Declare and initialize a single static timer thread to be used by
121 // all TimeLimitedCollector instances. The JVM assures that
122 // this only happens once.
123 private final static TimerThread TIMER_THREAD = new TimerThread();
126 TIMER_THREAD.start();
129 private final long t0;
130 private final long timeout;
131 private final Collector collector;
136 * Create a TimeLimitedCollector wrapper over another {@link Collector} with a specified timeout.
137 * @param collector the wrapped {@link Collector}
138 * @param timeAllowed max time allowed for collecting hits after which {@link TimeExceededException} is thrown
140 public TimeLimitingCollector(final Collector collector, final long timeAllowed ) {
141 this.collector = collector;
142 t0 = TIMER_THREAD.getMilliseconds();
143 this.timeout = t0 + timeAllowed;
147 * Return the timer resolution.
148 * @see #setResolution(long)
150 public static long getResolution() {
155 * Set the timer resolution.
156 * The default timer resolution is 20 milliseconds.
157 * This means that a search required to take no longer than
158 * 800 milliseconds may be stopped after 780 to 820 milliseconds.
161 * <li>Finer (smaller) resolution is more accurate but less efficient.</li>
162 * <li>Setting resolution to less than 5 milliseconds will be silently modified to 5 milliseconds.</li>
163 * <li>Setting resolution smaller than current resolution might take effect only after current
164 * resolution. (Assume current resolution of 20 milliseconds is modified to 5 milliseconds,
165 * then it can take up to 20 milliseconds for the change to have effect.</li>
168 public static void setResolution(long newResolution) {
169 resolution = Math.max(newResolution,5); // 5 milliseconds is about the minimum reasonable time for a Object.wait(long) call.
173 * Checks if this time limited collector is greedy in collecting the last hit.
174 * A non greedy collector, upon a timeout, would throw a {@link TimeExceededException}
175 * without allowing the wrapped collector to collect current doc. A greedy one would
176 * first allow the wrapped hit collector to collect current doc and only then
177 * throw a {@link TimeExceededException}.
178 * @see #setGreedy(boolean)
180 public boolean isGreedy() {
185 * Sets whether this time limited collector is greedy.
186 * @param greedy true to make this time limited greedy
189 public void setGreedy(boolean greedy) {
190 this.greedy = greedy;
194 * Calls {@link Collector#collect(int)} on the decorated {@link Collector}
195 * unless the allowed time has passed, in which case it throws an exception.
197 * @throws TimeExceededException
198 * if the time allowed has exceeded.
201 public void collect(final int doc) throws IOException {
202 long time = TIMER_THREAD.getMilliseconds();
203 if (timeout < time) {
205 //System.out.println(this+" greedy: before failing, collecting doc: "+(docBase + doc)+" "+(time-t0));
206 collector.collect(doc);
208 //System.out.println(this+" failing on: "+(docBase + doc)+" "+(time-t0));
209 throw new TimeExceededException( timeout-t0, time-t0, docBase + doc );
211 //System.out.println(this+" collecting: "+(docBase + doc)+" "+(time-t0));
212 collector.collect(doc);
216 public void setNextReader(IndexReader reader, int base) throws IOException {
217 collector.setNextReader(reader, base);
222 public void setScorer(Scorer scorer) throws IOException {
223 collector.setScorer(scorer);
227 public boolean acceptsDocsOutOfOrder() {
228 return collector.acceptsDocsOutOfOrder();