docs(code-runtime-python): split the deadlock into its own bullet and qualify the done_value claim

Addresses the review's two registration-text accuracy findings:
- The cross-thread t.join() deadlock is a process-isolation-backend property (the
  pump runs on the child's main event loop), so it is split out of the wide-binding
  REPLY bullet into its own Known Limitations entry with the correct attribution
  (fix belongs in this backend, not packages/core/session); the zh half-width
  space is removed.
- The settlement note's _done_with_value def-time default-arg sentence is
  qualified: it guards a rebind of _check_done_value/_encode_json_plain, while a
  transitive encoder dep (_dump_scalar/io) rebind can still downgrade, which is
  registered as an accepted residual in the package README.
Pairing re-recorded; corpus-wide verify-translation-pairing passes 1004.
This commit is contained in:
Chinesezjc
2026-08-31 14:39:07 +08:00
committed by Tianyi Cui
parent c8bc96007b
commit e6b23e829b
6 changed files with 10 additions and 8 deletions
@@ -41,4 +41,5 @@ No direct invalidation; the named consumer owns any request-prefix changes.
- **A 1-second dual-limit `ulimit -t 1` CPU overrun is reported as `worker-exit`, not a timeout.** When the host starts under a hard CPU limit equal to the soft (`ulimit -t N` sets both) and that limit is 1, `_clamped` cannot lower the soft to 0, so the kernel SIGKILLs the busy loop in the same tick and SIGXCPU is never delivered. The host classifies a CPU overrun only on `signal === 'SIGXCPU'`, so the overrun is reported as `worker-exit`. For a dual limit of 2 or more the soft is lowered by one unit, SIGXCPU fires, and the run is a timeout. Containment holds in both cases; only the classification is degraded.
- **A program that traps SIGXCPU can exceed the soft CPU limit during settlement encoding and still report success.** The settlement CPU recheck runs before the completion value is flushed and encoded; a program that traps SIGXCPU (soft limit) and keeps burning past it through the build-and-encode window returns a result before `die_if_cpu_exhausted` re-checks, so the run reports success. Containment holds — the hard limit (soft + 1s) and the wall clock still bound it — and only the classification is degraded. The CPU recheck does not run mid-encode because doing so would have to meter the encode itself, and the encode is the path the budget already bounds.
- **The encoder's direct dependencies resolve at call time.** `_encode_json_plain` reaches `_dump_scalar`/`_dump_string`/`json` via module-global lookup, so a program running as `__main__` that rebinds one of those names (e.g. `__main__._dump_scalar = boom`) after returning a legitimate value can make the encode throw and downgrade a success to `exception`. The value path's top-level `_check_done_value`/`_encode_json_plain` are bound as def-time defaults, but their transitive deps are not; this is an accepted residual for the same reason the analogous `_dump_*` helpers are not rebound in practice.
- **A wide binding REPLY expands host-side state per member.** Resolutions cross through `snapshotJsonValue` in [`@deepseek-ai/dsh-session`](../../core/session/README.md), whose `walkJsonValue` pushes one task frame per member, and binding resolution carries no seam-level byte cap. A legitimate reply of several million elements can therefore exhaust the host heap. A cross-thread binding that the program joins with a synchronous `t.join()` can also deadlock: the joining thread blocks the main coroutine while the binding's reply still needs the pump to deliver it, so `await` never resumes until the wall clock. The property belongs to that shared walk, not to this backend -- the worker-thread backend consumes the same function -- so the fix belongs in `packages/core/session` where every consumer benefits.
- **A wide binding REPLY expands host-side state per member.** Resolutions cross through `snapshotJsonValue` in [`@deepseek-ai/dsh-session`](../../core/session/README.md), whose `walkJsonValue` pushes one task frame per member, and binding resolution carries no seam-level byte cap. A legitimate reply of several million elements can therefore exhaust the host heap. The property belongs to that shared walk, not to this backend -- the worker-thread backend consumes the same function -- so the fix belongs in `packages/core/session` where every consumer benefits.
- **A cross-thread binding that the program joins with a synchronous `t.join()` can deadlock.** This is specific to the `process` isolation backend: the reply pump runs on the child's main event loop, so a worker thread that joins the main coroutine with `t.join()` blocks the loop the pump needs to deliver the binding's reply, and `await` never resumes until the wall clock. The worker-thread backend does not share this structure, so the fix belongs here, not in `packages/core/session`.