1227 lines
36 KiB
C#
1227 lines
36 KiB
C#
/*
|
|
* 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 NUnit.Framework;
|
|
|
|
using WhitespaceAnalyzer = Lucene.Net.Analysis.WhitespaceAnalyzer;
|
|
using Document = Lucene.Net.Documents.Document;
|
|
using Field = Lucene.Net.Documents.Field;
|
|
using Index = Lucene.Net.Documents.Field.Index;
|
|
using Store = Lucene.Net.Documents.Field.Store;
|
|
using TermVector = Lucene.Net.Documents.Field.TermVector;
|
|
using AlreadyClosedException = Lucene.Net.Store.AlreadyClosedException;
|
|
using Directory = Lucene.Net.Store.Directory;
|
|
using MockRAMDirectory = Lucene.Net.Store.MockRAMDirectory;
|
|
using IndexSearcher = Lucene.Net.Search.IndexSearcher;
|
|
using Query = Lucene.Net.Search.Query;
|
|
using TermQuery = Lucene.Net.Search.TermQuery;
|
|
using LuceneTestCase = Lucene.Net.Util.LuceneTestCase;
|
|
using _TestUtil = Lucene.Net.Util._TestUtil;
|
|
|
|
namespace Lucene.Net.Index
|
|
{
|
|
|
|
[TestFixture]
|
|
public class TestIndexWriterReader:LuceneTestCase
|
|
{
|
|
private class AnonymousClassThread:ThreadClass
|
|
{
|
|
public AnonymousClassThread(long endTime, Lucene.Net.Index.IndexWriter writer, Lucene.Net.Store.Directory[] dirs, System.Collections.IList excs, TestIndexWriterReader enclosingInstance)
|
|
{
|
|
InitBlock(endTime, writer, dirs, excs, enclosingInstance);
|
|
}
|
|
private void InitBlock(long endTime, Lucene.Net.Index.IndexWriter writer, Lucene.Net.Store.Directory[] dirs, System.Collections.IList excs, TestIndexWriterReader enclosingInstance)
|
|
{
|
|
this.endTime = endTime;
|
|
this.writer = writer;
|
|
this.dirs = dirs;
|
|
this.excs = excs;
|
|
this.enclosingInstance = enclosingInstance;
|
|
}
|
|
private long endTime;
|
|
private Lucene.Net.Index.IndexWriter writer;
|
|
private Lucene.Net.Store.Directory[] dirs;
|
|
private System.Collections.IList excs;
|
|
private TestIndexWriterReader enclosingInstance;
|
|
public TestIndexWriterReader Enclosing_Instance
|
|
{
|
|
get
|
|
{
|
|
return enclosingInstance;
|
|
}
|
|
|
|
}
|
|
override public void Run()
|
|
{
|
|
while ((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) < endTime)
|
|
{
|
|
try
|
|
{
|
|
writer.AddIndexesNoOptimize(dirs);
|
|
}
|
|
catch (System.Exception t)
|
|
{
|
|
excs.Add(t);
|
|
throw new System.SystemException("", t);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
private class AnonymousClassThread1:ThreadClass
|
|
{
|
|
public AnonymousClassThread1(long endTime, Lucene.Net.Index.IndexWriter writer, System.Collections.IList excs, TestIndexWriterReader enclosingInstance)
|
|
{
|
|
InitBlock(endTime, writer, excs, enclosingInstance);
|
|
}
|
|
private void InitBlock(long endTime, Lucene.Net.Index.IndexWriter writer, System.Collections.IList excs, TestIndexWriterReader enclosingInstance)
|
|
{
|
|
this.endTime = endTime;
|
|
this.writer = writer;
|
|
this.excs = excs;
|
|
this.enclosingInstance = enclosingInstance;
|
|
}
|
|
private long endTime;
|
|
private Lucene.Net.Index.IndexWriter writer;
|
|
private System.Collections.IList excs;
|
|
private TestIndexWriterReader enclosingInstance;
|
|
public TestIndexWriterReader Enclosing_Instance
|
|
{
|
|
get
|
|
{
|
|
return enclosingInstance;
|
|
}
|
|
|
|
}
|
|
override public void Run()
|
|
{
|
|
int count = 0;
|
|
System.Random r = new System.Random();
|
|
while ((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) < endTime)
|
|
{
|
|
try
|
|
{
|
|
for (int docUpto = 0; docUpto < 10; docUpto++)
|
|
{
|
|
writer.AddDocument(Lucene.Net.Index.TestIndexWriterReader.CreateDocument(10 * count + docUpto, "test", 4));
|
|
}
|
|
count++;
|
|
int limit = count * 10;
|
|
for (int delUpto = 0; delUpto < 5; delUpto++)
|
|
{
|
|
int x = r.Next(limit);
|
|
writer.DeleteDocuments(new Term("field3", "b" + x));
|
|
}
|
|
}
|
|
catch (System.Exception t)
|
|
{
|
|
excs.Add(t);
|
|
throw new System.SystemException("", t);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
internal static System.IO.StreamWriter infoStream;
|
|
|
|
public class HeavyAtomicInt
|
|
{
|
|
private int value_Renamed;
|
|
public HeavyAtomicInt(int start)
|
|
{
|
|
value_Renamed = start;
|
|
}
|
|
public virtual int AddAndGet(int inc)
|
|
{
|
|
lock (this)
|
|
{
|
|
value_Renamed += inc;
|
|
return value_Renamed;
|
|
}
|
|
}
|
|
public virtual int IncrementAndGet()
|
|
{
|
|
lock (this)
|
|
{
|
|
value_Renamed++;
|
|
return value_Renamed;
|
|
}
|
|
}
|
|
public virtual int IntValue()
|
|
{
|
|
lock (this)
|
|
{
|
|
return value_Renamed;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static int Count(Term t, IndexReader r)
|
|
{
|
|
int count = 0;
|
|
TermDocs td = r.TermDocs(t);
|
|
while (td.Next())
|
|
{
|
|
var d = td.Doc;
|
|
count++;
|
|
}
|
|
td.Close();
|
|
return count;
|
|
}
|
|
|
|
[Test]
|
|
public virtual void TestUpdateDocument()
|
|
{
|
|
bool optimize = true;
|
|
|
|
Directory dir1 = new MockRAMDirectory();
|
|
IndexWriter writer = new IndexWriter(dir1, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED);
|
|
|
|
// create the index
|
|
CreateIndexNoClose(!optimize, "index1", writer);
|
|
|
|
// writer.flush(false, true, true);
|
|
|
|
// get a reader
|
|
IndexReader r1 = writer.GetReader();
|
|
Assert.IsTrue(r1.IsCurrent());
|
|
|
|
System.String id10 = r1.Document(10).GetField("id").StringValue;
|
|
|
|
Document newDoc = r1.Document(10);
|
|
newDoc.RemoveField("id");
|
|
newDoc.Add(new Field("id", System.Convert.ToString(8000), Field.Store.YES, Field.Index.NOT_ANALYZED));
|
|
writer.UpdateDocument(new Term("id", id10), newDoc);
|
|
Assert.IsFalse(r1.IsCurrent());
|
|
|
|
IndexReader r2 = writer.GetReader();
|
|
Assert.IsTrue(r2.IsCurrent());
|
|
Assert.AreEqual(0, Count(new Term("id", id10), r2));
|
|
Assert.AreEqual(1, Count(new Term("id", System.Convert.ToString(8000)), r2));
|
|
|
|
r1.Close();
|
|
writer.Close();
|
|
Assert.IsTrue(r2.IsCurrent());
|
|
|
|
IndexReader r3 = IndexReader.Open(dir1, true);
|
|
Assert.IsTrue(r3.IsCurrent());
|
|
Assert.IsTrue(r2.IsCurrent());
|
|
Assert.AreEqual(0, Count(new Term("id", id10), r3));
|
|
Assert.AreEqual(1, Count(new Term("id", System.Convert.ToString(8000)), r3));
|
|
|
|
writer = new IndexWriter(dir1, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED);
|
|
Document doc = new Document();
|
|
doc.Add(new Field("field", "a b c", Field.Store.NO, Field.Index.ANALYZED));
|
|
writer.AddDocument(doc);
|
|
Assert.IsTrue(r2.IsCurrent());
|
|
Assert.IsTrue(r3.IsCurrent());
|
|
|
|
writer.Close();
|
|
|
|
Assert.IsFalse(r2.IsCurrent());
|
|
Assert.IsTrue(!r3.IsCurrent());
|
|
|
|
r2.Close();
|
|
r3.Close();
|
|
|
|
dir1.Close();
|
|
}
|
|
|
|
/// <summary> Test using IW.addIndexes
|
|
///
|
|
/// </summary>
|
|
/// <throws> Exception </throws>
|
|
[Test]
|
|
public virtual void TestAddIndexes()
|
|
{
|
|
bool optimize = false;
|
|
|
|
Directory dir1 = new MockRAMDirectory();
|
|
IndexWriter writer = new IndexWriter(dir1, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED);
|
|
writer.SetInfoStream(infoStream);
|
|
// create the index
|
|
CreateIndexNoClose(!optimize, "index1", writer);
|
|
writer.Flush(false, true, true);
|
|
|
|
// create a 2nd index
|
|
Directory dir2 = new MockRAMDirectory();
|
|
IndexWriter writer2 = new IndexWriter(dir2, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED);
|
|
writer2.SetInfoStream(infoStream);
|
|
CreateIndexNoClose(!optimize, "index2", writer2);
|
|
writer2.Close();
|
|
|
|
IndexReader r0 = writer.GetReader();
|
|
Assert.IsTrue(r0.IsCurrent());
|
|
writer.AddIndexesNoOptimize(new Directory[]{dir2});
|
|
Assert.IsFalse(r0.IsCurrent());
|
|
r0.Close();
|
|
|
|
IndexReader r1 = writer.GetReader();
|
|
Assert.IsTrue(r1.IsCurrent());
|
|
|
|
writer.Commit();
|
|
Assert.IsFalse(r1.IsCurrent());
|
|
|
|
Assert.AreEqual(200, r1.MaxDoc);
|
|
|
|
int index2df = r1.DocFreq(new Term("indexname", "index2"));
|
|
|
|
Assert.AreEqual(100, index2df);
|
|
|
|
// verify the docs are from different indexes
|
|
Document doc5 = r1.Document(5);
|
|
Assert.AreEqual("index1", doc5.Get("indexname"));
|
|
Document doc150 = r1.Document(150);
|
|
Assert.AreEqual("index2", doc150.Get("indexname"));
|
|
r1.Close();
|
|
writer.Close();
|
|
dir1.Close();
|
|
}
|
|
|
|
[Test]
|
|
public virtual void TestAddIndexes2()
|
|
{
|
|
bool optimize = false;
|
|
|
|
Directory dir1 = new MockRAMDirectory();
|
|
IndexWriter writer = new IndexWriter(dir1, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED);
|
|
writer.SetInfoStream(infoStream);
|
|
|
|
// create a 2nd index
|
|
Directory dir2 = new MockRAMDirectory();
|
|
IndexWriter writer2 = new IndexWriter(dir2, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED);
|
|
writer2.SetInfoStream(infoStream);
|
|
CreateIndexNoClose(!optimize, "index2", writer2);
|
|
writer2.Close();
|
|
|
|
writer.AddIndexesNoOptimize(new Directory[]{dir2});
|
|
writer.AddIndexesNoOptimize(new Directory[]{dir2});
|
|
writer.AddIndexesNoOptimize(new Directory[]{dir2});
|
|
writer.AddIndexesNoOptimize(new Directory[]{dir2});
|
|
writer.AddIndexesNoOptimize(new Directory[]{dir2});
|
|
|
|
IndexReader r1 = writer.GetReader();
|
|
Assert.AreEqual(500, r1.MaxDoc);
|
|
|
|
r1.Close();
|
|
writer.Close();
|
|
dir1.Close();
|
|
}
|
|
|
|
/// <summary> Deletes using IW.deleteDocuments
|
|
///
|
|
/// </summary>
|
|
/// <throws> Exception </throws>
|
|
[Test]
|
|
public virtual void TestDeleteFromIndexWriter()
|
|
{
|
|
bool optimize = true;
|
|
|
|
Directory dir1 = new MockRAMDirectory();
|
|
IndexWriter writer = new IndexWriter(dir1, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED);
|
|
writer.ReaderTermsIndexDivisor = 2;
|
|
writer.SetInfoStream(infoStream);
|
|
// create the index
|
|
CreateIndexNoClose(!optimize, "index1", writer);
|
|
writer.Flush(false, true, true);
|
|
// get a reader
|
|
IndexReader r1 = writer.GetReader();
|
|
|
|
System.String id10 = r1.Document(10).GetField("id").StringValue;
|
|
|
|
// deleted IW docs should not show up in the next getReader
|
|
writer.DeleteDocuments(new Term("id", id10));
|
|
IndexReader r2 = writer.GetReader();
|
|
Assert.AreEqual(1, Count(new Term("id", id10), r1));
|
|
Assert.AreEqual(0, Count(new Term("id", id10), r2));
|
|
|
|
System.String id50 = r1.Document(50).GetField("id").StringValue;
|
|
Assert.AreEqual(1, Count(new Term("id", id50), r1));
|
|
|
|
writer.DeleteDocuments(new Term("id", id50));
|
|
|
|
IndexReader r3 = writer.GetReader();
|
|
Assert.AreEqual(0, Count(new Term("id", id10), r3));
|
|
Assert.AreEqual(0, Count(new Term("id", id50), r3));
|
|
|
|
System.String id75 = r1.Document(75).GetField("id").StringValue;
|
|
writer.DeleteDocuments(new TermQuery(new Term("id", id75)));
|
|
IndexReader r4 = writer.GetReader();
|
|
Assert.AreEqual(1, Count(new Term("id", id75), r3));
|
|
Assert.AreEqual(0, Count(new Term("id", id75), r4));
|
|
|
|
r1.Close();
|
|
r2.Close();
|
|
r3.Close();
|
|
r4.Close();
|
|
writer.Close();
|
|
|
|
// reopen the writer to verify the delete made it to the directory
|
|
writer = new IndexWriter(dir1, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED);
|
|
writer.SetInfoStream(infoStream);
|
|
IndexReader w2r1 = writer.GetReader();
|
|
Assert.AreEqual(0, Count(new Term("id", id10), w2r1));
|
|
w2r1.Close();
|
|
writer.Close();
|
|
dir1.Close();
|
|
}
|
|
|
|
[Test]
|
|
public virtual void TestAddIndexesAndDoDeletesThreads()
|
|
{
|
|
int numIter = 5;
|
|
int numDirs = 3;
|
|
|
|
Directory mainDir = new MockRAMDirectory();
|
|
IndexWriter mainWriter = new IndexWriter(mainDir, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED);
|
|
mainWriter.SetInfoStream(infoStream);
|
|
AddDirectoriesThreads addDirThreads = new AddDirectoriesThreads(this, numIter, mainWriter);
|
|
addDirThreads.LaunchThreads(numDirs);
|
|
addDirThreads.JoinThreads();
|
|
|
|
//Assert.AreEqual(100 + numDirs * (3 * numIter / 4) * addDirThreads.NUM_THREADS
|
|
// * addDirThreads.NUM_INIT_DOCS, addDirThreads.mainWriter.numDocs());
|
|
Assert.AreEqual(addDirThreads.count.IntValue(), addDirThreads.mainWriter.NumDocs());
|
|
|
|
addDirThreads.Close(true);
|
|
|
|
Assert.IsTrue(addDirThreads.failures.Count == 0);
|
|
|
|
_TestUtil.CheckIndex(mainDir);
|
|
|
|
IndexReader reader = IndexReader.Open(mainDir, true);
|
|
Assert.AreEqual(addDirThreads.count.IntValue(), reader.NumDocs());
|
|
//Assert.AreEqual(100 + numDirs * (3 * numIter / 4) * addDirThreads.NUM_THREADS
|
|
// * addDirThreads.NUM_INIT_DOCS, reader.numDocs());
|
|
reader.Close();
|
|
|
|
addDirThreads.CloseDir();
|
|
mainDir.Close();
|
|
}
|
|
|
|
private class DeleteThreads
|
|
{
|
|
private class AnonymousClassThread2:ThreadClass
|
|
{
|
|
public AnonymousClassThread2(DeleteThreads enclosingInstance)
|
|
{
|
|
InitBlock(enclosingInstance);
|
|
}
|
|
private void InitBlock(DeleteThreads enclosingInstance)
|
|
{
|
|
this.enclosingInstance = enclosingInstance;
|
|
}
|
|
private DeleteThreads enclosingInstance;
|
|
public DeleteThreads Enclosing_Instance
|
|
{
|
|
get
|
|
{
|
|
return enclosingInstance;
|
|
}
|
|
|
|
}
|
|
override public void Run()
|
|
{
|
|
try
|
|
{
|
|
Term term = Enclosing_Instance.GetDeleteTerm();
|
|
Enclosing_Instance.mainWriter.DeleteDocuments(term);
|
|
lock (Enclosing_Instance.deletedTerms.SyncRoot)
|
|
{
|
|
Enclosing_Instance.deletedTerms.Add(term);
|
|
}
|
|
}
|
|
catch (System.Exception t)
|
|
{
|
|
Enclosing_Instance.Handle(t);
|
|
}
|
|
}
|
|
}
|
|
private void InitBlock(TestIndexWriterReader enclosingInstance)
|
|
{
|
|
this.enclosingInstance = enclosingInstance;
|
|
threads = new ThreadClass[NUM_THREADS];
|
|
}
|
|
private TestIndexWriterReader enclosingInstance;
|
|
public TestIndexWriterReader Enclosing_Instance
|
|
{
|
|
get
|
|
{
|
|
return enclosingInstance;
|
|
}
|
|
|
|
}
|
|
internal const int NUM_THREADS = 5;
|
|
internal ThreadClass[] threads;
|
|
internal IndexWriter mainWriter;
|
|
internal System.Collections.IList deletedTerms = new System.Collections.ArrayList();
|
|
internal System.Collections.ArrayList toDeleteTerms = new System.Collections.ArrayList();
|
|
internal System.Random random;
|
|
internal System.Collections.IList failures = new System.Collections.ArrayList();
|
|
|
|
public DeleteThreads(TestIndexWriterReader enclosingInstance, IndexWriter mainWriter)
|
|
{
|
|
InitBlock(enclosingInstance);
|
|
this.mainWriter = mainWriter;
|
|
IndexReader reader = mainWriter.GetReader();
|
|
int maxDoc = reader.MaxDoc;
|
|
random = Enclosing_Instance.NewRandom();
|
|
int iter = random.Next(maxDoc);
|
|
for (int x = 0; x < iter; x++)
|
|
{
|
|
int doc = random.Next(iter);
|
|
System.String id = reader.Document(doc).Get("id");
|
|
toDeleteTerms.Add(new Term("id", id));
|
|
}
|
|
}
|
|
|
|
internal virtual Term GetDeleteTerm()
|
|
{
|
|
lock (toDeleteTerms.SyncRoot)
|
|
{
|
|
System.Object tempObject;
|
|
tempObject = toDeleteTerms[0];
|
|
toDeleteTerms.RemoveAt(0);
|
|
return (Term) tempObject;
|
|
}
|
|
}
|
|
|
|
internal virtual void LaunchThreads(int numIter)
|
|
{
|
|
for (int i = 0; i < NUM_THREADS; i++)
|
|
{
|
|
threads[i] = new AnonymousClassThread2(this);
|
|
}
|
|
}
|
|
|
|
internal virtual void Handle(System.Exception t)
|
|
{
|
|
System.Console.Out.WriteLine(t.StackTrace);
|
|
lock (failures.SyncRoot)
|
|
{
|
|
failures.Add(t);
|
|
}
|
|
}
|
|
|
|
internal virtual void JoinThreads()
|
|
{
|
|
for (int i = 0; i < NUM_THREADS; i++)
|
|
try
|
|
{
|
|
threads[i].Join();
|
|
}
|
|
catch (System.Threading.ThreadInterruptedException ie)
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
private class AddDirectoriesThreads
|
|
{
|
|
private class AnonymousClassThread2:ThreadClass
|
|
{
|
|
public AnonymousClassThread2(int numIter, AddDirectoriesThreads enclosingInstance)
|
|
{
|
|
InitBlock(numIter, enclosingInstance);
|
|
}
|
|
private void InitBlock(int numIter, AddDirectoriesThreads enclosingInstance)
|
|
{
|
|
this.numIter = numIter;
|
|
this.enclosingInstance = enclosingInstance;
|
|
}
|
|
private int numIter;
|
|
private AddDirectoriesThreads enclosingInstance;
|
|
public AddDirectoriesThreads Enclosing_Instance
|
|
{
|
|
get
|
|
{
|
|
return enclosingInstance;
|
|
}
|
|
|
|
}
|
|
override public void Run()
|
|
{
|
|
try
|
|
{
|
|
Directory[] dirs = new Directory[Enclosing_Instance.numDirs];
|
|
for (int k = 0; k < Enclosing_Instance.numDirs; k++)
|
|
dirs[k] = new MockRAMDirectory(Enclosing_Instance.addDir);
|
|
//int j = 0;
|
|
//while (true) {
|
|
// System.out.println(Thread.currentThread().getName() + ": iter
|
|
// j=" + j);
|
|
for (int x = 0; x < numIter; x++)
|
|
{
|
|
// only do addIndexesNoOptimize
|
|
Enclosing_Instance.DoBody(x, dirs);
|
|
}
|
|
//if (numIter > 0 && j == numIter)
|
|
// break;
|
|
//doBody(j++, dirs);
|
|
//doBody(5, dirs);
|
|
//}
|
|
}
|
|
catch (System.Exception t)
|
|
{
|
|
Enclosing_Instance.Handle(t);
|
|
}
|
|
}
|
|
}
|
|
private void InitBlock(TestIndexWriterReader enclosingInstance)
|
|
{
|
|
this.enclosingInstance = enclosingInstance;
|
|
threads = new ThreadClass[NUM_THREADS];
|
|
}
|
|
private TestIndexWriterReader enclosingInstance;
|
|
public TestIndexWriterReader Enclosing_Instance
|
|
{
|
|
get
|
|
{
|
|
return enclosingInstance;
|
|
}
|
|
|
|
}
|
|
internal Directory addDir;
|
|
internal const int NUM_THREADS = 5;
|
|
internal const int NUM_INIT_DOCS = 100;
|
|
internal int numDirs;
|
|
internal ThreadClass[] threads;
|
|
internal IndexWriter mainWriter;
|
|
internal System.Collections.IList failures = new System.Collections.ArrayList();
|
|
internal IndexReader[] readers;
|
|
internal bool didClose = false;
|
|
internal HeavyAtomicInt count = new HeavyAtomicInt(0);
|
|
internal HeavyAtomicInt numAddIndexesNoOptimize = new HeavyAtomicInt(0);
|
|
|
|
public AddDirectoriesThreads(TestIndexWriterReader enclosingInstance, int numDirs, IndexWriter mainWriter)
|
|
{
|
|
InitBlock(enclosingInstance);
|
|
this.numDirs = numDirs;
|
|
this.mainWriter = mainWriter;
|
|
addDir = new MockRAMDirectory();
|
|
IndexWriter writer = new IndexWriter(addDir, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED);
|
|
writer.SetMaxBufferedDocs(2);
|
|
for (int i = 0; i < NUM_INIT_DOCS; i++)
|
|
{
|
|
Document doc = Lucene.Net.Index.TestIndexWriterReader.CreateDocument(i, "addindex", 4);
|
|
writer.AddDocument(doc);
|
|
}
|
|
|
|
writer.Close();
|
|
|
|
readers = new IndexReader[numDirs];
|
|
for (int i = 0; i < numDirs; i++)
|
|
readers[i] = IndexReader.Open(addDir, false);
|
|
}
|
|
|
|
internal virtual void JoinThreads()
|
|
{
|
|
for (int i = 0; i < NUM_THREADS; i++)
|
|
try
|
|
{
|
|
threads[i].Join();
|
|
}
|
|
catch (System.Threading.ThreadInterruptedException ie)
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
|
|
internal virtual void Close(bool doWait)
|
|
{
|
|
didClose = true;
|
|
mainWriter.Close(doWait);
|
|
}
|
|
|
|
internal virtual void CloseDir()
|
|
{
|
|
for (int i = 0; i < numDirs; i++)
|
|
readers[i].Close();
|
|
addDir.Close();
|
|
}
|
|
|
|
internal virtual void Handle(System.Exception t)
|
|
{
|
|
System.Console.Out.WriteLine(t.StackTrace);
|
|
lock (failures.SyncRoot)
|
|
{
|
|
failures.Add(t);
|
|
}
|
|
}
|
|
|
|
internal virtual void LaunchThreads(int numIter)
|
|
{
|
|
for (int i = 0; i < NUM_THREADS; i++)
|
|
{
|
|
threads[i] = new AnonymousClassThread2(numIter, this);
|
|
}
|
|
for (int i = 0; i < NUM_THREADS; i++)
|
|
threads[i].Start();
|
|
}
|
|
|
|
internal virtual void DoBody(int j, Directory[] dirs)
|
|
{
|
|
switch (j % 4)
|
|
{
|
|
|
|
case 0:
|
|
mainWriter.AddIndexesNoOptimize(dirs);
|
|
mainWriter.Optimize();
|
|
break;
|
|
|
|
case 1:
|
|
mainWriter.AddIndexesNoOptimize(dirs);
|
|
numAddIndexesNoOptimize.IncrementAndGet();
|
|
break;
|
|
|
|
case 2:
|
|
mainWriter.AddIndexes(readers);
|
|
break;
|
|
|
|
case 3:
|
|
mainWriter.Commit();
|
|
break;
|
|
}
|
|
count.AddAndGet(dirs.Length * NUM_INIT_DOCS);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public virtual void TestIndexWriterReopenSegmentOptimize()
|
|
{
|
|
DoTestIndexWriterReopenSegment(true);
|
|
}
|
|
|
|
[Test]
|
|
public virtual void TestIndexWriterReopenSegment()
|
|
{
|
|
DoTestIndexWriterReopenSegment(false);
|
|
}
|
|
|
|
/// <summary> Tests creating a segment, then check to insure the segment can be seen via
|
|
/// IW.getReader
|
|
/// </summary>
|
|
public virtual void DoTestIndexWriterReopenSegment(bool optimize)
|
|
{
|
|
Directory dir1 = new MockRAMDirectory();
|
|
IndexWriter writer = new IndexWriter(dir1, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED);
|
|
writer.SetInfoStream(infoStream);
|
|
IndexReader r1 = writer.GetReader();
|
|
Assert.AreEqual(0, r1.MaxDoc);
|
|
CreateIndexNoClose(false, "index1", writer);
|
|
writer.Flush(!optimize, true, true);
|
|
|
|
IndexReader iwr1 = writer.GetReader();
|
|
Assert.AreEqual(100, iwr1.MaxDoc);
|
|
|
|
IndexReader r2 = writer.GetReader();
|
|
Assert.AreEqual(r2.MaxDoc, 100);
|
|
// add 100 documents
|
|
for (int x = 10000; x < 10000 + 100; x++)
|
|
{
|
|
Document d = CreateDocument(x, "index1", 5);
|
|
writer.AddDocument(d);
|
|
}
|
|
writer.Flush(false, true, true);
|
|
// verify the reader was reopened internally
|
|
IndexReader iwr2 = writer.GetReader();
|
|
Assert.IsTrue(iwr2 != r1);
|
|
Assert.AreEqual(200, iwr2.MaxDoc);
|
|
// should have flushed out a segment
|
|
IndexReader r3 = writer.GetReader();
|
|
Assert.IsTrue(r2 != r3);
|
|
Assert.AreEqual(200, r3.MaxDoc);
|
|
|
|
// dec ref the readers rather than close them because
|
|
// closing flushes changes to the writer
|
|
r1.Close();
|
|
iwr1.Close();
|
|
r2.Close();
|
|
r3.Close();
|
|
iwr2.Close();
|
|
writer.Close();
|
|
|
|
// test whether the changes made it to the directory
|
|
writer = new IndexWriter(dir1, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED);
|
|
IndexReader w2r1 = writer.GetReader();
|
|
// insure the deletes were actually flushed to the directory
|
|
Assert.AreEqual(200, w2r1.MaxDoc);
|
|
w2r1.Close();
|
|
writer.Close();
|
|
|
|
dir1.Close();
|
|
}
|
|
|
|
|
|
public static Document CreateDocument(int n, System.String indexName, int numFields)
|
|
{
|
|
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
|
Document doc = new Document();
|
|
doc.Add(new Field("id", System.Convert.ToString(n), Field.Store.YES, Field.Index.NOT_ANALYZED, TermVector.WITH_POSITIONS_OFFSETS));
|
|
doc.Add(new Field("indexname", indexName, Field.Store.YES, Field.Index.NOT_ANALYZED, TermVector.WITH_POSITIONS_OFFSETS));
|
|
sb.Append("a");
|
|
sb.Append(n);
|
|
doc.Add(new Field("field1", sb.ToString(), Field.Store.YES, Field.Index.ANALYZED, TermVector.WITH_POSITIONS_OFFSETS));
|
|
sb.Append(" b");
|
|
sb.Append(n);
|
|
for (int i = 1; i < numFields; i++)
|
|
{
|
|
doc.Add(new Field("field" + (i + 1), sb.ToString(), Field.Store.YES, Field.Index.ANALYZED, TermVector.WITH_POSITIONS_OFFSETS));
|
|
}
|
|
return doc;
|
|
}
|
|
|
|
/// <summary> Delete a document by term and return the doc id
|
|
///
|
|
/// </summary>
|
|
/// <returns>
|
|
///
|
|
/// public static int deleteDocument(Term term, IndexWriter writer) throws
|
|
/// IOException { IndexReader reader = writer.getReader(); TermDocs td =
|
|
/// reader.termDocs(term); int doc = -1; //if (td.next()) { // doc = td.doc();
|
|
/// //} //writer.deleteDocuments(term); td.close(); return doc; }
|
|
/// </returns>
|
|
public static void CreateIndex(Directory dir1, System.String indexName, bool multiSegment)
|
|
{
|
|
IndexWriter w = new IndexWriter(dir1, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED);
|
|
w.SetMergePolicy(new LogDocMergePolicy(w));
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
w.AddDocument(CreateDocument(i, indexName, 4));
|
|
if (multiSegment && (i % 10) == 0)
|
|
{
|
|
}
|
|
}
|
|
if (!multiSegment)
|
|
{
|
|
w.Optimize();
|
|
}
|
|
w.Close();
|
|
}
|
|
|
|
public static void CreateIndexNoClose(bool multiSegment, System.String indexName, IndexWriter w)
|
|
{
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
w.AddDocument(CreateDocument(i, indexName, 4));
|
|
}
|
|
if (!multiSegment)
|
|
{
|
|
w.Optimize();
|
|
}
|
|
}
|
|
|
|
private class MyWarmer:IndexWriter.IndexReaderWarmer
|
|
{
|
|
internal int warmCount;
|
|
public override void Warm(IndexReader reader)
|
|
{
|
|
warmCount++;
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public virtual void TestMergeWarmer()
|
|
{
|
|
|
|
Directory dir1 = new MockRAMDirectory();
|
|
IndexWriter writer = new IndexWriter(dir1, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED);
|
|
writer.SetInfoStream(infoStream);
|
|
|
|
// create the index
|
|
CreateIndexNoClose(false, "test", writer);
|
|
|
|
// get a reader to put writer into near real-time mode
|
|
IndexReader r1 = writer.GetReader();
|
|
|
|
// Enroll warmer
|
|
MyWarmer warmer = new MyWarmer();
|
|
writer.MergedSegmentWarmer = warmer;
|
|
writer.MergeFactor = 2;
|
|
writer.SetMaxBufferedDocs(2);
|
|
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
writer.AddDocument(CreateDocument(i, "test", 4));
|
|
}
|
|
((ConcurrentMergeScheduler) writer.MergeScheduler).Sync();
|
|
|
|
Assert.IsTrue(warmer.warmCount > 0);
|
|
int count = warmer.warmCount;
|
|
|
|
writer.AddDocument(CreateDocument(17, "test", 4));
|
|
writer.Optimize();
|
|
Assert.IsTrue(warmer.warmCount > count);
|
|
|
|
writer.Close();
|
|
r1.Close();
|
|
dir1.Close();
|
|
}
|
|
|
|
[Test]
|
|
public virtual void TestAfterCommit()
|
|
{
|
|
Directory dir1 = new MockRAMDirectory();
|
|
IndexWriter writer = new IndexWriter(dir1, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED);
|
|
writer.SetInfoStream(infoStream);
|
|
|
|
// create the index
|
|
CreateIndexNoClose(false, "test", writer);
|
|
|
|
// get a reader to put writer into near real-time mode
|
|
IndexReader r1 = writer.GetReader();
|
|
_TestUtil.CheckIndex(dir1);
|
|
writer.Commit();
|
|
_TestUtil.CheckIndex(dir1);
|
|
Assert.AreEqual(100, r1.NumDocs());
|
|
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
writer.AddDocument(CreateDocument(i, "test", 4));
|
|
}
|
|
((ConcurrentMergeScheduler) writer.MergeScheduler).Sync();
|
|
|
|
IndexReader r2 = r1.Reopen();
|
|
if (r2 != r1)
|
|
{
|
|
r1.Close();
|
|
r1 = r2;
|
|
}
|
|
Assert.AreEqual(110, r1.NumDocs());
|
|
writer.Close();
|
|
r1.Close();
|
|
dir1.Close();
|
|
}
|
|
|
|
// Make sure reader remains usable even if IndexWriter closes
|
|
[Test]
|
|
public virtual void TestAfterClose()
|
|
{
|
|
Directory dir1 = new MockRAMDirectory();
|
|
IndexWriter writer = new IndexWriter(dir1, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED);
|
|
writer.SetInfoStream(infoStream);
|
|
|
|
// create the index
|
|
CreateIndexNoClose(false, "test", writer);
|
|
|
|
IndexReader r = writer.GetReader();
|
|
writer.Close();
|
|
|
|
_TestUtil.CheckIndex(dir1);
|
|
|
|
// reader should remain usable even after IndexWriter is closed:
|
|
Assert.AreEqual(100, r.NumDocs());
|
|
Query q = new TermQuery(new Term("indexname", "test"));
|
|
Assert.AreEqual(100, new IndexSearcher(r).Search(q, 10).TotalHits);
|
|
|
|
Assert.Throws<AlreadyClosedException>(() => r.Reopen(), "failed to hit AlreadyClosedException");
|
|
|
|
r.Close();
|
|
dir1.Close();
|
|
}
|
|
|
|
// Stress test reopen during addIndexes
|
|
[Test]
|
|
public virtual void TestDuringAddIndexes()
|
|
{
|
|
MockRAMDirectory dir1 = new MockRAMDirectory();
|
|
IndexWriter writer = new IndexWriter(dir1, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED);
|
|
writer.SetInfoStream(infoStream);
|
|
writer.MergeFactor = 2;
|
|
|
|
// create the index
|
|
CreateIndexNoClose(false, "test", writer);
|
|
writer.Commit();
|
|
|
|
Directory[] dirs = new Directory[10];
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
dirs[i] = new MockRAMDirectory(dir1);
|
|
}
|
|
|
|
IndexReader r = writer.GetReader();
|
|
|
|
int NUM_THREAD = 5;
|
|
float SECONDS = 3;
|
|
|
|
long endTime = (long) ((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) + 1000.0 * SECONDS);
|
|
System.Collections.IList excs = (System.Collections.IList) System.Collections.ArrayList.Synchronized(new System.Collections.ArrayList(new System.Collections.ArrayList()));
|
|
|
|
ThreadClass[] threads = new ThreadClass[NUM_THREAD];
|
|
for (int i = 0; i < NUM_THREAD; i++)
|
|
{
|
|
threads[i] = new AnonymousClassThread(endTime, writer, dirs, excs, this);
|
|
threads[i].IsBackground = true;
|
|
threads[i].Start();
|
|
}
|
|
|
|
int lastCount = 0;
|
|
while ((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) < endTime)
|
|
{
|
|
IndexReader r2 = r.Reopen();
|
|
if (r2 != r)
|
|
{
|
|
r.Close();
|
|
r = r2;
|
|
}
|
|
Query q = new TermQuery(new Term("indexname", "test"));
|
|
int count = new IndexSearcher(r).Search(q, 10).TotalHits;
|
|
Assert.IsTrue(count >= lastCount);
|
|
lastCount = count;
|
|
}
|
|
|
|
for (int i = 0; i < NUM_THREAD; i++)
|
|
{
|
|
threads[i].Join();
|
|
}
|
|
|
|
Assert.AreEqual(0, excs.Count);
|
|
r.Close();
|
|
try
|
|
{
|
|
Assert.AreEqual(0, dir1.GetOpenDeletedFiles().Count);
|
|
}
|
|
catch
|
|
{
|
|
//DIGY:
|
|
//I think this is an expected behaviour.
|
|
//There isn't any pending files to be deleted after "writer.Close()".
|
|
//But, since lucene.java's test case is designed that way
|
|
//and I might be wrong, I will add a warning
|
|
|
|
// Assert only in debug mode, so that CheckIndex is called during release.
|
|
#if DEBUG
|
|
Assert.Inconclusive("", 0, dir1.GetOpenDeletedFiles().Count);
|
|
#endif
|
|
}
|
|
writer.Close();
|
|
|
|
_TestUtil.CheckIndex(dir1);
|
|
|
|
dir1.Close();
|
|
}
|
|
|
|
|
|
// Stress test GetReader during addIndexes. Similar to the test above. But this one uses writer.GetReader()
|
|
// instead of reader.Reopen(). (only for Lucene.Net)
|
|
[Test]
|
|
public virtual void TestDuringAddIndexes_LuceneNet()
|
|
{
|
|
MockRAMDirectory dir1 = new MockRAMDirectory();
|
|
IndexWriter writer = new IndexWriter(dir1, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED);
|
|
writer.SetInfoStream(infoStream);
|
|
writer.MergeFactor = 2;
|
|
|
|
// create the index
|
|
CreateIndexNoClose(false, "test", writer);
|
|
writer.Commit();
|
|
|
|
Directory[] dirs = new Directory[10];
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
dirs[i] = new MockRAMDirectory(dir1);
|
|
}
|
|
|
|
IndexReader r = writer.GetReader();
|
|
|
|
int NUM_THREAD = 5;
|
|
float SECONDS = 3;
|
|
|
|
long endTime = (long)((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) + 1000.0 * SECONDS);
|
|
System.Collections.IList excs = (System.Collections.IList)System.Collections.ArrayList.Synchronized(new System.Collections.ArrayList(new System.Collections.ArrayList()));
|
|
|
|
ThreadClass[] threads = new ThreadClass[NUM_THREAD];
|
|
for (int i = 0; i < NUM_THREAD; i++)
|
|
{
|
|
threads[i] = new AnonymousClassThread(endTime, writer, dirs, excs, this);
|
|
threads[i].IsBackground = true;
|
|
threads[i].Start();
|
|
}
|
|
|
|
int lastCount = 0;
|
|
while ((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) < endTime)
|
|
{
|
|
using (IndexReader r2 = writer.GetReader())
|
|
{
|
|
Query q = new TermQuery(new Term("indexname", "test"));
|
|
int count = new IndexSearcher(r2).Search(q, 10).TotalHits;
|
|
Assert.IsTrue(count >= lastCount);
|
|
lastCount = count;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < NUM_THREAD; i++)
|
|
{
|
|
threads[i].Join();
|
|
}
|
|
|
|
Assert.AreEqual(0, excs.Count);
|
|
r.Close();
|
|
Assert.AreEqual(0, dir1.GetOpenDeletedFiles().Count);
|
|
writer.Close();
|
|
|
|
_TestUtil.CheckIndex(dir1);
|
|
|
|
dir1.Close();
|
|
}
|
|
|
|
// Stress test reopen during add/delete
|
|
[Test]
|
|
public virtual void TestDuringAddDelete()
|
|
{
|
|
Directory dir1 = new MockRAMDirectory();
|
|
IndexWriter writer = new IndexWriter(dir1, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED);
|
|
writer.SetInfoStream(infoStream);
|
|
writer.MergeFactor = 2;
|
|
|
|
// create the index
|
|
CreateIndexNoClose(false, "test", writer);
|
|
writer.Commit();
|
|
|
|
IndexReader r = writer.GetReader();
|
|
|
|
int NUM_THREAD = 5;
|
|
float SECONDS = 3;
|
|
|
|
long endTime = (long) ((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) + 1000.0 * SECONDS);
|
|
System.Collections.IList excs = (System.Collections.IList) System.Collections.ArrayList.Synchronized(new System.Collections.ArrayList(new System.Collections.ArrayList()));
|
|
|
|
ThreadClass[] threads = new ThreadClass[NUM_THREAD];
|
|
for (int i = 0; i < NUM_THREAD; i++)
|
|
{
|
|
threads[i] = new AnonymousClassThread1(endTime, writer, excs, this);
|
|
threads[i].IsBackground = true;
|
|
threads[i].Start();
|
|
}
|
|
|
|
int sum = 0;
|
|
while ((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) < endTime)
|
|
{
|
|
IndexReader r2 = r.Reopen();
|
|
if (r2 != r)
|
|
{
|
|
r.Close();
|
|
r = r2;
|
|
}
|
|
Query q = new TermQuery(new Term("indexname", "test"));
|
|
sum += new IndexSearcher(r).Search(q, 10).TotalHits;
|
|
}
|
|
|
|
for (int i = 0; i < NUM_THREAD; i++)
|
|
{
|
|
threads[i].Join();
|
|
}
|
|
Assert.IsTrue(sum > 0);
|
|
|
|
Assert.AreEqual(0, excs.Count);
|
|
writer.Close();
|
|
|
|
_TestUtil.CheckIndex(dir1);
|
|
r.Close();
|
|
dir1.Close();
|
|
}
|
|
|
|
[Test]
|
|
public virtual void TestExpungeDeletes()
|
|
{
|
|
Directory dir = new MockRAMDirectory();
|
|
IndexWriter w = new IndexWriter(dir, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.LIMITED);
|
|
Document doc = new Document();
|
|
doc.Add(new Field("field", "a b c", Field.Store.NO, Field.Index.ANALYZED));
|
|
Field id = new Field("id", "", Field.Store.NO, Field.Index.NOT_ANALYZED);
|
|
doc.Add(id);
|
|
id.SetValue("0");
|
|
w.AddDocument(doc);
|
|
id.SetValue("1");
|
|
w.AddDocument(doc);
|
|
w.DeleteDocuments(new Term("id", "0"));
|
|
|
|
IndexReader r = w.GetReader();
|
|
w.ExpungeDeletes();
|
|
w.Close();
|
|
r.Close();
|
|
r = IndexReader.Open(dir, true);
|
|
Assert.AreEqual(1, r.NumDocs());
|
|
Assert.IsFalse(r.HasDeletions);
|
|
r.Close();
|
|
dir.Close();
|
|
}
|
|
|
|
[Test]
|
|
public void TestDeletesNumDocs()
|
|
{
|
|
Directory dir = new MockRAMDirectory();
|
|
IndexWriter w = new IndexWriter(dir, new WhitespaceAnalyzer(),
|
|
IndexWriter.MaxFieldLength.LIMITED);
|
|
Document doc = new Document();
|
|
doc.Add(new Field("field", "a b c", Field.Store.NO, Field.Index.ANALYZED));
|
|
Field id = new Field("id", "", Field.Store.NO, Field.Index.NOT_ANALYZED);
|
|
doc.Add(id);
|
|
id.SetValue("0");
|
|
w.AddDocument(doc);
|
|
id.SetValue("1");
|
|
w.AddDocument(doc);
|
|
IndexReader r = w.GetReader();
|
|
Assert.AreEqual(2, r.NumDocs());
|
|
r.Close();
|
|
|
|
w.DeleteDocuments(new Term("id", "0"));
|
|
r = w.GetReader();
|
|
Assert.AreEqual(1, r.NumDocs());
|
|
r.Close();
|
|
|
|
w.DeleteDocuments(new Term("id", "1"));
|
|
r = w.GetReader();
|
|
Assert.AreEqual(0, r.NumDocs());
|
|
r.Close();
|
|
|
|
w.Close();
|
|
dir.Close();
|
|
}
|
|
|
|
class AnonymousIndexReaderWarmer : IndexWriter.IndexReaderWarmer
|
|
{
|
|
public override void Warm(IndexReader r)
|
|
{
|
|
IndexSearcher s = new IndexSearcher(r);
|
|
Lucene.Net.Search.TopDocs hits = s.Search(new TermQuery(new Term("foo", "bar")), 10);
|
|
Assert.AreEqual(20, hits.TotalHits);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void TestSegmentWarmer()
|
|
{
|
|
Directory dir = new MockRAMDirectory();
|
|
IndexWriter w = new IndexWriter(dir, new WhitespaceAnalyzer(), IndexWriter.MaxFieldLength.UNLIMITED);
|
|
w.SetMaxBufferedDocs(2);
|
|
w.GetReader().Close();
|
|
w.MergedSegmentWarmer = new AnonymousIndexReaderWarmer();
|
|
|
|
Document doc = new Document();
|
|
doc.Add(new Field("foo", "bar", Field.Store.YES, Field.Index.NOT_ANALYZED));
|
|
for (int i = 0; i < 20; i++)
|
|
{
|
|
w.AddDocument(doc);
|
|
}
|
|
w.WaitForMerges();
|
|
w.Close();
|
|
dir.Close();
|
|
}
|
|
}
|
|
} |