fix(advanced-search): H/I/J/K/L/M — remove busy-spin, cache reflection, order filters, background+safe-exit workers, consistent volatility

This commit is contained in:
Kamron Batman 2026-07-19 22:13:38 -07:00
parent 3a4dee0bf1
commit 97802ac710

View file

@ -12,6 +12,7 @@ namespace Server.Engines.AdvancedSearch;
public class AdvancedSearchThreadWorker
{
private static readonly ILogger _logger = LogFactory.GetLogger(typeof(AdvancedSearchThreadWorker));
private static readonly ConcurrentDictionary<Type, PropertyInfo[]> _propCache = new();
private readonly Thread _thread;
private readonly AutoResetEvent _startEvent; // Main thread tells the thread to start working
@ -29,7 +30,10 @@ public class AdvancedSearchThreadWorker
_startEvent = new AutoResetEvent(false);
_stopEvent = new AutoResetEvent(false);
_entities = new ConcurrentQueue<IEntity>();
_thread = new Thread(Execute);
_thread = new Thread(Execute)
{
IsBackground = true
};
_thread.Start(this);
}
@ -55,10 +59,16 @@ public class AdvancedSearchThreadWorker
public void Exit()
{
_exit = true;
Volatile.Write(ref _exit, true);
Wake(WorldLocation.Zero, null, null, null);
Sleep();
// Tolerate a worker that has already terminated (e.g. Core.Closing raced us) so
// shutdown can't deadlock waiting on a stopEvent that will never be set.
if (_thread.IsAlive)
{
Sleep();
}
}
public void Push(IEntity entity)
@ -91,12 +101,17 @@ public class AdvancedSearchThreadWorker
worker._filter = null;
break;
}
else
{
// Queue is transiently empty but we're not paused yet; avoid a 100%-CPU busy-spin.
Thread.SpinWait(1);
}
}
worker._stopEvent.Set(); // Allow the main thread to continue now that we are finished
worker._pause = false;
Volatile.Write(ref worker._pause, false);
if (Core.Closing || worker._exit)
if (Core.Closing || Volatile.Read(ref worker._exit))
{
return;
}
@ -123,9 +138,11 @@ public class AdvancedSearchThreadWorker
private AdvancedSearchResult DoEntitySearchCore(IEntity entity)
{
if (_filter.HideValidInternalMap)
if (_filter == null)
{
HandleValidInternal(entity);
// Exit() clears the filter as part of tearing down the worker; a straggler
// entity dequeued after that point must bail out instead of NREing below.
return null;
}
// Check for valid map
@ -159,6 +176,13 @@ public class AdvancedSearchThreadWorker
return null;
}
// Deferred until after the cheap map/range/region filters so entities that don't
// even qualify for this search skip the expensive house/keyring enumeration below.
if (_filter.HideValidInternalMap)
{
HandleValidInternal(entity);
}
if (entity is Mobile mobile)
{
return DoMobileSearch(mobile);
@ -349,7 +373,7 @@ public class AdvancedSearchThreadWorker
return false;
}
var properties = entity.GetType().GetProperties();
var properties = _propCache.GetOrAdd(entity.GetType(), static t => t.GetProperties());
PropertyInfo property = null;
for (var i = 0; i < properties.Length; ++i)
{