-
Notifications
You must be signed in to change notification settings - Fork 5k
spawn: do not forward SIGPWR on Linux #31121
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
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import { expect, test } from "bun:test"; | ||
| import { bunEnv, bunExe, isLinux } from "harness"; | ||
|
|
||
| // On Linux, JavaScriptCore uses SIGPWR to suspend and resume threads during | ||
| // garbage collection. Bun.openInEditor() spawns the editor via bun.spawnSync | ||
| // on a detached background thread, which installs signal-forwarding handlers | ||
| // for the duration of the spawn. Those handlers must not replace JSC's SIGPWR | ||
| // handler, otherwise a concurrent GC will terminate the process with SIGPWR. | ||
| test.skipIf(!isLinux)("Bun.openInEditor concurrent with GC does not terminate the process with SIGPWR", () => { | ||
| const script = ` | ||
| for (let k = 0; k < 50; k++) { | ||
| try { Bun.openInEditor("foo" + k); } catch {} | ||
| } | ||
| for (let i = 0; i < 200; i++) Bun.gc(true); | ||
| `; | ||
| const { exitCode, signalCode } = Bun.spawnSync({ | ||
| cmd: [bunExe(), "-e", script], | ||
| env: { ...bunEnv, EDITOR: undefined, VISUAL: undefined }, | ||
|
Check warning on line 18 in test/js/bun/util/open-in-editor-gc.test.ts
|
||
| stdout: "ignore", | ||
| stderr: "ignore", | ||
| }); | ||
| expect(signalCode).toBeUndefined(); | ||
| expect(exitCode).toBe(0); | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
🟡 Unsetting
EDITOR/VISUALis not enough to make this test hermetic:bunEnvspreadsprocess.env(includingPATH), and when noEDITOR/VISUALis setEditorContext::detect_editorfalls through toEditor::by_fallback, which probesPATHforcode/subl/nvim/vim/etc. On a Linux dev box with VS Code installed, running this test will spawn 50 detachedcode fooNprocesses (50 editor tabs); on CI it may leave orphanedxdg-openinvocations. AddPATH: "/dev/null"to the env override (or pass{ editor: "/nonexistent" }as the second arg toBun.openInEditor) — the SIGPWR race is still exercised either way since the detached spawn thread still runs.Extended reasoning...
What the bug is
The new regression test attempts to prevent
Bun.openInEditorfrom launching a real editor by settingenv: { ...bunEnv, EDITOR: undefined, VISUAL: undefined }. This is insufficient: when neither an expliciteditoroption norEDITOR/VISUALis present, Bun falls back to probingPATHfor a list of common editors and will happily launch whichever one it finds — 50 times, on detached background threads.Code path
bunEnvis defined as{ ...process.env, ... }(test/harness.ts:50-51) and never overridesPATH, so the spawned child inherits the hostPATH.Bun.openInEditor("foo" + k)with no second argument, soopen_in_editor(src/runtime/api/BunObject.rs:1084-1095) sees no explicit editor and callsedit.auto_detect_editor(env).detect_editor(src/runtime/cli/open.rs:619-716) findsself.nameempty andEDITOR/VISUALunset, so it falls through toEditor::by_fallbackat line 701.by_fallback(open.rs:221-246) iteratesDEFAULT_PREFERENCE_LIST = [Vscode, Sublime, Atom, Neovim, Webstorm, Intellij, Textmate, Vim](open.rs:392) and callsby_path_for_editor, which runswhich()againstenv.get(b"PATH").editor.open()spawns a detachedstd::threadthat callssync::spawn(the sameBun__registerSignalsForForwardingpath this PR fixes) with the resolved binary asargv[0].Why the existing guard doesn't help
The
EDITOR: undefined, VISUAL: undefinedoverride only short-circuits the first two branches ofdetect_editor(theby_namelookups for$EDITORand$VISUAL). The third branch —by_fallback— is gated solely onPATH, which the test leaves untouched. On Linux,bin_path()'s hardcoded fallback locations are macOS-only (open.rs:424-437), soPATHis the only thing that matters.Step-by-step example
On a Linux developer machine with VS Code installed (
/usr/bin/codeinPATH):bun test test/js/bun/util/open-in-editor-gc.test.tsspawns the child withPATH=/usr/bin:...,EDITORandVISUALunset.k=0:detect_editor→by_fallback→which("code")resolves/usr/bin/code→Editor::Vscode,edit.path = "/usr/bin/code".editor.open()builds argv["/usr/bin/code", "foo0"](Vscode uses the binary directly, noxdg-openprefix — open.rs:286) and spawns it on a detached thread.foo0..foo49.For
vim/nvimthe argv is prefixed withOPENER = b"xdg-open"(open.rs:20, 276-284), which on headless CI typically fails fast, so the impact there is lower — but it still spawns 50 processes per test run.Impact
This is a test-hermeticity issue, not a correctness bug. The test itself will not fail or hang: the JS thread never joins the detached spawn threads, and
auto_closeswallows spawn errors vialet _ = sync::spawn(...). The SIGPWR regression is still correctly exercised regardless of whether an editor is found (the detached thread still callsBun__registerSignalsForForwardingeither way). However:code/subl/ideainPATH, every run of this test pops 50 editor windows.vim/nviminPATH(very common on Linux images), it spawns 50 short-livedxdg-openprocesses per run, adding noise and orphans.Fix
Add
PATH: "/dev/null"(orPATH: "") to the env override:Alternatively, pass an explicit non-existent editor so
detect_editornever runs the fallback probe:Either change keeps the regression coverage intact (the detached-thread
spawnSync+ concurrent GC race still fires) while making the test hermetic.