| using Lucene.Net.Support; |
| using System; |
| #if FEATURE_SERIALIZABLE |
| using System.Runtime.Serialization; |
| #endif |
| using System.Threading; |
| |
| namespace Lucene.Net.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. |
| */ |
| |
| using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext; |
| using Counter = Lucene.Net.Util.Counter; |
| |
| /// <summary> |
| /// The <seealso cref="TimeLimitingCollector"/> 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 |
| /// <seealso cref="TimeExceededException"/>. |
| /// </summary> |
| #if FEATURE_SERIALIZABLE |
| [Serializable] |
| #endif |
| public class TimeLimitingCollector : ICollector |
| { |
| /// <summary> |
| /// Thrown when elapsed search time exceeds allowed search time. </summary> |
| // LUCENENET: All exeption classes should be marked serializable |
| #if FEATURE_SERIALIZABLE |
| [Serializable] |
| #endif |
| public class TimeExceededException : Exception |
| { |
| 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; |
| } |
| |
| // For testing purposes |
| internal TimeExceededException(string message) |
| : base(message) |
| { |
| } |
| |
| #if FEATURE_SERIALIZABLE |
| /// <summary> |
| /// Initializes a new instance of this class with serialized data. |
| /// </summary> |
| /// <param name="info">The <see cref="SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param> |
| /// <param name="context">The <see cref="StreamingContext"/> that contains contextual information about the source or destination.</param> |
| public TimeExceededException(SerializationInfo info, StreamingContext context) |
| : base(info, context) |
| { |
| } |
| #endif |
| |
| /// <summary> |
| /// Returns allowed time (milliseconds). </summary> |
| public virtual long TimeAllowed |
| { |
| get |
| { |
| return timeAllowed; |
| } |
| } |
| |
| /// <summary> |
| /// Returns elapsed time (milliseconds). </summary> |
| public virtual long TimeElapsed |
| { |
| get |
| { |
| return timeElapsed; |
| } |
| } |
| |
| /// <summary> |
| /// Returns last doc (absolute doc id) that was collected when the search time exceeded. </summary> |
| public virtual int LastDocCollected |
| { |
| get |
| { |
| return lastDocCollected; |
| } |
| } |
| } |
| |
| private long t0 = long.MinValue; |
| private long timeout = long.MinValue; |
| private ICollector collector; |
| private readonly Counter clock; |
| private readonly long ticksAllowed; |
| private bool greedy = false; |
| private int docBase; |
| |
| /// <summary> |
| /// Create a TimeLimitedCollector wrapper over another <seealso cref="ICollector"/> with a specified timeout. </summary> |
| /// <param name="collector"> the wrapped <seealso cref="ICollector"/> </param> |
| /// <param name="clock"> the timer clock </param> |
| /// <param name="ticksAllowed"> max time allowed for collecting |
| /// hits after which <seealso cref="TimeExceededException"/> is thrown </param> |
| public TimeLimitingCollector(ICollector collector, Counter clock, long ticksAllowed) |
| { |
| this.collector = collector; |
| this.clock = clock; |
| this.ticksAllowed = ticksAllowed; |
| } |
| |
| /// <summary> |
| /// Sets the baseline for this collector. By default the collectors baseline is |
| /// initialized once the first reader is passed to the collector. |
| /// To include operations executed in prior to the actual document collection |
| /// set the baseline through this method in your prelude. |
| /// <p> |
| /// Example usage: |
| /// <pre class="prettyprint"> |
| /// Counter clock = ...; |
| /// long baseline = clock.get(); |
| /// // ... prepare search |
| /// TimeLimitingCollector collector = new TimeLimitingCollector(c, clock, numTicks); |
| /// collector.setBaseline(baseline); |
| /// indexSearcher.search(query, collector); |
| /// </pre> |
| /// </p> </summary> |
| /// <seealso cref= #setBaseline() </seealso> |
| public virtual void SetBaseline(long clockTime) |
| { |
| t0 = clockTime; |
| timeout = t0 + ticksAllowed; |
| } |
| |
| /// <summary> |
| /// Syntactic sugar for <seealso cref="#setBaseline(long)"/> using <seealso cref="Counter#get()"/> |
| /// on the clock passed to the constructor. |
| /// </summary> |
| public virtual void SetBaseline() |
| { |
| SetBaseline(clock.Get()); |
| } |
| |
| /// <summary> |
| /// Checks if this time limited collector is greedy in collecting the last hit. |
| /// A non greedy collector, upon a timeout, would throw a <seealso cref="TimeExceededException"/> |
| /// 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 <seealso cref="TimeExceededException"/>. </summary> |
| /// <seealso cref= #setGreedy(boolean) </seealso> |
| public virtual bool IsGreedy |
| { |
| get |
| { |
| return greedy; |
| } |
| set |
| { |
| this.greedy = value; |
| } |
| } |
| |
| /// <summary> |
| /// Calls <seealso cref="ICollector#collect(int)"/> on the decorated <seealso cref="ICollector"/> |
| /// unless the allowed time has passed, in which case it throws an exception. |
| /// </summary> |
| /// <exception cref="TimeExceededException"> |
| /// if the time allowed has exceeded. </exception> |
| public virtual void Collect(int doc) |
| { |
| long time = clock.Get(); |
| if (timeout < time) |
| { |
| if (greedy) |
| { |
| //System.out.println(this+" greedy: before failing, collecting doc: "+(docBase + doc)+" "+(time-t0)); |
| collector.Collect(doc); |
| } |
| //System.out.println(this+" failing on: "+(docBase + doc)+" "+(time-t0)); |
| throw new TimeExceededException(timeout - t0, time - t0, docBase + doc); |
| } |
| //System.out.println(this+" collecting: "+(docBase + doc)+" "+(time-t0)); |
| collector.Collect(doc); |
| } |
| |
| public virtual void SetNextReader(AtomicReaderContext context) |
| { |
| collector.SetNextReader(context); |
| this.docBase = context.DocBase; |
| if (long.MinValue == t0) |
| { |
| SetBaseline(); |
| } |
| } |
| |
| public virtual void SetScorer(Scorer scorer) |
| { |
| collector.SetScorer(scorer); |
| } |
| |
| public virtual bool AcceptsDocsOutOfOrder |
| { |
| get { return collector.AcceptsDocsOutOfOrder; } |
| } |
| |
| /// <summary> |
| /// this is so the same timer can be used with a multi-phase search process such as grouping. |
| /// We don't want to create a new TimeLimitingCollector for each phase because that would |
| /// reset the timer for each phase. Once time is up subsequent phases need to timeout quickly. |
| /// </summary> |
| /// <param name="collector"> The actual collector performing search functionality </param> |
| public virtual void SetCollector(ICollector collector) |
| { |
| this.collector = collector; |
| } |
| |
| /// <summary> |
| /// Returns the global TimerThreads <seealso cref="Counter"/> |
| /// <p> |
| /// Invoking this creates may create a new instance of <seealso cref="TimerThread"/> iff |
| /// the global <seealso cref="TimerThread"/> has never been accessed before. The thread |
| /// returned from this method is started on creation and will be alive unless |
| /// you stop the <seealso cref="TimerThread"/> via <seealso cref="TimerThread#stopTimer()"/>. |
| /// </p> </summary> |
| /// <returns> the global TimerThreads <seealso cref="Counter"/> |
| /// @lucene.experimental </returns> |
| public static Counter GlobalCounter |
| { |
| get |
| { |
| return TimerThreadHolder.THREAD.counter; |
| } |
| } |
| |
| /// <summary> |
| /// Returns the global <seealso cref="TimerThread"/>. |
| /// <p> |
| /// Invoking this creates may create a new instance of <seealso cref="TimerThread"/> iff |
| /// the global <seealso cref="TimerThread"/> has never been accessed before. The thread |
| /// returned from this method is started on creation and will be alive unless |
| /// you stop the <seealso cref="TimerThread"/> via <seealso cref="TimerThread#stopTimer()"/>. |
| /// </p> |
| /// </summary> |
| /// <returns> the global <seealso cref="TimerThread"/> |
| /// @lucene.experimental </returns> |
| public static TimerThread GlobalTimerThread |
| { |
| get |
| { |
| return TimerThreadHolder.THREAD; |
| } |
| } |
| |
| #if FEATURE_SERIALIZABLE |
| [Serializable] |
| #endif |
| private sealed class TimerThreadHolder |
| { |
| internal static readonly TimerThread THREAD; |
| |
| static TimerThreadHolder() |
| { |
| THREAD = new TimerThread(Counter.NewCounter(true)); |
| THREAD.Start(); |
| } |
| } |
| |
| /// <summary> |
| /// Thread used to timeout search requests. |
| /// Can be stopped completely with <seealso cref="TimerThread#stopTimer()"/> |
| /// @lucene.experimental |
| /// </summary> |
| #if FEATURE_SERIALIZABLE |
| [Serializable] |
| #endif |
| public sealed class TimerThread : ThreadClass |
| { |
| public const string THREAD_NAME = "TimeLimitedCollector timer thread"; |
| public const int DEFAULT_RESOLUTION = 20; |
| |
| // 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 instantaneous, we can |
| // afford losing a tick or two. |
| // |
| // See section 17 of the Java Language Specification for details. |
| private long time = 0; |
| |
| private volatile bool stop = false; |
| private long resolution; |
| internal readonly Counter counter; |
| |
| public TimerThread(long resolution, Counter counter) |
| : base(THREAD_NAME) |
| { |
| this.resolution = resolution; |
| this.counter = counter; |
| this.SetDaemon(true); |
| } |
| |
| public TimerThread(Counter counter) |
| : this(DEFAULT_RESOLUTION, counter) |
| { |
| } |
| |
| public override void Run() |
| { |
| while (!stop) |
| { |
| // TODO: Use System.nanoTime() when Lucene moves to Java SE 5. |
| counter.AddAndGet(resolution); |
| #if !NETSTANDARD |
| try |
| { |
| #endif |
| Thread.Sleep(TimeSpan.FromMilliseconds(Interlocked.Read(ref resolution))); |
| #if !NETSTANDARD |
| } |
| catch (ThreadInterruptedException ie) |
| { |
| throw new ThreadInterruptedException("Thread Interrupted Exception", ie); |
| } |
| #endif |
| } |
| } |
| |
| /// <summary> |
| /// Get the timer value in milliseconds. |
| /// </summary> |
| public long Milliseconds |
| { |
| get |
| { |
| return time; |
| } |
| } |
| |
| /// <summary> |
| /// Stops the timer thread |
| /// </summary> |
| public void StopTimer() |
| { |
| stop = true; |
| } |
| |
| /// <summary> |
| /// Return the timer resolution. </summary> |
| /// <seealso cref= #setResolution(long) </seealso> |
| public long Resolution |
| { |
| get |
| { |
| return resolution; |
| } |
| set |
| { |
| this.resolution = Math.Max(value, 5); // 5 milliseconds is about the minimum reasonable time for a Object.wait(long) call. |
| } |
| } |
| } |
| } |
| } |