This is an automated email from the ASF dual-hosted git repository.

nightowl888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucenenet.git


The following commit(s) were added to refs/heads/master by this push:
     new 0817ee9  Lucene.Net.Search.ControlledRealTimeReopenThread: Refactor to 
integrate changes from #513 (#572)
0817ee9 is described below

commit 0817ee98d0d45e707954845edee078edba75e0cb
Author: Shad Storhaug <[email protected]>
AuthorDate: Wed Dec 15 12:27:09 2021 +0700

    Lucene.Net.Search.ControlledRealTimeReopenThread: Refactor to integrate 
changes from #513 (#572)
    
    * Revert "Revert "Bug Fix: Re-ported ControlledRealTimeReopenThread and 
added 2 new tests""
    
    This reverts commit 60aef3a2766d30f67e2b4d2a854fcf1e9fa158cf.
    
    * Lucene.Net.Search.ControlledRealTimeReopenThread: Updated to use 
Time.SecondsPerNanosecond and Time.MillisecondsPerNanosecond constants rather 
than literals
    
    * Lucene.Net.Search.ControlledRealTimeReopenThread: Made Dispose() lockless 
and made refreshStartGen and isDisposed atomic
    
    * Lucene.Net.Index.ThreadedIndexingAndSearchingTestCase::RunTest(): use 
using block on LineFileDocs to ensure it is disposed.
    
    * Lucene.Net.Search.TestControlledRealTimeReopenThread: Ignore 
TestStraightForwardDemonstration() and TestMultiThreadedWaitForGeneration(), 
since they don't play well with other tests due to precision timing
    
    * Lucene.Net.Index.ThreadedIndexingAndSearchingTestCase: Use 
ConucurrentSet<T> rather than ConcurrentHashSet<T>. The latter may have 
problems with its UnionWith() method.
    
    * Lucene.Net.Search.ControlledRealTimeReopenThread: Marked wait handle 
protected and renamed m_signal so subclasses can receive events
    
    * Lucene.Net.Search.ControlledRealTimeReopenThread: Renamed member variable 
from m_signal to m_notify for clarity
    
    * Revert "Lucene.Net.Index.ThreadedIndexingAndSearchingTestCase: Use 
ConucurrentSet<T> rather than ConcurrentHashSet<T>. The latter may have 
problems with its UnionWith() method."
    
    This reverts commit a93a4a3919764308ef1126c23634ed65e9d68331.
    
    * Lucene.Net.Index.IndexReader::Dispose(): Added missing call to 
GC.SuppressFinalize(this)
---
 .../Index/ThreadedIndexingAndSearchingTestCase.cs  |   2 +-
 .../Search/TestControlledRealTimeReopenThread.cs   | 268 ++++++++++++++++++++-
 src/Lucene.Net/Index/IndexReader.cs                |   1 +
 .../Search/ControlledRealTimeReopenThread.cs       | 129 ++++++----
 4 files changed, 351 insertions(+), 49 deletions(-)

diff --git 
a/src/Lucene.Net.TestFramework/Index/ThreadedIndexingAndSearchingTestCase.cs 
b/src/Lucene.Net.TestFramework/Index/ThreadedIndexingAndSearchingTestCase.cs
index 7eeea39..8fac354 100644
--- a/src/Lucene.Net.TestFramework/Index/ThreadedIndexingAndSearchingTestCase.cs
+++ b/src/Lucene.Net.TestFramework/Index/ThreadedIndexingAndSearchingTestCase.cs
@@ -566,7 +566,7 @@ namespace Lucene.Net.Index
             long t0 = J2N.Time.NanoTime() / 
J2N.Time.MillisecondsPerNanosecond; // LUCENENET: Use NanoTime() rather than 
CurrentTimeMilliseconds() for more accurate/reliable results
 
             Random random = new J2N.Randomizer(Random.NextInt64());
-            LineFileDocs docs = new LineFileDocs(random, 
DefaultCodecSupportsDocValues);
+            using LineFileDocs docs = new LineFileDocs(random, 
DefaultCodecSupportsDocValues);
             DirectoryInfo tempDir = CreateTempDir(testName);
             m_dir = GetDirectory(NewMockFSDirectory(tempDir)); // some 
subclasses rely on this being MDW
             if (m_dir is BaseDirectoryWrapper baseDirectoryWrapper)
