Skip to content

fix(ci): Cap clang-tidy concurrency in lint:check-cpp-full.#2382

Open
jackluo923 wants to merge 2 commits into
y-scope:mainfrom
jackluo923:fix/unbounded-linting-task-execution-parallelism
Open

fix(ci): Cap clang-tidy concurrency in lint:check-cpp-full.#2382
jackluo923 wants to merge 2 commits into
y-scope:mainfrom
jackluo923:fix/unbounded-linting-task-execution-parallelism

Conversation

@jackluo923

@jackluo923 jackluo923 commented Jul 16, 2026

Copy link
Copy Markdown
Member

Description

clp-core-build-macos's lint:check-cpp-full step runs clang-tidy over every C++ source file by fanning out one clang-tidy process per file through a Taskfile deps: for: loop (clang-tidy-parallel in yscope-dev-utils). That loop has no built-in concurrency limit, so ~hundreds of clang-tidy processes — each holding a full preprocessed translation unit / AST — are spawned at once.

The GitHub runners don't have a huge amount of memory. As the number of source files and the project's complexity grow, the peak memory of this unbounded fan-out grows until it exceeds what the runner has, and the OS likely swaps / thrashes / OOMs instead of making progress — this is hard to confirm directly, but the observed behavior is consistent with it. The full pass then can no longer finish within the runner's timeout and is cancelled.

This has been happening on main: clp-core-build-macos hasn't succeeded since ~2026-06-30; recent runs were cancelled after 6–9h, e.g.:

