Skip to content

fix(sandbox): worker/本机路由满池时 sandbox.get 同样返回 down(#649 补全) - #662

Merged
yifanxuaaa merged 2 commits into
Ephemeral-AI-Lab:layerfs_v2_0_0from
jiangkoumo:fix/sandbox-get-worker-down
Nov 13, 2026
Merged

yifanxuaaa merged 2 commits into
Ephemeral-AI-Lab:layerfs_v2_0_0from
jiangkoumo:fix/sandbox-get-worker-down

Conversation

@jiangkoumo

Copy link
Copy Markdown
Collaborator

相关:#649、#655

背景

#655 让转发路由在 daemon 满池时把容量通知带进 sandbox.get 的 down。worker 与本机自己拨的路由收到的是同一个拒绝,却仍以 Capacity 错误结束整个 get,两条路由对同一故障的回答不一致。

改动

  • get.rs:仅当拒绝为 Capacity 且 detail.resource == daemon_control_slot 时,返回 state=running、workspaces=[]、down_reason(daemon 原话)、down{code,resource,pool_vacant,reserved_vacant,retained};不二次拨号,daemon_capacity 缺席。
  • direct.rs:down_detail 改为 pub(crate),与转发路由共用同一打标签动作。
  • 其他拒绝(其他 resource、Busy、已准入连接折叠成 {phase} 的拒绝)行为不变。
  • 文档:sandbox/get.md、sandbox/README.md、routing/daemon-channel.md。
  • 生产代码 +10 行。已 merge 最新 layerfs_v2_0_0(be317c045)。

验证

  • 新增 api/crates/layerfs-api/tests/sandbox_capacity_worker.rs(3 条):改前失败(exit 100),改后 3 passed;断言 down 只有 5 个键、worker 只被问 1 次。
  • Linux 全量(照 ci.yml fast + nightly slow/mount,Rust 1.85.1,merge 上游后 HEAD e73d50c):fast 1883 passed、slow 146 passed、mount 45/45。
  • clippy -D warnings、fmt、product boundary 通过。

未覆盖

  • 本机路由未做端到端,按同一代码路径推断。
  • 无真实 worker/daemon 满池场景;满池 get 仍需等 60s 准入超时。

…emeral-AI-Lab#649)

A Sandbox whose Workspace list runs in a machine's worker, or in this process
(`Executor::Local`), sends the same daemon the same call, and a daemon whose
pool has no vacant control slot refuses it before any handshake. The forwarded
route already keeps that notice as the partial `down` counts (Ephemeral-AI-Lab#649); on these
routes the refusal is the answer the call returned - `Capacity` with
`resource: daemon_control_slot` and the notice's counts, and no `code` - and
`get` propagated it, so the whole read failed `Capacity` with no `down_reason`
and no `down`. One event read two ways is a defect.

`get` now reads that one refusal as the forwarded route's down Sandbox:
`slot_refused` names it (`Capacity` on `daemon_control_slot`, and nothing
else), and the record is answered with its derived `state`, `workspaces: []`,
the refusal's own message as `down_reason`, and `down` from the same notice
through `direct::down_detail` (now `pub(crate)`, so both routes use one
tagging action instead of a second implementation). `daemon_capacity` is
absent: a notice is never mapped into that model, and the get dials nothing
twice. A `Capacity` on another resource, a refusal an admitted connection
returned (`values::refusal`'s `{phase}`), and `Busy` still fail the get. No
RPC, no retry, no new wait: the admission window is still the product's 60 s,
and mount, commit and unmount keep their own `Capacity`.

Docs: `docs/architecture/api/sdk/sandbox/get.md` (the `down_reason` and
`down` rows, the route diagram and the errors table),
`docs/architecture/api/sdk/sandbox/README.md` (`get`'s row and the capacity
paragraph), and `docs/architecture/control/routing/daemon-channel.md`
(`sandbox.get`'s cell).

Tests: new `layerfs-api` --test sandbox_capacity_worker (3 passed, 0.03 s). A
stand-in worker answers its secret, reads one request and returns the full
pool's `Capacity`; the get answers `state: running`, `workspaces: []`, the
refusal's words and `down { code: Capacity, resource: daemon_control_slot,
pool_vacant: 0, reserved_vacant: 1, retained: 3 }` and no other field, with no
`daemon_capacity`, asking the worker once. Its two other cases pin that
`Capacity` on another resource and `Busy` still fail the get, and that a
`Capacity` carrying only `{phase}` fails it too. The same file on the
unmodified head 03883a6: 1 failed / 2 passed (exit 100; the positive case
got the bare `Capacity`), and with this change 3 passed.

Checks: layerfs-sandbox 27 passed; layerfs-api --test sandbox_capacity (6 fast
passed, and the two 60 s cases once each: 60.30 s and 60.37 s) with --test
sandbox_capacity_worker, --test sandbox_family, --test sandbox_records and
--test sandbox_lost 13 passed / 2 filtered; clippy --workspace --exclude
layerfs-node --all-targets -D warnings clean; `cargo fmt --all -- --check`
clean; tools/check_product_boundary.py PASS (1352 files); tools unittest 182
OK.

Production LOC: 158705 -> 158715 (delta +10)
Scope and method: first-party production Rust/SQL/JavaScript in core, engine,
control and api (tests, fixtures, tools and docs excluded), counted with
tools/production_loc.py on a `git archive` of 03883a6 and of this commit's
staged tree.
Control 39012 -> 39022 (+10, all of it layerfs-sandbox 8238 -> 8248); core
39787, engine 74342 and api 5564 unchanged. The new test file is not counted.
Reference absent (retired R9c; last counted 65,417 at tree 498dd19).
Copilot AI balanced review requested due to automatic review settings November 12, 2026 08:00

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@jiangkoumo

Copy link
Copy Markdown
Collaborator Author

@yhcedpn @mengmengmengqiang 这个 PR 补上 #655 没覆盖到的 worker 与本机路由:daemon 满池时 sandbox.get 同样返回 down,而不是整个报 Capacity。形状与 #655 一致(复用 down_detail),生产代码 +10 行。已合入最新 layerfs_v2_0_0,Linux 全量 CI 通过。有空麻烦看一下,谢谢!

@yifanxuaaa
yifanxuaaa merged commit cc51cd7 into Ephemeral-AI-Lab:layerfs_v2_0_0 Nov 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants