fix(gateway): run the plugin re-arm off the event loop and share the loader thread prefix

rearm_failed_platform takes the manager's discovery lock, which a reload sweep or a deferred load holds for its whole import, so call it through the scope-carrying executor hop like the lookup. The hung-load guard now matches the loader thread name through one _LOADER_THREAD_PREFIX constant instead of a copied literal. Gate r2 Lows (2c, efficiency, quality).
This commit is contained in:
kshitijk4poor
2026-10-05 16:04:38 +05:30
committed by kshitij
parent 897030b7d4
commit 06ac7f01cc
2 changed files with 8 additions and 5 deletions

View File

@@ -828,9 +828,11 @@ class GatewayAdapterLifecycleMixin:
return
# Unregistered plugin: keep it queued and re-arm a failed load (capped) for the next tick.
from hermes_cli.plugins import get_plugin_manager
if info.get("load_rearms", 0) < _MAX_PLUGIN_LOAD_REARMS and get_plugin_manager().rearm_failed_platform(
platform.value):
info["load_rearms"] = info.get("load_rearms", 0) + 1
rearms = info.get("load_rearms", 0)
# Off the loop: the re-arm takes the manager's discovery lock, which a sweep or deferred load holds.
if rearms < _MAX_PLUGIN_LOAD_REARMS and await self._run_in_executor_with_context(
get_plugin_manager().rearm_failed_platform, platform.value):
info["load_rearms"] = rearms + 1
backoff = self._bump_reconnect_backoff(
platform, info, attempt, "adapter_unavailable", _adapter_unavailable_message(platform),
)

View File

@@ -44,6 +44,7 @@ _BARE_MODULE_SCOPE: Dict[str, str] = {} # bare module name -> owning scope_key
_LOAD_TIMEOUT_SECS = 10.0
_MAX_LOAD_TIMEOUT_SECS = 600.0
_MAX_ABANDONED_LOADERS = 8
_LOADER_THREAD_PREFIX = "plugin-load:" # names each deadline-bounded load worker; the re-arm guard matches it
_ABANDONED_LOADERS: List[threading.Thread] = []
_ABANDONED_LOADERS_LOCK = threading.Lock()
# PluginContexts of the outermost deadline-bounded load; set only inside its worker.
@@ -135,7 +136,7 @@ def run_with_load_deadline(plugin_key: str, ctx: "PluginContext", fn: Callable[[
_IN_PLUGIN_LOAD.reset(token)
worker = threading.Thread(
target=contextvars.copy_context().run, args=(_worker,), name=f"plugin-load:{plugin_key}", daemon=True,
target=contextvars.copy_context().run, args=(_worker,), name=f"{_LOADER_THREAD_PREFIX}{plugin_key}", daemon=True,
)
worker.start()
worker.join(timeout)
@@ -282,7 +283,7 @@ class PluginLoaderMixin:
failed, _get_disabled_plugins(), _get_enabled_plugins()).action not in ("defer", "load"):
return False
with _ABANDONED_LOADERS_LOCK: # its hung import is still running: another retry only leaks a thread
if any(t.is_alive() and t.name == f"plugin-load:{manifest_key(failed)}" for t in _ABANDONED_LOADERS):
if any(t.is_alive() and t.name == f"{_LOADER_THREAD_PREFIX}{manifest_key(failed)}" for t in _ABANDONED_LOADERS):
return False
logger.info("Re-arming failed platform plugin load: %s", platform_name)
self._register_deferred_platform(failed)