Repository navigation
install: forward SIGINT/SIGTERM/SIGHUP to running lifecycle scripts and wait for them #41921
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
Open
robobun
wants to merge
6
commits into
main
Choose a base branch
from
robobun/85a3ecdc/install-forward-signals-to-lifecycle-scripts
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
4eab15f
install: forward SIGINT/SIGTERM/SIGHUP to running lifecycle scripts a…
robobun f8556c9
Keep a signal received across a re-hook, enqueue from the handler wit…
robobun cfd7304
Trim comments
robobun 5524f38
Shorter module docs
robobun e820e7a
Die by a signal that lands while the last script's exit is handled
robobun b5465a2
ci: retrigger
robobun 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
Some comments aren't visible on the classic Files Changed page.
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
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,110 @@ | ||
| //! SIGINT, SIGTERM and SIGHUP sent to `bun install` while a lifecycle script | ||
| //! runs (CI cancellation, `docker stop`, `timeout(1)`, a hung-up ssh session). | ||
| //! | ||
| //! Without this the default action ends `bun install` at once and every | ||
| //! running script is reparented to init, still writing into `node_modules`. | ||
| //! Instead, while at least one script runs, the signal is forwarded to each | ||
| //! script and `bun install` waits. No new script starts. When the last | ||
| //! script has exited, `bun install` dies by the forwarded signal, the same way | ||
| //! it already does for a script that dies by a signal on its own. The hook | ||
| //! comes out as soon as the signal is forwarded, so a second signal takes the | ||
| //! default action (on Linux the scripts then die with `bun install`, see | ||
| //! `linux_pdeathsig` in the runner). | ||
| //! | ||
| //! The hook itself is `bun_spawn::exit_signals`. This module is the install | ||
| //! half: which scripts run, what to forward, when to exit. | ||
|
robobun marked this conversation as resolved.
Resolved
|
||
|
|
||
| use core::ffi::c_int; | ||
| use core::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering}; | ||
|
|
||
| use bun_spawn::exit_signals; | ||
|
|
||
| use crate::PackageManager; | ||
| use crate::lifecycle_script_runner::LifecycleScriptSubprocess; | ||
|
|
||
| /// Number of lifecycle scripts that are running right now. | ||
| static RUNNING: AtomicUsize = AtomicUsize::new(0); | ||
| /// Set once the signal has been forwarded and the install is draining. | ||
| static DRAINING: AtomicBool = AtomicBool::new(false); | ||
| static MANAGER: AtomicPtr<PackageManager> = AtomicPtr::new(core::ptr::null_mut()); | ||
|
|
||
| /// The signal that `bun install` forwarded to the running scripts, if any. | ||
| /// While this is `Some`, no new lifecycle script starts and an exiting script | ||
| /// does not chain into the next one. | ||
|
robobun marked this conversation as resolved.
Resolved
|
||
| pub(crate) fn pending() -> Option<bun_core::SignalCode> { | ||
| if !DRAINING.load(Ordering::Relaxed) { | ||
| return None; | ||
| } | ||
| exit_signals::received().map(signal_code) | ||
| } | ||
|
|
||
| fn signal_code(sig: c_int) -> bun_core::SignalCode { | ||
| u8::try_from(sig) | ||
| .ok() | ||
| .and_then(bun_core::SignalCode::from_raw) | ||
| .unwrap_or(bun_core::SignalCode::DEFAULT) | ||
| } | ||
|
|
||
| /// A lifecycle script is about to be spawned. Hooks the signals on the first | ||
| /// one. | ||
| /// | ||
| /// `manager` is the live `PackageManager` that owns the script. The signal | ||
| /// callback dereferences it until the last script has exited, so it must | ||
| /// carry allocation-rooted provenance, not a transient `&mut` reborrow. | ||
|
robobun marked this conversation as resolved.
Resolved
|
||
| pub(crate) fn on_script_started(manager: *mut PackageManager) { | ||
| if RUNNING.fetch_add(1, Ordering::Relaxed) != 0 { | ||
| return; | ||
| } | ||
| MANAGER.store(manager, Ordering::Relaxed); | ||
| // SAFETY: `manager` is live (caller contract). Only the `event_loop` | ||
| // field is borrowed, and the borrow ends with this statement. | ||
| let event_loop = | ||
| bun_event_loop::EventLoopHandle::from_any(unsafe { &mut (*manager).event_loop }); | ||
| exit_signals::hook(event_loop, on_signal); | ||
| } | ||
|
|
||
| /// A lifecycle script exited (after it left `active_lifecycle_scripts`), or | ||
| /// failed to spawn. After the last one: dies by the forwarded signal when | ||
| /// draining, else unhooks. | ||
|
robobun marked this conversation as resolved.
Resolved
|
||
| pub(crate) fn on_script_exited() { | ||
| if RUNNING.fetch_sub(1, Ordering::Relaxed) != 1 { | ||
| return; | ||
| } | ||
| if let Some(sig) = pending() { | ||
| die(sig); | ||
| } | ||
| exit_signals::unhook(); | ||
|
robobun marked this conversation as resolved.
Resolved
|
||
| MANAGER.store(core::ptr::null_mut(), Ordering::Relaxed); | ||
| } | ||
|
|
||
| fn die(sig: bun_core::SignalCode) -> ! { | ||
| bun_core::Output::flush(); | ||
| bun_core::Global::raise_ignoring_panic_handler(sig); | ||
| } | ||
|
|
||
| /// Runs on the install thread, between event loop ticks. | ||
| fn on_signal(sig: c_int) { | ||
| let manager = MANAGER.load(Ordering::Relaxed); | ||
| if manager.is_null() || RUNNING.load(Ordering::Relaxed) == 0 { | ||
| // The last script exited between the handler and this callback. | ||
| // Nothing to wait for: the default action, now. | ||
|
robobun marked this conversation as resolved.
Resolved
|
||
| die(signal_code(sig)); | ||
| } | ||
| DRAINING.store(true, Ordering::Relaxed); | ||
| exit_signals::unhook(); | ||
| let forward = signal_code(sig) as u8; | ||
| // SAFETY: `manager` is the live `PackageManager` stored by | ||
| // `on_script_started`; nothing else walks or mutates the heap while this | ||
| // runs on the install thread. | ||
| unsafe { | ||
| (*manager).active_lifecycle_scripts.for_each( | ||
| |script: *mut LifecycleScriptSubprocess<'static>| { | ||
| if let Some(process) = &(*script).process | ||
| && !process.process_mut().has_exited() | ||
| { | ||
| let _ = process.kill(forward); | ||
| } | ||
| }, | ||
| ); | ||
| } | ||
| } | ||
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
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.