blob: fc77f3468f9fddd12d1885042dc6db5cdd769385 [file] [log] [blame]
using J2N.Threading;
using J2N.Threading.Atomic;
using Lucene.Net.Search;
using Lucene.Net.Support.Threading;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using Assert = Lucene.Net.TestFramework.Assert;
using JCG = J2N.Collections.Generic;
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 BytesRef = Lucene.Net.Util.BytesRef;
using DeleteSlice = Lucene.Net.Index.DocumentsWriterDeleteQueue.DeleteSlice;
using LuceneTestCase = Lucene.Net.Util.LuceneTestCase;
using TermQuery = Lucene.Net.Search.TermQuery;
/// <summary>
/// Unit test for <seealso cref="DocumentsWriterDeleteQueue"/>
/// </summary>
[TestFixture]
public class TestDocumentsWriterDeleteQueue : LuceneTestCase
{
[Test]
public virtual void TestUpdateDelteSlices()
{
DocumentsWriterDeleteQueue queue = new DocumentsWriterDeleteQueue();
int size = 200 + Random.Next(500) * RandomMultiplier;
int?[] ids = new int?[size];
for (int i = 0; i < ids.Length; i++)
{
ids[i] = Random.Next();
}
DeleteSlice slice1 = queue.NewSlice();
DeleteSlice slice2 = queue.NewSlice();
BufferedUpdates bd1 = new BufferedUpdates();
BufferedUpdates bd2 = new BufferedUpdates();
int last1 = 0;
int last2 = 0;
ISet<Term> uniqueValues = new JCG.HashSet<Term>();
for (int j = 0; j < ids.Length; j++)
{
int? i = ids[j];
// create an array here since we compare identity below against tailItem
Term[] term = new Term[] { new Term("id", i.ToString()) };
uniqueValues.Add(term[0]);
queue.AddDelete(term);
if (Random.Next(20) == 0 || j == ids.Length - 1)
{
queue.UpdateSlice(slice1);
Assert.IsTrue(slice1.IsTailItem(term));
slice1.Apply(bd1, j);
AssertAllBetween(last1, j, bd1, ids);
last1 = j + 1;
}
if (Random.Next(10) == 5 || j == ids.Length - 1)
{
queue.UpdateSlice(slice2);
Assert.IsTrue(slice2.IsTailItem(term));
slice2.Apply(bd2, j);
AssertAllBetween(last2, j, bd2, ids);
last2 = j + 1;
}
Assert.AreEqual(j + 1, queue.NumGlobalTermDeletes);
}
assertEquals(uniqueValues, bd1.terms.Keys);
assertEquals(uniqueValues, bd2.terms.Keys);
var frozenSet = new JCG.HashSet<Term>();
foreach (Term t in queue.FreezeGlobalBuffer(null).GetTermsEnumerable())
{
BytesRef bytesRef = new BytesRef();
bytesRef.CopyBytes(t.Bytes);
frozenSet.Add(new Term(t.Field, bytesRef));
}
assertEquals(uniqueValues, frozenSet);
assertEquals("num deletes must be 0 after freeze", 0, queue.NumGlobalTermDeletes);
}
// LUCENENET specific: Since the keys of a dictionary do not implement ISet<T>, we
// re-implement the comparison rather than reallocate the entire collection
internal void assertEquals(ISet<Term> expected, ICollection<Term> uniqueActual)
{
if (!SetEqualsCollection(expected, uniqueActual))
fail();
}
private bool SetEqualsCollection(ISet<Term> setA, ICollection<Term> setB)
{
if (ReferenceEquals(setA, setB))
return true;
if (setA is null)
return setB is null;
else if (setB is null)
return false;
if (setA.Count != setB.Count)
return false;
// same operation as containsAll()
foreach (var eB in setB)
{
bool contains = false;
foreach (var eA in setA)
{
if (eA.Equals(eB))
{
contains = true;
break;
}
}
if (!contains)
return false;
}
return true;
}
private void AssertAllBetween(int start, int end, BufferedUpdates deletes, int?[] ids)
{
for (int i = start; i <= end; i++)
{
Assert.AreEqual(Convert.ToInt32(end), deletes.terms[new Term("id", ids[i].ToString())]);
}
}
[Test]
public virtual void TestClear()
{
DocumentsWriterDeleteQueue queue = new DocumentsWriterDeleteQueue();
Assert.IsFalse(queue.AnyChanges());
queue.Clear();
Assert.IsFalse(queue.AnyChanges());
int size = 200 + Random.Next(500) * RandomMultiplier;
int termsSinceFreeze = 0;
int queriesSinceFreeze = 0;
for (int i = 0; i < size; i++)
{
Term term = new Term("id", "" + i);
if (Random.Next(10) == 0)
{
queue.AddDelete(new TermQuery(term));
queriesSinceFreeze++;
}
else
{
queue.AddDelete(term);
termsSinceFreeze++;
}
Assert.IsTrue(queue.AnyChanges());
if (Random.Next(10) == 0)
{
queue.Clear();
queue.TryApplyGlobalSlice();
Assert.IsFalse(queue.AnyChanges());
}
}
}
[Test]
public virtual void TestAnyChanges()
{
DocumentsWriterDeleteQueue queue = new DocumentsWriterDeleteQueue();
int size = 200 + Random.Next(500) * RandomMultiplier;
int termsSinceFreeze = 0;
int queriesSinceFreeze = 0;
for (int i = 0; i < size; i++)
{
Term term = new Term("id", "" + i);
if (Random.Next(10) == 0)
{
queue.AddDelete(new TermQuery(term));
queriesSinceFreeze++;
}
else
{
queue.AddDelete(term);
termsSinceFreeze++;
}
Assert.IsTrue(queue.AnyChanges());
if (Random.Next(5) == 0)
{
FrozenBufferedUpdates freezeGlobalBuffer = queue.FreezeGlobalBuffer(null);
Assert.AreEqual(termsSinceFreeze, freezeGlobalBuffer.termCount);
Assert.AreEqual(queriesSinceFreeze, ((Query[])freezeGlobalBuffer.queries.Clone()).Length);
queriesSinceFreeze = 0;
termsSinceFreeze = 0;
Assert.IsFalse(queue.AnyChanges());
}
}
}
[Test]
public virtual void TestPartiallyAppliedGlobalSlice()
{
DocumentsWriterDeleteQueue queue = new DocumentsWriterDeleteQueue();
System.Reflection.FieldInfo field = typeof(DocumentsWriterDeleteQueue).GetField("globalBufferLock",
BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance);
ReentrantLock @lock = (ReentrantLock)field.GetValue(queue);
@lock.Lock();
var t = new ThreadAnonymousClass(this, queue);
t.Start();
t.Join();
@lock.Unlock();
Assert.IsTrue(queue.AnyChanges(), "changes in del queue but not in slice yet");
queue.TryApplyGlobalSlice();
Assert.IsTrue(queue.AnyChanges(), "changes in global buffer");
FrozenBufferedUpdates freezeGlobalBuffer = queue.FreezeGlobalBuffer(null);
Assert.IsTrue(freezeGlobalBuffer.Any());
Assert.AreEqual(1, freezeGlobalBuffer.termCount);
Assert.IsFalse(queue.AnyChanges(), "all changes applied");
}
private class ThreadAnonymousClass : ThreadJob
{
private readonly TestDocumentsWriterDeleteQueue outerInstance;
private DocumentsWriterDeleteQueue queue;
public ThreadAnonymousClass(TestDocumentsWriterDeleteQueue outerInstance, DocumentsWriterDeleteQueue queue)
{
this.outerInstance = outerInstance;
this.queue = queue;
}
public override void Run()
{
queue.AddDelete(new Term("foo", "bar"));
}
}
[Test]
[Slow]
public virtual void TestStressDeleteQueue()
{
DocumentsWriterDeleteQueue queue = new DocumentsWriterDeleteQueue();
ISet<Term> uniqueValues = new JCG.HashSet<Term>();
int size = 10000 + Random.Next(500) * RandomMultiplier;
int?[] ids = new int?[size];
for (int i = 0; i < ids.Length; i++)
{
ids[i] = Random.Next();
uniqueValues.Add(new Term("id", ids[i].ToString()));
}
CountdownEvent latch = new CountdownEvent(1);
AtomicInt32 index = new AtomicInt32(0);
int numThreads = 2 + Random.Next(5);
UpdateThread[] threads = new UpdateThread[numThreads];
for (int i = 0; i < threads.Length; i++)
{
threads[i] = new UpdateThread(queue, index, ids, latch);
threads[i].Start();
}
latch.Signal();
for (int i = 0; i < threads.Length; i++)
{
threads[i].Join();
}
foreach (UpdateThread updateThread in threads)
{
DeleteSlice slice = updateThread.slice;
queue.UpdateSlice(slice);
BufferedUpdates deletes = updateThread.deletes;
slice.Apply(deletes, BufferedUpdates.MAX_INT32);
assertEquals(uniqueValues, deletes.terms.Keys);
}
queue.TryApplyGlobalSlice();
ISet<Term> frozenSet = new JCG.HashSet<Term>();
foreach (Term t in queue.FreezeGlobalBuffer(null).GetTermsEnumerable())
{
BytesRef bytesRef = new BytesRef();
bytesRef.CopyBytes(t.Bytes);
frozenSet.Add(new Term(t.Field, bytesRef));
}
Assert.AreEqual(0, queue.NumGlobalTermDeletes, "num deletes must be 0 after freeze");
Assert.AreEqual(uniqueValues.Count, frozenSet.Count);
assertEquals(uniqueValues, frozenSet);
}
private class UpdateThread : ThreadJob
{
internal readonly DocumentsWriterDeleteQueue queue;
internal readonly AtomicInt32 index;
internal readonly int?[] ids;
internal readonly DeleteSlice slice;
internal readonly BufferedUpdates deletes;
internal readonly CountdownEvent latch;
protected internal UpdateThread(DocumentsWriterDeleteQueue queue, AtomicInt32 index, int?[] ids, CountdownEvent latch)
{
this.queue = queue;
this.index = index;
this.ids = ids;
this.slice = queue.NewSlice();
deletes = new BufferedUpdates();
this.latch = latch;
}
public override void Run()
{
//#if FEATURE_THREAD_INTERRUPT
// try
// {
//#endif
latch.Wait();
//#if FEATURE_THREAD_INTERRUPT
// }
// catch (ThreadInterruptedException e) // LUCENENET NOTE: Senseless to catch and rethrow the same exception type
// {
// throw new ThreadInterruptedException("Thread Interrupted Exception", e);
// }
//#endif
int i = 0;
while ((i = index.GetAndIncrement()) < ids.Length)
{
Term term = new Term("id", ids[i].ToString());
queue.Add(term, slice);
Assert.IsTrue(slice.IsTailItem(term));
slice.Apply(deletes, BufferedUpdates.MAX_INT32);
}
}
}
}
}