fix(code-runtime-python): bind the reply-pump exception names as def-time default arguments

A body-local X = X binding in _pump_replies is too late: _run reaches the
model's top-level statements (which run first, since there is no suspension
point between create_task and await __dsh_main__) before the pump's first step,
so a __main__.RuntimeError rebind there would be captured by the body local and
a closed-loop failure would escape the except, killing the pump. Bind
_RuntimeError, _BindingRejection, str, and bool as DEF-TIME default arguments of
_pump_replies (evaluated at import, before any model code runs). Add a regression
test that rebinds __main__.RuntimeError as the first program statement and drives
the closed-loop worker pattern, asserting the pump survives and delivers the
later binding. Update the settlement note (en + zh) to describe the default-arg
capture; pairing re-recorded and consistent.
This commit is contained in:
Chinesezjc
2026-08-31 14:34:09 +08:00
committed by Tianyi Cui
parent b018abf405
commit 923fb56128
5 changed files with 64 additions and 14 deletions
@@ -1115,6 +1115,19 @@ async def _pump_replies(
channel: ProtocolChannel,
pending: dict[int, tuple[asyncio.AbstractEventLoop, asyncio.Future[Any]]],
pending_lock: "threading.Lock",
# Bound as DEFAULT ARGUMENTS so they are captured at def/import time, before
# ANY model code runs. This bootstrap IS `__main__`, so `__main__.RuntimeError
# = ...` (or `__main__._BindingRejection`, `__main__.str`, `__main__.bool`)
# as a program top-level statement would otherwise rebind the module globals
# these clauses resolve at runtime. A body-local `X = X` binding is too late:
# `_run` reaches `await __dsh_main__` (whose top-level statements run first)
# with no suspension point after `create_task`, so the model's rebind executes
# before the pump body. Defaults are evaluated in the enclosing scope at def
# time, truly before the program.
_RuntimeError: Any = RuntimeError,
_BindingRejection: Any = _BindingRejection,
_str: Any = str,
_bool: Any = bool,
) -> None:
"""Background task: read reply frames and settle pending futures.
@@ -1131,14 +1144,6 @@ async def _pump_replies(
so a reply cannot race the claim that registers its id.
"""
# The exception class the closed-loop catch below resolves is bound into a
# LOCAL here, after the docstring, before any model code runs. This bootstrap
# IS `__main__`, so `__main__.RuntimeError = ...` would otherwise rebind the
# module global the `except RuntimeError` clause resolves at runtime, and a
# closed-loop scheduling failure would then escape the catch, killing the
# pump and stranding every later reply.
_RuntimeError = RuntimeError
def complete(fut: asyncio.Future[Any], ok: bool, value: Any, message: Any) -> None:
# Runs on the Future's own loop. `done()` re-checked here because
# cancellation or a duplicate reply may have settled it between the pop
@@ -1148,7 +1153,7 @@ async def _pump_replies(
if ok:
fut.set_result(value)
else:
fut.set_exception(_BindingRejection(str(message)))
fut.set_exception(_BindingRejection(_str(message)))
while True:
frame = await channel.read_frame_async()
@@ -1161,7 +1166,7 @@ async def _pump_replies(
if entry is None:
continue
loop, fut = entry
ok = bool(frame.get("ok"))
ok = _bool(frame.get("ok"))
value = frame.get("value")
message = frame.get("message")
try: