Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IndexSearcher fix for virtual calls from constructor #837

56 changes: 41 additions & 15 deletions src/Lucene.Net/Search/IndexSearcher.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
using Lucene.Net.Diagnostics;
using Lucene.Net.Support;
using Lucene.Net.Support.Threading;
using Lucene.Net.Util;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace Lucene.Net.Search
Expand Down Expand Up @@ -118,7 +117,7 @@ public IndexSearcher(IndexReader r)
/// @lucene.experimental
/// </summary>
public IndexSearcher(IndexReader r, TaskScheduler executor)
: this(r.Context, executor)
: this(r?.Context, executor)
{
}

Expand All @@ -135,13 +134,44 @@ public IndexSearcher(IndexReader r, TaskScheduler executor)
/// <seealso cref="IndexReaderContext"/>
/// <seealso cref="IndexReader.Context"/>
public IndexSearcher(IndexReaderContext context, TaskScheduler executor)
: this(context, executor, allocateLeafSlices: executor is not null)
{
if (Debugging.AssertsEnabled) Debugging.Assert(context.IsTopLevel,"IndexSearcher's ReaderContext must be topLevel for reader {0}", context.Reader);
}

/// <summary>
/// LUCENENET specific constructor that can be used by the subclasses to
/// control whether the leaf slices are allocated in the base class or subclass.
/// </summary>
/// <remarks>
/// If executor is non-<c>null</c> and you choose to skip allocating the leaf slices
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops. I missed that the second word here should be <paramref name="executor"/>.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated!

/// (i.e. <paramref name="allocateLeafSlices"/> == <c>false</c>), you must
/// set the <see cref="m_leafSlices"/> field in your subclass constructor.
/// This is commonly done by calling <see cref="GetSlices(IList{AtomicReaderContext})"/>
/// and using the result to set <see cref="m_leafSlices"/>. You may wish to do this if you
/// have state to pass into your constructor and need to set it prior to the call to
/// <see cref="GetSlices(IList{AtomicReaderContext})"/> so it is available for use
/// as a member field or property inside a custom override of
/// <see cref="GetSlices(IList{AtomicReaderContext})"/>.
/// </remarks>
[SuppressMessage("CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "This is a SonarCloud issue")]
[SuppressMessage("CodeQuality", "S1699:Constructors should only call non-overridable methods", Justification = "Required for continuity with Lucene's design")]
protected IndexSearcher(IndexReaderContext context, TaskScheduler executor, bool allocateLeafSlices)
{
if (context is null)
throw new ArgumentNullException(nameof(context));

if (Debugging.AssertsEnabled)
NightOwl888 marked this conversation as resolved.
Show resolved Hide resolved
Debugging.Assert(context.IsTopLevel,"IndexSearcher's ReaderContext must be topLevel for reader {0}", context.Reader);

reader = context.Reader;
this.executor = executor;
this.m_readerContext = context;
m_leafContexts = context.Leaves;
this.m_leafSlices = executor is null ? null : GetSlicesInternal(m_leafContexts);

if (allocateLeafSlices)
{
this.m_leafSlices = GetSlices(m_leafContexts);
}
}

/// <summary>
Expand All @@ -160,18 +190,8 @@ public IndexSearcher(IndexReaderContext context)
/// Expert: Creates an array of leaf slices each holding a subset of the given leaves.
/// Each <see cref="LeafSlice"/> is executed in a single thread. By default there
/// will be one <see cref="LeafSlice"/> per leaf (<see cref="AtomicReaderContext"/>).
///
/// NOTE: When overriding this method, be aware that the constructor of this class calls
/// a private method and not this virtual method. So if you need to override
/// the behavior during the initialization, call your own private method from the constructor
/// with whatever custom behavior you need.
/// </summary>
// LUCENENET specific - renamed to GetSlices to better indicate the purpose of this method
protected virtual LeafSlice[] GetSlices(IList<AtomicReaderContext> leaves)
=> GetSlicesInternal(leaves);

// LUCENENET specific - creating this so that we can call it from the constructor
protected LeafSlice[] GetSlicesInternal(IList<AtomicReaderContext> leaves)
{
LeafSlice[] slices = new LeafSlice[leaves.Count];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not a fan of these type of additions. This is not present in the original source, not even in the latest version. We could add a lot of extra typing/code (that's not even interesting, just copy paste of exception docs and guar checks). This is why I wasn't sure what you meant by guard clauses as it seemed excessive.

for (int i = 0; i < slices.Length; i++)
Expand Down Expand Up @@ -464,6 +484,9 @@ protected virtual TopDocs Search(Weight weight, ScoreDoc after, int nDocs)
ReentrantLock @lock = new ReentrantLock();
ExecutionHelper<TopDocs> runner = new ExecutionHelper<TopDocs>(executor);

if (m_leafSlices is null)
throw new InvalidOperationException($"When the constructor is passed a non-null {nameof(TaskScheduler)}, {nameof(m_leafSlices)} must also be set to a non-null value in the constructor.");

for (int i = 0; i < m_leafSlices.Length; i++) // search each sub
{
runner.Submit(new SearcherCallableNoSort(@lock, this, m_leafSlices[i], weight, after, nDocs, hq).Call);
Expand Down Expand Up @@ -560,6 +583,9 @@ protected virtual TopFieldDocs Search(Weight weight, FieldDoc after, int nDocs,
ReentrantLock @lock = new ReentrantLock();
ExecutionHelper<TopFieldDocs> runner = new ExecutionHelper<TopFieldDocs>(executor);

if (m_leafSlices is null)
throw new InvalidOperationException($"When the constructor is passed a non-null {nameof(TaskScheduler)}, {nameof(m_leafSlices)} must also be set to a non-null value in the constructor.");

for (int i = 0; i < m_leafSlices.Length; i++) // search each leaf slice
{
runner.Submit(new SearcherCallableWithSort(@lock, this, m_leafSlices[i], weight, after, nDocs, topCollector, sort, doDocScores, doMaxScore).Call);
Expand Down