mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-27 13:40:37 +00:00
* fix(sandbox): keep a concurrent quarantine fence when retire() races it `SandboxQuarantine.retire()` unlinks every generation record and then calls `directory.rmdir()` unguarded. A peer fencing the same logical sandbox between the `iterdir()` and the `rmdir()` — a concurrent teardown of a replacement, which this caller's cross-worker lease does not exclude because it fences only its own logical ID — leaves a fresh record in the directory, so `rmdir()` raises ENOTEMPTY. That OSError escapes `_retire_absent_quarantine`'s teardown path and aborts the release flow on a fence a peer just wrote. Its sibling `retire_replaced_generations()` already guards the identical `rmdir` with `try/except OSError` for exactly this reason. Apply the same guard here: the fresh record proves the directory is live, so it is kept and teardown does not abort. Regression test replays the race deterministically by wrapping `Path.rmdir`; red on main (OSError: directory not empty), green with the guard. * docs(sandbox): scope the retire() fence guard to what it actually holds Review feedback from @willem-bd on #6961: two non-blocking docstring accuracy notes, both about the paragraph added by e62e0c3c. Docstring-only, no behaviour change. 1. The mechanism was described as if a peer teardown of a replacement were a different key. It is not. Per `_claim_ownership` / `_held_teardown_lease` the teardown lease is keyed by the logical ID and a replacement shares that ID, so while the `del:` marker is held a peer's claim — and an acquire-side `take()` for the replacement — *are* excluded. What is not guaranteed is that the exclusion is still held at `rmdir` time: it is a TTL lease, not a lock, so the heartbeat can lose it mid-teardown (the documented "Lost the teardown exclusion" path stops refreshing while the stop continues) and the config schema permits TTLs below a normal stop. Reworded to that effect, which also corrects the same sentence in e62e0c3c's message. 2. The paragraph read as if the listing → `rmdir` window were the only one. Added an explicit scope clause: the guard is best-effort rather than full fence preservation. A peer record landing before the listing is still unlinked along with the rest, and the unlink loop has unguarded sibling windows of its own — a concurrent `retire()` can remove the directory between the existence check and the listing (`FileNotFoundError`), and on Windows a peer's `mark()` still holding the fresh record open for its `fsync` raises `PermissionError`. Validation: `ruff check` and `ruff format --check` pass on the file and across `backend/`; the new regression test plus the existing quarantine suite are 9 passed; `scripts/check_agent_guidance.py` reports 46 AGENTS.md / 0 errors. --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>