Run Event Duration
29217894914 (PR #2373) pull_request cancelled @ 8h59m
29222777130 schedule cancelled @ 8h30m
29386691995 schedule cancelled @ 6h10m

(In each, the build + unit tests succeed in ~16–22 min; the remaining hours are all spent inside lint:check-cpp-full.)

This also breaks the lint cache (see #1419). The Update lint:check-cpp-static-full cache step only runs on main and only after the full pass completes successfully. Since the passes keep getting cancelled, that save step never fires, so the macOS cache stays a near-empty shell (~37 KB of checksum files, no build/lint-clang-tidy logs). With no logs restored, the per-file clang-tidy tasks can't be skipped, so every full pass runs from scratch — a doom-loop that the timeout keeps resetting.

The fix

Cap the lint concurrency to the logical-processor count at the call site:

- run: >-
    task --concurrency
    "${CLP_CPP_LINT_CONCURRENCY:-$(getconf _NPROCESSORS_ONLN)}"
    lint:check-cpp-full
  shell: "bash"

This is a CLI-level cap (task --concurrency), independent of the yscope-dev-utils submodule version, so it needs no submodule change. It keeps the pass within memory so it can complete — which in turn lets the cache save step fire on main, store real logs, and let future unchanged-source PRs skip the step entirely.

The default is $(getconf _NPROCESSORS_ONLN); CLP_CPP_LINT_CONCURRENCY overrides it (e.g. 1 for maximum memory safety) without a workflow edit.

This also drops the CLP_CPP_MAX_PARALLELISM_PER_BUILD_TASK=$(getconf _NPROCESSORS_ONLN) prefix the lint step previously carried. That prefix only looked like it limited lint parallelism: the variable is consumed solely by the cmake/make compilation jobs (deps:core, core-build) via G_CPP_MAX_PARALLELISM_PER_BUILD_TASK, never by clang-tidy or the lint task chain — clang-tidy is single-threaded per invocation, and its parallelism comes entirely from the Taskfile deps: for: loop. It was added to the lint step in #966 to limit core's build parallelism, not the clang-tidy fan-out, so it never addressed the thrash. It was thus a no-op on the lint step, and task --concurrency is the actual lever.

Checklist

  • The PR satisfies the contribution guidelines.
  • This isn't a breaking change. (CI-only; changes how clang-tidy is scheduled, not what is checked or any build output.)
  • No docs need to be updated.

Validation performed

Applied the same cap onto feat/webui-nodejs-24 (PR #2373's branch — the worst offender) and ran it on y-scope/clp, and separately ran this PR's own branch through CI:

Run Cap? Result
29217894914 no ❌ cancelled @ 8h59m
29469661714 yes success — 46m24s (shared) / 40m41s (static)
29474344830 (this PR's branch) yes success — 45m36s (shared) / 45m35s (static)

Both capped runs complete in well under an hour on both matrix legs, vs. the uncapped run cancelled at 8h59m. The PR's own run (29474344830) is the most direct evidence — it's this exact branch on y-scope/clp.

With the cap, the log shows clang-tidy running at a steady, controlled cadence (one file every few seconds, a few in parallel) and the full pass completes, vs. the unbounded run whose log goes quiet for hours once all processes are launched and then makes no further measurable progress until cancelled.

Concretely, without the cap the log emits a burst of task: [utils:cpp-lint:clang-tidy:...] start-lines for many files at once — i.e. the deps: for: loop launches all processes together — after which the run chokes and the log goes quiet. A representative excerpt from an uncapped run (29469389246):

task: [utils:cpp-lint:clang-tidy:.../clp/ffi/ir_stream/byteswap.hpp] ...
task: [utils:cpp-lint:clang-tidy:.../clp/streaming_compression/zstd/Constants.hpp] ...
task: [utils:cpp-lint:clang-tidy:.../clp_s/RangeIndexWriter.hpp] ...
task: [utils:cpp-lint:clang-tidy:.../clp/GlobalMetadataDBConfig.cpp] ...
task: [utils:cpp-lint:clang-tidy:.../clp_s/filter/tests/test-clp_s-bloom_filter.cpp] ...

The start-lines land together in a burst, and then the log goes quiet for a long time — the processes are launched together and then make no further measurable progress. With the cap, the pass instead proceeds at a steady cadence, with completions appearing roughly one every few seconds.

Note: a non-main run doesn't populate the cache (the save step is main-only), so the cache-skip benefit will only materialize once this lands on main and a main run completes a full pass.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The macOS workflow now limits parallel clang-tidy processes for the full C++ lint task using CLP_CPP_LINT_CONCURRENCY, with the logical processor count as the default.

Changes

macOS lint workflow

Layer / File(s) Summary
Configure macOS lint concurrency
.github/workflows/clp-core-build-macos.yaml
Runs lint:check-cpp-full with configurable concurrency, defaulting to getconf _NPROCESSORS_ONLN.

Estimated code review effort: 1 (Trivial) | ~5 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarises the main CI change: capping clang-tidy concurrency for lint:check-cpp-full.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

`lint:check-cpp-full` runs `clang-tidy` over every C++ source file by
fanning out one process per file through a Taskfile `deps: for:` loop,
which has no built-in concurrency limit. Each `clang-tidy` process holds
a full preprocessed translation unit, so spawning ~hundreds at once peaks
high in memory. The runners don't have a huge amount of memory, and as the
source tree and project complexity grow, that peak grows until it exceeds
what the runner has and the OS swaps/thrashes instead of making progress,
so the full pass can no longer finish within the runner's timeout.

Cap concurrency to the logical-processor count via `task --concurrency`
(default `$(getconf _NPROCESSORS_ONLN)`), overridable with
`CLP_CPP_LINT_CONCURRENCY` (e.g. "1" for maximum memory safety). Keeping
the pass within memory lets it complete, which also lets the
`Update lint:check-cpp-static-full cache` step (main only) save a cache so
future unchanged-source PRs can skip the step.
@jackluo923 jackluo923 force-pushed the fix/unbounded-linting-task-execution-parallelism branch from a9a4ced to 5be19cf Compare July 16, 2026 05:29
@jackluo923 jackluo923 marked this pull request as ready for review July 16, 2026 05:50
@jackluo923 jackluo923 requested a review from a team as a code owner July 16, 2026 05:50

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/clp-core-build-macos.yaml:
- Around line 117-120: Update the workflow step invoking task with
lint:check-cpp-full to validate CLP_CPP_LINT_CONCURRENCY before passing it as
--concurrency. Reject zero and any non-positive or invalid value, while
preserving the getconf fallback and allowing only positive integers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6ae306b1-750a-4c06-942a-9b1db9ae463f

📥 Commits

Reviewing files that changed from the base of the PR and between 498e206 and 0335a4c.

📒 Files selected for processing (1)
  • .github/workflows/clp-core-build-macos.yaml

Comment thread .github/workflows/clp-core-build-macos.yaml
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