Skip to content

Report the error that caused in_parallel_with_slice() to stop, fixing a flaky test#2708

Merged
Sebastian Thiel (Byron) merged 2 commits into
GitoxideLabs:mainfrom
nikicat:pr-parallel-causal-error
Jul 11, 2026
Merged

Report the error that caused in_parallel_with_slice() to stop, fixing a flaky test#2708
Sebastian Thiel (Byron) merged 2 commits into
GitoxideLabs:mainfrom
nikicat:pr-parallel-causal-error

Conversation

@nikicat

@nikicat Nik B (nikicat) commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Closes #2701.

The race

When a consumer in in_parallel_with_slice() fails, the failing thread sets the shared stop flag and returns its error. Other worker threads observe the flag and bail out with a reactive error — in Tree::traverse() that is Error::Interrupted (resolve.rs). Threads were then joined in thread-id order and the first error encountered was returned, so whenever a merely-interrupted thread had a lower id than the actually-failing one, Interrupted masked the real cause.

That is exactly the mechanism Amey Pawar (@ameyypawar) proposed in #2701: respects_alloc_limit_bytes runs with thread_limit: None, every worker races to fail with OutOfMemory, and sometimes the surfaced error chain only contains Interrupted, failing the assertion on "Entry too large to fit in memory". Besides the Windows runs in the issue, the same failure showed up on test (ubuntu) and test-32bit (i386) in the runs of #2704/#2705, e.g. this job — so it is not Windows-specific.

The fix

The first consumer to fail marks its error as the cause, and when collecting join results the causal error is preferred over reactive ones. Causality is tracked on a flag private to the harness (swap decides the winner) rather than on the stop flag itself, since consumers receive the stop flag and may legitimately set it themselves before returning an error (as gitoxide-core's corpus engine does) — Codex rightly pointed out that an earlier revision of this PR would misclassify the causal error in that case. External interrupts are unaffected: there no consumer failure precedes the stop signal, so the first reactive error (Interrupted) is returned as before.

This fixes the library-level error masking rather than pinning the test to a single thread, so the test keeps exercising the multi-threaded path — callers now deterministically see the error that actually stopped the traversal instead of a bystander's Interrupted.

Testing

🤖 Generated with Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f22c4d632e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "Codex (@codex) address that feedback".

Comment thread gix-features/src/parallel/in_parallel.rs Outdated
Previously, when a consumer failed, worker threads were joined in
thread-id order and the first error encountered was returned. Threads
that merely noticed the stop signal can bail out with a reactive error
like `Interrupted` though, and when such a thread had a lower id than
the failing one, that reaction would mask the actual cause of the stop.

Now the first consumer to fail owns the causal error, and it is
preferred over reactive errors when the join results are collected.
Causality is tracked on a flag private to the harness as consumers may
set the exposed stop flag themselves before returning their error.

This makes the error of `Tree::traverse()` in `gix-pack` deterministic,
and with it the previously intermittent assertion on `OutOfMemory` in
`bundle::write_to_directory::respects_alloc_limit_bytes` (GitoxideLabs#2701).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@nikicat
Nik B (nikicat) force-pushed the pr-parallel-causal-error branch from f22c4d6 to b0aea79 Compare July 10, 2026 07:34
@nikicat

Copy link
Copy Markdown
Contributor Author

Codex (@codex) review

Co-authored-by: GPT 5.6 <codex@openai.com>
@Byron
Sebastian Thiel (Byron) merged commit 3c95dc6 into GitoxideLabs:main Jul 11, 2026
32 checks passed
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.

The test bundle::write_to_directory::respects_alloc_limit_bytes fails intermittently on Windows

2 participants