Skip to content

Commit

Permalink
Always spawn a main2 thread to normalize main stack size issues (#10479)
Browse files Browse the repository at this point in the history
Also removes UV_STACK_SIZE and uses RUST_MIN_STACK instead, tweaking
docs to reflect the differences.

Fixes #10367
  • Loading branch information
Gankra authored Jan 15, 2025
1 parent a7fe84a commit 80ac8db
Show file tree
Hide file tree
Showing 8 changed files with 54 additions and 113 deletions.
33 changes: 0 additions & 33 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -305,9 +305,6 @@ jobs:
- name: "Smoke test"
working-directory: ${{ env.UV_WORKSPACE }}
env:
# Avoid debug build stack overflows.
UV_STACK_SIZE: 3000000 # 3 megabyte, triple the default on windows
run: |
Set-Alias -Name uv -Value ./target/debug/uv
uv venv -v
Expand All @@ -316,9 +313,6 @@ jobs:
- name: "Smoke test completion"
working-directory: ${{ env.UV_WORKSPACE }}
shell: powershell
env:
# Avoid debug build stack overflows.
UV_STACK_SIZE: 3000000 # 3 megabyte, triple the default on windows
run: |
Set-Alias -Name uv -Value ./target/debug/uv
Set-Alias -Name uvx -Value ./target/debug/uvx
Expand Down Expand Up @@ -744,9 +738,6 @@ jobs:
needs: build-binary-windows
name: "integration test | free-threaded on windows"
runs-on: windows-latest
env:
# Avoid debug build stack overflows.
UV_STACK_SIZE: 3000000 # 3 megabyte, triple the default on windows

steps:
- name: "Download binary"
Expand Down Expand Up @@ -908,9 +899,6 @@ jobs:
& .venv\Scripts\python.exe --version
- name: "Check install"
env:
# Avoid debug build stack overflows.
UV_STACK_SIZE: 3000000 # 3 megabyte, triple the default on windows
run: |
.\uv.exe pip install anyio
Expand Down Expand Up @@ -1040,9 +1028,6 @@ jobs:
& .venv\Scripts\python.exe --version
- name: "Check install"
env:
# Avoid debug build stack overflows.
UV_STACK_SIZE: 3000000 # 3 megabyte, triple the default on windows
run: |
.\uv.exe pip install anyio
Expand Down Expand Up @@ -1534,9 +1519,6 @@ jobs:
needs: build-binary-windows
name: "check system | python3.10 on windows"
runs-on: windows-latest
env:
# Avoid debug build stack overflows.
UV_STACK_SIZE: 3000000 # 3 megabyte, triple the default on windows
steps:
- uses: actions/checkout@v4

Expand All @@ -1560,9 +1542,6 @@ jobs:
needs: build-binary-windows
name: "check system | python3.10 on windows x86"
runs-on: windows-latest
env:
# Avoid debug build stack overflows.
UV_STACK_SIZE: 3000000 # 3 megabyte, triple the default on windows
steps:
- uses: actions/checkout@v4

Expand All @@ -1587,9 +1566,6 @@ jobs:
needs: build-binary-windows
name: "check system | python3.13 on windows"
runs-on: windows-latest
env:
# Avoid debug build stack overflows.
UV_STACK_SIZE: 3000000 # 3 megabyte, triple the default on windows
steps:
- uses: actions/checkout@v4

Expand All @@ -1615,9 +1591,6 @@ jobs:
needs: build-binary-windows
name: "check system | python3.12 via chocolatey"
runs-on: windows-latest
env:
# Avoid debug build stack overflows.
UV_STACK_SIZE: 3000000 # 3 megabyte, triple the default on windows
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -1750,9 +1723,6 @@ jobs:

- name: "Validate global Python install"
shell: bash -el {0}
env:
# Avoid debug build stack overflows.
UV_STACK_SIZE: 3000000 # 3 megabyte, triple the default on windows
run: python ./scripts/check_system_python.py --uv ./uv

system-test-amazonlinux:
Expand Down Expand Up @@ -1789,9 +1759,6 @@ jobs:
needs: build-binary-windows
name: "check system | embedded python3.10 on windows"
runs-on: windows-latest
env:
# Avoid debug build stack overflows.
UV_STACK_SIZE: 3000000 # 3 megabyte, triple the default on windows
steps:
- uses: actions/checkout@v4

Expand Down
12 changes: 0 additions & 12 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,18 +82,6 @@ cargo run -- venv
cargo run -- pip install requests
```

### Testing on Windows

When testing debug builds on Windows, the stack can overflow resulting in a `STATUS_STACK_OVERFLOW`
error code. This is due to a small stack size limit on Windows that we encounter when running
unoptimized builds — the release builds do not have this problem. We
[added a `UV_STACK_SIZE` variable](https://github.com/astral-sh/uv/pull/941) to bypass this problem
during testing. We recommend bumping the stack size from the default of 1MB to 3MB, for example:

```powershell
$Env:UV_STACK_SIZE = '3000000'
```

## Running inside a Docker container

Source distributions can run arbitrary code on build and can make unwanted modifications to your
Expand Down
12 changes: 9 additions & 3 deletions crates/uv-static/src/env_vars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,6 @@ impl EnvVars {
/// Use to disable line wrapping for diagnostics.
pub const UV_NO_WRAP: &'static str = "UV_NO_WRAP";

/// Use to increase the stack size used by uv in debug builds on Windows.
pub const UV_STACK_SIZE: &'static str = "UV_STACK_SIZE";

/// Generates the environment variable key for the HTTP Basic authentication username.
#[attr_env_var_pattern("UV_INDEX_{name}_USERNAME")]
pub fn index_username(name: &str) -> String {
Expand Down Expand Up @@ -505,6 +502,15 @@ impl EnvVars {
/// for more.
pub const RUST_LOG: &'static str = "RUST_LOG";

/// Use to set the stack size used by uv.
///
/// The value is in bytes, and the default is typically 2MB (2097152).
/// Unlike the normal `RUST_MIN_STACK` semantics, this can affect main thread
/// stack size, because we actually spawn our own main2 thread to work around
/// the fact that Windows' real main thread is only 1MB. That thread has size
/// `max(RUST_MIN_STACK, 4MB)`.
pub const RUST_MIN_STACK: &'static str = "RUST_MIN_STACK";

/// The directory containing the `Cargo.toml` manifest for a package.
#[attr_hidden]
pub const CARGO_MANIFEST_DIR: &'static str = "CARGO_MANIFEST_DIR";
Expand Down
64 changes: 35 additions & 29 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1819,45 +1819,51 @@ where
}
};

// Windows has a default stack size of 1MB, which is lower than the linux and mac default.
// Running out of stack has been an issue for us. We box types and futures in various places
// to mitigate this, with this being an especially important case.
//
// Non-main threads should all have 2MB, as Rust forces platform consistency there,
// but that can be overridden with the RUST_MIN_STACK environment variable if you need more.
//
// Main thread stack-size is the real issue. There's BIG variety here across platforms
// and it's harder to control (which is why Rust doesn't by default). Notably
// on macOS and Linux you will typically get 8MB main thread, while on Windows you will
// typically get 1MB, which is *tiny*:
// https://learn.microsoft.com/en-us/cpp/build/reference/stack-stack-allocations?view=msvc-170
// We support increasing the stack size to avoid stack overflows in debug mode on Windows. In
// addition, we box types and futures in various places. This includes the `Box::pin(run())`
// here, which prevents the large (non-send) main future alone from overflowing the stack.
let result = if let Ok(stack_size) = std::env::var(EnvVars::UV_STACK_SIZE) {
let stack_size = stack_size.parse().expect("Invalid stack size");
let tokio_main = move || {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.thread_stack_size(stack_size)
.build()
.expect("Failed building the Runtime");
// Box the large main future to avoid stack overflows.
let result = runtime.block_on(Box::pin(run(cli)));
// Avoid waiting for pending tasks to complete.
//
// The resolver may have kicked off HTTP requests during resolution that
// turned out to be unnecessary. Waiting for those to complete can cause
// the CLI to hang before exiting.
runtime.shutdown_background();
result
};
std::thread::Builder::new()
.stack_size(stack_size)
.spawn(tokio_main)
.expect("Tokio executor failed, was there a panic?")
.join()
.expect("Tokio executor failed, was there a panic?")
} else {
//
// To normalize this we just spawn a new thread called main2 with a size we can set
// ourselves. 2MB is typically too small (especially for our debug builds), while 4MB
// seems fine. Also we still try to respect RUST_MIN_STACK if it's set, in case useful,
// but don't let it ask for a smaller stack to avoid messy misconfiguration since we
// know we use quite a bit of main stack space.
let main_stack_size = std::env::var(EnvVars::RUST_MIN_STACK)
.ok()
.and_then(|var| var.parse::<usize>().ok())
.unwrap_or(0)
.max(4 * 1024 * 1024);

let main2 = move || {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed building the Runtime");
// Box the large main future to avoid stack overflows.
let result = runtime.block_on(Box::pin(run(cli)));
// Avoid waiting for pending tasks to complete.
//
// The resolver may have kicked off HTTP requests during resolution that
// turned out to be unnecessary. Waiting for those to complete can cause
// the CLI to hang before exiting.
runtime.shutdown_background();
result
};
let result = std::thread::Builder::new()
.name("main2".to_owned())
.stack_size(main_stack_size)
.spawn(main2)
.expect("Tokio executor failed, was there a panic?")
.join()
.expect("Tokio executor failed, was there a panic?");

match result {
Ok(code) => code.into(),
Expand Down
20 changes: 0 additions & 20 deletions crates/uv/tests/it/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,12 +489,6 @@ impl TestContext {
// Avoid locale issues in tests
command.env(EnvVars::LC_ALL, "C");
}

if cfg!(all(windows, debug_assertions)) {
// TODO(konstin): Reduce stack usage in debug mode enough that the tests pass with the
// default windows stack of 1MB
command.env(EnvVars::UV_STACK_SIZE, (4 * 1024 * 1024).to_string());
}
}

/// Create a `pip compile` command for testing.
Expand Down Expand Up @@ -581,13 +575,6 @@ impl TestContext {
let mut command = self.new_command();
command.arg("help");
command.env_remove(EnvVars::UV_CACHE_DIR);

if cfg!(all(windows, debug_assertions)) {
// TODO(konstin): Reduce stack usage in debug mode enough that the tests pass with the
// default windows stack of 1MB
command.env(EnvVars::UV_STACK_SIZE, (4 * 1024 * 1024).to_string());
}

command
}

Expand Down Expand Up @@ -636,13 +623,6 @@ impl TestContext {
pub fn publish(&self) -> Command {
let mut command = self.new_command();
command.arg("publish");

if cfg!(all(windows, debug_assertions)) {
// TODO(konstin): Reduce stack usage in debug mode enough that the tests pass with the
// default windows stack of 1MB
command.env(EnvVars::UV_STACK_SIZE, (4 * 1024 * 1024).to_string());
}

command
}

Expand Down
5 changes: 0 additions & 5 deletions crates/uv/tests/it/ecosystem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,6 @@ fn lock_ecosystem_package(python_version: &str, name: &str) -> Result<()> {
.env(EnvVars::UV_EXCLUDE_NEWER, EXCLUDE_NEWER)
.current_dir(context.temp_dir.path());

if cfg!(all(windows, debug_assertions)) {
// TODO(konstin): Reduce stack usage in debug mode enough that the tests pass with the
// default windows stack of 1MB
command.env(EnvVars::UV_STACK_SIZE, (4 * 1024 * 1024).to_string());
}
let (snapshot, _) = common::run_and_format(
&mut command,
context.filters(),
Expand Down
7 changes: 0 additions & 7 deletions crates/uv/tests/it/show_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,6 @@ fn add_shared_args(mut command: Command, cwd: &Path) -> Command {
// Avoid locale issues in tests
command.env(EnvVars::LC_ALL, "C");
}

if cfg!(all(windows, debug_assertions)) {
// TODO(konstin): Reduce stack usage in debug mode enough that the tests pass with the
// default windows stack of 1MB
command.env(EnvVars::UV_STACK_SIZE, (4 * 1024 * 1024).to_string());
}

command
}

Expand Down
14 changes: 10 additions & 4 deletions docs/configuration/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,10 +320,6 @@ uv will require that all dependencies have a hash specified in the requirements
Equivalent to the `--resolution` command-line argument. For example, if set to
`lowest-direct`, uv will install the lowest compatible versions of all direct dependencies.

### `UV_STACK_SIZE`

Use to increase the stack size used by uv in debug builds on Windows.

### `UV_SYSTEM_PYTHON`

Equivalent to the `--system` command-line argument. If set to `true`, uv will
Expand Down Expand Up @@ -484,6 +480,16 @@ For example:
See the [tracing documentation](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#example-syntax)
for more.

### `RUST_MIN_STACK`

Use to set the stack size used by uv.

The value is in bytes, and the default is typically 2MB (2097152).
Unlike the normal `RUST_MIN_STACK` semantics, this can affect main thread
stack size, because we actually spawn our own main2 thread to work around
the fact that Windows' real main thread is only 1MB. That thread has size
`max(RUST_MIN_STACK, 4MB)`.

### `SHELL`

The standard `SHELL` posix env var.
Expand Down

0 comments on commit 80ac8db

Please sign in to comment.