Skip to content

mcpx(progspec): concurrent resolves of one nix spec each run their own nix build — the memo only dedupes sequential calls #2826

Closed

Description

@dezren39

mcpx(progspec): concurrent resolves of one nix spec each run their own nix build — the memo only dedupes sequential calls

Repo: developing-today/code · Main: 1281cb37 · File: pkgs/mcpx/internal/progspec/progspec.go — memo check at lines 136-158, resolveNixMemoized at 168-177 (new in PR #2585, merged 2026-10-30)

Summary

progspec memoises resolved nix specs so "a daemon that restarts a server repeatedly pays the build once" (package doc, lines 22-24). The memo is only consulted before resolving and only populated after a resolution finishes:

memoMu.Lock()
if r, ok := memo[key]; ok { ... return r, nil }
memoMu.Unlock()
return resolveNixMemoized(ctx, spec, dir, key)   // resolves, then stores

There is no in-flight tracking, so N concurrent Resolve/ResolveInDir calls for the same spec — the normal shape when a daemon starts or restarts several servers at once, or several servers share one nix: command — each spawn their own nix build --no-link --print-out-paths. Nix's store locking serialises the actual build, but every invocation still pays full flake evaluation (the expensive part for a large flake such as nixpkgs), and each is independently allowed up to DefaultTimeout (3 minutes, line 43).

Evidence (verified this run, real module)

Laptop probe (~/git/fleet/probe-2026-10-30-laneA-progspec/; the repo's real progspec.go, md5 69fd5418546542c4de1f127906d59ed5 identical on sandbox main 1281cb37 and the laptop copy; stub nix on PATH in the style of the package's own tests, sleeping 0.3 s per invocation and counting calls):

Scenario nix invocations
8 goroutines calling Resolve(ctx, "nix:nixpkgs#faketool") concurrently, same spec/dir 8 (elapsed 312 ms — fully parallel, no dedupe)
2 sequential resolves of the same spec (existing test TestResolveNixSpecBuildsOnceAndSpawnsTheStorePath) 1 (memo works sequentially — not in dispute)

Callers that resolve at spawn time, all reachable concurrently: pkgs/mcpx/internal/mcpclient/stdio.go:103 (ResolvePathInDir), pkgs/mcpx/internal/mcpclient/supervised.go:243 (spawnChild, per child restart) and :609 (ResolvePathFreshInDir).

Why it matters

The memo's stated purpose is paying the build once per daemon. Concurrent first-resolves — daemon startup with several nix: servers, a supervisor restarting children together, pooled servers sharing one spec — silently pay it once per caller instead, multiplying the slowest, most failure-prone step of launch (a network/flake evaluation with a 3-minute timeout) exactly when the daemon is busiest. It also multiplies the failure surface: one transient nix failure can surface as N slightly-different launch errors for what is logically one resolution.

Acceptance criteria

  • Concurrent Resolve/ResolveInDir calls for the same memo key share one in-flight nix build: a regression test with the stub-nix harness (slow stub, N concurrent resolves) counts exactly 1 invocation and all callers receive the same Resolved.
  • A failed in-flight resolution is not memoised (today's behaviour): every waiter receives that error, and a later call retries the build.
  • The deleted-output eviction behaviour (TestResolveNixSpecMemoEvictsDeletedOutput) and the per-directory memo keying still hold; a resolution whose memo entry is evicted mid-flight does not resurrect a dead path.
  • ResolveFreshInDir's contract is preserved or explicitly re-pinned in its doc comment (it bypasses the memo read by design — triage decides whether fresh callers also share an in-flight resolution or stay fully independent).
  • Existing pkgs/mcpx/internal/progspec tests still pass (go test ./internal/progspec -count=1 in CI / on the laptop — not go test ./... locally, per pkgs/mcpx/internal/AGENTS.md).

Dedup notes: searched open + closed issues this run for progspec / singleflight / concurrent resolve / memo — no hit. Related but distinct: #1921 (the feature this shipped in, still open for its environment half), #1908 (a durable, cross-process content-addressed cache for flake resolutions — this issue is only the in-process concurrent case), #2754/#2726 (mcpx result-validation / settings panics, unrelated paths). NOT self-solved: the singleflight shape (and the ResolveFreshInDir question above) is triage's pin, and fleet verification for Go changes runs on the laptop/CI, not in a creator run.

Activity

  1. added
    kind/performancePerformance problem or optimization
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    priority/normalOrdinary priority (the default)
    app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)
    effort/sSmall: expected under 2 hours of work
    on Oct 30, 2026
  2. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator-code-a
    • Session / ID: cron job new-issue-creator-code-a, run 2026-10-30 15:05 CDT
    • Found via: filed this run
    • Task: Filed this issue: pkgs/mcpx/internal/progspec/progspec.go:136-177 (new in PR mcpx: program-spec resolver — nix:<ref>#<attr> resolves once to a store path (#1921 Part 1) #2585) memoises resolved nix specs only between sequential calls — the memo is checked before resolving and populated after, with no in-flight tracking, so concurrent resolves of one spec each run their own nix build. Verified on the laptop with the real module (md5-matched to main 1281cb37): 8 concurrent Resolve calls for nix:nixpkgs#faketool against a 0.3 s stub nix produced 8 stub invocations (312 ms elapsed, fully parallel), while the sequential control in the package's own tests builds once.
    • Expectation: effort/s — done when concurrent resolves of one memo key share a single in-flight build (stub-counted regression test), failures are not memoised, and the ResolveFreshInDir sharing question is pinned. Singleflight shape is triage's pin.
    • Links: probe (laptop) ~/git/fleet/probe-2026-10-30-laneA-progspec/ · evidence body in goal hidden_files/issue-code-progspec-concurrent-body.md
  3. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  4. 82 remaining items

  5. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    agent-note:v1 — holder: repo-worker-code; action: picked_up; at: 2026-11-02T12:51:45Z. Queue task 5431 claimed, resuming from verified ownership stop. Fresh metadata confirms #2826 is OPEN and assigned to dezren39; PR #4246 is OPEN, non-draft, authored by and assigned to dezren39. No PR internals, branch, worktree, CI, or reviews inspected.

  6. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 2, 2026
  7. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    agent-note:v1 — holder: repo-worker-code; action: parked; at: 2026-11-02T12:52:10Z. Task 5431 parked for 30 minutes: fresh metadata confirms issue #2826 and open PR #4246 are Drew-owned. No PR contents, branch, worktree, CI, or reviews inspected. Resume only after explicit ownership handoff/release, then revalidate metadata before any work.

  8. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  9. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

  10. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done — PR #4246 merged

  11. removed
    task/triageQueue packet kind: verify, reproduce, and route this item
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    work/assignedQueue worker lifecycle state
    on Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)effort/sSmall: expected under 2 hours of workkind/performancePerformance problem or optimizationpriority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator job

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions