-
-
Notifications
You must be signed in to change notification settings - Fork 6.8k
Fix LSP spawning by resetting exception ports in child processes #40716
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix LSP spawning by resetting exception ports in child processes #40716
Conversation
Fixes zed-industries#36754 LSPs fail to spawn after the crash handler is initialized. This is caused by a timing issue where child processes inherit the parent's crash handler exception ports before they're fully stabilized, which can block process spawning. This fix resets exception ports in child processes before exec using the pre_exec() hook, preventing them from inheriting the parent's crash handler exception ports. Changes: - Reset exception ports to system defaults in child processes - Use pre_exec() to run in child after fork but before exec - Graceful error handling with warning logs
|
We require contributors to sign our Contributor License Agreement, and we don't have @RemiKalbe on file. You can sign our CLA at https://zed.dev/cla. Once you've signed, post a comment here that says '@cla-bot check'. |
|
@cla-bot check |
|
We require contributors to sign our Contributor License Agreement, and we don't have @RemiKalbe on file. You can sign our CLA at https://zed.dev/cla. Once you've signed, post a comment here that says '@cla-bot check'. |
|
The cla-bot has been summoned, and re-checked this pull request! |
|
@cla-bot check |
|
The cla-bot has been summoned, and re-checked this pull request! |
Veykril
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch, this does sound reasonable, but gonna ask for a second opinion from @P1n3appl3
P1n3appl3
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just double checked that the crash reporting signal handling still works correctly on mac with this change. I wish it were easier to test the case you're talking about with the language servers, but if it fixes it for you and doesn't regress other stuff I'm happy to merge. Sorry it took so long, this got lost in the sea of PRs in my inbox 😅 .
|
You're fine! Yeah this bug is weird but that does fix it, I've been using this build ever since and I've not encountered any issues 😄 |
) ## Summary Fixes #36754 This PR fixes an issue where LSPs fail to spawn after the crash handler is initialized. ## Problem After PR #35263 added minidump crash reporting, some users experienced LSP spawn failures. The issue manifests as: - LSPs fail to spawn with no clear error messages - The problem only occurs after crash handler initialization - LSPs work when a debugger is attached, revealing a timing issue ### Root Cause The crash handler installs Mach exception ports for minidump generation. Due to a timing issue, child processes inherit these exception ports before they're fully stabilized, which can block child process spawning. ## Solution Reset exception ports in child processes using the `pre_exec()` hook, which runs after `fork()` but before `exec()`. This prevents children from inheriting the parent's crash handler exception ports. ### Implementation - Adds macOS-specific implementation of `new_smol_command()` that resets exception ports before exec - Calls `task_set_exception_ports` to reset all exception ports to `MACH_PORT_NULL` - Graceful error handling: logs warnings but doesn't fail process spawning if port reset fails Release Notes: - Fixed LSPs failing to spawn on some macOS systems --------- Co-authored-by: Julia Ryan <juliaryan3.14@gmail.com>
) ## Summary Fixes #36754 This PR fixes an issue where LSPs fail to spawn after the crash handler is initialized. ## Problem After PR #35263 added minidump crash reporting, some users experienced LSP spawn failures. The issue manifests as: - LSPs fail to spawn with no clear error messages - The problem only occurs after crash handler initialization - LSPs work when a debugger is attached, revealing a timing issue ### Root Cause The crash handler installs Mach exception ports for minidump generation. Due to a timing issue, child processes inherit these exception ports before they're fully stabilized, which can block child process spawning. ## Solution Reset exception ports in child processes using the `pre_exec()` hook, which runs after `fork()` but before `exec()`. This prevents children from inheriting the parent's crash handler exception ports. ### Implementation - Adds macOS-specific implementation of `new_smol_command()` that resets exception ports before exec - Calls `task_set_exception_ports` to reset all exception ports to `MACH_PORT_NULL` - Graceful error handling: logs warnings but doesn't fail process spawning if port reset fails Release Notes: - Fixed LSPs failing to spawn on some macOS systems --------- Co-authored-by: Julia Ryan <juliaryan3.14@gmail.com>
…-industries#40716) ## Summary Fixes zed-industries#36754 This PR fixes an issue where LSPs fail to spawn after the crash handler is initialized. ## Problem After PR zed-industries#35263 added minidump crash reporting, some users experienced LSP spawn failures. The issue manifests as: - LSPs fail to spawn with no clear error messages - The problem only occurs after crash handler initialization - LSPs work when a debugger is attached, revealing a timing issue ### Root Cause The crash handler installs Mach exception ports for minidump generation. Due to a timing issue, child processes inherit these exception ports before they're fully stabilized, which can block child process spawning. ## Solution Reset exception ports in child processes using the `pre_exec()` hook, which runs after `fork()` but before `exec()`. This prevents children from inheriting the parent's crash handler exception ports. ### Implementation - Adds macOS-specific implementation of `new_smol_command()` that resets exception ports before exec - Calls `task_set_exception_ports` to reset all exception ports to `MACH_PORT_NULL` - Graceful error handling: logs warnings but doesn't fail process spawning if port reset fails Release Notes: - Fixed LSPs failing to spawn on some macOS systems --------- Co-authored-by: Julia Ryan <juliaryan3.14@gmail.com>
…-industries#40716) ## Summary Fixes zed-industries#36754 This PR fixes an issue where LSPs fail to spawn after the crash handler is initialized. ## Problem After PR zed-industries#35263 added minidump crash reporting, some users experienced LSP spawn failures. The issue manifests as: - LSPs fail to spawn with no clear error messages - The problem only occurs after crash handler initialization - LSPs work when a debugger is attached, revealing a timing issue ### Root Cause The crash handler installs Mach exception ports for minidump generation. Due to a timing issue, child processes inherit these exception ports before they're fully stabilized, which can block child process spawning. ## Solution Reset exception ports in child processes using the `pre_exec()` hook, which runs after `fork()` but before `exec()`. This prevents children from inheriting the parent's crash handler exception ports. ### Implementation - Adds macOS-specific implementation of `new_smol_command()` that resets exception ports before exec - Calls `task_set_exception_ports` to reset all exception ports to `MACH_PORT_NULL` - Graceful error handling: logs warnings but doesn't fail process spawning if port reset fails Release Notes: - Fixed LSPs failing to spawn on some macOS systems --------- Co-authored-by: Julia Ryan <juliaryan3.14@gmail.com>
The original fix in zed-industries#40716 only applied to processes spawned via `new_smol_command()`, but shell environment capture uses a different code path: `new_std_command()` → `set_pre_exec_to_start_new_session()` → convert to smol::process::Command. This caused an issue where: 1. Zed spawns a login shell to capture environment variables 2. The shell sources user config (e.g., .zshrc with oh-my-zsh plugins) 3. Plugins may spawn background processes (e.g., `poetry completions zsh &|`) 4. These processes inherit the crash handler's exception ports 5. The inherited exception ports can cause the processes to hang 6. The hanging process keeps the pipe to Zed open 7. Zed's `read_to_end()` blocks waiting for the pipe to close 8. This prevents Zed from spawning any more processes (including LSPs) Fix by also calling `reset_exception_ports()` in `set_pre_exec_to_start_new_session()`, which is used by shell environment capture, terminal spawning, and DAP transport. Fixes zed-industries#36754
The original fix in zed-industries#40716 only applied to processes spawned via `new_smol_command()`, but shell environment capture uses a different code path: `new_std_command()` → `set_pre_exec_to_start_new_session()` → convert to smol::process::Command. This caused an issue where: 1. Zed spawns a login shell to capture environment variables 2. The shell sources user config (e.g., .zshrc with oh-my-zsh plugins) 3. Plugins may spawn background processes (e.g., `poetry completions zsh &|`) 4. These processes inherit the crash handler's exception ports 5. The inherited exception ports can cause the processes to hang 6. The hanging process keeps the pipe to Zed open 7. Zed's `read_to_end()` blocks waiting for the pipe to close 8. This prevents Zed from spawning any more processes (including LSPs) Fix by also calling `reset_exception_ports()` in `set_pre_exec_to_start_new_session()`, which is used by shell environment capture, terminal spawning, and DAP transport. Fixes zed-industries#36754
## Summary Follow-up to #40716. This applies the same `reset_exception_ports()` fix to `set_pre_exec_to_start_new_session()`, which is used by shell environment capture, terminal spawning, and DAP transport. ### Root Cause After more debugging, I finally figured out what was causing the issue on my machine. Here's what was happening: 1. Zed spawns a login shell (zsh) to capture environment variables 2. A pipe is created: reader in Zed, writer mapped to fd 0 in zsh 3. zsh sources `.zshrc` → loads oh-my-zsh → runs poetry plugin 4. Poetry plugin runs `poetry completions zsh &|` in background 5. Poetry inherits fd 0 (the pipe's write end) from zsh 6. zsh finishes `zed --printenv` and exits 7. Poetry still holds fd 0 open 8. Zed's `reader.read_to_end()` blocks waiting for all writers to close 9. Poetry hangs (likely due to inherited crash handler exception ports interfering with its normal operation) 10. Pipe stays open → Zed stuck → no more processes spawn (including LSPs) I confirmed this by killing the hanging `poetry` process, which immediately unblocked Zed and allowed LSPs to start. However, this workaround was needed every time I started Zed. While poetry was the culprit in my case, this can affect any shell configuration that spawns background processes during initialization (oh-my-zsh plugins, direnv, asdf, nvm, etc.). Fixes #36754 ## Test plan - [x] Build with `ZED_GENERATE_MINIDUMPS=true` to force crash handler initialization - [x] Verify crash handler logs appear ("spawning crash handler process", "crash handler registered") - [x] Confirm LSPs start correctly with shell plugins that spawn background processes Release Notes: - Fixed an issue on macOS where LSPs could fail to start when shell plugins spawn background processes during environment capture.
…ies#44193) ## Summary Follow-up to zed-industries#40716. This applies the same `reset_exception_ports()` fix to `set_pre_exec_to_start_new_session()`, which is used by shell environment capture, terminal spawning, and DAP transport. ### Root Cause After more debugging, I finally figured out what was causing the issue on my machine. Here's what was happening: 1. Zed spawns a login shell (zsh) to capture environment variables 2. A pipe is created: reader in Zed, writer mapped to fd 0 in zsh 3. zsh sources `.zshrc` → loads oh-my-zsh → runs poetry plugin 4. Poetry plugin runs `poetry completions zsh &|` in background 5. Poetry inherits fd 0 (the pipe's write end) from zsh 6. zsh finishes `zed --printenv` and exits 7. Poetry still holds fd 0 open 8. Zed's `reader.read_to_end()` blocks waiting for all writers to close 9. Poetry hangs (likely due to inherited crash handler exception ports interfering with its normal operation) 10. Pipe stays open → Zed stuck → no more processes spawn (including LSPs) I confirmed this by killing the hanging `poetry` process, which immediately unblocked Zed and allowed LSPs to start. However, this workaround was needed every time I started Zed. While poetry was the culprit in my case, this can affect any shell configuration that spawns background processes during initialization (oh-my-zsh plugins, direnv, asdf, nvm, etc.). Fixes zed-industries#36754 ## Test plan - [x] Build with `ZED_GENERATE_MINIDUMPS=true` to force crash handler initialization - [x] Verify crash handler logs appear ("spawning crash handler process", "crash handler registered") - [x] Confirm LSPs start correctly with shell plugins that spawn background processes Release Notes: - Fixed an issue on macOS where LSPs could fail to start when shell plugins spawn background processes during environment capture.
…ies#44193) ## Summary Follow-up to zed-industries#40716. This applies the same `reset_exception_ports()` fix to `set_pre_exec_to_start_new_session()`, which is used by shell environment capture, terminal spawning, and DAP transport. ### Root Cause After more debugging, I finally figured out what was causing the issue on my machine. Here's what was happening: 1. Zed spawns a login shell (zsh) to capture environment variables 2. A pipe is created: reader in Zed, writer mapped to fd 0 in zsh 3. zsh sources `.zshrc` → loads oh-my-zsh → runs poetry plugin 4. Poetry plugin runs `poetry completions zsh &|` in background 5. Poetry inherits fd 0 (the pipe's write end) from zsh 6. zsh finishes `zed --printenv` and exits 7. Poetry still holds fd 0 open 8. Zed's `reader.read_to_end()` blocks waiting for all writers to close 9. Poetry hangs (likely due to inherited crash handler exception ports interfering with its normal operation) 10. Pipe stays open → Zed stuck → no more processes spawn (including LSPs) I confirmed this by killing the hanging `poetry` process, which immediately unblocked Zed and allowed LSPs to start. However, this workaround was needed every time I started Zed. While poetry was the culprit in my case, this can affect any shell configuration that spawns background processes during initialization (oh-my-zsh plugins, direnv, asdf, nvm, etc.). Fixes zed-industries#36754 ## Test plan - [x] Build with `ZED_GENERATE_MINIDUMPS=true` to force crash handler initialization - [x] Verify crash handler logs appear ("spawning crash handler process", "crash handler registered") - [x] Confirm LSPs start correctly with shell plugins that spawn background processes Release Notes: - Fixed an issue on macOS where LSPs could fail to start when shell plugins spawn background processes during environment capture.
Summary
Fixes #36754
This PR fixes an issue where LSPs fail to spawn after the crash handler is initialized.
Problem
After PR #35263 added minidump crash reporting, some users experienced LSP spawn failures. The issue manifests as:
Root Cause
The crash handler installs Mach exception ports for minidump generation. Due to a timing issue, child processes inherit these exception ports before they're fully stabilized, which can block child process spawning.
Solution
Reset exception ports in child processes using the
pre_exec()hook, which runs afterfork()but beforeexec(). This prevents children from inheriting the parent's crash handler exception ports.Implementation
new_smol_command()that resets exception ports before exectask_set_exception_portsto reset all exception ports toMACH_PORT_NULLRelease Notes: