blob: 3c83af3a3c9b999461893437c4185bfc7e269aa7 [file] [log] [blame]
// Lucene version compatibility level 4.8.1
using J2N.Threading.Atomic;
using System;
using System.Collections.Concurrent;
using System.IO;
using NUnit.Framework;
using Assert = Lucene.Net.TestFramework.Assert;
using Console = Lucene.Net.Util.SystemConsole;
using J2N.Threading;
namespace Lucene.Net.Facet.Taxonomy.Directory
{
using Document = Lucene.Net.Documents.Document;
using ITaxonomyWriterCache = Lucene.Net.Facet.Taxonomy.WriterCache.ITaxonomyWriterCache;
using Cl2oTaxonomyWriterCache = Lucene.Net.Facet.Taxonomy.WriterCache.Cl2oTaxonomyWriterCache;
using LruTaxonomyWriterCache = Lucene.Net.Facet.Taxonomy.WriterCache.LruTaxonomyWriterCache;
using IndexWriter = Lucene.Net.Index.IndexWriter;
using OpenMode = Lucene.Net.Index.OpenMode;
using Directory = Lucene.Net.Store.Directory;
using IOUtils = Lucene.Net.Util.IOUtils;
/*
* 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.
*/
[TestFixture]
public class TestConcurrentFacetedIndexing : FacetTestCase
{
// A No-Op ITaxonomyWriterCache which always discards all given categories, and
// always returns true in put(), to indicate some cache entries were cleared.
private static ITaxonomyWriterCache NO_OP_CACHE = new TaxonomyWriterCacheAnonymousInnerClassHelper();
private class TaxonomyWriterCacheAnonymousInnerClassHelper : ITaxonomyWriterCache
{
public TaxonomyWriterCacheAnonymousInnerClassHelper()
{
}
public virtual void Dispose()
{
}
public virtual int Get(FacetLabel categoryPath)
{
return -1;
}
public virtual bool Put(FacetLabel categoryPath, int ordinal)
{
return true;
}
public virtual bool IsFull => true;
public virtual void Clear()
{
}
}
internal static FacetField NewCategory()
{
Random r = Random;
string l1 = "l1." + r.Next(10); // l1.0-l1.9 (10 categories)
string l2 = "l2." + r.Next(30); // l2.0-l2.29 (30 categories)
string l3 = "l3." + r.Next(100); // l3.0-l3.99 (100 categories)
return new FacetField(l1, l2, l3);
}
internal static ITaxonomyWriterCache NewTaxoWriterCache(int ndocs)
{
double d = Random.NextDouble();
if (d < 0.7)
{
// this is the fastest, yet most memory consuming
return new Cl2oTaxonomyWriterCache(1024, 0.15f, 3);
}
// LUCENENET specific - this option takes too long to get under the 1 hour job limit in Azure DevOps
//else if (TestNightly && d > 0.98)
//{
// // this is the slowest, but tests the writer concurrency when no caching is done.
// // only pick it during NIGHTLY tests, and even then, with very low chances.
// return NO_OP_CACHE;
//}
else
{
// this is slower than CL2O, but less memory consuming, and exercises finding categories on disk too.
return new LruTaxonomyWriterCache(ndocs / 10);
}
}
[Test]
public virtual void TestConcurrency()
{
AtomicInt32 numDocs = new AtomicInt32(AtLeast(10000));
Directory indexDir = NewDirectory();
Directory taxoDir = NewDirectory();
ConcurrentDictionary<string, string> values = new ConcurrentDictionary<string, string>();
IndexWriter iw = new IndexWriter(indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, null));
var tw = new DirectoryTaxonomyWriter(taxoDir, OpenMode.CREATE, NewTaxoWriterCache(numDocs));
ThreadJob[] indexThreads = new ThreadJob[AtLeast(4)];
FacetsConfig config = new FacetsConfig();
for (int i = 0; i < 10; i++)
{
config.SetHierarchical("l1." + i, true);
config.SetMultiValued("l1." + i, true);
}
for (int i = 0; i < indexThreads.Length; i++)
{
indexThreads[i] = new ThreadAnonymousInnerClassHelper(this, numDocs, values, iw, tw, config);
}
foreach (ThreadJob t in indexThreads)
{
t.Start();
}
foreach (ThreadJob t in indexThreads)
{
t.Join();
}
var tr = new DirectoryTaxonomyReader(tw);
// +1 for root category
if (values.Count + 1 != tr.Count)
{
foreach (string value in values.Keys)
{
FacetLabel label = new FacetLabel(FacetsConfig.StringToPath(value));
if (tr.GetOrdinal(label) == -1)
{
Console.WriteLine("FAIL: path=" + label + " not recognized");
}
}
fail("mismatch number of categories");
}
int[] parents = tr.ParallelTaxonomyArrays.Parents;
foreach (string cat in values.Keys)
{
FacetLabel cp = new FacetLabel(FacetsConfig.StringToPath(cat));
Assert.IsTrue(tr.GetOrdinal(cp) > 0, "category not found " + cp);
int level = cp.Length;
int parentOrd = 0; // for root, parent is always virtual ROOT (ord=0)
FacetLabel path = null;
for (int i = 0; i < level; i++)
{
path = cp.Subpath(i + 1);
int ord = tr.GetOrdinal(path);
Assert.AreEqual(parentOrd, parents[ord], "invalid parent for cp=" + path);
parentOrd = ord; // next level should have this parent
}
}
IOUtils.Dispose(tw, iw, tr, taxoDir, indexDir);
}
private class ThreadAnonymousInnerClassHelper : ThreadJob
{
private readonly TestConcurrentFacetedIndexing outerInstance;
private readonly AtomicInt32 numDocs;
private readonly ConcurrentDictionary<string, string> values;
private readonly IndexWriter iw;
private readonly DirectoryTaxonomyWriter tw;
private readonly FacetsConfig config;
public ThreadAnonymousInnerClassHelper(TestConcurrentFacetedIndexing outerInstance, AtomicInt32 numDocs, ConcurrentDictionary<string, string> values, IndexWriter iw, DirectoryTaxonomyWriter tw, FacetsConfig config)
{
this.outerInstance = outerInstance;
this.numDocs = numDocs;
this.values = values;
this.iw = iw;
this.tw = tw;
this.config = config;
}
public override void Run()
{
Random random = Random;
while (numDocs.DecrementAndGet() > 0)
{
try
{
Document doc = new Document();
int numCats = random.Next(3) + 1; // 1-3
while (numCats-- > 0)
{
FacetField ff = NewCategory();
doc.Add(ff);
FacetLabel label = new FacetLabel(ff.Dim, ff.Path);
// add all prefixes to values
int level = label.Length;
while (level > 0)
{
string s = FacetsConfig.PathToString(label.Components, level);
values[s] = s;
--level;
}
}
iw.AddDocument(config.Build(tw, doc));
}
catch (IOException e)
{
throw new Exception(e.ToString(), e);
}
}
}
}
}
}