Repository navigation
land: perf-spawn-without-fork - #506
aft-alfonso[bot] wants to merge 3 commits into
Conversation
Re-capture repository identity after probing git and cache only an unchanged stamp. Refuse symlink file identities so linked graft targets are always probed. Add a real-history seam regression and a symlinked-grafts regression. Keep the timeout test's process-group cleanup contract, but classify zombies as exited rather than alive merely because kill-zero succeeds. An explicit unreaped-child fixture covers that distinction; macOS uses a typed native query with zombie lookup enabled because sandboxed tests cannot spawn ps. Verified Linux search_index/format/effective_path (300 passed, 3 ignored), native macOS format/effective_path (163 passed), Windows tests cross-compile with warnings denied, and rustfmt. Independent mutations reddened only their expected tests, including missing group kill on Linux and macOS. Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
…probes Executor workers in the 8 GiB daemon were sampled inside fork -> _malloc_fork_parent -> _xzm_foreach_lock. A spawn census of the daemon (a DYLD interposer counting fork vs posix_spawn per thread) showed each edit/write paid one formatter fork plus about one `git rev-list --max-parents=0 HEAD` from the call-graph refresh, and in a daemon started from an AFT-governed shell every git helper forked too. - format::isolate_in_process_group uses process_group(0) instead of a pre_exec(setsid). std maps it to POSIX_SPAWN_SETPGROUP, so formatters, checkers and inspect's cargo check stay on posix_spawn. killpg(pid) on timeout is unchanged; a new test checks that grandchildren die. - effective_path::command_with_path: when the PATH it must pass differs from the process PATH (startup scrubs the outer gh-shim dir after the effective PATH is computed), a bare program is resolved against that PATH in the parent, the way execvp searches it, and spawned by absolute path with arg0 and PATH kept. Relative or empty PATH entries keep the old child-side lookup. - search_index::repo_root_commit_with_retry, behind artifact_cache_key, reuses a Commit result while the repository state that decides HEAD is unchanged: HEAD and branch bytes, gitdir/commondir pointers, and the identities of packed-refs, shallow, grafts and refs/replace. It never caches when git env overrides, reftable or non-branch HEADs are in play. The configure path is untouched. Census (20 edits): before 26 children, 20-26 of them forks; after 20 children (rustfmt only), 0 forks, no rev-list. Background bash keeps its fork path. Tests: per-thread pthread_atfork fork counter seam; no-fork tests for formatter/checker and distinct-PATH spawns; a root-commit reuse and invalidation test. Four mutations each reddened only their own test. Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
Re-capture repository identity after probing git and cache only an unchanged stamp. Refuse symlink file identities so linked graft targets are always probed. Add a real-history seam regression and a symlinked-grafts regression. Keep the timeout test's process-group cleanup contract, but classify zombies as exited rather than alive merely because kill-zero succeeds. An explicit unreaped-child fixture covers that distinction; macOS uses a typed native query with zombie lookup enabled because sandboxed tests cannot spawn ps. Verified Linux search_index/format/effective_path (300 passed, 3 ignored), native macOS format/effective_path (163 passed), Windows tests cross-compile with warnings denied, and rustfmt. Independent mutations reddened only their expected tests, including missing group kill on Linux and macOS. Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
c5c7503 to
61135d2
Compare
| directories.into_iter().find_map(|directory| { | ||
| let candidate = directory.join(program); | ||
| let text = std::ffi::CString::new(candidate.as_os_str().as_bytes()).ok()?; | ||
| let executable_file = std::fs::metadata(&candidate).is_ok_and(|meta| meta.is_file()) | ||
| // SAFETY: `text` is a valid NUL-terminated path for the call. | ||
| && unsafe { libc::access(text.as_ptr(), libc::X_OK) } == 0; | ||
| executable_file.then_some(candidate) |
There was a problem hiding this comment.
If the effective PATH differs from the process PATH, resolve_in_path stops at the first executable file. If that file is a script whose interpreter is missing, spawning its absolute path fails instead of trying later directories as execvp would. A formatter, checker, or Git command then fails to start even when a working copy exists later in PATH.
Preserve the fallback to later candidates when the selected file cannot run.
| let mut watched = Vec::new(); | ||
| for path in [ | ||
| common_dir.join("packed-refs"), | ||
| common_dir.join("shallow"), | ||
| common_dir.join("info").join("grafts"), | ||
| common_dir.join("refs").join("replace"), | ||
| ] { | ||
| // `refs/replace` holds one file per replacement, so its entry | ||
| // list is exactly what matters there. | ||
| watched.push(RootCommitFileIdentity::of(&path, true)?); |
There was a problem hiding this comment.
RootCommitStamp does not track Git configuration. If an existing replacement changes ancestry, changing core.useReplaceRefs changes the roots returned by git rev-list without changing any watched file. The daemon keeps returning the old root and artifact key until another watched file changes.
Include the effective replacement setting in the stamp, or bypass caching when its changes cannot be tracked.
Accepted agent delivery; branch-keyed search descriptor included.
Summary by cubic
Stops formatter, checker, and git spawns in the daemon from forking the process: they now spawn through
posix_spawn, and the per-edit git root-commit probe is reused until the repository's git files change. Forking the large multi-threaded daemon took every malloc lock and stalled allocation process-wide on macOS.Details
isolate_in_process_groupsetsprocess_group(0)instead of apre_execsetsid, keeping child spawns on theposix_spawnpath; timeout kills still reach grandchildren through the process group, and unreaped zombies count as exited, not alive.argv[0]and child environment; relative or empty PATH entries keep the child-side lookup.Written for commit 61135d2. Summary will update on new commits.
Fix the tool-search fallback and stale root keys before merging.
Findings
Summary
The PR reduces daemon forks and reuses Git root probes.
Reviews (3) · Last reviewed commit: "mason: validate root-commit cache stamps..." · Reviewed by Greptile