~slub.team/goobi-indexserver/3.x

« back to all changes in this revision

Viewing changes to lucene/src/java/org/apache/lucene/search/TimeLimitingCollector.java

  • Committer: Sebastian Meyer
  • Date: 2012-08-03 09:12:40 UTC
  • Revision ID: sebastian.meyer@slub-dresden.de-20120803091240-x6861b0vabq1xror
Remove Lucene and Solr source code and add patches instead
Fix Bug #985487: Auto-suggestion for the search interface

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
}