Skip to content

land: perf-spawn-without-fork - #506

Open
aft-alfonso[bot] wants to merge 3 commits into
mainfrom
land/perf-spawn-without-fork
Open

aft-alfonso[bot] wants to merge 3 commits into
mainfrom
land/perf-spawn-without-fork

Conversation

@aft-alfonso

@aft-alfonso aft-alfonso Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

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_group sets process_group(0) instead of a pre_exec setsid, keeping child spawns on the posix_spawn path; timeout kills still reach grandchildren through the process group, and unreaped zombies count as exited, not alive.
  • A bare program name whose effective PATH differs from the process PATH is resolved against that PATH in the parent and spawned by absolute path with the same argv[0] and child environment; relative or empty PATH entries keep the child-side lookup.
  • Root-commit results are cached keyed on HEAD and the git files that decide it; the cache holds only when a stamp re-captured after probing matches. Nothing is cached for git env overrides, reftable, symlink identities, or non-branch HEADs.
  • Background bash keeps its fork path.

Written for commit 61135d2. Summary will update on new commits.

View guided diff Turn on auto-fix

RetriggerView in GreptileConfidence Score: 3/5

Fix the tool-search fallback and stale root keys before merging.

Findings

  1. P1 Working tools are skipped ▶
  2. P1 History changes keep old keys ▶

Summary

The PR reduces daemon forks and reuses Git root probes.

  • Formatters and Git helpers avoid fork-based launches.
  • Repeated lookups reuse a Git root-commit result while repository history stays unchanged.

Reviews (3) · Last reviewed commit: "mason: validate root-commit cache stamps..." · Reviewed by Greptile

Comment thread crates/aft/src/search_index.rs Outdated
Comment thread crates/aft/src/search_index.rs
Comment thread crates/aft/src/format.rs
ualtinok added a commit that referenced this pull request Oct 18, 2026
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>
ualtinok and others added 3 commits October 18, 2026 03:07
…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>
@ualtinok
ualtinok force-pushed the land/perf-spawn-without-fork branch from c5c7503 to 61135d2 Compare October 18, 2026 01:08
Comment on lines +163 to +169
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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Working tools are skipped

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.

Comment on lines +7433 to +7442
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)?);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 History changes keep old keys

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.

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.

1 participant