blob: e8da46f7e57421cb16203d45fb2881ce97287a5c [file] [log] [blame]
using J2N.Collections.Generic.Extensions;
using J2N.Threading;
using J2N.Threading.Atomic;
using Lucene.Net.Analysis;
using Lucene.Net.Attributes;
using Lucene.Net.Documents;
using Lucene.Net.Index.Extensions;
using Lucene.Net.Store;
using Lucene.Net.Support;
using Lucene.Net.Support.IO;
using Lucene.Net.Util;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
using Console = Lucene.Net.Support.SystemConsole;
namespace Lucene.Net.Index
{
/*
* 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 Analyzer = Lucene.Net.Analysis.Analyzer;
using Directory = Lucene.Net.Store.Directory;
using Document = Documents.Document;
using Field = Field;
using FieldType = FieldType;
using IndexSearcher = Lucene.Net.Search.IndexSearcher;
using IOUtils = Lucene.Net.Util.IOUtils;
using LuceneTestCase = Lucene.Net.Util.LuceneTestCase;
using MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer;
using MockDirectoryWrapper = Lucene.Net.Store.MockDirectoryWrapper;
using MockTokenizer = Lucene.Net.Analysis.MockTokenizer;
using NumericDocValuesField = NumericDocValuesField;
using RAMDirectory = Lucene.Net.Store.RAMDirectory;
using ScoreDoc = Lucene.Net.Search.ScoreDoc;
using StringField = StringField;
using TermQuery = Lucene.Net.Search.TermQuery;
using TestUtil = Lucene.Net.Util.TestUtil;
[TestFixture]
public class TestIndexWriterDelete : LuceneTestCase
{
// test the simple case
[Test]
public virtual void TestSimpleCase()
{
string[] keywords = new string[] { "1", "2" };
string[] unindexed = new string[] { "Netherlands", "Italy" };
string[] unstored = new string[] { "Amsterdam has lots of bridges", "Venice has lots of canals" };
string[] text = new string[] { "Amsterdam", "Venice" };
Directory dir = NewDirectory();
IndexWriter modifier = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random, MockTokenizer.WHITESPACE, false)).SetMaxBufferedDeleteTerms(1));
FieldType custom1 = new FieldType();
custom1.IsStored = true;
for (int i = 0; i < keywords.Length; i++)
{
Document doc = new Document();
doc.Add(NewStringField("id", keywords[i], Field.Store.YES));
doc.Add(NewField("country", unindexed[i], custom1));
doc.Add(NewTextField("contents", unstored[i], Field.Store.NO));
doc.Add(NewTextField("city", text[i], Field.Store.YES));
modifier.AddDocument(doc);
}
modifier.ForceMerge(1);
modifier.Commit();
Term term = new Term("city", "Amsterdam");
int hitCount = GetHitCount(dir, term);
Assert.AreEqual(1, hitCount);
if (VERBOSE)
{
Console.WriteLine("\nTEST: now delete by term=" + term);
}
modifier.DeleteDocuments(term);
modifier.Commit();
if (VERBOSE)
{
Console.WriteLine("\nTEST: now getHitCount");
}
hitCount = GetHitCount(dir, term);
Assert.AreEqual(0, hitCount);
modifier.Dispose();
dir.Dispose();
}
// test when delete terms only apply to disk segments
[Test]
public virtual void TestNonRAMDelete()
{
Directory dir = NewDirectory();
IndexWriter modifier = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random, MockTokenizer.WHITESPACE, false)).SetMaxBufferedDocs(2).SetMaxBufferedDeleteTerms(2));
int id = 0;
int value = 100;
for (int i = 0; i < 7; i++)
{
AddDoc(modifier, ++id, value);
}
modifier.Commit();
Assert.AreEqual(0, modifier.NumBufferedDocuments);
Assert.IsTrue(0 < modifier.SegmentCount);
modifier.Commit();
IndexReader reader = DirectoryReader.Open(dir);
Assert.AreEqual(7, reader.NumDocs);
reader.Dispose();
modifier.DeleteDocuments(new Term("value", Convert.ToString(value)));
modifier.Commit();
reader = DirectoryReader.Open(dir);
Assert.AreEqual(0, reader.NumDocs);
reader.Dispose();
modifier.Dispose();
dir.Dispose();
}
[Test]
public virtual void TestMaxBufferedDeletes()
{
Directory dir = NewDirectory();
IndexWriter writer = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random, MockTokenizer.WHITESPACE, false)).SetMaxBufferedDeleteTerms(1));
writer.AddDocument(new Document());
writer.DeleteDocuments(new Term("foobar", "1"));
writer.DeleteDocuments(new Term("foobar", "1"));
writer.DeleteDocuments(new Term("foobar", "1"));
Assert.AreEqual(3, writer.FlushDeletesCount);
writer.Dispose();
dir.Dispose();
}
// test when delete terms only apply to ram segments
[Test]
public virtual void TestRAMDeletes()
{
for (int t = 0; t < 2; t++)
{
if (VERBOSE)
{
Console.WriteLine("TEST: t=" + t);
}
Directory dir = NewDirectory();
IndexWriter modifier = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random, MockTokenizer.WHITESPACE, false)).SetMaxBufferedDocs(4).SetMaxBufferedDeleteTerms(4));
int id = 0;
int value = 100;
AddDoc(modifier, ++id, value);
if (0 == t)
{
modifier.DeleteDocuments(new Term("value", Convert.ToString(value)));
}
else
{
modifier.DeleteDocuments(new TermQuery(new Term("value", Convert.ToString(value))));
}
AddDoc(modifier, ++id, value);
if (0 == t)
{
modifier.DeleteDocuments(new Term("value", Convert.ToString(value)));
Assert.AreEqual(2, modifier.NumBufferedDeleteTerms);
Assert.AreEqual(1, modifier.BufferedDeleteTermsSize);
}
else
{
modifier.DeleteDocuments(new TermQuery(new Term("value", Convert.ToString(value))));
}
AddDoc(modifier, ++id, value);
Assert.AreEqual(0, modifier.SegmentCount);
modifier.Commit();
IndexReader reader = DirectoryReader.Open(dir);
Assert.AreEqual(1, reader.NumDocs);
int hitCount = GetHitCount(dir, new Term("id", Convert.ToString(id)));
Assert.AreEqual(1, hitCount);
reader.Dispose();
modifier.Dispose();
dir.Dispose();
}
}
// test when delete terms apply to both disk and ram segments
[Test]
public virtual void TestBothDeletes()
{
Directory dir = NewDirectory();
IndexWriter modifier = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random, MockTokenizer.WHITESPACE, false)).SetMaxBufferedDocs(100).SetMaxBufferedDeleteTerms(100));
int id = 0;
int value = 100;
for (int i = 0; i < 5; i++)
{
AddDoc(modifier, ++id, value);
}
value = 200;
for (int i = 0; i < 5; i++)
{
AddDoc(modifier, ++id, value);
}
modifier.Commit();
for (int i = 0; i < 5; i++)
{
AddDoc(modifier, ++id, value);
}
modifier.DeleteDocuments(new Term("value", Convert.ToString(value)));
modifier.Commit();
IndexReader reader = DirectoryReader.Open(dir);
Assert.AreEqual(5, reader.NumDocs);
modifier.Dispose();
reader.Dispose();
dir.Dispose();
}
// test that batched delete terms are flushed together
[Test]
public virtual void TestBatchDeletes()
{
Directory dir = NewDirectory();
IndexWriter modifier = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random, MockTokenizer.WHITESPACE, false)).SetMaxBufferedDocs(2).SetMaxBufferedDeleteTerms(2));
int id = 0;
int value = 100;
for (int i = 0; i < 7; i++)
{
AddDoc(modifier, ++id, value);
}
modifier.Commit();
IndexReader reader = DirectoryReader.Open(dir);
Assert.AreEqual(7, reader.NumDocs);
reader.Dispose();
id = 0;
modifier.DeleteDocuments(new Term("id", Convert.ToString(++id)));
modifier.DeleteDocuments(new Term("id", Convert.ToString(++id)));
modifier.Commit();
reader = DirectoryReader.Open(dir);
Assert.AreEqual(5, reader.NumDocs);
reader.Dispose();
Term[] terms = new Term[3];
for (int i = 0; i < terms.Length; i++)
{
terms[i] = new Term("id", Convert.ToString(++id));
}
modifier.DeleteDocuments(terms);
modifier.Commit();
reader = DirectoryReader.Open(dir);
Assert.AreEqual(2, reader.NumDocs);
reader.Dispose();
modifier.Dispose();
dir.Dispose();
}
// test deleteAll()
[Test]
public virtual void TestDeleteAll()
{
Directory dir = NewDirectory();
IndexWriter modifier = new IndexWriter(dir, (IndexWriterConfig)NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random, MockTokenizer.WHITESPACE, false)).SetMaxBufferedDocs(2).SetMaxBufferedDeleteTerms(2));
int id = 0;
int value = 100;
for (int i = 0; i < 7; i++)
{
AddDoc(modifier, ++id, value);
}
modifier.Commit();
IndexReader reader = DirectoryReader.Open(dir);
Assert.AreEqual(7, reader.NumDocs);
reader.Dispose();
// Add 1 doc (so we will have something buffered)
AddDoc(modifier, 99, value);
// Delete all
modifier.DeleteAll();
// Delete all shouldn't be on disk yet
reader = DirectoryReader.Open(dir);
Assert.AreEqual(7, reader.NumDocs);
reader.Dispose();
// Add a doc and update a doc (after the deleteAll, before the commit)
AddDoc(modifier, 101, value);
UpdateDoc(modifier, 102, value);
// commit the delete all
modifier.Commit();
// Validate there are no docs left
reader = DirectoryReader.Open(dir);
Assert.AreEqual(2, reader.NumDocs);
reader.Dispose();
modifier.Dispose();
dir.Dispose();
}
[Test]
public virtual void TestDeleteAllNoDeadLock()
{
Directory dir = NewDirectory();
RandomIndexWriter modifier = new RandomIndexWriter(
#if FEATURE_INSTANCE_TESTDATA_INITIALIZATION
this,
#endif
Random, dir);
int numThreads = AtLeast(2);
ThreadJob[] threads = new ThreadJob[numThreads];
CountdownEvent latch = new CountdownEvent(1);
CountdownEvent doneLatch = new CountdownEvent(numThreads);
for (int i = 0; i < numThreads; i++)
{
int offset = i;
threads[i] = new ThreadAnonymousInnerClassHelper(this, modifier, latch, doneLatch, offset);
threads[i].Start();
}
latch.Signal();
//Wait for 1 millisecond
while (!doneLatch.Wait(new TimeSpan(0, 0, 0, 0, 1)))
{
modifier.DeleteAll();
if (VERBOSE)
{
Console.WriteLine("del all");
}
}
modifier.DeleteAll();
foreach (ThreadJob thread in threads)
{
thread.Join();
}
modifier.Dispose();
DirectoryReader reader = DirectoryReader.Open(dir);
Assert.AreEqual(reader.MaxDoc, 0);
Assert.AreEqual(reader.NumDocs, 0);
Assert.AreEqual(reader.NumDeletedDocs, 0);
reader.Dispose();
dir.Dispose();
}
private class ThreadAnonymousInnerClassHelper : ThreadJob
{
private readonly TestIndexWriterDelete OuterInstance;
private RandomIndexWriter Modifier;
private CountdownEvent Latch;
private CountdownEvent DoneLatch;
private int Offset;
public ThreadAnonymousInnerClassHelper(TestIndexWriterDelete outerInstance, RandomIndexWriter modifier, CountdownEvent latch, CountdownEvent doneLatch, int offset)
{
this.OuterInstance = outerInstance;
this.Modifier = modifier;
this.Latch = latch;
this.DoneLatch = doneLatch;
this.Offset = offset;
}
public override void Run()
{
int id = Offset * 1000;
int value = 100;
try
{
Latch.Wait();
for (int j = 0; j < 1000; j++)
{
Document doc = new Document();
doc.Add(NewTextField("content", "aaa", Field.Store.NO));
doc.Add(NewStringField("id", Convert.ToString(id++), Field.Store.YES));
doc.Add(NewStringField("value", Convert.ToString(value), Field.Store.NO));
if (DefaultCodecSupportsDocValues)
{
doc.Add(new NumericDocValuesField("dv", value));
}
Modifier.AddDocument(doc);
if (VERBOSE)
{
Console.WriteLine("\tThread[" + Offset + "]: add doc: " + id);
}
}
}
catch (Exception e)
{
throw new Exception(e.Message, e);
}
finally
{
DoneLatch.Signal();
if (VERBOSE)
{
Console.WriteLine("\tThread[" + Offset + "]: done indexing");
}
}
}
}
// test rollback of deleteAll()
[Test]
public virtual void TestDeleteAllRollback()
{
Directory dir = NewDirectory();
IndexWriter modifier = new IndexWriter(dir, (IndexWriterConfig)NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random, MockTokenizer.WHITESPACE, false)).SetMaxBufferedDocs(2).SetMaxBufferedDeleteTerms(2));
int id = 0;
int value = 100;
for (int i = 0; i < 7; i++)
{
AddDoc(modifier, ++id, value);
}
modifier.Commit();
AddDoc(modifier, ++id, value);
IndexReader reader = DirectoryReader.Open(dir);
Assert.AreEqual(7, reader.NumDocs);
reader.Dispose();
// Delete all
modifier.DeleteAll();
// Roll it back
modifier.Rollback();
modifier.Dispose();
// Validate that the docs are still there
reader = DirectoryReader.Open(dir);
Assert.AreEqual(7, reader.NumDocs);
reader.Dispose();
dir.Dispose();
}
// test deleteAll() w/ near real-time reader
[Test]
public virtual void TestDeleteAllNRT()
{
Directory dir = NewDirectory();
IndexWriter modifier = new IndexWriter(dir, (IndexWriterConfig)NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random, MockTokenizer.WHITESPACE, false)).SetMaxBufferedDocs(2).SetMaxBufferedDeleteTerms(2));
int id = 0;
int value = 100;
for (int i = 0; i < 7; i++)
{
AddDoc(modifier, ++id, value);
}
modifier.Commit();
IndexReader reader = modifier.GetReader();
Assert.AreEqual(7, reader.NumDocs);
reader.Dispose();
AddDoc(modifier, ++id, value);
AddDoc(modifier, ++id, value);
// Delete all
modifier.DeleteAll();
reader = modifier.GetReader();
Assert.AreEqual(0, reader.NumDocs);
reader.Dispose();
// Roll it back
modifier.Rollback();
modifier.Dispose();
// Validate that the docs are still there
reader = DirectoryReader.Open(dir);
Assert.AreEqual(7, reader.NumDocs);
reader.Dispose();
dir.Dispose();
}
private void UpdateDoc(IndexWriter modifier, int id, int value)
{
Document doc = new Document();
doc.Add(NewTextField("content", "aaa", Field.Store.NO));
doc.Add(NewStringField("id", Convert.ToString(id), Field.Store.YES));
doc.Add(NewStringField("value", Convert.ToString(value), Field.Store.NO));
if (DefaultCodecSupportsDocValues)
{
doc.Add(new NumericDocValuesField("dv", value));
}
modifier.UpdateDocument(new Term("id", Convert.ToString(id)), doc);
}
private void AddDoc(IndexWriter modifier, int id, int value)
{
Document doc = new Document();
doc.Add(NewTextField("content", "aaa", Field.Store.NO));
doc.Add(NewStringField("id", Convert.ToString(id), Field.Store.YES));
doc.Add(NewStringField("value", Convert.ToString(value), Field.Store.NO));
if (DefaultCodecSupportsDocValues)
{
doc.Add(new NumericDocValuesField("dv", value));
}
modifier.AddDocument(doc);
}
private int GetHitCount(Directory dir, Term term)
{
IndexReader reader = DirectoryReader.Open(dir);
IndexSearcher searcher = NewSearcher(reader);
int hitCount = searcher.Search(new TermQuery(term), null, 1000).TotalHits;
reader.Dispose();
return hitCount;
}
[Test]
public virtual void TestDeletesOnDiskFull(
[ValueSource(typeof(ConcurrentMergeSchedulerFactories), "Values")]Func<IConcurrentMergeScheduler> newScheduler)
{
DoTestOperationsOnDiskFull(newScheduler, false);
}
[Test]
public virtual void TestUpdatesOnDiskFull(
[ValueSource(typeof(ConcurrentMergeSchedulerFactories), "Values")]Func<IConcurrentMergeScheduler> newScheduler)
{
DoTestOperationsOnDiskFull(newScheduler, true);
}
/// <summary>
/// Make sure if modifier tries to commit but hits disk full that modifier
/// remains consistent and usable. Similar to TestIndexReader.testDiskFull().
/// </summary>
private void DoTestOperationsOnDiskFull(Func<IConcurrentMergeScheduler> newScheduler, bool updates)
{
Term searchTerm = new Term("content", "aaa");
int START_COUNT = 157;
int END_COUNT = 144;
// First build up a starting index:
MockDirectoryWrapper startDir = NewMockDirectory();
// TODO: find the resource leak that only occurs sometimes here.
startDir.NoDeleteOpenFile = false;
IndexWriter writer = new IndexWriter(startDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random, MockTokenizer.WHITESPACE, false)));
for (int i = 0; i < 157; i++)
{
Document d = new Document();
d.Add(NewStringField("id", Convert.ToString(i), Field.Store.YES));
d.Add(NewTextField("content", "aaa " + i, Field.Store.NO));
if (DefaultCodecSupportsDocValues)
{
d.Add(new NumericDocValuesField("dv", i));
}
writer.AddDocument(d);
}
writer.Dispose();
long diskUsage = startDir.GetSizeInBytes();
long diskFree = diskUsage + 10;
IOException err = null;
bool done = false;
// Iterate w/ ever increasing free disk space:
while (!done)
{
if (VERBOSE)
{
Console.WriteLine("TEST: cycle");
}
MockDirectoryWrapper dir = new MockDirectoryWrapper(Random, new RAMDirectory(startDir, NewIOContext(Random)));
dir.PreventDoubleWrite = false;
dir.AllowRandomFileNotFoundException = false;
var config = NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random, MockTokenizer.WHITESPACE, false))
.SetMaxBufferedDocs(1000)
.SetMaxBufferedDeleteTerms(1000)
.SetMergeScheduler(newScheduler());
IConcurrentMergeScheduler scheduler = config.MergeScheduler as IConcurrentMergeScheduler;
if (scheduler != null)
{
scheduler.SetSuppressExceptions();
}
IndexWriter modifier = new IndexWriter(dir, config);
// For each disk size, first try to commit against
// dir that will hit random IOExceptions & disk
// full; after, give it infinite disk space & turn
// off random IOExceptions & retry w/ same reader:
bool success = false;
for (int x = 0; x < 2; x++)
{
if (VERBOSE)
{
Console.WriteLine("TEST: x=" + x);
}
double rate = 0.1;
double diskRatio = ((double)diskFree) / diskUsage;
long thisDiskFree;
string testName;
if (0 == x)
{
thisDiskFree = diskFree;
if (diskRatio >= 2.0)
{
rate /= 2;
}
if (diskRatio >= 4.0)
{
rate /= 2;
}
if (diskRatio >= 6.0)
{
rate = 0.0;
}
if (VERBOSE)
{
Console.WriteLine("\ncycle: " + diskFree + " bytes");
}
testName = "disk full during reader.Dispose() @ " + thisDiskFree + " bytes";
dir.RandomIOExceptionRateOnOpen = Random.NextDouble() * 0.01;
}
else
{
thisDiskFree = 0;
rate = 0.0;
if (VERBOSE)
{
Console.WriteLine("\ncycle: same writer: unlimited disk space");
}
testName = "reader re-use after disk full";
dir.RandomIOExceptionRateOnOpen = 0.0;
}
dir.MaxSizeInBytes = thisDiskFree;
dir.RandomIOExceptionRate = rate;
try
{
if (0 == x)
{
int docId = 12;
for (int i = 0; i < 13; i++)
{
if (updates)
{
Document d = new Document();
d.Add(NewStringField("id", Convert.ToString(i), Field.Store.YES));
d.Add(NewTextField("content", "bbb " + i, Field.Store.NO));
if (DefaultCodecSupportsDocValues)
{
d.Add(new NumericDocValuesField("dv", i));
}
modifier.UpdateDocument(new Term("id", Convert.ToString(docId)), d);
} // deletes
else
{
modifier.DeleteDocuments(new Term("id", Convert.ToString(docId)));
// modifier.setNorm(docId, "contents", (float)2.0);
}
docId += 12;
}
}
modifier.Dispose();
success = true;
if (0 == x)
{
done = true;
}
}
catch (IOException e)
{
if (VERBOSE)
{
Console.WriteLine(" hit IOException: " + e);
Console.WriteLine(e.StackTrace);
}
err = e;
if (1 == x)
{
Console.WriteLine(e.ToString());
Console.Write(e.StackTrace);
Assert.Fail(testName + " hit IOException after disk space was freed up");
}
}
// prevent throwing a random exception here!!
double randomIOExceptionRate = dir.RandomIOExceptionRate;
long maxSizeInBytes = dir.MaxSizeInBytes;
dir.RandomIOExceptionRate = 0.0;
dir.RandomIOExceptionRateOnOpen = 0.0;
dir.MaxSizeInBytes = 0;
if (!success)
{
// Must force the close else the writer can have
// open files which cause exc in MockRAMDir.close
if (VERBOSE)
{
Console.WriteLine("TEST: now rollback");
}
modifier.Rollback();
}
// If the close() succeeded, make sure there are
// no unreferenced files.
if (success)
{
TestUtil.CheckIndex(dir);
TestIndexWriter.AssertNoUnreferencedFiles(dir, "after writer.close");
}
dir.RandomIOExceptionRate = randomIOExceptionRate;
dir.MaxSizeInBytes = maxSizeInBytes;
// Finally, verify index is not corrupt, and, if
// we succeeded, we see all docs changed, and if
// we failed, we see either all docs or no docs
// changed (transactional semantics):
IndexReader newReader = null;
try
{
newReader = DirectoryReader.Open(dir);
}
catch (IOException e)
{
Console.WriteLine(e.ToString());
Console.Write(e.StackTrace);
Assert.Fail(testName + ":exception when creating IndexReader after disk full during close: " + e);
}
IndexSearcher searcher = NewSearcher(newReader);
ScoreDoc[] hits = null;
try
{
hits = searcher.Search(new TermQuery(searchTerm), null, 1000).ScoreDocs;
}
catch (IOException e)
{
Console.WriteLine(e.ToString());
Console.Write(e.StackTrace);
Assert.Fail(testName + ": exception when searching: " + e);
}
int result2 = hits.Length;
if (success)
{
if (x == 0 && result2 != END_COUNT)
{
Assert.Fail(testName + ": method did not throw exception but hits.Length for search on term 'aaa' is " + result2 + " instead of expected " + END_COUNT);
}
else if (x == 1 && result2 != START_COUNT && result2 != END_COUNT)
{
// It's possible that the first exception was
// "recoverable" wrt pending deletes, in which
// case the pending deletes are retained and
// then re-flushing (with plenty of disk
// space) will succeed in flushing the
// deletes:
Assert.Fail(testName + ": method did not throw exception but hits.Length for search on term 'aaa' is " + result2 + " instead of expected " + START_COUNT + " or " + END_COUNT);
}
}
else
{
// On hitting exception we still may have added
// all docs:
if (result2 != START_COUNT && result2 != END_COUNT)
{
Console.WriteLine(err.ToString());
Console.Write(err.StackTrace);
Assert.Fail(testName + ": method did throw exception but hits.Length for search on term 'aaa' is " + result2 + " instead of expected " + START_COUNT + " or " + END_COUNT);
}
}
newReader.Dispose();
if (result2 == END_COUNT)
{
break;
}
}
dir.Dispose();
modifier.Dispose();
// Try again with 10 more bytes of free space:
diskFree += 10;
}
startDir.Dispose();
}
// this test tests that buffered deletes are cleared when
// an Exception is hit during flush.
[Test]
public virtual void TestErrorAfterApplyDeletes()
{
Failure failure = new FailureAnonymousInnerClassHelper(this);
// create a couple of files
string[] keywords = new string[] { "1", "2" };
string[] unindexed = new string[] { "Netherlands", "Italy" };
string[] unstored = new string[] { "Amsterdam has lots of bridges", "Venice has lots of canals" };
string[] text = new string[] { "Amsterdam", "Venice" };
MockDirectoryWrapper dir = NewMockDirectory();
IndexWriter modifier = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random, MockTokenizer.WHITESPACE, false)).SetMaxBufferedDeleteTerms(2).SetReaderPooling(false).SetMergePolicy(NewLogMergePolicy()));
MergePolicy lmp = modifier.Config.MergePolicy;
lmp.NoCFSRatio = 1.0;
dir.FailOn(failure.Reset());
FieldType custom1 = new FieldType();
custom1.IsStored = true;
for (int i = 0; i < keywords.Length; i++)
{
Document doc = new Document();
doc.Add(NewStringField("id", keywords[i], Field.Store.YES));
doc.Add(NewField("country", unindexed[i], custom1));
doc.Add(NewTextField("contents", unstored[i], Field.Store.NO));
doc.Add(NewTextField("city", text[i], Field.Store.YES));
modifier.AddDocument(doc);
}
// flush (and commit if ac)
if (VERBOSE)
{
Console.WriteLine("TEST: now full merge");
}
modifier.ForceMerge(1);
if (VERBOSE)
{
Console.WriteLine("TEST: now commit");
}
modifier.Commit();
// one of the two files hits
Term term = new Term("city", "Amsterdam");
int hitCount = GetHitCount(dir, term);
Assert.AreEqual(1, hitCount);
// open the writer again (closed above)
// delete the doc
// max buf del terms is two, so this is buffered
if (VERBOSE)
{
Console.WriteLine("TEST: delete term=" + term);
}
modifier.DeleteDocuments(term);
// add a doc (needed for the !ac case; see below)
// doc remains buffered
if (VERBOSE)
{
Console.WriteLine("TEST: add empty doc");
}
Document doc_ = new Document();
modifier.AddDocument(doc_);
// commit the changes, the buffered deletes, and the new doc
// The failure object will fail on the first write after the del
// file gets created when processing the buffered delete
// in the ac case, this will be when writing the new segments
// files so we really don't need the new doc, but it's harmless
// a new segments file won't be created but in this
// case, creation of the cfs file happens next so we
// need the doc (to test that it's okay that we don't
// lose deletes if failing while creating the cfs file)
bool failed = false;
try
{
if (VERBOSE)
{
Console.WriteLine("TEST: now commit for failure");
}
modifier.Commit();
}
#pragma warning disable 168
catch (IOException ioe)
#pragma warning restore 168
{
// expected
failed = true;
}
Assert.IsTrue(failed);
// The commit above failed, so we need to retry it (which will
// succeed, because the failure is a one-shot)
modifier.Commit();
hitCount = GetHitCount(dir, term);
// Make sure the delete was successfully flushed:
Assert.AreEqual(0, hitCount);
modifier.Dispose();
dir.Dispose();
}
private class FailureAnonymousInnerClassHelper : Failure
{
private readonly TestIndexWriterDelete OuterInstance;
public FailureAnonymousInnerClassHelper(TestIndexWriterDelete outerInstance)
{
this.OuterInstance = outerInstance;
sawMaybe = false;
failed = false;
}
internal bool sawMaybe;
internal bool failed;
internal Thread thread;
public override Failure Reset()
{
thread = Thread.CurrentThread;
sawMaybe = false;
failed = false;
return this;
}
public override void Eval(MockDirectoryWrapper dir)
{
if (Thread.CurrentThread != thread)
{
// don't fail during merging
return;
}
if (sawMaybe && !failed)
{
// LUCENENET specific: for these to work in release mode, we have added [MethodImpl(MethodImplOptions.NoInlining)]
// to each possible target of the StackTraceHelper. If these change, so must the attribute on the target methods.
bool seen =
StackTraceHelper.DoesStackTraceContainMethod("ApplyDeletesAndUpdates") ||
StackTraceHelper.DoesStackTraceContainMethod("SlowFileExists");
if (!seen)
{
// Only fail once we are no longer in applyDeletes
failed = true;
if (VERBOSE)
{
Console.WriteLine("TEST: mock failure: now fail");
Console.WriteLine(Environment.StackTrace);
}
throw new IOException("fail after applyDeletes");
}
}
if (!failed)
{
// LUCENENET specific: for these to work in release mode, we have added [MethodImpl(MethodImplOptions.NoInlining)]
// to each possible target of the StackTraceHelper. If these change, so must the attribute on the target methods.
if (StackTraceHelper.DoesStackTraceContainMethod("ApplyDeletesAndUpdates"))
{
if (VERBOSE)
{
Console.WriteLine("TEST: mock failure: saw applyDeletes");
Console.WriteLine(Environment.StackTrace);
}
sawMaybe = true;
}
}
}
}
// this test tests that the files created by the docs writer before
// a segment is written are cleaned up if there's an i/o error
[Test]
public virtual void TestErrorInDocsWriterAdd()
{
Failure failure = new FailureAnonymousInnerClassHelper2(this);
// create a couple of files
string[] keywords = new string[] { "1", "2" };
string[] unindexed = new string[] { "Netherlands", "Italy" };
string[] unstored = new string[] { "Amsterdam has lots of bridges", "Venice has lots of canals" };
string[] text = new string[] { "Amsterdam", "Venice" };
MockDirectoryWrapper dir = NewMockDirectory();
IndexWriter modifier = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random, MockTokenizer.WHITESPACE, false)));
modifier.Commit();
dir.FailOn(failure.Reset());
FieldType custom1 = new FieldType();
custom1.IsStored = true;
for (int i = 0; i < keywords.Length; i++)
{
Document doc = new Document();
doc.Add(NewStringField("id", keywords[i], Field.Store.YES));
doc.Add(NewField("country", unindexed[i], custom1));
doc.Add(NewTextField("contents", unstored[i], Field.Store.NO));
doc.Add(NewTextField("city", text[i], Field.Store.YES));
try
{
modifier.AddDocument(doc);
}
catch (IOException io)
{
if (VERBOSE)
{
Console.WriteLine("TEST: got expected exc:");
Console.WriteLine(io.StackTrace);
}
break;
}
}
modifier.Dispose();
TestIndexWriter.AssertNoUnreferencedFiles(dir, "docsWriter.abort() failed to delete unreferenced files");
dir.Dispose();
}
private class FailureAnonymousInnerClassHelper2 : Failure
{
private readonly TestIndexWriterDelete OuterInstance;
public FailureAnonymousInnerClassHelper2(TestIndexWriterDelete outerInstance)
{
this.OuterInstance = outerInstance;
failed = false;
}
internal bool failed;
public override Failure Reset()
{
failed = false;
return this;
}
public override void Eval(MockDirectoryWrapper dir)
{
if (!failed)
{
failed = true;
throw new IOException("fail in add doc");
}
}
}
[Test]
public virtual void TestDeleteNullQuery()
{
Directory dir = NewDirectory();
IndexWriter modifier = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random, MockTokenizer.WHITESPACE, false)));
for (int i = 0; i < 5; i++)
{
AddDoc(modifier, i, 2 * i);
}
modifier.DeleteDocuments(new TermQuery(new Term("nada", "nada")));
modifier.Commit();
Assert.AreEqual(5, modifier.NumDocs);
modifier.Dispose();
dir.Dispose();
}
[Test]
public virtual void TestDeleteAllSlowly()
{
Directory dir = NewDirectory();
RandomIndexWriter w = new RandomIndexWriter(
#if FEATURE_INSTANCE_TESTDATA_INITIALIZATION
this,
#endif
Random, dir);
int NUM_DOCS = AtLeast(1000);
IList<int?> ids = new List<int?>(NUM_DOCS);
for (int id = 0; id < NUM_DOCS; id++)
{
ids.Add(id);
}
ids.Shuffle();
foreach (int id in ids)
{
Document doc = new Document();
doc.Add(NewStringField("id", "" + id, Field.Store.NO));
w.AddDocument(doc);
}
ids.Shuffle();
int upto = 0;
while (upto < ids.Count)
{
int left = ids.Count - upto;
int inc = Math.Min(left, TestUtil.NextInt32(Random, 1, 20));
int limit = upto + inc;
while (upto < limit)
{
w.DeleteDocuments(new Term("id", "" + ids[upto++]));
}
IndexReader r = w.GetReader();
Assert.AreEqual(NUM_DOCS - upto, r.NumDocs);
r.Dispose();
}
w.Dispose();
dir.Dispose();
}
[Test]
public virtual void TestIndexingThenDeleting()
{
// TODO: move this test to its own class and just @SuppressCodecs?
// TODO: is it enough to just use newFSDirectory?
string fieldFormat = TestUtil.GetPostingsFormat("field");
AssumeFalse("this test cannot run with Memory codec", fieldFormat.Equals("Memory", StringComparison.Ordinal));
AssumeFalse("this test cannot run with SimpleText codec", fieldFormat.Equals("SimpleText", StringComparison.Ordinal));
AssumeFalse("this test cannot run with Direct codec", fieldFormat.Equals("Direct", StringComparison.Ordinal));
Random r = Random;
Directory dir = NewDirectory();
// note this test explicitly disables payloads
Analyzer analyzer = new AnalyzerAnonymousInnerClassHelper(this);
IndexWriter w = new IndexWriter(dir, (IndexWriterConfig)NewIndexWriterConfig(TEST_VERSION_CURRENT, analyzer).SetRAMBufferSizeMB(1.0).SetMaxBufferedDocs(IndexWriterConfig.DISABLE_AUTO_FLUSH).SetMaxBufferedDeleteTerms(IndexWriterConfig.DISABLE_AUTO_FLUSH));
Document doc = new Document();
doc.Add(NewTextField("field", "go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20", Field.Store.NO));
int num = AtLeast(3);
for (int iter = 0; iter < num; iter++)
{
int count = 0;
bool doIndexing = r.NextBoolean();
if (VERBOSE)
{
Console.WriteLine("TEST: iter doIndexing=" + doIndexing);
}
if (doIndexing)
{
// Add docs until a flush is triggered
int startFlushCount = w.FlushCount;
while (w.FlushCount == startFlushCount)
{
w.AddDocument(doc);
count++;
}
}
else
{
// Delete docs until a flush is triggered
int startFlushCount = w.FlushCount;
while (w.FlushCount == startFlushCount)
{
w.DeleteDocuments(new Term("foo", "" + count));
count++;
}
}
Assert.IsTrue(count > 2500, "flush happened too quickly during " + (doIndexing ? "indexing" : "deleting") + " count=" + count);
}
w.Dispose();
dir.Dispose();
}
private class AnalyzerAnonymousInnerClassHelper : Analyzer
{
private readonly TestIndexWriterDelete OuterInstance;
public AnalyzerAnonymousInnerClassHelper(TestIndexWriterDelete outerInstance)
{
this.OuterInstance = outerInstance;
}
protected internal override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
{
return new TokenStreamComponents(new MockTokenizer(reader, MockTokenizer.WHITESPACE, true));
}
}
// LUCENE-3340: make sure deletes that we don't apply
// during flush (ie are just pushed into the stream) are
// in fact later flushed due to their RAM usage:
[Test]
public virtual void TestFlushPushedDeletesByRAM()
{
Directory dir = NewDirectory();
// Cannot use RandomIndexWriter because we don't want to
// ever call commit() for this test:
// note: tiny rambuffer used, as with a 1MB buffer the test is too slow (flush @ 128,999)
IndexWriter w = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)).SetRAMBufferSizeMB(0.1f).SetMaxBufferedDocs(1000).SetMergePolicy(NoMergePolicy.NO_COMPOUND_FILES).SetReaderPooling(false));
int count = 0;
while (true)
{
Document doc = new Document();
doc.Add(new StringField("id", count + "", Field.Store.NO));
Term delTerm;
if (count == 1010)
{
// this is the only delete that applies
delTerm = new Term("id", "" + 0);
}
else
{
// These get buffered, taking up RAM, but delete
// nothing when applied:
delTerm = new Term("id", "x" + count);
}
w.UpdateDocument(delTerm, doc);
// Eventually segment 0 should get a del docs:
// TODO: fix this test
if (SlowFileExists(dir, "_0_1.del") || SlowFileExists(dir, "_0_1.liv"))
{
if (VERBOSE)
{
Console.WriteLine("TEST: deletes created @ count=" + count);
}
break;
}
count++;
// Today we applyDeletes @ count=21553; even if we make
// sizable improvements to RAM efficiency of buffered
// del term we're unlikely to go over 100K:
if (count > 100000)
{
Assert.Fail("delete's were not applied");
}
}
w.Dispose();
dir.Dispose();
}
// LUCENE-3340: make sure deletes that we don't apply
// during flush (ie are just pushed into the stream) are
// in fact later flushed due to their RAM usage:
[Test]
public virtual void TestFlushPushedDeletesByCount()
{
Directory dir = NewDirectory();
// Cannot use RandomIndexWriter because we don't want to
// ever call commit() for this test:
int flushAtDelCount = AtLeast(1020);
IndexWriter w = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)).SetMaxBufferedDeleteTerms(flushAtDelCount).SetMaxBufferedDocs(1000).SetRAMBufferSizeMB(IndexWriterConfig.DISABLE_AUTO_FLUSH).SetMergePolicy(NoMergePolicy.NO_COMPOUND_FILES).SetReaderPooling(false));
int count = 0;
while (true)
{
Document doc = new Document();
doc.Add(new StringField("id", count + "", Field.Store.NO));
Term delTerm;
if (count == 1010)
{
// this is the only delete that applies
delTerm = new Term("id", "" + 0);
}
else
{
// These get buffered, taking up RAM, but delete
// nothing when applied:
delTerm = new Term("id", "x" + count);
}
w.UpdateDocument(delTerm, doc);
// Eventually segment 0 should get a del docs:
// TODO: fix this test
if (SlowFileExists(dir, "_0_1.del") || SlowFileExists(dir, "_0_1.liv"))
{
break;
}
count++;
if (count > flushAtDelCount)
{
Assert.Fail("delete's were not applied at count=" + flushAtDelCount);
}
}
w.Dispose();
dir.Dispose();
}
// Make sure buffered (pushed) deletes don't use up so
// much RAM that it forces long tail of tiny segments:
[Test, LongRunningTest]
public virtual void TestApplyDeletesOnFlush()
{
Directory dir = NewDirectory();
// Cannot use RandomIndexWriter because we don't want to
// ever call commit() for this test:
AtomicInt32 docsInSegment = new AtomicInt32();
AtomicBoolean closing = new AtomicBoolean();
AtomicBoolean sawAfterFlush = new AtomicBoolean();
IndexWriter w = new IndexWriterAnonymousInnerClassHelper(this, dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)).SetRAMBufferSizeMB(0.5).SetMaxBufferedDocs(-1).SetMergePolicy(NoMergePolicy.NO_COMPOUND_FILES).SetReaderPooling(false), docsInSegment, closing, sawAfterFlush);
int id = 0;
while (true)
{
StringBuilder sb = new StringBuilder();
for (int termIDX = 0; termIDX < 100; termIDX++)
{
sb.Append(' ').Append(TestUtil.RandomRealisticUnicodeString(Random));
}
if (id == 500)
{
w.DeleteDocuments(new Term("id", "0"));
}
Document doc = new Document();
doc.Add(NewStringField("id", "" + id, Field.Store.NO));
doc.Add(NewTextField("body", sb.ToString(), Field.Store.NO));
w.UpdateDocument(new Term("id", "" + id), doc);
docsInSegment.IncrementAndGet();
// TODO: fix this test
if (SlowFileExists(dir, "_0_1.del") || SlowFileExists(dir, "_0_1.liv"))
{
if (VERBOSE)
{
Console.WriteLine("TEST: deletes created @ id=" + id);
}
break;
}
id++;
}
closing.Value = (true);
Assert.IsTrue(sawAfterFlush);
w.Dispose();
dir.Dispose();
}
private class IndexWriterAnonymousInnerClassHelper : IndexWriter
{
private readonly TestIndexWriterDelete OuterInstance;
private AtomicInt32 DocsInSegment;
private AtomicBoolean Closing;
private AtomicBoolean SawAfterFlush;
public IndexWriterAnonymousInnerClassHelper(TestIndexWriterDelete outerInstance, Directory dir, IndexWriterConfig setReaderPooling, AtomicInt32 docsInSegment, AtomicBoolean closing, AtomicBoolean sawAfterFlush)
: base(dir, setReaderPooling)
{
this.OuterInstance = outerInstance;
this.DocsInSegment = docsInSegment;
this.Closing = closing;
this.SawAfterFlush = sawAfterFlush;
}
protected override void DoAfterFlush()
{
Assert.IsTrue(Closing || DocsInSegment >= 7, "only " + DocsInSegment + " in segment");
DocsInSegment.Value = 0;
SawAfterFlush.Value = (true);
}
}
// LUCENE-4455
[Test]
public virtual void TestDeletesCheckIndexOutput()
{
Directory dir = NewDirectory();
IndexWriterConfig iwc = new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random));
iwc.SetMaxBufferedDocs(2);
IndexWriter w = new IndexWriter(dir, (IndexWriterConfig)iwc.Clone());
Document doc = new Document();
doc.Add(NewField("field", "0", StringField.TYPE_NOT_STORED));
w.AddDocument(doc);
doc = new Document();
doc.Add(NewField("field", "1", StringField.TYPE_NOT_STORED));
w.AddDocument(doc);
w.Commit();
Assert.AreEqual(1, w.SegmentCount);
w.DeleteDocuments(new Term("field", "0"));
w.Commit();
Assert.AreEqual(1, w.SegmentCount);
w.Dispose();
ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
//MemoryStream bos = new MemoryStream(1024);
CheckIndex checker = new CheckIndex(dir);
checker.InfoStream = new StreamWriter(bos, Encoding.UTF8);
CheckIndex.Status indexStatus = checker.DoCheckIndex(null);
Assert.IsTrue(indexStatus.Clean);
checker.FlushInfoStream();
string s = bos.ToString();
// Segment should have deletions:
Assert.IsTrue(s.Contains("has deletions"), "string was: " + s);
w = new IndexWriter(dir, (IndexWriterConfig)iwc.Clone());
w.ForceMerge(1);
w.Dispose();
bos = new ByteArrayOutputStream(1024);
checker.InfoStream = new StreamWriter(bos, Encoding.UTF8);
indexStatus = checker.DoCheckIndex(null);
Assert.IsTrue(indexStatus.Clean);
checker.FlushInfoStream();
s = bos.ToString();
Assert.IsFalse(s.Contains("has deletions"));
dir.Dispose();
}
[Test]
public virtual void TestTryDeleteDocument()
{
Directory d = NewDirectory();
IndexWriterConfig iwc = new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random));
IndexWriter w = new IndexWriter(d, iwc);
Document doc = new Document();
w.AddDocument(doc);
w.AddDocument(doc);
w.AddDocument(doc);
w.Dispose();
iwc = new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random));
iwc.SetOpenMode(OpenMode.APPEND);
w = new IndexWriter(d, iwc);
IndexReader r = DirectoryReader.Open(w, false);
Assert.IsTrue(w.TryDeleteDocument(r, 1));
Assert.IsTrue(w.TryDeleteDocument(r.Leaves[0].Reader, 0));
r.Dispose();
w.Dispose();
r = DirectoryReader.Open(d);
Assert.AreEqual(2, r.NumDeletedDocs);
Assert.IsNotNull(MultiFields.GetLiveDocs(r));
r.Dispose();
d.Dispose();
}
}
}