/*
* 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.
*/
using System;
using Lucene.Net.Support;
using IndexReader = Lucene.Net.Index.IndexReader;
namespace Lucene.Net.Search
{
/// The 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
/// .
///
public class TimeLimitingCollector:Collector
{
private void InitBlock()
{
greedy = DEFAULT_GREEDY;
}
/// Default timer resolution.
///
///
public const int DEFAULT_RESOLUTION = 20;
/// Default for .
///
///
public bool DEFAULT_GREEDY = false;
private static uint resolution = DEFAULT_RESOLUTION;
private bool greedy;
private sealed class TimerThread:ThreadClass
{
// 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 uint 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.
///
internal TimerThread():base("TimeLimitedCollector timer thread")
{
this.IsBackground = true;
}
override public void Run()
{
while (true)
{
// TODO: Use System.nanoTime() when Lucene moves to Java SE 5.
time += Lucene.Net.Search.TimeLimitingCollector.resolution;
System.Threading.Thread.Sleep(new System.TimeSpan((System.Int64) 10000 * Lucene.Net.Search.TimeLimitingCollector.resolution));
}
}
/// Get the timer value in milliseconds.
public long Milliseconds
{
get { return time; }
}
}
/// Thrown when elapsed search time exceeds allowed search time.
[Serializable]
public class TimeExceededException:System.SystemException
{
private long timeAllowed;
private long timeElapsed;
private int lastDocCollected;
internal TimeExceededException(long timeAllowed, long timeElapsed, int lastDocCollected):base("Elapsed time: " + timeElapsed + "Exceeded allowed search time: " + timeAllowed + " ms.")
{
this.timeAllowed = timeAllowed;
this.timeElapsed = timeElapsed;
this.lastDocCollected = lastDocCollected;
}
/// Returns allowed time (milliseconds).
public virtual long TimeAllowed
{
get { return timeAllowed; }
}
/// Returns elapsed time (milliseconds).
public virtual long TimeElapsed
{
get { return timeElapsed; }
}
/// Returns last doc(absolute doc id) that was collected when the search time exceeded.
public virtual int LastDocCollected
{
get { return lastDocCollected; }
}
}
// Declare and initialize a single static timer thread to be used by
// all TimeLimitedCollector instances. The JVM assures that
// this only happens once.
private static readonly TimerThread TIMER_THREAD = new TimerThread();
private long t0;
private long timeout;
private Collector collector;
private int docBase;
/// Create a TimeLimitedCollector wrapper over another with a specified timeout.
/// the wrapped
///
/// max time allowed for collecting hits after which is thrown
///
public TimeLimitingCollector(Collector collector, long timeAllowed)
{
InitBlock();
this.collector = collector;
t0 = TIMER_THREAD.Milliseconds;
this.timeout = t0 + timeAllowed;
}
///
/// Gets or sets the timer resolution.
/// The default timer resolution is 20 milliseconds.
/// This means that a search required to take no longer than
/// 800 milliseconds may be stopped after 780 to 820 milliseconds.
///
Note that:
///
/// - Finer (smaller) resolution is more accurate but less efficient.
/// - Setting resolution to less than 5 milliseconds will be silently modified to 5 milliseconds.
/// - Setting resolution smaller than current resolution might take effect only after current
/// resolution. (Assume current resolution of 20 milliseconds is modified to 5 milliseconds,
/// then it can take up to 20 milliseconds for the change to have effect.
///
///
public static long Resolution
{
get { return resolution; }
set
{
// 5 milliseconds is about the minimum reasonable time for a Object.wait(long) call.
resolution = (uint)System.Math.Max(value, 5);
}
}
/// Checks if this time limited collector is greedy in collecting the last hit.
/// A non greedy collector, upon a timeout, would throw a
/// without allowing the wrapped collector to collect current doc. A greedy one would
/// first allow the wrapped hit collector to collect current doc and only then
/// throw a .
///
public virtual bool IsGreedy
{
get { return greedy; }
set { this.greedy = value; }
}
/// Calls on the decorated
/// unless the allowed time has passed, in which case it throws an exception.
///
///
/// TimeExceededException
/// if the time allowed has exceeded.
///
public override void Collect(int doc)
{
long time = TIMER_THREAD.Milliseconds;
if (timeout < time)
{
if (greedy)
{
//System.out.println(this+" greedy: before failing, collecting doc: "+doc+" "+(time-t0));
collector.Collect(doc);
}
//System.out.println(this+" failing on: "+doc+" "+(time-t0));
throw new TimeExceededException(timeout - t0, time - t0, docBase + doc);
}
//System.out.println(this+" collecting: "+doc+" "+(time-t0));
collector.Collect(doc);
}
public override void SetNextReader(IndexReader reader, int base_Renamed)
{
collector.SetNextReader(reader, base_Renamed);
this.docBase = base_Renamed;
}
public override void SetScorer(Scorer scorer)
{
collector.SetScorer(scorer);
}
public override bool AcceptsDocsOutOfOrder
{
get { return collector.AcceptsDocsOutOfOrder; }
}
static TimeLimitingCollector()
{
{
TIMER_THREAD.Start();
}
}
}
}