add --shared
[pylucene.git] / lucene-java-3.4.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.ThreadInterruptedException;
24
25 /**
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}.
30  */
31 public class TimeLimitingCollector extends Collector {
32
33   /** 
34    * Default timer resolution.
35    * @see #setResolution(long) 
36    */
37   public static final int DEFAULT_RESOLUTION = 20;
38
39   /**
40    * Default for {@link #isGreedy()}.
41    * @see #isGreedy()
42    */
43   public boolean DEFAULT_GREEDY = false; 
44
45   private static long resolution = DEFAULT_RESOLUTION;
46   
47   private boolean greedy = DEFAULT_GREEDY ;
48
49   private static final class TimerThread extends Thread  {
50
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
56     //   other threads).
57     // * visibility of changes does not need to be instantaneous, we can
58     //   afford losing a tick or two.
59     //
60     // See section 17 of the Java Language Specification for details.
61     private volatile long time = 0;
62
63     /**
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.
68      */
69     private TimerThread() {
70       super("TimeLimitedCollector timer thread");
71       this.setDaemon( true );
72     }
73
74     @Override
75     public void run() {
76       while (true) {
77         // TODO: Use System.nanoTime() when Lucene moves to Java SE 5.
78         time += resolution;
79         try {
80           Thread.sleep( resolution );
81         } catch (InterruptedException ie) {
82           throw new ThreadInterruptedException(ie);
83         }
84       }
85     }
86
87     /**
88      * Get the timer value in milliseconds.
89      */
90     public long getMilliseconds() {
91       return time;
92     }
93   }
94
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;
105     }
106     /** Returns allowed time (milliseconds). */
107     public long getTimeAllowed() {
108       return timeAllowed;
109     }
110     /** Returns elapsed time (milliseconds). */
111     public long getTimeElapsed() {
112       return timeElapsed;
113     }
114     /** Returns last doc (absolute doc id) that was collected when the search time exceeded. */
115     public int getLastDocCollected() {
116       return lastDocCollected;
117     }
118   }
119
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();
124   
125   static  {
126     TIMER_THREAD.start();
127   }
128
129   private final long t0;
130   private final long timeout;
131   private final Collector collector;
132   
133   private int docBase;
134
135   /**
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
139    */
140   public TimeLimitingCollector(final Collector collector, final long timeAllowed ) {
141     this.collector = collector;
142     t0 = TIMER_THREAD.getMilliseconds();
143     this.timeout = t0 + timeAllowed;
144   }
145
146   /** 
147    * Return the timer resolution.
148    * @see #setResolution(long)
149    */
150   public static long getResolution() {
151     return resolution;
152   }
153
154   /**
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.
159    * <br>Note that: 
160    * <ul>
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>
166    * </ul>      
167    */
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.
170   }
171
172   /**
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)
179    */
180   public boolean isGreedy() {
181     return greedy;
182   }
183
184   /**
185    * Sets whether this time limited collector is greedy.
186    * @param greedy true to make this time limited greedy
187    * @see #isGreedy()
188    */
189   public void setGreedy(boolean greedy) {
190     this.greedy = greedy;
191   }
192   
193   /**
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.
196    * 
197    * @throws TimeExceededException
198    *           if the time allowed has exceeded.
199    */
200   @Override
201   public void collect(final int doc) throws IOException {
202     long time = TIMER_THREAD.getMilliseconds();
203     if (timeout < time) {
204       if (greedy) {
205         //System.out.println(this+"  greedy: before failing, collecting doc: "+(docBase + doc)+"  "+(time-t0));
206         collector.collect(doc);
207       }
208       //System.out.println(this+"  failing on:  "+(docBase + doc)+"  "+(time-t0));
209       throw new TimeExceededException( timeout-t0, time-t0, docBase + doc );
210     }
211     //System.out.println(this+"  collecting: "+(docBase + doc)+"  "+(time-t0));
212     collector.collect(doc);
213   }
214   
215   @Override
216   public void setNextReader(IndexReader reader, int base) throws IOException {
217     collector.setNextReader(reader, base);
218     this.docBase = base;
219   }
220   
221   @Override
222   public void setScorer(Scorer scorer) throws IOException {
223     collector.setScorer(scorer);
224   }
225
226   @Override
227   public boolean acceptsDocsOutOfOrder() {
228     return collector.acceptsDocsOutOfOrder();
229   }
230
231 }