pylucene 3.5.0-3
[pylucene.git] / lucene-java-3.5.0 / lucene / src / java / org / apache / lucene / search / TimeLimitingCollector.java
1 package org.apache.lucene.search;
2
3 /**
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
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
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.
18  */
19
20 import java.io.IOException;
21
22 import org.apache.lucene.index.IndexReader;
23 import org.apache.lucene.util.Counter;
24 import org.apache.lucene.util.ThreadInterruptedException;
25
26 /**
27  * The {@link TimeLimitingCollector} is used to timeout search requests that
28  * take longer than the maximum allowed search time limit. After this time is
29  * exceeded, the search thread is stopped by throwing a
30  * {@link TimeExceededException}.
31  */
32 public class TimeLimitingCollector extends Collector {
33
34
35   /** Thrown when elapsed search time exceeds allowed search time. */
36   @SuppressWarnings("serial")
37   public static class TimeExceededException extends RuntimeException {
38     private long timeAllowed;
39     private long timeElapsed;
40     private int lastDocCollected;
41     private TimeExceededException(long timeAllowed, long timeElapsed, int lastDocCollected) {
42       super("Elapsed time: " + timeElapsed + "Exceeded allowed search time: " + timeAllowed + " ms.");
43       this.timeAllowed = timeAllowed;
44       this.timeElapsed = timeElapsed;
45       this.lastDocCollected = lastDocCollected;
46     }
47     /** Returns allowed time (milliseconds). */
48     public long getTimeAllowed() {
49       return timeAllowed;
50     }
51     /** Returns elapsed time (milliseconds). */
52     public long getTimeElapsed() {
53       return timeElapsed;
54     }
55     /** Returns last doc (absolute doc id) that was collected when the search time exceeded. */
56     public int getLastDocCollected() {
57       return lastDocCollected;
58     }
59   }
60
61   private long t0 = Long.MIN_VALUE;
62   private long timeout = Long.MIN_VALUE;
63   private final Collector collector;
64   private final Counter clock;
65   private final long ticksAllowed;
66   private boolean greedy = false;
67   private int docBase;
68
69   /**
70    * Create a TimeLimitedCollector wrapper over another {@link Collector} with a specified timeout.
71    * @param collector the wrapped {@link Collector}
72    * @param clock the timer clock
73    * @param ticksAllowed max time allowed for collecting
74    * hits after which {@link TimeExceededException} is thrown
75    */
76   public TimeLimitingCollector(final Collector collector, Counter clock, final long ticksAllowed ) {
77     this.collector = collector;
78     this.clock = clock;
79     this.ticksAllowed = ticksAllowed;
80   }
81   
82   /**
83    * Sets the baseline for this collector. By default the collectors baseline is 
84    * initialized once the first reader is passed to the collector. 
85    * To include operations executed in prior to the actual document collection
86    * set the baseline through this method in your prelude.
87    * <p>
88    * Example usage:
89    * <pre>
90    *   Counter clock = ...;
91    *   long baseline = clock.get();
92    *   // ... prepare search
93    *   TimeLimitingCollector collector = new TimeLimitingCollector(c, clock, numTicks);
94    *   collector.setBaseline(baseline);
95    *   indexSearcher.search(query, collector);
96    * </pre>
97    * </p>
98    * @see #setBaseline() 
99    * @param clockTime
100    */
101   public void setBaseline(long clockTime) {
102     t0 = clockTime;
103     timeout = t0 + ticksAllowed;
104   }
105   
106   /**
107    * Syntactic sugar for {@link #setBaseline(long)} using {@link Counter#get()}
108    * on the clock passed to the construcutor.
109    */
110   public void setBaseline() {
111     setBaseline(clock.get());
112   }
113   
114   /**
115    * Checks if this time limited collector is greedy in collecting the last hit.
116    * A non greedy collector, upon a timeout, would throw a {@link TimeExceededException} 
117    * without allowing the wrapped collector to collect current doc. A greedy one would 
118    * first allow the wrapped hit collector to collect current doc and only then 
119    * throw a {@link TimeExceededException}.
120    * @see #setGreedy(boolean)
121    */
122   public boolean isGreedy() {
123     return greedy;
124   }
125
126   /**
127    * Sets whether this time limited collector is greedy.
128    * @param greedy true to make this time limited greedy
129    * @see #isGreedy()
130    */
131   public void setGreedy(boolean greedy) {
132     this.greedy = greedy;
133   }
134   
135   /**
136    * Calls {@link Collector#collect(int)} on the decorated {@link Collector}
137    * unless the allowed time has passed, in which case it throws an exception.
138    * 
139    * @throws TimeExceededException
140    *           if the time allowed has exceeded.
141    */
142   @Override
143   public void collect(final int doc) throws IOException {
144     final long time = clock.get();
145     if (timeout < time) {
146       if (greedy) {
147         //System.out.println(this+"  greedy: before failing, collecting doc: "+(docBase + doc)+"  "+(time-t0));
148         collector.collect(doc);
149       }
150       //System.out.println(this+"  failing on:  "+(docBase + doc)+"  "+(time-t0));
151       throw new TimeExceededException( timeout-t0, time-t0, docBase + doc );
152     }
153     //System.out.println(this+"  collecting: "+(docBase + doc)+"  "+(time-t0));
154     collector.collect(doc);
155   }
156   
157   @Override
158   public void setNextReader(IndexReader reader, int base) throws IOException {
159     collector.setNextReader(reader, base);
160     this.docBase = base;
161     if (Long.MIN_VALUE == t0) {
162       setBaseline();
163     }
164   }
165   
166   @Override
167   public void setScorer(Scorer scorer) throws IOException {
168     collector.setScorer(scorer);
169   }
170
171   @Override
172   public boolean acceptsDocsOutOfOrder() {
173     return collector.acceptsDocsOutOfOrder();
174   }
175
176
177   /**
178    * Returns the global TimerThreads {@link Counter}
179    * <p>
180    * Invoking this creates may create a new instance of {@link TimerThread} iff
181    * the global {@link TimerThread} has never been accessed before. The thread
182    * returned from this method is started on creation and will be alive unless
183    * you stop the {@link TimerThread} via {@link TimerThread#stopTimer()}.
184    * </p>
185    * @return the global TimerThreads {@link Counter}
186    * @lucene.experimental
187    */
188   public static Counter getGlobalCounter() {
189     return TimerThreadHolder.THREAD.counter;
190   }
191   
192   /**
193    * Returns the global {@link TimerThread}.
194    * <p>
195    * Invoking this creates may create a new instance of {@link TimerThread} iff
196    * the global {@link TimerThread} has never been accessed before. The thread
197    * returned from this method is started on creation and will be alive unless
198    * you stop the {@link TimerThread} via {@link TimerThread#stopTimer()}.
199    * </p>
200    * 
201    * @return the global {@link TimerThread}
202    * @lucene.experimental
203    */
204   public static TimerThread getGlobalTimerThread() {
205     return TimerThreadHolder.THREAD;
206   }
207   
208   private static final class TimerThreadHolder {
209     static final TimerThread THREAD;
210     static {
211       THREAD = new TimerThread(Counter.newCounter(true));
212       THREAD.start();
213     }
214   }
215
216   /**
217    * @lucene.experimental
218    */
219   public static final class TimerThread extends Thread  {
220     
221     public static final String THREAD_NAME = "TimeLimitedCollector timer thread";
222     public static final int DEFAULT_RESOLUTION = 20;
223     // NOTE: we can avoid explicit synchronization here for several reasons:
224     // * updates to volatile long variables are atomic
225     // * only single thread modifies this value
226     // * use of volatile keyword ensures that it does not reside in
227     //   a register, but in main memory (so that changes are visible to
228     //   other threads).
229     // * visibility of changes does not need to be instantaneous, we can
230     //   afford losing a tick or two.
231     //
232     // See section 17 of the Java Language Specification for details.
233     private volatile long time = 0;
234     private volatile boolean stop = false;
235     private volatile long resolution;
236     final Counter counter;
237     
238     public TimerThread(long resolution, Counter counter) {
239       super(THREAD_NAME);
240       this.resolution = resolution;
241       this.counter = counter;
242       this.setDaemon(true);
243     }
244     
245     public TimerThread(Counter counter) {
246       this(DEFAULT_RESOLUTION, counter);
247     }
248
249     @Override
250     public void run() {
251       while (!stop) {
252         // TODO: Use System.nanoTime() when Lucene moves to Java SE 5.
253         counter.addAndGet(resolution);
254         try {
255           Thread.sleep( resolution );
256         } catch (InterruptedException ie) {
257           throw new ThreadInterruptedException(ie);
258         }
259       }
260     }
261
262     /**
263      * Get the timer value in milliseconds.
264      */
265     public long getMilliseconds() {
266       return time;
267     }
268     
269     /**
270      * Stops the timer thread 
271      */
272     public void stopTimer() {
273       stop = true;
274     }
275     
276     /** 
277      * Return the timer resolution.
278      * @see #setResolution(long)
279      */
280     public long getResolution() {
281       return resolution;
282     }
283     
284     /**
285      * Set the timer resolution.
286      * The default timer resolution is 20 milliseconds. 
287      * This means that a search required to take no longer than 
288      * 800 milliseconds may be stopped after 780 to 820 milliseconds.
289      * <br>Note that: 
290      * <ul>
291      * <li>Finer (smaller) resolution is more accurate but less efficient.</li>
292      * <li>Setting resolution to less than 5 milliseconds will be silently modified to 5 milliseconds.</li>
293      * <li>Setting resolution smaller than current resolution might take effect only after current 
294      * resolution. (Assume current resolution of 20 milliseconds is modified to 5 milliseconds, 
295      * then it can take up to 20 milliseconds for the change to have effect.</li>
296      * </ul>      
297      */
298     public void setResolution(long resolution) {
299       this.resolution = Math.max(resolution, 5); // 5 milliseconds is about the minimum reasonable time for a Object.wait(long) call.
300     }
301   }
302   
303 }