From 97802ac7109827e57c34b960ab6c018b15b91c3b Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sun, 19 Jul 2026 22:13:38 -0700 Subject: [PATCH] =?UTF-8?q?fix(advanced-search):=20H/I/J/K/L/M=20=E2=80=94?= =?UTF-8?q?=20remove=20busy-spin,=20cache=20reflection,=20order=20filters,?= =?UTF-8?q?=20background+safe-exit=20workers,=20consistent=20volatility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AdvancedSearchThreadWorker.cs | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/Projects/UOContent/Engines/Advanced Search/AdvancedSearchThreadWorker.cs b/Projects/UOContent/Engines/Advanced Search/AdvancedSearchThreadWorker.cs index 660df1058..47a2ead6e 100644 --- a/Projects/UOContent/Engines/Advanced Search/AdvancedSearchThreadWorker.cs +++ b/Projects/UOContent/Engines/Advanced Search/AdvancedSearchThreadWorker.cs @@ -12,6 +12,7 @@ namespace Server.Engines.AdvancedSearch; public class AdvancedSearchThreadWorker { private static readonly ILogger _logger = LogFactory.GetLogger(typeof(AdvancedSearchThreadWorker)); + private static readonly ConcurrentDictionary _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(); - _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) {