fix(robomp): run sandbox setup/teardown off the event loop safely

Workspace setup/teardown (git clone/fetch, worktree add/remove, chown)
ran synchronously on the asyncio dispatcher loop, so one stalled
subprocess froze the entire process.

- Offload every ensure_workspace/remove_workspace call to a worker thread
  via a new _run_workspace_op helper that drains the thread to completion
  on cancellation, so a cancelled event cannot reap/release a slot the
  setup thread still owns.
- Serialize same-repo setup with a per-repo threading.RLock while letting
  distinct repos run concurrently.
- Bound the direct git/chown subprocesses with a 120s timeout
  (returncode 124); treat a timed-out branch probe as an error rather
  than "branch absent" to avoid silently rebasing a follow-up onto the
  default branch and losing the PR's commits.
- When a timed-out worktree remove leaves the checkout behind, rmtree it
  and run `git worktree prune` so the pool's dangling registration cannot
  trip a later worktree add for the same path.

Adds regression tests for event-loop liveness, cancellation-safe offload,
per-repo lock serialization, subprocess timeout mapping, the branch-probe
timeout guard, and worktree-prune after a failed remove.

Op: correct
Restores: spec:dispatcher-event-loop-never-blocks-on-workspace-io
This commit is contained in:
metaphorics
2026-07-02 07:46:36 +09:00
parent 0823892295
commit 6d16c19a04
5 changed files with 655 additions and 152 deletions
+1
View File
@@ -152,6 +152,7 @@ def compute_key(repo_dir: Path, *, target: str | None = None) -> str:
capture_output=True,
check=True,
env=_git_safe_directory_env(repo_dir),
timeout=120.0,
)
lines = proc.stdout.splitlines()
if len(lines) != len(CACHE_KEY_PATHS):