You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Lucene.Net调用J2N HashSet AddIfNotPresent时出现诡异NullReferenceException

IIS进程唤醒后Lucene.Net触发NullReferenceException异常排查

该NullReferenceException异常并非频繁出现,难以复现,但通常在IIS进程从空闲状态恢复后触发(启动程序后闲置一段时间,再访问涉及Lucene.Net搜索的页面时出现)。对IIS相关知识不太熟悉,希望能得到解决帮助。

异常堆栈

Exception: System.NullReferenceException: Object reference not set to an instance of an object.
   at J2N.Collections.Generic.HashSet`1.AddIfNotPresent(T value)
   at Lucene.Net.Index.IndexReader.SubscribeToGetCacheKeysEvent(GetCacheKeysEvent getCacheKeysEvent)
   at Lucene.Net.Search.FieldCacheImpl.Cache`2.Get(AtomicReader reader, TKey key, Boolean setDocsWithField)
   at Lucene.Net.Search.FieldCacheImpl.GetInt64s(AtomicReader reader, String field, IInt64Parser parser, Boolean setDocsWithField)
   at Lucene.Net.Search.FieldComparer.Int64Comparer.SetNextReader(AtomicReaderContext context)
   at Lucene.Net.Search.TopFieldCollector.OneComparerNonScoringCollector.SetNextReader(AtomicReaderContext context)
   at Lucene.Net.Search.IndexSearcher.Search(IList`1 leaves, Weight weight, ICollector collector)

调用Lucene API的代码

var sectionResults = await Task.WhenAll(sectionsData
  .Select(data => lucene.ExploreQuery(data.Sorting, data.PageTypeFilter, null, data.CategoryId, null, null, null, null, data.Keyword))
  .Select(f => lucene.RetrieveResults(1, 4, f.Sort, null, f.Filter)));

初步怀疑

怀疑可以通过增加应用池空闲超时时间来降低异常发生频率。

查看AddIfNotPresent方法

已经查看了AddIfNotPresent方法,但不清楚运行时哪个引用为空,且该方法属于库代码无法修改:

private bool AddIfNotPresent(T value)
{
    if (_buckets == null)
    {
        Initialize(0);
    }

    int hashCode;
    int bucket;
    int collisionCount = 0;
    Slot[] slots = _slots;

    IEqualityComparer<T>? comparer = _comparer;

    if (comparer == null)
    {
        hashCode = value == null ? 0 : InternalGetHashCode(value.GetHashCode());
        bucket = hashCode % _buckets!.Length;

        if (default(T)! != null) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
        {
            for (int i = _buckets[bucket] - 1; i >= 0; i = slots[i].next)
            {
                if (slots[i].hashCode == hashCode && EqualityComparer<T>.Default.Equals(slots[i].value, value))
                {
                    return false;
                }

                if (collisionCount >= slots.Length)
                {
                // The chain of entries forms a loop, which means a concurrent update has happened.
                    throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported);
                }
                collisionCount++;
            }
        }
        else
        {
        // Object type: Shared Generic, EqualityComparer<TValue>.Default won't devirtualize
        // https://github.com/dotnet/coreclr/issues/17273
        // So cache in a local rather than get EqualityComparer per loop iteration
            IEqualityComparer<T> defaultComparer = EqualityComparer<T>.Default;

            for (int i = _buckets[bucket] - 1; i >= 0; i = slots[i].next)
            {
                if (slots[i].hashCode == hashCode && defaultComparer.Equals(slots[i].value, value))
                {
                    return false;
                }

                if (collisionCount >= slots.Length)
                {
                // The chain of entries forms a loop, which means a concurrent update has happened.
                    throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported);
                }
                collisionCount++;
            }
        }
    }
    else
    {
        hashCode = value == null ? 0 : InternalGetHashCode(comparer.GetHashCode(value));
        bucket = hashCode % _buckets!.Length;

        for (int i = _buckets[bucket] - 1; i >= 0; i = slots[i].next)
        {
            if (slots[i].hashCode == hashCode && comparer.Equals(slots[i].value, value))
            {
                return false;
            }

            if (collisionCount >= slots.Length)
            {
                // The chain of entries forms a loop, which means a concurrent update has happened.
                    throw new InvalidOperationException(SR.InvalidOperation_ConcurrentOperationsNotSupported);
            }
            collisionCount++;
        }
    }

    int index;
    if (_freeList >= 0)
    {
        index = _freeList;
        _freeList = slots[index].next;
    }
    else
    {
        if (_lastIndex == slots.Length)
        {
            IncreaseCapacity();
            // this will change during resize
            slots = _slots;
            bucket = hashCode % _buckets.Length;
        }
        index = _lastIndex;
        _lastIndex++;
    }
    slots[index].hashCode = hashCode;
    slots[index].value = value;
    slots[index].next = _buckets[bucket] - 1;
    _buckets[bucket] = index + 1;
    _count++;
    _version++;

    return true;
}

更新:找到问题根源

Tim Schmelter的建议非常有用。Lucene.Net的IndexReader.cs中的以下代码显示,线程不安全的实例变量getCacheKeysEvents会在多线程上下文下导致异常:

[ExcludeFromRamUsageEstimation]
private readonly ISet<WeakEvents.GetCacheKeysEvent> getCacheKeysEvents = new JCG.HashSet<WeakEvents.GetCacheKeysEvent>();
internal void SubscribeToGetParentReadersEvent(WeakEvents.GetParentReadersEvent getParentReadersEvent)
{
    if (getParentReadersEvent is null)
        throw new ArgumentNullException(nameof(getParentReadersEvent));
    if (getParentReadersEvents.Add(getParentReadersEvent))
        getParentReadersEvent.Subscribe(OnGetParentReaders);
}

internal void SubscribeToGetCacheKeysEvent(WeakEvents.GetCacheKeysEvent getCacheKeysEvent)
{
    if (getCacheKeysEvent is null)
        throw new ArgumentNullException(nameof(getCacheKeysEvent));
    if (getCacheKeysEvents.Add(getCacheKeysEvent))
        getCacheKeysEvent.Subscribe(OnGetCacheKeys);
} 

内容的提问来源于stack exchange,提问作者Ryan Xu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 00:12:04