Question
The hourly broad fetch (git fetch origin +refs/heads/*:refs/heads/* --tags --prune, repo_manager.py:158) currently runs on the foreground launch path under the repo flock. Per the #144 decision, it belongs in the detached child devlaunch already spawns (update_cache_background, dl.py:408-436 → dl --update-cache, dl.py:2210). Make the child sweep the cache; leave the foreground untouched for now (the transient double-gate is harmless — last_fetched is shared).
- Add
try_hold_lock to devlaunch/worktree/locks.py: non-blocking sibling of hold_lock (which already attempts LOCK_NB first, locks.py:56); yields held-or-not, never waits, never unlinks. Document the repo→metadata lock ordering as an invariant in the module docstring while there.
- In the
--update-cache child, after update_completion_cache(): for each repo in storage.list_repositories(), under try_hold_lock on repo_manager.lock_path(...), run lazy_fetch; skip silently when contended or on fetch failure (it retries next interval).
Failing tests first: (1) child with elapsed interval fetches and advances last_fetched; (2) child with the repo lock held elsewhere skips without blocking and records no fetch; (3) child within interval fetches nothing. Seam: fake subprocess.run recording git argv, prior art test/test_devpod_spawn_counts.py.
Files: devlaunch/worktree/locks.py, devlaunch/dl.py (--update-cache branch ~:2210), devlaunch/worktree/repo_manager.py (no signature change here).
Question
The hourly broad fetch (
git fetch origin +refs/heads/*:refs/heads/* --tags --prune, repo_manager.py:158) currently runs on the foreground launch path under the repo flock. Per the #144 decision, it belongs in the detached child devlaunch already spawns (update_cache_background, dl.py:408-436 →dl --update-cache, dl.py:2210). Make the child sweep the cache; leave the foreground untouched for now (the transient double-gate is harmless —last_fetchedis shared).try_hold_lockto devlaunch/worktree/locks.py: non-blocking sibling ofhold_lock(which already attemptsLOCK_NBfirst, locks.py:56); yields held-or-not, never waits, never unlinks. Document the repo→metadata lock ordering as an invariant in the module docstring while there.--update-cachechild, afterupdate_completion_cache(): for each repo instorage.list_repositories(), undertry_hold_lockonrepo_manager.lock_path(...), runlazy_fetch; skip silently when contended or on fetch failure (it retries next interval).Failing tests first: (1) child with elapsed interval fetches and advances
last_fetched; (2) child with the repo lock held elsewhere skips without blocking and records no fetch; (3) child within interval fetches nothing. Seam: fakesubprocess.runrecording git argv, prior art test/test_devpod_spawn_counts.py.Files: devlaunch/worktree/locks.py, devlaunch/dl.py (
--update-cachebranch ~:2210), devlaunch/worktree/repo_manager.py (no signature change here).