uv_configuration/threading.rs
1//! Configure rayon and determine thread stack sizes.
2
3use std::sync::LazyLock;
4use std::sync::atomic::{AtomicUsize, Ordering};
5use uv_static::EnvVars;
6
7/// The default minimum stack size for uv threads.
8pub const UV_DEFAULT_STACK_SIZE: usize = 4 * 1024 * 1024;
9/// We don't allow setting a smaller stack size than 1MB.
10#[expect(clippy::identity_op)]
11pub const UV_MIN_STACK_SIZE: usize = 1 * 1024 * 1024;
12
13/// Running out of stack has been an issue for us. We box types and futures in various places
14/// to mitigate this.
15///
16/// Main thread stack-size has a BIG variety here across platforms and it's harder to control
17/// (which is why Rust doesn't by default). Notably on macOS and Linux you will typically get 8MB
18/// main thread, while on Windows you will typically get 1MB, which is *tiny*:
19/// <https://learn.microsoft.com/en-us/cpp/build/reference/stack-stack-allocations?view=msvc-170>
20///
21/// To normalize this we just spawn a new thread called main2 with a size we can set
22/// ourselves. 2MB is typically too small (especially for our debug builds), while 4MB
23/// seems fine. This value can be changed with `UV_STACK_SIZE`, with a fallback to reading
24/// `RUST_MIN_STACK`, to allow checking a larger or smaller stack size. There is a hardcoded stack
25/// size minimum of 1MB, which is the lowest platform default we observed.
26///
27/// Non-main threads should all have 2MB, as Rust forces platform consistency there,
28/// but even then stack overflows can occur in release mode
29/// (<https://github.com/astral-sh/uv/issues/12769>), so rayon and tokio get the same stack size,
30/// with the 4MB default.
31pub fn min_stack_size() -> usize {
32 let stack_size = if let Some(uv_stack_size) = std::env::var(EnvVars::UV_STACK_SIZE)
33 .ok()
34 .and_then(|var| var.parse::<usize>().ok())
35 {
36 uv_stack_size
37 } else if let Some(uv_stack_size) = std::env::var(EnvVars::RUST_MIN_STACK)
38 .ok()
39 .and_then(|var| var.parse::<usize>().ok())
40 {
41 uv_stack_size
42 } else {
43 UV_DEFAULT_STACK_SIZE
44 };
45
46 if stack_size < UV_MIN_STACK_SIZE {
47 return UV_DEFAULT_STACK_SIZE;
48 }
49
50 stack_size
51}
52
53/// The number of threads for the rayon threadpool.
54///
55/// The default of 0 makes rayon use its default.
56pub static RAYON_PARALLELISM: AtomicUsize = AtomicUsize::new(0);
57
58/// Initialize the threadpool lazily. Always call before using rayon the potentially first time.
59///
60/// The `uv` crate sets [`RAYON_PARALLELISM`] from the user settings, and the extract and install
61/// code initialize the threadpool lazily only if they are actually used by calling
62/// `LazyLock::force(&RAYON_INITIALIZE)`.
63pub static RAYON_INITIALIZE: LazyLock<()> = LazyLock::new(|| {
64 rayon::ThreadPoolBuilder::new()
65 .num_threads(RAYON_PARALLELISM.load(Ordering::Relaxed))
66 .stack_size(min_stack_size())
67 .build_global()
68 .expect("failed to initialize global rayon pool");
69});