Index: src/java/org/apache/lucene/search/TimeLimitedCollector.java
===================================================================
--- src/java/org/apache/lucene/search/TimeLimitedCollector.java	(revision 0)
+++ src/java/org/apache/lucene/search/TimeLimitedCollector.java	(revision 0)
@@ -0,0 +1,130 @@
+package org.apache.lucene.search;
+
+/**
+ * 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.
+ */
+
+/**
+ * <p>The TimeLimitedCollector is used to timeout search requests that
+ * take longer than the maximum allowed search time limit.  After this
+ * time is exceeded, the search thread is stopped by throwing a
+ * TimeExceeded Exception.</p>
+ */
+public class TimeLimitedCollector extends HitCollector
+{
+  private static class TimerThread extends Thread
+  {
+    private final long resolution = 10;  // this is about the minimum reasonable time for a Object.wait(long) call.
+
+    // NOTE: we can avoid explicit synchronization here for several reasons:
+    // * updates to volatile long variables are atomic
+    // * only single thread modifies this value
+    // * use of volatile keyword ensures that it does not reside in
+    //   a register, but in main memory (so that changes are visible to
+    //   other threads).
+    // * visibility of changes does not need to be instantanous, we can
+    //   afford losing a tick or two.
+    //
+    // See section 17 of the Java Language Specification for details.
+    private volatile long time = 0;
+
+    /**
+     * TimerThread provides a pseudo-clock service to all searching
+     * threads, so that they can count elapsed time with less overhead
+     * than repeatedly calling System.currentTimeMillis.  A single
+     * thread should be created to be used for all searches.
+     */
+    private TimerThread()
+    {
+      super("TimeLimitedCollector timer thread");
+      this.setDaemon( true );
+    }
+
+    public void run()
+    {
+      boolean interrupted = false;
+      try
+      {
+        while( true ) {
+          // TODO: Use System.nanoTime() when Lucene moves to Java SE 5.
+          time += resolution;
+          try {
+            Thread.sleep( resolution );
+          }
+          catch( final InterruptedException e ) {
+            interrupted = true;
+          }
+        }
+      }
+      finally {
+        if( interrupted ) {
+          Thread.currentThread().interrupt();
+        }
+      }
+    }
+
+    /**
+     * Get the timer value in milliseconds.
+     */
+    public long getMilliseconds()
+    {
+      return time;
+    }
+  }
+
+  public static class TimeExceeded extends RuntimeException {
+    public long maxTime;
+    public int maxDoc;
+    public TimeExceeded(long maxTime, int maxDoc) {
+      super("Exceeded search time: " + maxTime + " ms.");
+      this.maxTime = maxTime;
+      this.maxDoc = maxDoc;
+    }
+  }
+
+  // Declare and initialize a single static timer thread to be used by
+  // all TimeLimitedCollector instances.  The JVM assures that
+  // this only happens once.
+  private final static TimerThread TIMER_THREAD = new TimerThread();
+  static
+  {
+    TIMER_THREAD.start();
+  }
+
+  private final long t0;
+  private final long timeout;
+  private final HitCollector hc;
+
+  public TimeLimitedCollector( final HitCollector hc, final long timeAllowed )
+  {
+    this.hc = hc;
+    t0 = TIMER_THREAD.getMilliseconds();
+    this.timeout = t0 + timeAllowed;
+  }
+
+  /**
+   * Calls collect() on the decorated HitCollector.
+   * 
+   * @throws TimeExceeded if the time allowed has been exceeded.
+   */
+  public void collect( final int doc, final float score )
+  {
+    if( timeout < TIMER_THREAD.getMilliseconds() ) {
+      throw new TimeExceeded( (TIMER_THREAD.getMilliseconds() - t0), doc );
+    }
+    hc.collect( doc, score );
+  }
+}
