fix(plugins): abandon only nested plugin loads still running at outer timeout
Gate finding BN10.r1.2c (Low): the inline re-entrant branch appended the nested ctx to the outer load's scope and never removed it, so a nested load that had already finished (and was recorded enabled under its own key) got fenced by _abandon_load() when the outer load later timed out. Keep the ctx in scope only while its fn() runs. The kept nested-inline test now times the outer load out with one finished and one hung nested load, asserting only outer + hung are abandoned; this also pins the nested-abandon behaviour of the previous commit.
This commit is contained in:
@@ -116,7 +116,10 @@ def run_with_load_deadline(plugin_key: str, ctx: "PluginContext", fn: Callable[[
|
||||
scope = _IN_PLUGIN_LOAD.get()
|
||||
if scope is not None:
|
||||
scope.append(ctx)
|
||||
return fn()
|
||||
try:
|
||||
return fn()
|
||||
finally:
|
||||
scope.remove(ctx)
|
||||
timeout = _resolve_plugin_load_timeout()
|
||||
if timeout <= 0:
|
||||
return fn()
|
||||
|
||||
@@ -1,24 +1,36 @@
|
||||
from hermes_cli import plugins_loader
|
||||
import threading
|
||||
|
||||
import pytest
|
||||
|
||||
from hermes_cli import plugins_loader
|
||||
|
||||
|
||||
def test_nested_plugin_load_runs_inline_on_deadline_worker(monkeypatch):
|
||||
monkeypatch.setattr(plugins_loader, "_resolve_plugin_load_timeout", lambda: 1.0)
|
||||
monkeypatch.setattr(plugins_loader, "_resolve_plugin_load_timeout", lambda: 0.3)
|
||||
abandoned = []
|
||||
|
||||
class Context:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def _abandon_load(self):
|
||||
raise AssertionError("unexpected timeout")
|
||||
abandoned.append(self.name)
|
||||
|
||||
observed_threads = []
|
||||
|
||||
def nested_load():
|
||||
observed_threads.append(threading.current_thread())
|
||||
release = threading.Event()
|
||||
|
||||
def outer_load():
|
||||
observed_threads.append(threading.current_thread())
|
||||
plugins_loader.run_with_load_deadline("nested", Context(), nested_load)
|
||||
plugins_loader.run_with_load_deadline(
|
||||
"done", Context("done"), lambda: observed_threads.append(threading.current_thread()),
|
||||
)
|
||||
plugins_loader.run_with_load_deadline("hung", Context("hung"), release.wait)
|
||||
|
||||
plugins_loader.run_with_load_deadline("outer", Context(), outer_load)
|
||||
with pytest.raises(plugins_loader.PluginLoadTimeout):
|
||||
plugins_loader.run_with_load_deadline("outer", Context("outer"), outer_load)
|
||||
release.set()
|
||||
|
||||
assert len(observed_threads) == 2
|
||||
assert observed_threads[0] is observed_threads[1]
|
||||
# The outer timeout abandons only contexts still loading, not a nested load that already finished.
|
||||
assert abandoned == ["outer", "hung"]
|
||||
|
||||
Reference in New Issue
Block a user