Repository navigation
Fix cabal issue 7557: don’t run benchmarks in parallel or with the build - #12407
Open
ulysses4ever wants to merge 5 commits into
Open
ulysses4ever wants to merge 5 commits into
ulysses4ever wants to merge 5 commits into
Conversation
With `-j`, every plan node runs in parallel, and with per-component builds each benchmark suite is its own node, so `cabal bench` could run several benchmarks at the same time, from the same package or across a project. Concurrent benchmarks compete for resources and skew each other's results. Add a `benchLock`, created in `rebuildTargets` next to `registerLock` and `cacheLock`, and take it around the bench phase in `buildAndRegisterUnpackedPackage`. Benchmarks now run one at a time, while configuring and building other components stays parallel. The lock is taken outside `timedDelegate`, so `--build-timings` does not count time spent waiting for other benchmarks. Add a cabal-testsuite test with three benchmarks in two packages, run with `-j3`, each of which fails if another benchmark runs at the same time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016nzdeKCYFDCkWsyM6etgkL
Holding a lock while running a benchmark keeps benchmarks from running at the same time, but other components could still be built while a benchmark runs, which skews its results just as much. Instead, the bench phase of a package no longer runs its benchmarks, but records them. Once all the packages of the plan are built (still in parallel), `rebuildTargets` runs the recorded benchmarks one at a time, in plan order, and records their failures in the build outcomes. Unless we keep going after failures, no benchmark is run if a package failed to build, and no more benchmarks are run once one of them failed. See Note [Running benchmarks]. This replaces the `benchLock` introduced by the previous commit. The Sequential test now also checks that nothing is built once the first benchmark started: pkg-b's benchmark depends on a library, so that it is still being built when pkg-a's benchmarks are ready to run. The golden outputs of MultipleBenchmarks and T5309 change accordingly: builds now come before benchmark runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016nzdeKCYFDCkWsyM6etgkL
Bodigrim
reviewed
Oct 3, 2026
A benchmark suite is not a package of its own: the units of the install plan are single components, or whole packages when they cannot be built per component. Say so in Note [Running benchmarks], and refer to units rather than packages in the related comments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GVJmRffSh16Wx8JR7Kd1dC
Each update of the deferred benchmarks is a transaction of its own, and they are only read once all the units are built, so a TVar buys nothing over an IORef updated with atomicModifyIORef'. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GVJmRffSh16Wx8JR7Kd1dC
Collaborator
Author
|
@Bodigrim are you interested in approving? I thought I'd ask since you left a comment... |
Bodigrim
approved these changes
Oct 6, 2026
Bodigrim
left a comment
Collaborator
There was a problem hiding this comment.
Thanks for working on this!
zlonast
reviewed
Oct 7, 2026
Mikolaj
reviewed
Oct 7, 2026
Mikolaj
reviewed
Oct 7, 2026
Mikolaj
reviewed
Oct 7, 2026
jappeace
approved these changes
Oct 7, 2026
Member
|
Would the "significant" changelog flag make sense? Maybe a release note snippet? This is sure to break peoples workflows, often for good. |
…icant - Walk the plan's execution order directly instead of building an intermediate list of benchmarks; give `go` a signature and drop the now-redundant `BuildFailure` annotation. - changelog.d/issue-7557.md: set the PR number, add `significance: significant`, and spell out the user-visible consequences. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0176vAefrw8jPKXdh9h1wKuj
Collaborator
Author
|
@Mikolaj, agreed on significance, added. Also expanded the changelog entry with the user-visible consequences, which should be helpful for release-notes readers. Let me know if you have specific suggestions for it though. Thanks for the comments, I think I addressed all of them by basically accepting all of your suggestions and resolved the comments threads (I hope I wasn't too eager to do it, let me know!). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Disclaimer: this was created with Claude Code (Opus 5.5, ultra code first and then medium). It went through a couple of design iterations with me.
Fixes #7557:
cabal benchshould not run benchmarks in parallel.With
-j, every node of the install plan runs in parallel, and with per-component builds each benchmark suite is its own node. Socabal benchcould run several benchmarks at the same time, from the same package or across a project, and also while other components were still being compiled. Both skew benchmark results.Change.
cabal benchnow builds everything first and only then runs the benchmarks, one at a time:buildAndRegisterUnpackedPackage) no longer runs its benchmarks, but records them in aDeferredBenchmarksqueue threaded fromrebuildTargets.InstallPlan.executehas finished (so all builds, and downloads, are done),rebuildTargetsruns the recorded benchmarks one at a time, in plan order (InstallPlan.executionOrder), and records their failures in theBuildOutcomes, exactly as before.-jdefaults are unchanged. No new concurrency primitive is needed. SeeNote [Running benchmarks].Behaviour changes to be aware of
-j1: builds now come before benchmark runs. The golden outputs ofNewBuild/CmdBench/MultipleBenchmarksandRegression/T5309were re-accepted; both diffs are pure reorderings (same lines).--keep-going, no benchmark is run if any package fails to build (previously, benchmarks that happened to be built before the failure could run), and no further benchmark is run after one fails. With--keep-going, the benchmarks of all successfully built packages run, one at a time.build-type: Custom) with both a library and a benchmark, its dependents may now be built before its benchmark runs, and a failing benchmark no longer marks them asDependentFailed.cabalprocesses are not serialised.prs: 0000inchangelog.d/issue-7557.mdis a placeholder until there is an upstream PR number.History. The first commit serialised benchmark runs with a lock; review pointed out that builds could still overlap a running benchmark, so the second commit replaces the lock with deferring benchmarks until everything is built. Squashing on merge is fine.
Testing (Linux, GHC 9.4.7)
cabal-testsuite/PackageTests/NewBuild/CmdBench/Sequential:v2-bench -j3 allover three benchmarks in two packages (one benchmark depends on a library). Each benchmark fails if another one runs at the same time, and the test checks that noConfiguring/Preprocessing/Building/Compiling/Linkingline appears after the firstBenchmark …: RUNNING.... It fails 3/3 on master (benchmarks overlap) and 3/3 with only the first, lock-based commit (a benchmark is still being linked while another runs), and passes 3/3 with this PR.NewBuild/CmdBench/*,NewBuild/CmdTest/*,NewBuild/T3460,NewBuild/CmdBuild/OnlyConfigure,TestChangeDir,Target,Regression/T5309,AutogenModules/MainIsBench,AllowNewer,AllowOlder,Freeze/*,ShowBuildInfo/Complex/single,GHCJS/BuildRunner. No other golden file in cabal-testsuite has a build step after a benchmark run.cabal-install:test:integration-tests2: 29/30 pass; the one failure is a haddock test that needs boot-library.haddockfiles missing from Ubuntu's GHC package, unrelated to this change.--keep-going, a build failure with and without--keep-going, and--build-timings.QA Notes
In a project with several benchmark suites (e.g. in two packages, one of whose benchmarks depends on a library), run
cabal bench all -j4from a clean state:Configuring,Building,[n of m] Compiling,Linking) should appear before the firstBenchmark <name>: RUNNING....Benchmark <name>: FINISH(orERROR) before the next one starts, in the order of the build plan.Make one benchmark exit with failure: without
--keep-goingthe following benchmarks are not run; with--keep-goingthey are, and the failure is reported at the end. Make one component fail to compile: without--keep-goingno benchmark is run; with it, the benchmarks of the components that built are run.Template Α: This PR modifies behaviour or interface
significance: significantin the changelog file.🤖 Generated with Claude Code
https://claude.ai/code/session_016nzdeKCYFDCkWsyM6etgkL