fix(runner): keep installation context within each stream resume

This commit is contained in:
Hyu
2026-09-15 13:51:27 +08:00
parent 9db097399c
commit d497defbf7
4 changed files with 203 additions and 24 deletions
+10 -7
View File
@@ -48,12 +48,15 @@ class RunnerInvoker:
context=context,
)
while True:
try:
result_dict = await self._next_with_deadline(gen, descriptor, context)
except StopAsyncIteration:
break
yield result_dict
try:
while True:
try:
result_dict = await self._next_with_deadline(gen, descriptor, context)
except StopAsyncIteration:
break
yield result_dict
finally:
await self._close_generator(gen, descriptor)
except asyncio.TimeoutError as e:
raise RunnerExecutionError(
@@ -128,4 +131,4 @@ class RunnerInvoker:
try:
await gen.aclose()
except Exception as e:
self.ap.logger.warning(f'Failed to close timed-out runner {descriptor.id}: {e}')
self.ap.logger.warning(f'Failed to close runner {descriptor.id}: {e}')
+29 -13
View File
@@ -2237,12 +2237,12 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
include_plugins=bound_plugins,
)
runtime_handler = self._runtime_handler()
with runtime_handler.installation_scope(binding):
gen = runtime_handler.execute_command(
command_ctx.model_dump(serialize_as_any=True),
include_plugins=bound_plugins,
)
async for ret in gen:
gen = runtime_handler.execute_command(
command_ctx.model_dump(serialize_as_any=True),
include_plugins=bound_plugins,
)
async with contextlib.aclosing(self._installation_scoped_stream(runtime_handler, binding, gen)) as scoped:
async for ret in scoped:
yield command_context.CommandReturn.model_validate(ret)
# Runner methods
@@ -2308,15 +2308,31 @@ class PluginRuntimeConnector(ManagedRuntimeConnector):
require_enabled=True,
)
runtime_handler = self._runtime_handler()
with runtime_handler.installation_scope(binding):
async for ret in runtime_handler.run_runner(
plugin_author,
plugin_name,
runner_name,
context,
):
gen = runtime_handler.run_runner(plugin_author, plugin_name, runner_name, context)
async with contextlib.aclosing(self._installation_scoped_stream(runtime_handler, binding, gen)) as scoped:
async for ret in scoped:
yield ret
@staticmethod
async def _installation_scoped_stream(runtime_handler, binding, gen):
"""Keep ContextVar tokens inside a single resume, never across yields.
Consumers may use a different Task for each anext (e.g. wait_for).
Reset the installation before exposing a result to the consumer, and
re-enter the same immutable binding for transport cleanup.
"""
try:
while True:
with runtime_handler.installation_scope(binding):
try:
result = await anext(gen)
except StopAsyncIteration:
return
yield result
finally:
with runtime_handler.installation_scope(binding):
await gen.aclose()
async def retrieve_knowledge(
self,
plugin_author: str,
+6 -4
View File
@@ -2933,8 +2933,9 @@ class RuntimeConnectionHandler(handler.Handler):
timeout=timeout,
)
async for ret in gen:
yield ret
async with contextlib.aclosing(gen):
async for ret in gen:
yield ret
def _get_runner_action_timeout(self, context: dict[str, Any]) -> float:
"""Use the run deadline as the transport idle timeout when available."""
@@ -3143,8 +3144,9 @@ class RuntimeConnectionHandler(handler.Handler):
timeout=180,
)
async for ret in gen:
yield ret
async with contextlib.aclosing(gen):
async for ret in gen:
yield ret
async def retrieve_knowledge(
self,