Skip to content

refactor(linter): always explicitly initialize Rayon thread pool#13122

Merged
graphite-app[bot] merged 1 commit intomainfrom
08-15-refactor_linter_always_explicitly_initialize_rayon_thread_pool
Aug 15, 2025
Merged

refactor(linter): always explicitly initialize Rayon thread pool#13122
graphite-app[bot] merged 1 commit intomainfrom
08-15-refactor_linter_always_explicitly_initialize_rayon_thread_pool

Conversation

@overlookmotel
Copy link
Member

@overlookmotel overlookmotel commented Aug 15, 2025

Previously we only explicitly initialized the global Rayon thread pool if --threads option is specified.

Instead, do it unconditionally, and always specify thread count. The purpose is to obtain a guarantee that rayon::current_num_threads() will always return the same number during the rest of the linter's work.

This invariant is not relied upon currently, but will become critical for soundness once we run JS plugins on multiple threads.

Also, if std::thread::available_parallelism() is not able to obtain CPU core count, log a warning that the linter will run single-threaded. Previously Rayon would use only a single thread, but would make that decision silently. I'm not sure in what circumstances available_parallelism() can return Err, but if it does, the user will probably want to know about it.

@github-actions github-actions bot added A-linter Area - Linter A-cli Area - CLI C-cleanup Category - technical debt or refactoring. Solution not expected to change behavior labels Aug 15, 2025
Copy link
Member Author


How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • 0-merge - adds this PR to the back of the merge queue
  • hotfix - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@overlookmotel overlookmotel marked this pull request as ready for review August 15, 2025 15:17
Copilot AI review requested due to automatic review settings August 15, 2025 15:17
@overlookmotel overlookmotel requested a review from camc314 as a code owner August 15, 2025 15:17
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR refactors the Rayon thread pool initialization to ensure thread count consistency across the linter's execution. The change guarantees that rayon::current_num_threads() will always return the same value, which is critical for future multi-threaded JS plugin support.

Key changes:

  • Always explicitly initialize the Rayon thread pool, even when using default thread count
  • Add fallback handling when CPU core count detection fails with user notification
  • Move thread pool initialization logic from conditional to unconditional execution

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
apps/oxlint/src/command/lint.rs Refactored thread pool initialization to always run with explicit thread count and added error handling for CPU detection failure
crates/oxc_linter/src/service/runtime.rs Added unconditional thread pool initialization as a safety measure in the Runtime constructor

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@codspeed-hq
Copy link

codspeed-hq bot commented Aug 15, 2025

CodSpeed Instrumentation Performance Report

Merging #13122 will not alter performance

Comparing 08-15-refactor_linter_always_explicitly_initialize_rayon_thread_pool (9f924f6) with main (18ad3c0)1

Summary

✅ 34 untouched benchmarks

Footnotes

  1. No successful run was found on main (9f924f6) during the generation of this report, so 18ad3c0 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

graphite-app bot pushed a commit that referenced this pull request Aug 15, 2025
Similar to #13122. Unconditionally initialize Rayon global thread pool in coverage runner.

The guarantees provided by explicitly initializing the thread pool are going to be critical for the linter.

Coverage currently doesn't cover linter, so it doesn't matter, but this doesn't hurt to do, and gives us greater safety if coverage does involve the linter at a later date.
@overlookmotel overlookmotel force-pushed the 08-15-refactor_linter_always_explicitly_initialize_rayon_thread_pool branch from 2257eb4 to 7ddeb68 Compare August 15, 2025 15:56
@overlookmotel
Copy link
Member Author

overlookmotel commented Aug 15, 2025

The warning that could not determine how many available threads is printed with eprintln!.

@camc314 Is this OK? Or should logging be going through some reporter abstraction?

@overlookmotel
Copy link
Member Author

Discussed with Boshen offline. He thinks this PR is fine. Going to merge it now as I have more to stack on top, and Cam can check it when he's next at his desk.

@overlookmotel overlookmotel added the 0-merge Merge with Graphite Merge Queue label Aug 15, 2025
Copy link
Member Author

overlookmotel commented Aug 15, 2025

Merge activity

)

Previously we only explicitly initialized the global Rayon thread pool if `--threads` option is specified.

Instead, do it unconditionally, and always specify thread count. The purpose is to obtain a guarantee that `rayon::current_num_threads()` will always return the same number during the rest of the linter's work.

This invariant is not relied upon currently, but will become critical for soundness once we run JS plugins on multiple threads.

Also, if `std::thread::available_parallelism()` is not able to obtain CPU core count, log a warning that the linter will run single-threaded. Previously Rayon would use only a single thread, but would make that decision silently. I'm not sure in what circumstances `available_parallelism()` can return `Err`, but if it does, the user will probably want to know about it.
@graphite-app graphite-app bot force-pushed the 08-15-refactor_linter_always_explicitly_initialize_rayon_thread_pool branch from 7ddeb68 to 9f924f6 Compare August 15, 2025 17:00
@graphite-app graphite-app bot merged commit 9f924f6 into main Aug 15, 2025
25 checks passed
@graphite-app graphite-app bot deleted the 08-15-refactor_linter_always_explicitly_initialize_rayon_thread_pool branch August 15, 2025 17:05
@graphite-app graphite-app bot removed the 0-merge Merge with Graphite Merge Queue label Aug 15, 2025
Comment on lines +92 to +99
} else if let Ok(thread_count) = std::thread::available_parallelism() {
thread_count.get()
} else {
eprintln!(
"Unable to determine available thread count. Defaulting to 1.\nConsider specifying the number of threads explicitly with `--threads` option."
);
1
};
Copy link
Contributor

Choose a reason for hiding this comment

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

this is going to be such a cold path, i think it's fine. for ref, rayon defaults to 1x thread if it can't call std::thread::available_parrellism

            let default = || {
                thread::available_parallelism()
                    .map(|n| n.get())
                    .unwrap_or(1)
            };

Copy link
Member Author

@overlookmotel overlookmotel Aug 16, 2025

Choose a reason for hiding this comment

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

Thanks for reviewing. Yes, that's the point of logging a warning. Rayon would default to 1 if it can't determine available parallelism, so we're not changing that behavior. But what we are changing is to warn the user that this is happening, as it's probably not what they want.

I have no idea in what circumstances std::thread::available_parallelism would return Err anyway. Maybe it's possible in some VMs? 🤷

Copy link
Contributor

Choose a reason for hiding this comment

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

/// This function will, but is not limited to, return errors in the following
/// cases:
///
/// - If the amount of parallelism is not known for the target platform.
/// - If the program lacks permission to query the amount of parallelism made
///   available to it.

stupid edge cases i think.

stderr is the right solution here, as it means it won't break people using stdout and parsing the json ect

@camc314
Copy link
Contributor

camc314 commented Aug 15, 2025

LGTM 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-cli Area - CLI A-linter Area - Linter C-cleanup Category - technical debt or refactoring. Solution not expected to change behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants