Skip to content

Commit eae84c2

Browse files
drichardsonclaude
andcommitted
Add PREK_MAX_CONCURRENCY environment variable
Allow users to cap the maximum number of concurrent hooks via the PREK_MAX_CONCURRENCY environment variable. The value is clamped between 1 and the available CPU core count. This is useful when ulimit -n is low and concurrent hook execution can exhaust file descriptors. Unlike ulimit, an environment variable can be set in CI configs, .env files, and wrapper scripts without requiring shell builtins. Closes #1696 Assisted by AI Co-Authored-By: Claude <noreply@anthropic.com>
1 parent f7b0c00 commit eae84c2

File tree

3 files changed

+72
-7
lines changed

3 files changed

+72
-7
lines changed

crates/prek-consts/src/env_vars.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ impl EnvVars {
2222
pub const PREK_SKIP: &'static str = "PREK_SKIP";
2323
pub const PREK_ALLOW_NO_CONFIG: &'static str = "PREK_ALLOW_NO_CONFIG";
2424
pub const PREK_NO_CONCURRENCY: &'static str = "PREK_NO_CONCURRENCY";
25+
pub const PREK_MAX_CONCURRENCY: &'static str = "PREK_MAX_CONCURRENCY";
2526
pub const PREK_NO_FAST_PATH: &'static str = "PREK_NO_FAST_PATH";
2627
pub const PREK_UV_SOURCE: &'static str = "PREK_UV_SOURCE";
2728
pub const PREK_NATIVE_TLS: &'static str = "PREK_NATIVE_TLS";

crates/prek/src/run.rs

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,36 @@ pub(crate) static USE_COLOR: LazyLock<bool> =
1919
ColorChoice::Auto => unreachable!(),
2020
});
2121

22-
pub(crate) static CONCURRENCY: LazyLock<usize> = LazyLock::new(|| {
23-
if EnvVars::is_set(EnvVars::PREK_NO_CONCURRENCY) {
24-
1
25-
} else {
26-
std::thread::available_parallelism()
27-
.map(std::num::NonZero::get)
28-
.unwrap_or(1)
22+
fn resolve_concurrency(no_concurrency: bool, max_concurrency: Option<&str>, cpu: usize) -> usize {
23+
if no_concurrency {
24+
return 1;
2925
}
26+
27+
if let Some(v) = max_concurrency {
28+
match v.parse::<usize>() {
29+
Ok(cap) => return cap.max(1),
30+
Err(_) => {
31+
tracing::warn!(
32+
"Invalid value for {}: {v:?}, using default ({cpu})",
33+
EnvVars::PREK_MAX_CONCURRENCY,
34+
);
35+
}
36+
}
37+
}
38+
39+
cpu
40+
}
41+
42+
pub(crate) static CONCURRENCY: LazyLock<usize> = LazyLock::new(|| {
43+
let cpu = std::thread::available_parallelism()
44+
.map(std::num::NonZero::get)
45+
.unwrap_or(1);
46+
47+
resolve_concurrency(
48+
EnvVars::is_set(EnvVars::PREK_NO_CONCURRENCY),
49+
EnvVars::var(EnvVars::PREK_MAX_CONCURRENCY).ok().as_deref(),
50+
cpu,
51+
)
3052
});
3153

3254
fn target_concurrency(serial: bool) -> usize {
@@ -352,6 +374,46 @@ mod tests {
352374
assert_eq!(total_files, 100);
353375
}
354376

377+
#[test]
378+
fn test_resolve_concurrency_defaults_to_cpu() {
379+
assert_eq!(resolve_concurrency(false, None, 16), 16);
380+
}
381+
382+
#[test]
383+
fn test_resolve_concurrency_max_caps_value() {
384+
assert_eq!(resolve_concurrency(false, Some("4"), 16), 4);
385+
}
386+
387+
#[test]
388+
fn test_resolve_concurrency_max_above_cpu() {
389+
assert_eq!(resolve_concurrency(false, Some("32"), 8), 32);
390+
}
391+
392+
#[test]
393+
fn test_resolve_concurrency_max_zero_floors_to_one() {
394+
assert_eq!(resolve_concurrency(false, Some("0"), 16), 1);
395+
}
396+
397+
#[test]
398+
fn test_resolve_concurrency_max_invalid_falls_back() {
399+
assert_eq!(resolve_concurrency(false, Some("abc"), 16), 16);
400+
}
401+
402+
#[test]
403+
fn test_resolve_concurrency_max_empty_falls_back() {
404+
assert_eq!(resolve_concurrency(false, Some(""), 16), 16);
405+
}
406+
407+
#[test]
408+
fn test_resolve_concurrency_no_concurrency() {
409+
assert_eq!(resolve_concurrency(true, None, 16), 1);
410+
}
411+
412+
#[test]
413+
fn test_resolve_concurrency_no_concurrency_overrides_max() {
414+
assert_eq!(resolve_concurrency(true, Some("8"), 16), 1);
415+
}
416+
355417
#[test]
356418
fn test_partitions_respects_cli_length_limit() {
357419
// Create files that will exceed CLI length limit

docs/configuration.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1332,6 +1332,8 @@ prek supports the following environment variables:
13321332

13331333
- `PREK_NO_CONCURRENCY` — Disable parallelism for installs and runs (If set, force concurrency to 1).
13341334

1335+
- `PREK_MAX_CONCURRENCY` — Set the maximum number of concurrent hooks (minimum 1). Defaults to the number of CPU cores when unset. Ignored when `PREK_NO_CONCURRENCY` is set. If you encounter "Too many open files" errors, lowering this value or raising the file descriptor limit with `ulimit -n` can help.
1336+
13351337
- `PREK_NO_FAST_PATH` — Disable Rust-native built-in hooks; always use the original hook implementation. See [Built-in Fast Hooks](builtin.md) for details.
13361338

13371339
- `PREK_UV_SOURCE` — Control how uv (Python package installer) is installed. Options:

0 commit comments

Comments
 (0)