From 9f449a79a6eacdfe7e347592b9084ac5d60e77e3 Mon Sep 17 00:00:00 2001 From: Chinesezjc Date: Sun, 2 Aug 2026 18:34:04 +0800 Subject: [PATCH] docs(code-runtime-python): correct the ProtocolChannel serialization docstring The class docstring still credited the GIL plus per-frame PIPE_BUF atomicity for serializing writes, which _write_lock's full-write loop already superseded. State the current contract (writers serialized by _write_lock around a full-write loop) and drop the double blank line under the binding-replies note heading. --- ...-07-31-code-runtime-python-settlement-fixes.i18n.yaml | 2 +- .../2026-07-31-code-runtime-python-settlement-fixes.md | 1 - .../code-runtime/code-runtime-python/py/bootstrap.py | 9 ++++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.i18n.yaml index 3e11741c5d..40492a3937 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.md -2026-07-31-code-runtime-python-settlement-fixes.md: d276c04e0182dc589f48a77d8dd73b233ae11d9d +2026-07-31-code-runtime-python-settlement-fixes.md: b51fb2e9c28d07efa1691b1036b7b6e899b21062 2026-07-31-code-runtime-python-settlement-fixes.zh.md: a59e5457f2469e86ed4120ce47ed8a5d8ae08f4f diff --git a/.agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.md b/.agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.md index d276c04e01..b51fb2e9c2 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.md +++ b/.agents/notes/implemented/bug-fix/2026-07-31-code-runtime-python-settlement-fixes.md @@ -40,7 +40,6 @@ In [`py/bootstrap.py`](../../../../packages/code-runtime/code-runtime-python/py/ ### Binding replies complete on the calling loop's thread - Also in `py/bootstrap.py`, a binding reply Future is created on the loop that ran `dispatch`. When the model calls a binding from a worker THREAD via `asyncio.run(tools.x(...))`, that Future belongs to the thread's loop, not the main loop where `_pump_replies` reads the reply. `asyncio.Future` is not thread-safe: completing it from another thread does not wake its own loop, so the direct `set_result`/`set_exception` left the awaiting thread stranded and the run degraded to a wall-clock timeout. Each pending entry now records its Future's loop alongside the Future, and `_pump_replies` completes it via that loop's `call_soon_threadsafe`. The shared `pending`/`next_id` state is guarded by a `threading.Lock` held across the id claim, the fd-3 write, and the counter advance, so concurrent callers cannot interleave frames out of the id order the host requires. `call_soon_threadsafe` onto a loop that has already CLOSED (the worker thread finished and abandoned its call before the reply arrived) raises `RuntimeError`; that schedule is wrapped so the moot reply is dropped rather than letting the exception end the pump task and strand every later reply. ## Testing diff --git a/packages/code-runtime/code-runtime-python/py/bootstrap.py b/packages/code-runtime/code-runtime-python/py/bootstrap.py index f4a47e5ade..72a6ea9040 100644 --- a/packages/code-runtime/code-runtime-python/py/bootstrap.py +++ b/packages/code-runtime/code-runtime-python/py/bootstrap.py @@ -345,9 +345,12 @@ class ProtocolChannel: Writes are unbuffered and go straight to the fd, so ``send_sync`` is safe from inside model code (which may run outside an asyncio task) and from - background tasks alike. The single writer is serialized by CPython's GIL - plus one os.write per frame (POSIX guarantees atomicity for writes below - ``PIPE_BUF``, and our frames are short JSON lines). + background tasks alike. Concurrent writers are serialized by ``_write_lock`` + around a full-write loop (see ``send_sync``): ``os.write`` releases the GIL, + a frame may exceed ``PIPE_BUF`` (logs up to ``maxLogBytes``, completions up + to ``maxValueBytes``, uncapped ``call`` args), and one ``os.write`` may + consume only part of a frame — so neither the GIL nor per-frame atomicity is + relied on for framing. """ def __init__(self, fd: int) -> None: