Skip to content

Conversation

@RossComputerGuy
Copy link
Member

@RossComputerGuy RossComputerGuy commented Jan 23, 2026

Motivation

Prevent nix_store_parse_path from failing to parse a valid store path.

Context

Mutlithreaded evaluation & instantiation currently will have issues because the async path writer isn't synced up.

Summary by CodeRabbit

  • Refactor
    • Added asynchronous path-writing to store and evaluation flows to improve responsiveness when preparing store paths.
  • Bug Fix
    • Wait for store paths to be ready before returning, reducing race conditions and improving reliability.

@coderabbitai
Copy link

coderabbitai bot commented Jan 23, 2026

📝 Walkthrough

Walkthrough

Embeds an AsyncPathWriter into created Store objects, passes it into EvalState where applicable, and adds synchronization by waiting for a parsed store path via asyncPathWriter->waitForPath(...) before returning the StorePath.

Changes

Cohort / File(s) Summary
Store creation & parse flow
src/libstore-c/nix_api_store.cc
Wrap nix::openStore(...) result with nix::AsyncPathWriter::make(store); return Store containing both store and asyncPathWriter; call asyncPathWriter->waitForPath(s) after parsing store paths.
Store struct update
src/libstore-c/nix_api_store_internal.h
Added member nix::ref<nix::AsyncPathWriter> asyncPathWriter; and included nix/store/async-path-writer.hh.
EvalState constructor & builder
src/libexpr/eval.cc, src/libexpr/include/nix/expr/eval.hh, src/libexpr-c/nix_api_expr.cc, src/libexpr-c/nix_api_expr_internal.h
Added an asyncPathWriter field to the eval builder; extended EvalState constructor signature to accept an optional std::shared_ptr<AsyncPathWriter> and use it (falling back to AsyncPathWriter::make(store) if null). Initialized builder's asyncPathWriter and passed it through EvalState construction.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant StoreModule as StoreModule
    participant Store as nix::Store
    participant AsyncWriter as AsyncPathWriter
    participant Eval as EvalState

    Caller->>StoreModule: openStore(uri, params)
    StoreModule->>Store: nix::openStore(...)
    StoreModule->>AsyncWriter: AsyncPathWriter::make(store)
    StoreModule-->>Caller: return Store{store, asyncPathWriter}

    Caller->>StoreModule: nix_store_parse_path(path_str)
    StoreModule->>StoreModule: parse path -> s
    StoreModule->>AsyncWriter: waitForPath(s)
    AsyncWriter-->>StoreModule: ready
    StoreModule-->>Caller: return StorePath(s)

    Caller->>Eval: construct EvalState(..., buildStore, asyncPathWriter)
    Eval->>AsyncWriter: use provided asyncPathWriter (or make(store) if null)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • edolstra
  • cole-h

Poem

🐇 I hop through stores with eager paws,

I stitch new writers into code's small laws,
I wait for paths to wake and gleam,
Async threads hum like a dream,
Hooray — the store and writer team!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding synchronization logic to wait on the async path writer during store path parsing in the C API.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch RossComputerGuy/fix-store-path-parse-capi

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Jan 23, 2026

@github-actions github-actions bot temporarily deployed to pull request January 23, 2026 19:38 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/libstore-c/nix_api_store.cc`:
- Around line 130-132: nix_store_parse_path currently creates an AsyncPathWriter
via AsyncPathWriter::make and calls waitForPath on a path returned from
parseStorePath even though that path was never added with addPath, which can
lead to calling .get() on an uninitialized future and indefinite blocking; fix
by changing nix_store_parse_path to avoid calling AsyncPathWriter::waitForPath
for parsed-only paths (skip the async writer path), or ensure the path is first
registered with AsyncPathWriter::addPath before waiting, add a bounded timeout
when calling future.get() inside waitForPath, and update AsyncPathWriter::make
to return an optional/nullable writer (or a no-op implementation) so caller code
gracefully handles stores that do not support async path writing (adjust callers
of AsyncPathWriter::make and waitForPath accordingly).

Comment on lines 130 to 132
auto asyncPathWriter = nix::AsyncPathWriter::make(store->ptr);
nix::StorePath s = store->ptr->parseStorePath(path);
asyncPathWriter->waitForPath(s);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -type f \( -name "*.hh" -o -name "*.h" \) | xargs grep -l "AsyncPathWriter" | head -5

Repository: DeterminateSystems/nix-src

Length of output: 210


🏁 Script executed:

find . -type f \( -name "*.cc" -o -name "*.cpp" \) | xargs grep -l "AsyncPathWriter" | head -5

Repository: DeterminateSystems/nix-src

Length of output: 191


🏁 Script executed:

rg -n "class AsyncPathWriter" --type cc --type h

Repository: DeterminateSystems/nix-src

Length of output: 96


🏁 Script executed:

rg -n -A10 "waitForPath" --type cc --type h

Repository: DeterminateSystems/nix-src

Length of output: 96


🏁 Script executed:

rg -n -A5 "AsyncPathWriter::make"

Repository: DeterminateSystems/nix-src

Length of output: 1375


🏁 Script executed:

cat -n src/libstore/include/nix/store/async-path-writer.hh

Repository: DeterminateSystems/nix-src

Length of output: 624


🏁 Script executed:

cat -n src/libstore/async-path-writer.cc

Repository: DeterminateSystems/nix-src

Length of output: 6152


🏁 Script executed:

cat -n src/libstore-c/nix_api_store.cc | sed -n '120,140p'

Repository: DeterminateSystems/nix-src

Length of output: 811


🏁 Script executed:

rg -B15 "AsyncPathWriter::make" src/libstore-c/nix_api_store.cc | head -25

Repository: DeterminateSystems/nix-src

Length of output: 647


The code calls waitForPath() on a path that was never added to the async writer, creating undefined behavior.

The nix_store_parse_path() function creates an AsyncPathWriter and then calls waitForPath() on a path obtained from parseStorePath(). However, waitForPath() only tracks paths added via addPath()—not paths created by parseStorePath(). When the path is not found in the futures map (line 109), the lambda returns early with a void return (line 110), but the calling code then tries to call .get() on an uninitialized future variable, resulting in undefined behavior.

Additionally:

  • waitForPath() calls future.get() without any timeout (line 113), risking indefinite blocking if an async write stalls.
  • AsyncPathWriter::make() unconditionally creates an AsyncPathWriterImpl regardless of store type, with no graceful degradation for stores that may not support async path writing.

The likely intent was either to skip the async writer call for simple parse operations, or to use a different synchronization mechanism for parsed paths.

🤖 Prompt for AI Agents
In `@src/libstore-c/nix_api_store.cc` around lines 130 - 132, nix_store_parse_path
currently creates an AsyncPathWriter via AsyncPathWriter::make and calls
waitForPath on a path returned from parseStorePath even though that path was
never added with addPath, which can lead to calling .get() on an uninitialized
future and indefinite blocking; fix by changing nix_store_parse_path to avoid
calling AsyncPathWriter::waitForPath for parsed-only paths (skip the async
writer path), or ensure the path is first registered with
AsyncPathWriter::addPath before waiting, add a bounded timeout when calling
future.get() inside waitForPath, and update AsyncPathWriter::make to return an
optional/nullable writer (or a no-op implementation) so caller code gracefully
handles stores that do not support async path writing (adjust callers of
AsyncPathWriter::make and waitForPath accordingly).

@RossComputerGuy RossComputerGuy force-pushed the RossComputerGuy/fix-store-path-parse-capi branch from 3eda24e to 7bff820 Compare January 23, 2026 22:29
@github-actions github-actions bot temporarily deployed to pull request January 23, 2026 22:32 Inactive
@edolstra
Copy link
Collaborator

edolstra commented Feb 2, 2026

I don't think this is correct. If the code isn't currently using AsyncPathWriter, then instantiating an AsyncPathWriter to call waitForPath() on won't actually do anything.

You probably want to do this in libexpr-c or libflake-c and call EvalState::{waitForPath,waitForAllPaths}(). There are already a couple of state->state.waitForAllPaths() calls in libexpr-c/nix_api_expr.cc.

Also, nix_store_parse_path() is probably not the best place to do this. You want to synchronize just before consuming the derivation (e.g. when calling readDerivation() or building it.)

@RossComputerGuy
Copy link
Member Author

Hmm, I wasn't sure what the fix would be since I saw this failure when calling nix_store_parse_path. This would cause functions to fail before I even did realisation or path info reading.

@github-actions github-actions bot temporarily deployed to pull request February 3, 2026 09:45 Inactive
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.

2 participants