diff --git a/src/Lucene.Net.Tests/Search/TestControlledRealTimeReopenThread.cs 
b/src/Lucene.Net.Tests/Search/TestControlledRealTimeReopenThread.cs
index c2393f9..ac07086 100644
--- a/src/Lucene.Net.Tests/Search/TestControlledRealTimeReopenThread.cs
+++ b/src/Lucene.Net.Tests/Search/TestControlledRealTimeReopenThread.cs
@@ -1,12 +1,16 @@
 using J2N.Threading;
 using J2N.Threading.Atomic;
+using Lucene.Net.Analysis.Standard;
+using Lucene.Net.Documents;
 using Lucene.Net.Index.Extensions;
+using Lucene.Net.Store;
 using Lucene.Net.Support.Threading;
 using Lucene.Net.Util;
 using NUnit.Framework;
 using RandomizedTesting.Generators;
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using System.Text;
 using System.Threading;
@@ -14,6 +18,7 @@ using System.Threading.Tasks;
 using JCG = J2N.Collections.Generic;
 using Assert = Lucene.Net.TestFramework.Assert;
 using Console = Lucene.Net.Util.SystemConsole;
+using Lucene.Net.Attributes;
 
 namespace Lucene.Net.Search
 {
@@ -57,7 +62,6 @@ namespace Lucene.Net.Search
     using TextField = Lucene.Net.Documents.TextField;
     using ThreadedIndexingAndSearchingTestCase = 
Lucene.Net.Index.ThreadedIndexingAndSearchingTestCase;
     using TrackingIndexWriter = Lucene.Net.Index.TrackingIndexWriter;
-    //using ThreadInterruptedException = 
Lucene.Net.Util.ThreadInterruptedException;
     using Version = Lucene.Net.Util.LuceneVersion;
 
     [SuppressCodecs("SimpleText", "Memory", "Direct")]
@@ -399,10 +403,12 @@ namespace Lucene.Net.Search
             LatchedIndexWriter _writer = new LatchedIndexWriter(d, conf, 
latch, signal);
             TrackingIndexWriter writer = new TrackingIndexWriter(_writer);
             SearcherManager manager = new SearcherManager(_writer, false, 
null);
+
             Document doc = new Document();
             doc.Add(NewTextField("test", "test", Field.Store.YES));
             writer.AddDocument(doc);
             manager.MaybeRefresh();
+
             var t = new ThreadAnonymousClass(this, latch, signal, writer, 
manager);
             t.Start();
             _writer.waitAfterUpdate = true; // wait in addDocument to let some 
reopens go through
@@ -419,6 +425,7 @@ namespace Lucene.Net.Search
             {
                 manager.Release(searcher);
             }
+
             ControlledRealTimeReopenThread<IndexSearcher> thread = new 
ControlledRealTimeReopenThread<IndexSearcher>(writer, manager, 0.01, 0.01);
             thread.Start(); // start reopening
             if (Verbose)
@@ -430,6 +437,7 @@ namespace Lucene.Net.Search
             var waiter = new ThreadAnonymousClass2(this, lastGen, thread, 
finished);
             waiter.Start();
             manager.MaybeRefresh();
+
             waiter.Join(1000);
             if (!finished)
             {
@@ -743,5 +751,263 @@ namespace Lucene.Net.Search
                 }
             }
         }
