| using J2N.Threading; |
| using Lucene.Net.Support; |
| using System; |
| using System.IO; |
| 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 TrackingIndexWriter = Lucene.Net.Index.TrackingIndexWriter; |
| |
| /// <summary> |
| /// Utility class that runs a thread to manage periodic |
| /// reopens of a <see cref="ReferenceManager{T}"/>, with methods to wait for a specific |
| /// index changes to become visible. To use this class you |
| /// must first wrap your <see cref="Index.IndexWriter"/> with a |
| /// <see cref="TrackingIndexWriter"/> and always use it to make changes |
| /// to the index, saving the returned generation. Then, |
| /// when a given search request needs to see a specific |
| /// index change, call the <see cref="WaitForGeneration(long)"/> to wait for |
| /// that change to be visible. Note that this will only |
| /// scale well if most searches do not need to wait for a |
| /// specific index generation. |
| /// <para/> |
| /// @lucene.experimental |
| /// </summary> |
| public class ControlledRealTimeReopenThread<T> : ThreadJob, IDisposable |
| where T : class |
| { |
| private readonly ReferenceManager<T> manager; |
| private readonly long targetMaxStaleNS; |
| private readonly long targetMinStaleNS; |
| private readonly TrackingIndexWriter writer; |
| private volatile bool finish; |
| private long waitingGen; |
| private long searchingGen; |
| private long refreshStartGen; |
| |
| private readonly EventWaitHandle reopenCond = new AutoResetEvent(false); // LUCENENET: marked readonly |
| private readonly EventWaitHandle available = new AutoResetEvent(false); // LUCENENET: marked readonly |
| |
| /// <summary> |
| /// Create <see cref="ControlledRealTimeReopenThread{T}"/>, to periodically |
| /// reopen the a <see cref="ReferenceManager{T}"/>. |
| /// </summary> |
| /// <param name="targetMaxStaleSec"> Maximum time until a new |
| /// reader must be opened; this sets the upper bound |
| /// on how slowly reopens may occur, when no |
| /// caller is waiting for a specific generation to |
| /// become visible. |
| /// </param> |
| /// <param name="targetMinStaleSec"> Mininum time until a new |
| /// reader can be opened; this sets the lower bound |
| /// on how quickly reopens may occur, when a caller |
| /// is waiting for a specific generation to |
| /// become visible. </param> |
| public ControlledRealTimeReopenThread(TrackingIndexWriter writer, ReferenceManager<T> manager, double targetMaxStaleSec, double targetMinStaleSec) |
| { |
| if (targetMaxStaleSec < targetMinStaleSec) |
| { |
| throw new ArgumentException("targetMaxScaleSec (= " + targetMaxStaleSec.ToString("0.0") + ") < targetMinStaleSec (=" + targetMinStaleSec.ToString("0.0") + ")"); |
| } |
| this.writer = writer; |
| this.manager = manager; |
| this.targetMaxStaleNS = (long)(1000000000 * targetMaxStaleSec); |
| this.targetMinStaleNS = (long)(1000000000 * targetMinStaleSec); |
| manager.AddListener(new HandleRefresh(this)); |
| } |
| |
| private class HandleRefresh : ReferenceManager.IRefreshListener |
| { |
| private readonly ControlledRealTimeReopenThread<T> outerInstance; |
| |
| public HandleRefresh(ControlledRealTimeReopenThread<T> outerInstance) |
| { |
| this.outerInstance = outerInstance; |
| } |
| |
| public virtual void BeforeRefresh() |
| { |
| } |
| |
| public virtual void AfterRefresh(bool didRefresh) |
| { |
| outerInstance.RefreshDone(); |
| } |
| } |
| |
| private void RefreshDone() |
| { |
| lock (this) |
| { |
| // if we're finishing, , make it out so that all waiting search threads will return |
| searchingGen = finish ? long.MaxValue : refreshStartGen; |
| available.Set(); |
| } |
| reopenCond.Reset(); |
| } |
| |
| /// <summary> |
| /// Releases all resources used by the <see cref="ControlledRealTimeReopenThread{T}"/>. |
| /// </summary> |
| public void Dispose() |
| { |
| Dispose(true); |
| GC.SuppressFinalize(this); |
| } |
| |
| /// <summary> |
| /// Releases resources used by the <see cref="ControlledRealTimeReopenThread{T}"/> and |
| /// if overridden in a derived class, optionally releases unmanaged resources. |
| /// </summary> |
| /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; |
| /// <c>false</c> to release only unmanaged resources.</param> |
| |
| // LUCENENET specific - implemented proper dispose pattern |
| protected virtual void Dispose(bool disposing) |
| { |
| if (disposing) |
| { |
| finish = true; |
| reopenCond.Set(); |
| //#if FEATURE_THREAD_INTERRUPT |
| // try |
| // { |
| //#endif |
| Join(); |
| //#if FEATURE_THREAD_INTERRUPT // LUCENENET NOTE: Senseless to catch and rethrow the same exception type |
| // } |
| // catch (ThreadInterruptedException ie) |
| // { |
| // throw new ThreadInterruptedException(ie.ToString(), ie); |
| // } |
| //#endif |
| // LUCENENET specific: dispose reset event |
| reopenCond.Dispose(); |
| available.Dispose(); |
| } |
| } |
| |
| /// <summary> |
| /// Waits for the target generation to become visible in |
| /// the searcher. |
| /// If the current searcher is older than the |
| /// target generation, this method will block |
| /// until the searcher is reopened, by another via |
| /// <see cref="ReferenceManager{T}.MaybeRefresh()"/> or until the <see cref="ReferenceManager{T}"/> is closed. |
| /// </summary> |
| /// <param name="targetGen"> The generation to wait for </param> |
| public virtual void WaitForGeneration(long targetGen) |
| { |
| WaitForGeneration(targetGen, -1); |
| } |
| |
| /// <summary> |
| /// Waits for the target generation to become visible in |
| /// the searcher, up to a maximum specified milli-seconds. |
| /// If the current searcher is older than the target |
| /// generation, this method will block until the |
| /// searcher has been reopened by another thread via |
| /// <see cref="ReferenceManager{T}.MaybeRefresh()"/>, the given waiting time has elapsed, or until |
| /// the <see cref="ReferenceManager{T}"/> is closed. |
| /// <para/> |
| /// NOTE: if the waiting time elapses before the requested target generation is |
| /// available the current <see cref="SearcherManager"/> is returned instead. |
| /// </summary> |
| /// <param name="targetGen"> |
| /// The generation to wait for </param> |
| /// <param name="maxMS"> |
| /// Maximum milliseconds to wait, or -1 to wait indefinitely </param> |
| /// <returns> <c>true</c> if the <paramref name="targetGen"/> is now available, |
| /// or false if <paramref name="maxMS"/> wait time was exceeded </returns> |
| public virtual bool WaitForGeneration(long targetGen, int maxMS) |
| { |
| long curGen = writer.Generation; |
| if (targetGen > curGen) |
| { |
| throw new ArgumentException("targetGen=" + targetGen + " was never returned by the ReferenceManager instance (current gen=" + curGen + ")"); |
| } |
| lock (this) |
| if (targetGen <= searchingGen) |
| return true; |
| else |
| { |
| waitingGen = Math.Max(waitingGen, targetGen); |
| reopenCond.Set(); |
| available.Reset(); |
| } |
| |
| long startMS = Time.NanoTime() / 1000000; |
| |
| // LUCENENET specific - reading searchingGen not thread safe, so use Interlocked.Read() |
| while (targetGen > Interlocked.Read(ref searchingGen)) |
| { |
| if (maxMS < 0) |
| { |
| available.WaitOne(); |
| } |
| else |
| { |
| long msLeft = (startMS + maxMS) - (Time.NanoTime()) / 1000000; |
| if (msLeft <= 0) |
| { |
| return false; |
| } |
| else |
| { |
| available.WaitOne(TimeSpan.FromMilliseconds(msLeft)); |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| public override void Run() |
| { |
| // TODO: maybe use private thread ticktock timer, in |
| // case clock shift messes up nanoTime? |
| long lastReopenStartNS = DateTime.UtcNow.Ticks * 100; |
| |
| //System.out.println("reopen: start"); |
| while (!finish) |
| { |
| bool hasWaiting; |
| |
| lock (this) |
| hasWaiting = waitingGen > searchingGen; |
| |
| long nextReopenStartNS = lastReopenStartNS + (hasWaiting ? targetMinStaleNS : targetMaxStaleNS); |
| long sleepNS = nextReopenStartNS - Time.NanoTime(); |
| |
| if (sleepNS > 0) |
| #if FEATURE_THREAD_INTERRUPT |
| try |
| { |
| #endif |
| reopenCond.WaitOne(TimeSpan.FromMilliseconds(sleepNS / Time.MILLISECONDS_PER_NANOSECOND));//Convert NS to Ticks |
| #if FEATURE_THREAD_INTERRUPT |
| } |
| #pragma warning disable 168 |
| catch (ThreadInterruptedException ie) |
| #pragma warning restore 168 |
| { |
| Thread.CurrentThread.Interrupt(); |
| return; |
| } |
| #endif |
| |
| if (finish) |
| { |
| break; |
| } |
| |
| lastReopenStartNS = Time.NanoTime(); |
| // Save the gen as of when we started the reopen; the |
| // listener (HandleRefresh above) copies this to |
| // searchingGen once the reopen completes: |
| refreshStartGen = writer.GetAndIncrementGeneration(); |
| try |
| { |
| manager.MaybeRefreshBlocking(); |
| } |
| catch (IOException ioe) |
| { |
| throw new Exception(ioe.ToString(), ioe); |
| } |
| } |
| // this will set the searchingGen so that all waiting threads will exit |
| RefreshDone(); |
| } |
| } |
| } |