blob: aef6aac9eeb3d920cbcb3333ac83ed56b21aa125 [file] [log] [blame]
using J2N.Threading;
using Lucene.Net.Documents;
using Lucene.Net.Index.Extensions;
using Lucene.Net.Support;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using JCG = J2N.Collections.Generic;
using Console = Lucene.Net.Support.SystemConsole;
namespace Lucene.Net.Index
{
using Directory = Lucene.Net.Store.Directory;
using Document = Documents.Document;
using Field = Field;
using FieldType = FieldType;
using IndexSearcher = Lucene.Net.Search.IndexSearcher;
using LuceneTestCase = Lucene.Net.Util.LuceneTestCase;
/*
* 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 MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer;
using ScoreDoc = Lucene.Net.Search.ScoreDoc;
using TermQuery = Lucene.Net.Search.TermQuery;
using TestUtil = Lucene.Net.Util.TestUtil;
using TextField = TextField;
[TestFixture]
public class TestDirectoryReaderReopen : LuceneTestCase
{
[Test]
public virtual void TestReopen_Mem()
{
Directory dir1 = NewDirectory();
CreateIndex(Random, dir1, false);
PerformDefaultTests(new TestReopenAnonymousInnerClassHelper(this, dir1));
dir1.Dispose();
Directory dir2 = NewDirectory();
CreateIndex(Random, dir2, true);
PerformDefaultTests(new TestReopenAnonymousInnerClassHelper2(this, dir2));
dir2.Dispose();
}
private class TestReopenAnonymousInnerClassHelper : TestReopen
{
private readonly TestDirectoryReaderReopen OuterInstance;
private Directory Dir1;
public TestReopenAnonymousInnerClassHelper(TestDirectoryReaderReopen outerInstance, Directory dir1)
{
this.OuterInstance = outerInstance;
this.Dir1 = dir1;
}
protected internal override void ModifyIndex(int i)
{
TestDirectoryReaderReopen.ModifyIndex(i, Dir1);
}
protected internal override DirectoryReader OpenReader()
{
return DirectoryReader.Open(Dir1);
}
}
private class TestReopenAnonymousInnerClassHelper2 : TestReopen
{
private readonly TestDirectoryReaderReopen OuterInstance;
private Directory Dir2;
public TestReopenAnonymousInnerClassHelper2(TestDirectoryReaderReopen outerInstance, Directory dir2)
{
this.OuterInstance = outerInstance;
this.Dir2 = dir2;
}
protected internal override void ModifyIndex(int i)
{
TestDirectoryReaderReopen.ModifyIndex(i, Dir2);
}
protected internal override DirectoryReader OpenReader()
{
return DirectoryReader.Open(Dir2);
}
}
// LUCENE-1228: IndexWriter.Commit() does not update the index version
// populate an index in iterations.
// at the end of every iteration, commit the index and reopen/recreate the reader.
// in each iteration verify the work of previous iteration.
// try this once with reopen once recreate, on both RAMDir and FSDir.
[Test]
public virtual void TestCommitReopen()
{
Directory dir = NewDirectory();
DoTestReopenWithCommit(Random, dir, true);
dir.Dispose();
}
[Test]
public virtual void TestCommitRecreate()
{
Directory dir = NewDirectory();
DoTestReopenWithCommit(Random, dir, false);
dir.Dispose();
}
private void DoTestReopenWithCommit(Random random, Directory dir, bool withReopen)
{
IndexWriter iwriter = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)).SetOpenMode(OpenMode.CREATE).SetMergeScheduler(new SerialMergeScheduler()).SetMergePolicy(NewLogMergePolicy()));
iwriter.Commit();
DirectoryReader reader = DirectoryReader.Open(dir);
try
{
int M = 3;
FieldType customType = new FieldType(TextField.TYPE_STORED);
customType.IsTokenized = false;
FieldType customType2 = new FieldType(TextField.TYPE_STORED);
customType2.IsTokenized = false;
customType2.OmitNorms = true;
FieldType customType3 = new FieldType();
customType3.IsStored = true;
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < M; j++)
{
Document doc = new Document();
doc.Add(NewField("id", i + "_" + j, customType));
doc.Add(NewField("id2", i + "_" + j, customType2));
doc.Add(NewField("id3", i + "_" + j, customType3));
iwriter.AddDocument(doc);
if (i > 0)
{
int k = i - 1;
int n = j + k * M;
Document prevItereationDoc = reader.Document(n);
Assert.IsNotNull(prevItereationDoc);
string id = prevItereationDoc.Get("id");
Assert.AreEqual(k + "_" + j, id);
}
}
iwriter.Commit();
if (withReopen)
{
// reopen
DirectoryReader r2 = DirectoryReader.OpenIfChanged(reader);
if (r2 != null)
{
reader.Dispose();
reader = r2;
}
}
else
{
// recreate
reader.Dispose();
reader = DirectoryReader.Open(dir);
}
}
}
finally
{
iwriter.Dispose();
reader.Dispose();
}
}
private void PerformDefaultTests(TestReopen test)
{
DirectoryReader index1 = test.OpenReader();
DirectoryReader index2 = test.OpenReader();
TestDirectoryReader.AssertIndexEquals(index1, index2);
// verify that reopen() does not return a new reader instance
// in case the index has no changes
ReaderCouple couple = RefreshReader(index2, false);
Assert.IsTrue(couple.RefreshedReader == index2);
couple = RefreshReader(index2, test, 0, true);
index1.Dispose();
index1 = couple.NewReader;
DirectoryReader index2_refreshed = couple.RefreshedReader;
index2.Dispose();
// test if refreshed reader and newly opened reader return equal results
TestDirectoryReader.AssertIndexEquals(index1, index2_refreshed);
index2_refreshed.Dispose();
AssertReaderClosed(index2, true);
AssertReaderClosed(index2_refreshed, true);
index2 = test.OpenReader();
for (int i = 1; i < 4; i++)
{
index1.Dispose();
couple = RefreshReader(index2, test, i, true);
// refresh DirectoryReader
index2.Dispose();
index2 = couple.RefreshedReader;
index1 = couple.NewReader;
TestDirectoryReader.AssertIndexEquals(index1, index2);
}
index1.Dispose();
index2.Dispose();
AssertReaderClosed(index1, true);
AssertReaderClosed(index2, true);
}
[Test]
public virtual void TestThreadSafety()
{
Directory dir = NewDirectory();
// NOTE: this also controls the number of threads!
int n = TestUtil.NextInt32(Random, 20, 40);
IndexWriter writer = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)));
for (int i = 0; i < n; i++)
{
writer.AddDocument(CreateDocument(i, 3));
}
writer.ForceMerge(1);
writer.Dispose();
TestReopen test = new TestReopenAnonymousInnerClassHelper3(this, dir, n);
IList<ReaderCouple> readers = new SynchronizedList<ReaderCouple>();
DirectoryReader firstReader = DirectoryReader.Open(dir);
DirectoryReader reader = firstReader;
ReaderThread[] threads = new ReaderThread[n];
ISet<DirectoryReader> readersToClose = new JCG.HashSet<DirectoryReader>().AsConcurrent();
for (int i = 0; i < n; i++)
{
if (i % 2 == 0)
{
DirectoryReader refreshed = DirectoryReader.OpenIfChanged(reader);
if (refreshed != null)
{
readersToClose.Add(reader);
reader = refreshed;
}
}
DirectoryReader r = reader;
int index = i;
ReaderThreadTask task;
if (i < 4 || (i >= 10 && i < 14) || i > 18)
{
task = new ReaderThreadTaskAnonymousInnerClassHelper(this, test, readers, readersToClose, r, index);
}
else
{
task = new ReaderThreadTaskAnonymousInnerClassHelper2(this, readers);
}
threads[i] = new ReaderThread(task);
threads[i].Start();
}
lock (this)
{
Monitor.Wait(this, TimeSpan.FromMilliseconds(1000));
}
for (int i = 0; i < n; i++)
{
if (threads[i] != null)
{
threads[i].StopThread();
}
}
for (int i = 0; i < n; i++)
{
if (threads[i] != null)
{
threads[i].Join();
if (threads[i].Error != null)
{
string msg = "Error occurred in thread " + threads[i].Name + ":\n" + threads[i].Error.Message;
Assert.Fail(msg);
}
}
}
foreach (DirectoryReader readerToClose in readersToClose)
{
readerToClose.Dispose();
}
firstReader.Dispose();
reader.Dispose();
foreach (DirectoryReader readerToClose in readersToClose)
{
AssertReaderClosed(readerToClose, true);
}
AssertReaderClosed(reader, true);
AssertReaderClosed(firstReader, true);
dir.Dispose();
}
private class TestReopenAnonymousInnerClassHelper3 : TestReopen
{
private readonly TestDirectoryReaderReopen OuterInstance;
private Directory Dir;
private int n;
public TestReopenAnonymousInnerClassHelper3(TestDirectoryReaderReopen outerInstance, Directory dir, int n)
{
this.OuterInstance = outerInstance;
this.Dir = dir;
this.n = n;
}
protected internal override void ModifyIndex(int i)
{
IndexWriter modifier = new IndexWriter(Dir, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)));
modifier.AddDocument(CreateDocument(n + i, 6));
modifier.Dispose();
}
protected internal override DirectoryReader OpenReader()
{
return DirectoryReader.Open(Dir);
}
}
private class ReaderThreadTaskAnonymousInnerClassHelper : ReaderThreadTask
{
private readonly TestDirectoryReaderReopen OuterInstance;
private Lucene.Net.Index.TestDirectoryReaderReopen.TestReopen Test;
private IList<ReaderCouple> Readers;
private ISet<DirectoryReader> ReadersToClose;
private DirectoryReader r;
private int Index;
public ReaderThreadTaskAnonymousInnerClassHelper(TestDirectoryReaderReopen outerInstance, Lucene.Net.Index.TestDirectoryReaderReopen.TestReopen test, IList<ReaderCouple> readers, ISet<DirectoryReader> readersToClose, DirectoryReader r, int index)
{
this.OuterInstance = outerInstance;
this.Test = test;
this.Readers = readers;
this.ReadersToClose = readersToClose;
this.r = r;
this.Index = index;
}
public override void Run()
{
Random rnd = LuceneTestCase.Random;
while (!Stopped)
{
if (Index % 2 == 0)
{
// refresh reader synchronized
ReaderCouple c = (OuterInstance.RefreshReader(r, Test, Index, true));
ReadersToClose.Add(c.NewReader);
ReadersToClose.Add(c.RefreshedReader);
Readers.Add(c);
// prevent too many readers
break;
}
else
{
// not synchronized
DirectoryReader refreshed = DirectoryReader.OpenIfChanged(r);
if (refreshed == null)
{
refreshed = r;
}
IndexSearcher searcher =
#if FEATURE_INSTANCE_TESTDATA_INITIALIZATION
OuterInstance.
#endif
NewSearcher(refreshed);
ScoreDoc[] hits = searcher.Search(new TermQuery(new Term("field1", "a" + rnd.Next(refreshed.MaxDoc))), null, 1000).ScoreDocs;
if (hits.Length > 0)
{
searcher.Doc(hits[0].Doc);
}
if (refreshed != r)
{
refreshed.Dispose();
}
}
lock (this)
{
Monitor.Wait(this, TimeSpan.FromMilliseconds(TestUtil.NextInt32(Random, 1, 100)));
}
}
}
}
private class ReaderThreadTaskAnonymousInnerClassHelper2 : ReaderThreadTask
{
private readonly TestDirectoryReaderReopen OuterInstance;
private IList<ReaderCouple> Readers;
public ReaderThreadTaskAnonymousInnerClassHelper2(TestDirectoryReaderReopen outerInstance, IList<ReaderCouple> readers)
{
this.OuterInstance = outerInstance;
this.Readers = readers;
}
public override void Run()
{
Random rnd = LuceneTestCase.Random;
while (!Stopped)
{
int numReaders = Readers.Count;
if (numReaders > 0)
{
ReaderCouple c = Readers[rnd.Next(numReaders)];
TestDirectoryReader.AssertIndexEquals(c.NewReader, c.RefreshedReader);
}
lock (this)
{
Monitor.Wait(this, TimeSpan.FromMilliseconds(TestUtil.NextInt32(Random, 1, 100)));
}
}
}
}
internal class ReaderCouple
{
internal ReaderCouple(DirectoryReader r1, DirectoryReader r2)
{
NewReader = r1;
RefreshedReader = r2;
}
internal DirectoryReader NewReader;
internal DirectoryReader RefreshedReader;
}
internal abstract class ReaderThreadTask
{
protected internal volatile bool Stopped;
public virtual void Stop()
{
this.Stopped = true;
}
public abstract void Run();
}
private class ReaderThread : ThreadJob
{
internal ReaderThreadTask Task;
internal Exception Error;
internal ReaderThread(ReaderThreadTask task)
{
this.Task = task;
}
public virtual void StopThread()
{
this.Task.Stop();
}
public override void Run()
{
try
{
this.Task.Run();
}
catch (Exception r)
{
Console.WriteLine(r.StackTrace);
this.Error = r;
}
}
}
private object CreateReaderMutex = new object();
private ReaderCouple RefreshReader(DirectoryReader reader, bool hasChanges)
{
return RefreshReader(reader, null, -1, hasChanges);
}
internal virtual ReaderCouple RefreshReader(DirectoryReader reader, TestReopen test, int modify, bool hasChanges)
{
lock (CreateReaderMutex)
{
DirectoryReader r = null;
if (test != null)
{
test.ModifyIndex(modify);
r = test.OpenReader();
}
DirectoryReader refreshed = null;
try
{
refreshed = DirectoryReader.OpenIfChanged(reader);
if (refreshed == null)
{
refreshed = reader;
}
}
finally
{
if (refreshed == null && r != null)
{
// Hit exception -- close opened reader
r.Dispose();
}
}
if (hasChanges)
{
if (refreshed == reader)
{
Assert.Fail("No new DirectoryReader instance created during refresh.");
}
}
else
{
if (refreshed != reader)
{
Assert.Fail("New DirectoryReader instance created during refresh even though index had no changes.");
}
}
return new ReaderCouple(r, refreshed);
}
}
/// <summary>
/// LUCENENET specific
/// Is non-static because NewIndexWriterConfig is no longer static.
/// </summary>
public void CreateIndex(Random random, Directory dir, bool multiSegment)
{
IndexWriter.Unlock(dir);
IndexWriter w = new IndexWriter(dir, NewIndexWriterConfig(random, TEST_VERSION_CURRENT, new MockAnalyzer(random)).SetMergePolicy(new LogDocMergePolicy()));
for (int i = 0; i < 100; i++)
{
w.AddDocument(CreateDocument(i, 4));
if (multiSegment && (i % 10) == 0)
{
w.Commit();
}
}
if (!multiSegment)
{
w.ForceMerge(1);
}
w.Dispose();
DirectoryReader r = DirectoryReader.Open(dir);
if (multiSegment)
{
Assert.IsTrue(r.Leaves.Count > 1);
}
else
{
Assert.IsTrue(r.Leaves.Count == 1);
}
r.Dispose();
}
public static Document CreateDocument(int n, int numFields)
{
StringBuilder sb = new StringBuilder();
Document doc = new Document();
sb.Append("a");
sb.Append(n);
FieldType customType2 = new FieldType(TextField.TYPE_STORED);
customType2.IsTokenized = false;
customType2.OmitNorms = true;
FieldType customType3 = new FieldType();
customType3.IsStored = true;
doc.Add(new TextField("field1", sb.ToString(), Field.Store.YES));
doc.Add(new Field("fielda", sb.ToString(), customType2));
doc.Add(new Field("fieldb", sb.ToString(), customType3));
sb.Append(" b");
sb.Append(n);
for (int i = 1; i < numFields; i++)
{
doc.Add(new TextField("field" + (i + 1), sb.ToString(), Field.Store.YES));
}
return doc;
}
internal static void ModifyIndex(int i, Directory dir)
{
switch (i)
{
case 0:
{
if (VERBOSE)
{
Console.WriteLine("TEST: modify index");
}
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)));
w.DeleteDocuments(new Term("field2", "a11"));
w.DeleteDocuments(new Term("field2", "b30"));
w.Dispose();
break;
}
case 1:
{
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)));
w.ForceMerge(1);
w.Dispose();
break;
}
case 2:
{
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)));
w.AddDocument(CreateDocument(101, 4));
w.ForceMerge(1);
w.AddDocument(CreateDocument(102, 4));
w.AddDocument(CreateDocument(103, 4));
w.Dispose();
break;
}
case 3:
{
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)));
w.AddDocument(CreateDocument(101, 4));
w.Dispose();
break;
}
}
}
internal static void AssertReaderClosed(IndexReader reader, bool checkSubReaders)
{
Assert.AreEqual(0, reader.RefCount);
if (checkSubReaders && reader is CompositeReader)
{
// we cannot use reader context here, as reader is
// already closed and calling getTopReaderContext() throws AlreadyClosed!
IList<IndexReader> subReaders = ((CompositeReader)reader).GetSequentialSubReaders();
foreach (IndexReader r in subReaders)
{
AssertReaderClosed(r, checkSubReaders);
}
}
}
internal abstract class TestReopen
{
protected internal abstract DirectoryReader OpenReader();
protected internal abstract void ModifyIndex(int i);
}
internal class KeepAllCommits : IndexDeletionPolicy
{
public override void OnInit<T>(IList<T> commits)
{
}
public override void OnCommit<T>(IList<T> commits)
{
}
}
[Test]
public virtual void TestReopenOnCommit()
{
Directory dir = NewDirectory();
IndexWriter writer = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)).SetIndexDeletionPolicy(new KeepAllCommits()).SetMaxBufferedDocs(-1).SetMergePolicy(NewLogMergePolicy(10)));
for (int i = 0; i < 4; i++)
{
Document doc = new Document();
doc.Add(NewStringField("id", "" + i, Field.Store.NO));
writer.AddDocument(doc);
IDictionary<string, string> data = new Dictionary<string, string>();
data["index"] = i + "";
writer.SetCommitData(data);
writer.Commit();
}
for (int i = 0; i < 4; i++)
{
writer.DeleteDocuments(new Term("id", "" + i));
IDictionary<string, string> data = new Dictionary<string, string>();
data["index"] = (4 + i) + "";
writer.SetCommitData(data);
writer.Commit();
}
writer.Dispose();
DirectoryReader r = DirectoryReader.Open(dir);
Assert.AreEqual(0, r.NumDocs);
ICollection<IndexCommit> commits = DirectoryReader.ListCommits(dir);
foreach (IndexCommit commit in commits)
{
DirectoryReader r2 = DirectoryReader.OpenIfChanged(r, commit);
Assert.IsNotNull(r2);
Assert.IsTrue(r2 != r);
IDictionary<string, string> s = commit.UserData;
int v;
if (s.Count == 0)
{
// First commit created by IW
v = -1;
}
else
{
v = Convert.ToInt32(s["index"]);
}
if (v < 4)
{
Assert.AreEqual(1 + v, r2.NumDocs);
}
else
{
Assert.AreEqual(7 - v, r2.NumDocs);
}
r.Dispose();
r = r2;
}
r.Dispose();
dir.Dispose();
}
[Test]
public virtual void TestOpenIfChangedNRTToCommit()
{
Directory dir = NewDirectory();
// Can't use RIW because it randomly commits:
IndexWriter w = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)));
Document doc = new Document();
doc.Add(NewStringField("field", "value", Field.Store.NO));
w.AddDocument(doc);
w.Commit();
IList<IndexCommit> commits = DirectoryReader.ListCommits(dir);
Assert.AreEqual(1, commits.Count);
w.AddDocument(doc);
DirectoryReader r = DirectoryReader.Open(w, true);
Assert.AreEqual(2, r.NumDocs);
IndexReader r2 = DirectoryReader.OpenIfChanged(r, commits[0]);
Assert.IsNotNull(r2);
r.Dispose();
Assert.AreEqual(1, r2.NumDocs);
w.Dispose();
r2.Dispose();
dir.Dispose();
}
}
}