+
+
+        /// <summary>
+        /// This test was purposely written in a way that demonstrates how to 
use the
+        /// ControlledRealTimeReopenThread.  It contains seperate Asserts for 
each of
+        /// several use cases rather then trying to brake these use cases up 
into
+        /// seperate unit tests.  This combined approach makes the behavior of 
+        /// ControlledRealTimeReopenThread easier to understand.
+        /// </summary>
+        // LUCENENET specific - An extra test to demonstrate use of 
ControlledRealTimeReopen.
+        [Test]
+        [LuceneNetSpecific]
+        [Ignore("Run Manually (contains timing code that doesn't play well 
with other tests)")]
+        public void TestStraightForwardDemonstration()
+        {
+
+            RAMDirectory indexDir = new RAMDirectory();
+
+            Analyzer standardAnalyzer = new 
StandardAnalyzer(TEST_VERSION_CURRENT);
+            IndexWriterConfig indexConfig = new 
IndexWriterConfig(TEST_VERSION_CURRENT, standardAnalyzer);
+            IndexWriter indexWriter = new IndexWriter(indexDir, indexConfig);
+            TrackingIndexWriter trackingWriter = new 
TrackingIndexWriter(indexWriter);
+
+            Document doc = new Document();
+            doc.Add(new Int32Field("id", 1, Field.Store.YES));
+            doc.Add(new StringField("name", "Doc1", Field.Store.YES));
+            trackingWriter.AddDocument(doc);
+
+            SearcherManager searcherManager = new SearcherManager(indexWriter, 
applyAllDeletes: true, null);
+
+            //Reopen SearcherManager every 1 secs via background thread if no 
thread waiting for newer generation.
+            //Reopen SearcherManager after .2 secs if another thread IS 
waiting on a newer generation.  
+            var controlledRealTimeReopenThread = new 
ControlledRealTimeReopenThread<IndexSearcher>(trackingWriter, searcherManager, 
1, 0.2);
+
+            //Start() will start a seperate thread that will invoke the 
object's Run(). However,
+            //calling Run() directly would execute that code on the current 
thread rather then a new thread
+            //which would defeat the purpose of using 
controlledRealTimeReopenThread. This aspect of the API
+            //is not as intuitive as it could be. ie. Call Start() not Run().
+            controlledRealTimeReopenThread.IsBackground = true;                
     //Set as a background thread
+            controlledRealTimeReopenThread.Name = "Controlled Real Time Reopen 
Thread";
+            controlledRealTimeReopenThread.Priority = 
(ThreadPriority)Math.Min((int)Thread.CurrentThread.Priority + 2, 
(int)ThreadPriority.Highest);
+            controlledRealTimeReopenThread.Start();
+
+            //An indexSearcher only sees Doc1
+            IndexSearcher indexSearcher = searcherManager.Acquire();
+            try
+            {
+                TopDocs topDocs = indexSearcher.Search(new 
MatchAllDocsQuery(), 1);
+                assertEquals(1, topDocs.TotalHits);             //There is 
only one doc
+            }
+            finally
+            {
+                searcherManager.Release(indexSearcher);
+            }
+
+            //Add a 2nd document
+            doc = new Document();
+            doc.Add(new Int32Field("id", 2, Field.Store.YES));
+            doc.Add(new StringField("name", "Doc2", Field.Store.YES));
+            trackingWriter.AddDocument(doc);
+
+            //Demonstrate that we can only see the first doc because we 
haven't 
+            //waited 1 sec or called WaitForGeneration
+            indexSearcher = searcherManager.Acquire();
+            try
+            {
+                TopDocs topDocs = indexSearcher.Search(new 
MatchAllDocsQuery(), 1);
+                assertEquals(1, topDocs.TotalHits);             //Can see both 
docs due to auto refresh after 1.1 secs
+            }
+            finally
+            {
+                searcherManager.Release(indexSearcher);
+            }
+
+
+            //Demonstrate that we can see both docs after we wait a little more
+            //then 1 sec so that controlledRealTimeReopenThread max interval 
is exceeded
+            //and it calls MaybeRefresh
+            Thread.Sleep(1100);     //wait 1.1 secs as ms
+            indexSearcher = searcherManager.Acquire();
+            try
+            {
+                TopDocs topDocs = indexSearcher.Search(new 
MatchAllDocsQuery(), 1);
+                assertEquals(2, topDocs.TotalHits);             //Can see both 
docs due to auto refresh after 1.1 secs
+            }
+            finally
+            {
+                searcherManager.Release(indexSearcher);
+            }
+
+
+            //Add a 3rd document
+            doc = new Document();
+            doc.Add(new Int32Field("id", 3, Field.Store.YES));
+            doc.Add(new StringField("name", "Doc3", Field.Store.YES));
+            long generation = trackingWriter.AddDocument(doc);
+
+            //Demonstrate that if we call WaitForGeneration our wait will be
+            // .2 secs or less (the min interval we set earlier) and then we 
will
+            //see all 3 documents.
+            Stopwatch stopwatch = Stopwatch.StartNew();
+            controlledRealTimeReopenThread.WaitForGeneration(generation);
+            stopwatch.Stop();
+            assertTrue(stopwatch.Elapsed.TotalMilliseconds <= 200 + 30);   
//30ms is fudged factor to account for call overhead.
+
+            indexSearcher = searcherManager.Acquire();
+            try
+            {
+                TopDocs topDocs = indexSearcher.Search(new 
MatchAllDocsQuery(), 1);
+                assertEquals(3, topDocs.TotalHits);             //Can see both 
docs due to auto refresh after 1.1 secs
+            }
+            finally
+            {
+                searcherManager.Release(indexSearcher);
+            }
+
+            controlledRealTimeReopenThread.Dispose();
+            searcherManager.Dispose();
+            indexWriter.Dispose();
+            indexDir.Dispose();
+        }
+
+
+
+        /// <summary>
+        /// In this test multiple threads are created each of which is waiting 
on the same 
+        /// generation before doing a search.  These threads will all stack up 
on the 
+        /// WaitForGeneration(generation) call.  All threads should return 
from this call
+        /// in approximately in the time expected, namely the value for 
targetMinStaleSec passed
+        /// to ControlledRealTimeReopenThread in it's constructor.
+        /// </summary>
+        // LUCENENET specific - An extra test to test multithreaded use of 
ControlledRealTimeReopen.
+        [Test]
+        [LuceneNetSpecific]
+        [Ignore("Run Manually (contains timing code that doesn't play well 
with other tests)")]
+        public void TestMultithreadedWaitForGeneration()
+        {
+            Thread CreateWorker(int threadNum, 
ControlledRealTimeReopenThread<IndexSearcher> controlledReopen, long generation,
+                                SearcherManager searcherManager, 
List<ThreadOutput> outputList)
+            {
+                ThreadStart threadStart = delegate
+                {
+
+                    Stopwatch stopwatch = Stopwatch.StartNew();
+                    controlledReopen.WaitForGeneration(generation);
+                    stopwatch.Stop();
+                    double milliSecsWaited = 
stopwatch.Elapsed.TotalMilliseconds;
+
+                    int numRecs = 0;
+                    IndexSearcher indexSearcher = searcherManager.Acquire();
+                    try
+                    {
+                        TopDocs topDocs = indexSearcher.Search(new 
MatchAllDocsQuery(), 1);
+                        numRecs = topDocs.TotalHits;
+                    }
+                    finally
+                    {
+                        searcherManager.Release(indexSearcher);
+                    }
+
+                    lock (outputList)
+                    {
+                        outputList.Add(new ThreadOutput { ThreadNum = 
threadNum, NumRecs = numRecs, MilliSecsWaited = milliSecsWaited });
+                    }
+
+                };
+                return new Thread(threadStart);
+            }
+
+            int threadCount = 3;
+            List<ThreadOutput> outputList = new List<ThreadOutput>();
+
+            RAMDirectory indexDir = new RAMDirectory();
+            Analyzer standardAnalyzer = new 
StandardAnalyzer(TEST_VERSION_CURRENT);
+            IndexWriterConfig indexConfig = new 
IndexWriterConfig(TEST_VERSION_CURRENT, standardAnalyzer);
+            IndexWriter indexWriter = new IndexWriter(indexDir, indexConfig);
+            TrackingIndexWriter trackingWriter = new 
TrackingIndexWriter(indexWriter);
+
+            //Add two documents
+            Document doc = new Document();
+            doc.Add(new Int32Field("id", 1, Field.Store.YES));
+            doc.Add(new StringField("name", "Doc1", Field.Store.YES));
+            long generation = trackingWriter.AddDocument(doc);
+
+            doc.Add(new Int32Field("id", 2, Field.Store.YES));
+            doc.Add(new StringField("name", "Doc3", Field.Store.YES));
+            generation = trackingWriter.AddDocument(doc);
+
+            SearcherManager searcherManager = new SearcherManager(indexWriter, 
applyAllDeletes: true, null);
+
+            //Reopen SearcherManager every 2 secs via background thread if no 
thread waiting for newer generation.
+            //Reopen SearcherManager after .2 secs if another thread IS 
waiting on a newer generation.  
+            double maxRefreshSecs = 2.0;
+            double minRefreshSecs = .2;
+            var controlledRealTimeReopenThread = new 
ControlledRealTimeReopenThread<IndexSearcher>(trackingWriter, searcherManager, 
maxRefreshSecs, minRefreshSecs);
+
+            //Start() will start a seperate thread that will invoke the 
object's Run(). However,
+            //calling Run() directly would execute that code on the current 
thread rather then a new thread
+            //which would defeat the purpose of using 
controlledRealTimeReopenThread. This aspect of the API
+            //is not as intuitive as it could be. ie. Call Start() not Run().
+            controlledRealTimeReopenThread.IsBackground = true;                
     //Set as a background thread
+            controlledRealTimeReopenThread.Name = "Controlled Real Time Reopen 
Thread";
+            controlledRealTimeReopenThread.Priority = 
(ThreadPriority)Math.Min((int)Thread.CurrentThread.Priority + 2, 
(int)ThreadPriority.Highest);
+            controlledRealTimeReopenThread.Start();
+
+
+            //Create the threads for doing searchers
+            List<Thread> threadList = new List<Thread>();
+            for (int i = 1; i <= threadCount; i++)
+            {
+                threadList.Add(CreateWorker(i, controlledRealTimeReopenThread, 
generation, searcherManager, outputList));
+            }
+
+            //Start all the threads
+            foreach (Thread thread in threadList)
+            {
+                thread.Start();
+            }
+
+            //wait for the threads to finish.
+            foreach (Thread thread in threadList)
+            {
+                thread.Join();                          //will wait here until 
the thread terminates.
+            }
+
+            //Now make sure that no thread waited longer then our min refresh 
time
+            //plus a small fudge factor. Also verify that all threads 
resported back and
+            //each saw 2 records.
+
+            //Verify all threads reported back a result.
+            assertEquals(threadCount, outputList.Count);
+
+            int millisecsPerSec = 1000;
+            foreach (ThreadOutput output in outputList)
+            {
+                //Verify the thread saw exactly 2 docs
+                assertEquals(2, output.NumRecs);
+
+                //Verify the thread wait time was around what was expected.
+                Assert.True(output.MilliSecsWaited <= (minRefreshSecs * 
millisecsPerSec) + 30);   //30ms is fudged factor to account for call overhead
+            }
+
+            controlledRealTimeReopenThread.Dispose();                       
//will kill and join to the thread
+            Assert.False(controlledRealTimeReopenThread.IsAlive);           
//to prove that Dispose really does kill the thread.
+
+            searcherManager.Dispose();
+            indexWriter.Dispose();
+            indexDir.Dispose();
+
+        }
+
+        [DebuggerDisplay("ThreadNum:{ThreadNum}, NumRecs:{NumRecs}, 
MilliSecsWaited:{MilliSecsWaited}")]
+        public class ThreadOutput
+        {
+            public int ThreadNum { get; set; }
+            public int NumRecs { get; set; }
+            public double MilliSecsWaited { get; set; }
+        }
     }
 }
\ No newline at end of file
diff --git a/src/Lucene.Net/Index/IndexReader.cs 
b/src/Lucene.Net/Index/IndexReader.cs
index 2e30b45..77777ec 100644
--- a/src/Lucene.Net/Index/IndexReader.cs
+++ b/src/Lucene.Net/Index/IndexReader.cs
@@ -571,6 +571,7 @@ namespace Lucene.Net.Index
         public void Dispose()
         {
             Dispose(true);
+            GC.SuppressFinalize(this);
         }
 
         /// <summary>
diff --git a/src/Lucene.Net/Search/ControlledRealTimeReopenThread.cs 
b/src/Lucene.Net/Search/ControlledRealTimeReopenThread.cs
index b1f8020..8c3e854 100644
--- a/src/Lucene.Net/Search/ControlledRealTimeReopenThread.cs
+++ b/src/Lucene.Net/Search/ControlledRealTimeReopenThread.cs
@@ -1,5 +1,6 @@
 using J2N;
 using J2N.Threading;
+using J2N.Threading.Atomic;
 using Lucene.Net.Support.Threading;
 using System;
 using System.Threading;
@@ -43,6 +44,7 @@ namespace Lucene.Net.Search
     public class ControlledRealTimeReopenThread<T> : ThreadJob, IDisposable
          where T : class
     {
+        // LUCENENET: java final converted readonly
         private readonly ReferenceManager<T> manager;
         private readonly long targetMaxStaleNS;
         private readonly long targetMinStaleNS;
@@ -50,10 +52,12 @@ namespace Lucene.Net.Search
         private volatile bool finish;
         private long waitingGen;
         private long searchingGen;
-        private long refreshStartGen;
+        private readonly AtomicInt64 refreshStartGen = new AtomicInt64();
+        private readonly AtomicBoolean isDisposed = new AtomicBoolean(false);
+
+        protected readonly EventWaitHandle m_notify = new 
ManualResetEvent(false);  // LUCENENET specific: used to mimic intrinsic 
monitor used by java wait and notifyAll keywords.
+        private readonly EventWaitHandle reopenCond = new 
AutoResetEvent(false);    // LUCENENET NOTE: unlike java, in c# we don't need 
to lock reopenCond when calling methods on it.
 
-        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
@@ -76,10 +80,11 @@ namespace Lucene.Net.Search
             {
                 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);
+            this.targetMaxStaleNS = (long)(Time.SecondsPerNanosecond * 
targetMaxStaleSec);
+            this.targetMinStaleNS = (long)(Time.SecondsPerNanosecond * 
targetMinStaleSec);
             manager.AddListener(new HandleRefresh(this));
         }
 
@@ -107,39 +112,46 @@ namespace Lucene.Net.Search
             UninterruptableMonitor.Enter(this);
             try
             {
-                // if we're finishing, , make it out so that all waiting 
search threads will return
+                               // if we're finishing, make it out so that all 
waiting search threads will return
                 searchingGen = finish ? long.MaxValue : refreshStartGen;
-                available.Set();
+                m_notify.Set();                        // LUCENENET NOTE:  
Will notify all and remain signaled, so it must be reset in WaitForGeneration
             }
             finally
             {
                 UninterruptableMonitor.Exit(this);
             }
-            reopenCond.Reset();
         }
 
         /// <summary>
-        /// Releases all resources used by the <see 
cref="ControlledRealTimeReopenThread{T}"/>.
+        /// Kills the thread and releases all resources used by the
+        /// <see cref="ControlledRealTimeReopenThread{T}"/>. Also joins to the
+        /// thread so that when this method returns the thread is no longer 
alive.
         /// </summary>
         public void Dispose()
         {
-            Dispose(true);
+            Dispose(disposing: 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.
+        /// Kills the thread and releases all resources used by the
+        /// <see cref="ControlledRealTimeReopenThread{T}"/>. Also joins to the
+        /// thread so that when this method returns the thread is no longer 
alive.
         /// </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
+        // LUCENENET specific - Support for Dispose(bool) since this is a 
non-sealed class.
         protected virtual void Dispose(bool disposing)
         {
+            // LUCENENET: Prevent double-dispose of our managed resources.
+            if (isDisposed.GetAndSet(true))
+            {
+                return;
+            }
+
             if (disposing)
             {
                 finish = true;
+
+                // So thread wakes up and notices it should finish:
                 reopenCond.Set();
 
                 try
@@ -152,9 +164,11 @@ namespace Lucene.Net.Search
                 }
                 finally
                 {
-                    // LUCENENET specific: dispose reset event
+                    RefreshDone();
+
+                    // LUCENENET specific: dispose reset events
                     reopenCond.Dispose();
-                    available.Dispose();
+                    m_notify.Dispose();
                 }
             }
         }
@@ -193,47 +207,52 @@ namespace Lucene.Net.Search
         ///         or false if <paramref name="maxMS"/> wait time was 
exceeded </returns>
         public virtual bool WaitForGeneration(long targetGen, int maxMS)
         {
+            // LUCENENET NOTE: Porting this method is a bit tricky because the 
java wait method releases the
+            //                 syncronize lock and c# has no similar 
primitive.  So we must handle locking a
+            //                 bit differently here to mimic that affect.
+
             long curGen = writer.Generation;
             if (targetGen > curGen)
             {
                 throw new ArgumentException("targetGen=" + targetGen + " was 
never returned by the ReferenceManager instance (current gen=" + curGen + ")");
             }
+
             UninterruptableMonitor.Enter(this);
             try
             {
                 if (targetGen <= searchingGen)
-                    return true;
-                else
                 {
-                    waitingGen = Math.Max(waitingGen, targetGen);
-                    reopenCond.Set();
-                    available.Reset();
+                    return true;
                 }
+
+                // Need to find waitingGen inside lock as its used to determine
+                // stale time
+                waitingGen = Math.Max(waitingGen, targetGen);
+                reopenCond.Set();                                   // 
LUCENENET NOTE: gives Run() an oppertunity to notice one is now waiting if one 
wasn't before.
+                m_notify.Reset();                                   // 
LUCENENET specific: required to "close the door". Java's notifyAll keyword 
didn't need this.
             }
             finally
             {
                 UninterruptableMonitor.Exit(this);
             }
 
-            long startMS = Time.NanoTime() / 1000000;
-
-            // LUCENENET specific - reading searchingGen not thread safe, so 
use Interlocked.Read()
-            while (targetGen > Interlocked.Read(ref searchingGen))
+            long startMS = Time.NanoTime() / Time.MillisecondsPerNanosecond;
+            while (targetGen > Interlocked.Read(ref searchingGen))      // 
LUCENENET specific - reading searchingGen not thread safe, so use 
Interlocked.Read()
             {
                 if (maxMS < 0)
                 {
-                    available.WaitOne();
+                    m_notify.WaitOne();
                 }
                 else
                 {
-                    long msLeft = (startMS + maxMS) - (Time.NanoTime()) / 
1000000;
+                    long msLeft = (startMS + maxMS) - (Time.NanoTime()) / 
Time.MillisecondsPerNanosecond;
                     if (msLeft <= 0)
                     {
                         return false;
                     }
                     else
                     {
-                        available.WaitOne(TimeSpan.FromMilliseconds(msLeft));
+                        m_notify.WaitOne(TimeSpan.FromMilliseconds(msLeft));
                     }
                 }
             }
@@ -245,30 +264,47 @@ namespace Lucene.Net.Search
         {
             // TODO: maybe use private thread ticktock timer, in
             // case clock shift messes up nanoTime?
+            // LUCENENET NOTE: Time.NanoTime() is not affected by clock 
changes.
             long lastReopenStartNS = Time.NanoTime();
 
             //System.out.println("reopen: start");
             while (!finish)
             {
-                bool hasWaiting;
 
-                UninterruptableMonitor.Enter(this);
-                try
-                {
-                    hasWaiting = waitingGen > searchingGen;
-                }
-                finally
-                {
-                    UninterruptableMonitor.Exit(this);
-                }
+                // TODO: try to guestimate how long reopen might
+                // take based on past data?
 
-                long nextReopenStartNS = lastReopenStartNS + (hasWaiting ? 
targetMinStaleNS : targetMaxStaleNS);
-                long sleepNS = nextReopenStartNS - Time.NanoTime();
+                // Loop until we've waiting long enough before the
+                // next reopen:
+                while (!finish)
+                {
 
-                if (sleepNS > 0)
                     try
                     {
-                        reopenCond.WaitOne(TimeSpan.FromMilliseconds(sleepNS / 
Time.MillisecondsPerNanosecond));//Convert NS to MS
+                        // Need lock before finding out if has waiting
+                        bool hasWaiting;
+                               UninterruptableMonitor.Enter(this);
+                               try
+                               {
+                            // True if we have someone waiting for reopened 
searcher:
+                            hasWaiting = waitingGen > searchingGen;
+                               }
+                               finally
+                               {
+                               UninterruptableMonitor.Exit(this);
+                               }
+
+                        long nextReopenStartNS = lastReopenStartNS + 
(hasWaiting ? targetMinStaleNS : targetMaxStaleNS);
+                        long sleepNS = nextReopenStartNS - Time.NanoTime();
+
+                        if (sleepNS > 0)
+                        {
+                            
reopenCond.WaitOne(TimeSpan.FromMilliseconds(sleepNS / 
Time.MillisecondsPerNanosecond));//Convert NS to MS
+                        }
+                        else
+                        {
+                            break;
+                        }
                     }
                     catch (Exception ie) when (ie.IsInterruptedException())
                     {
@@ -276,6 +312,7 @@ namespace Lucene.Net.Search
                         return;
                     }
 
+                }
                 if (finish)
                 {
                     break;
@@ -285,7 +322,7 @@ namespace Lucene.Net.Search
                 // 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();
+                refreshStartGen.Value = writer.GetAndIncrementGeneration();
                 try
                 {
                     manager.MaybeRefreshBlocking();
@@ -295,8 +332,6 @@ namespace Lucene.Net.Search
                     throw RuntimeException.Create(ioe);
                 }
             }
-            // this will set the searchingGen so that all waiting threads will 
exit
-            RefreshDone();
         }
     }
 }
\ No newline at end of file

Reply via email to