aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/util/process_executor.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-11 12:19:31 +0900
committernsfisis <nsfisis@gmail.com>2026-08-11 12:19:31 +0900
commit9539fea16e0d8d07faf7edf50d480e0a80c4ccfc (patch)
treec989aae95701e24aace29fec25c9d33c4c065e6b /crates/shirabe/src/util/process_executor.rs
parent144d059b725e2d178d7f4a6403cde9474fe65dcc (diff)
downloadphp-shirabe-9539fea16e0d8d07faf7edf50d480e0a80c4ccfc.tar.gz
php-shirabe-9539fea16e0d8d07faf7edf50d480e0a80c4ccfc.tar.zst
php-shirabe-9539fea16e0d8d07faf7edf50d480e0a80c4ccfc.zip
feat(signal): abort on SIGINT, SIGTERM and SIGHUP at checkpoints
The SignalHandler port was a no-op stub, so all four of Composer's abort paths were dead code: nothing removed a half-created project, reverted composer.json, or cleaned up half-installed packages. Composer runs those handlers from pcntl callbacks, which a Rust signal handler cannot do -- it may touch nothing beyond atomics. SignalSubscription records the signal instead, and the abort runs from checkpoints on the normal call stack, where the clean-up can borrow the state it needs. That also resolves the closure-capture TODO(phase-c)s in RequireCommand and InstallationManager, and replaces exit_with_last_signal's exit(0) with the restore-and-re-raise Seld\Signal does. A subscription is live only inside the four abort regions, so elsewhere the signals keep their default disposition and kill the process at once. It is installed without SA_RESTART so a signal interrupts an interactive prompt rather than resuming the read. A signal reaches only the innermost subscription, reproducing SignalHandler's single-stack dispatch. Drop SignalRegistry, SignalableCommandInterface and the Application wiring for them: nothing in Composer reaches that path, and SignalHandler discards whatever they register. Signal handling from plugins and scripts is undefined behavior; see docs/dev/signals.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/util/process_executor.rs')
-rw-r--r--crates/shirabe/src/util/process_executor.rs37
1 files changed, 16 insertions, 21 deletions
diff --git a/crates/shirabe/src/util/process_executor.rs b/crates/shirabe/src/util/process_executor.rs
index 97f4ed3d..4808d0be 100644
--- a/crates/shirabe/src/util/process_executor.rs
+++ b/crates/shirabe/src/util/process_executor.rs
@@ -3,6 +3,7 @@
use crate::io::IOInterface;
use crate::io::IOInterfaceImmutable;
use crate::io::io_interface;
+use crate::signal::SignalSubscription;
use crate::util::GitHub;
use crate::util::Platform;
use indexmap::IndexMap;
@@ -14,7 +15,6 @@ use shirabe_php_shim::{
php_regex, rtrim, str_replace, strcspn, strlen, strpbrk, strtolower, strtr_array,
substr_replace, trim,
};
-use shirabe_seld_signal::SignalHandler;
use shirabe_symfony_process::ExecutableFinder;
use shirabe_symfony_process::Process;
use shirabe_symfony_process::ProcessMock;
@@ -267,22 +267,7 @@ impl ProcessExecutor {
// ignore TTY enabling errors
}
- let io_for_signal = self.io.clone();
- let signal_handler = SignalHandler::create(
- vec![
- SignalHandler::SIGINT.to_string(),
- SignalHandler::SIGTERM.to_string(),
- SignalHandler::SIGHUP.to_string(),
- ],
- Box::new(move |signal: String, _h: &SignalHandler| {
- if let Some(io) = &io_for_signal {
- io.write_error(&format!(
- "Received {}, aborting when child process is done",
- signal
- ));
- }
- }),
- );
+ let signals = SignalSubscription::new();
let result: anyhow::Result<()> = (|| -> anyhow::Result<()> {
match output.to_callback() {
@@ -306,23 +291,33 @@ impl ProcessExecutor {
self.error_output = process.get_error_output()?;
Ok(())
})();
+ if signals.is_triggered()
+ && let Some(io) = &self.io
+ {
+ io.write_error3(
+ &format!(
+ "Received {}, aborting when child process is done",
+ signals.last_signal().as_str()
+ ),
+ true,
+ io_interface::DEBUG,
+ );
+ }
let final_result: anyhow::Result<()> = match result {
Ok(()) => Ok(()),
Err(e) => {
if let Some(pse) = e.catch::<ProcessSignaledException>() {
- if signal_handler.is_triggered() {
+ if signals.is_triggered() {
// exiting as we were signaled and the child process exited too due to the signal
- signal_handler.exit_with_last_signal();
+ signals.exit_with_last_signal();
}
let _ = pse;
Ok(())
} else {
- signal_handler.unregister();
return Err(e);
}
}
};
- signal_handler.unregister();
final_result?;
Ok(process.get_exit_code())