Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 46 additions & 24 deletions AzureSearchEmulator/Indexing/LuceneNetSearchIndexer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using AzureSearchEmulator.Models;
using System.Collections.Concurrent;
using AzureSearchEmulator.Models;
using AzureSearchEmulator.SearchData;
using Lucene.Net.Index;
using Lucene.Net.Util;
Expand All @@ -10,41 +11,62 @@ public class LuceneNetSearchIndexer(
ILuceneIndexReaderFactory luceneIndexReaderFactory)
: ISearchIndexer
{
// One writer gate per index. Lucene allows only a single IndexWriter per
// directory; this class creates a fresh writer per request, so two
// concurrent indexing requests against the same index would race — the
// loser either fails with LockObtainFailedException or, worse, observes
// a torn commit and permanently corrupts the segment files
// (FileNotFoundException: .../_N.si on every subsequent write until the
// index directory is deleted). Serializing IndexDocuments per index name
// removes both failure modes; requests against different indexes still
// run in parallel.
private static readonly ConcurrentDictionary<string, SemaphoreSlim> IndexWriteGates = new();

public IndexDocumentsResult IndexDocuments(SearchIndex index, IList<IndexDocumentAction> actions)
{
var analyzer = AnalyzerHelper.GetPerFieldIndexAnalyzer(index.Fields);
var gate = IndexWriteGates.GetOrAdd(index.Name, static _ => new SemaphoreSlim(1, 1));
gate.Wait();

var config = new IndexWriterConfig(LuceneVersion.LUCENE_48, analyzer);
try
{
var analyzer = AnalyzerHelper.GetPerFieldIndexAnalyzer(index.Fields);

var directory = luceneDirectoryFactory.GetDirectory(index.Name);
using var writer = new IndexWriter(directory, config);
var config = new IndexWriterConfig(LuceneVersion.LUCENE_48, analyzer);

var key = index.GetKeyField();
var directory = luceneDirectoryFactory.GetDirectory(index.Name);
using var writer = new IndexWriter(directory, config);

var results = new IndexDocumentsResult();
var key = index.GetKeyField();

// ReSharper disable once AccessToDisposedClosure
var readerLazy = new Lazy<IndexReader>(() => writer.GetReader(true));
var results = new IndexDocumentsResult();

var context = new IndexingContext(index, key, writer, readerLazy);
// ReSharper disable once AccessToDisposedClosure
var readerLazy = new Lazy<IndexReader>(() => writer.GetReader(true));

foreach (var action in actions)
{
var result = action.PerformIndexingAsync(context);
results.Value.Add(result);
}
var context = new IndexingContext(index, key, writer, readerLazy);

if (readerLazy.IsValueCreated)
{
var reader = readerLazy.Value;
reader.Dispose();
}
foreach (var action in actions)
{
var result = action.PerformIndexingAsync(context);
results.Value.Add(result);
}

if (readerLazy.IsValueCreated)
{
var reader = readerLazy.Value;
reader.Dispose();
}

writer.Commit();
writer.Flush(true, true);
writer.Commit();
writer.Flush(true, true);

luceneIndexReaderFactory.RefreshReader(index.Name);
luceneIndexReaderFactory.RefreshReader(index.Name);

return results;
return results;
}
finally
{
gate.Release();
}
}
}