diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-11 12:19:31 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-11 12:19:31 +0900 |
| commit | 9539fea16e0d8d07faf7edf50d480e0a80c4ccfc (patch) | |
| tree | c989aae95701e24aace29fec25c9d33c4c065e6b /crates/shirabe-symfony-console/src | |
| parent | 144d059b725e2d178d7f4a6403cde9474fe65dcc (diff) | |
| download | php-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-symfony-console/src')
5 files changed, 0 insertions, 119 deletions
diff --git a/crates/shirabe-symfony-console/src/command.rs b/crates/shirabe-symfony-console/src/command.rs index 5ab97400..7efaf401 100644 --- a/crates/shirabe-symfony-console/src/command.rs +++ b/crates/shirabe-symfony-console/src/command.rs @@ -3,11 +3,9 @@ mod complete_command; mod dump_completion_command; mod help_command; mod list_command; -mod signalable_command_interface; pub use command::*; pub use complete_command::*; pub use dump_completion_command::*; pub use help_command::*; pub use list_command::*; -pub use signalable_command_interface::*; diff --git a/crates/shirabe-symfony-console/src/command/signalable_command_interface.rs b/crates/shirabe-symfony-console/src/command/signalable_command_interface.rs deleted file mode 100644 index 8777eb5e..00000000 --- a/crates/shirabe-symfony-console/src/command/signalable_command_interface.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! ref: composer/vendor/symfony/console/Command/SignalableCommandInterface.php - -/// Interface for command reacting to signal. -pub trait SignalableCommandInterface { - /// Returns the list of signals to subscribe. - fn get_subscribed_signals(&self) -> Vec<i64>; - - /// The method will be called when the application is signaled. - fn handle_signal(&mut self, signal: i64); -} diff --git a/crates/shirabe-symfony-console/src/lib.rs b/crates/shirabe-symfony-console/src/lib.rs index 814e53a3..4a5a4ceb 100644 --- a/crates/shirabe-symfony-console/src/lib.rs +++ b/crates/shirabe-symfony-console/src/lib.rs @@ -12,7 +12,6 @@ pub mod helper; pub mod input; pub mod output; pub mod question; -pub mod signal_registry; pub mod style; pub mod terminal; pub mod tester; @@ -31,6 +30,5 @@ pub use helper::*; pub use input::*; pub use output::*; pub use question::*; -pub use signal_registry::*; pub use style::*; pub use terminal::*; diff --git a/crates/shirabe-symfony-console/src/signal_registry.rs b/crates/shirabe-symfony-console/src/signal_registry.rs deleted file mode 100644 index ab48848e..00000000 --- a/crates/shirabe-symfony-console/src/signal_registry.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod signal_registry; - -pub use signal_registry::*; diff --git a/crates/shirabe-symfony-console/src/signal_registry/signal_registry.rs b/crates/shirabe-symfony-console/src/signal_registry/signal_registry.rs deleted file mode 100644 index f1f67d09..00000000 --- a/crates/shirabe-symfony-console/src/signal_registry/signal_registry.rs +++ /dev/null @@ -1,102 +0,0 @@ -//! ref: composer/vendor/symfony/console/SignalRegistry/SignalRegistry.php - -use indexmap::IndexMap; - -/// A signal handler receives the signal number and whether a further handler follows. -pub type SignalHandler = Box<dyn Fn(i64, bool)>; - -pub struct SignalRegistry { - // signal number => list of handlers - signal_handlers: IndexMap<i64, Vec<SignalHandler>>, -} - -impl std::fmt::Debug for SignalRegistry { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("SignalRegistry") - .field("signal_handlers", &self.signal_handlers.keys()) - .finish_non_exhaustive() - } -} - -impl Default for SignalRegistry { - fn default() -> Self { - Self::new() - } -} - -impl SignalRegistry { - pub fn new() -> Self { - if shirabe_php_shim::function_exists("pcntl_async_signals") { - shirabe_php_shim::pcntl_async_signals(true); - } - - Self { - signal_handlers: IndexMap::new(), - } - } - - pub fn register(&mut self, signal: i64, signal_handler: SignalHandler) { - if !self.signal_handlers.contains_key(&signal) { - let previous_callback = shirabe_php_shim::pcntl_signal_get_handler(signal); - - if shirabe_php_shim::is_callable(&previous_callback) { - // $this->signalHandlers[$signal][] = $previousCallback; - // The previous handler is an opaque PHP callable obtained from pcntl; - // it is invoked through the runtime callable mechanism. - self.signal_handlers - .entry(signal) - .or_default() - .push(Box::new(move |signal, has_next| { - shirabe_php_shim::call_php_callable( - &previous_callback, - &[ - shirabe_php_shim::PhpMixed::Int(signal), - shirabe_php_shim::PhpMixed::Bool(has_next), - ], - ); - })); - } - } - - self.signal_handlers - .entry(signal) - .or_default() - .push(signal_handler); - - // pcntl_signal($signal, [$this, 'handle']) - // TODO(plugin): the PHP callback `[$this, 'handle']` captures the registry - // instance. Wiring this object method as a C-level signal handler requires the - // runtime callable mechanism; see review notes. - shirabe_php_shim::pcntl_signal(signal, shirabe_php_shim::PhpMixed::Null); - } - - pub fn is_supported() -> bool { - if !shirabe_php_shim::function_exists("pcntl_signal") { - return false; - } - - if shirabe_php_shim::explode( - ",", - &shirabe_php_shim::ini_get("disable_functions").unwrap_or_default(), - ) - .contains(&"pcntl_signal".to_string()) - { - return false; - } - - true - } - - pub fn handle(&self, signal: i64) { - let handlers = match self.signal_handlers.get(&signal) { - Some(handlers) => handlers, - None => return, - }; - let count = handlers.len(); - - for (i, signal_handler) in handlers.iter().enumerate() { - let has_next = i != count - 1; - signal_handler(signal, has_next); - } - } -} |
