aboutsummaryrefslogtreecommitdiffhomepage
path: root/docs/dev
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 /docs/dev
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 'docs/dev')
-rw-r--r--docs/dev/signals.md46
1 files changed, 46 insertions, 0 deletions
diff --git a/docs/dev/signals.md b/docs/dev/signals.md
new file mode 100644
index 00000000..9616f0ee
--- /dev/null
+++ b/docs/dev/signals.md
@@ -0,0 +1,46 @@
+# Signals
+
+## In Composer core
+
+Composer has three routes for handling signals:
+
+* `Seld\Signal\SignalHandler`
+* `Symfony\Component\Console\SignalRegistry\SignalRegistry` and `SignalableCommandInterface`
+* PHP builtins (`pcntl_signal()`, etc.)
+
+Of these, upstream Composer itself only ever uses `SignalHandler`, and Shirabe
+behaves roughly the same way. The difference is when the interruption takes
+effect, as described in [known incompatibilities](../known-incompatibilities.md).
+
+> Composer runs its abort handler almost immediately after the signal arrives.
+> Shirabe, however, runs it at the next checkpoint instead, so stopping `shirabe`
+> command by `Ctrl+C` may take more time than Composer.
+
+In PHP, the VM checks for a pending signal at the end of a loop and on a
+function call, which gets the signal handler run almost immediately after the
+signal arrives. Rust has no such mechanism, so we place the checkpoints by
+hand; grep for `signals.is_triggered()` to find them. They are not as
+fine-grained as a function call, so more work runs between receiving the signal
+and starting the abort than Composer would let through, and it takes longer.
+
+## In plugins and scripts
+
+Shirabe treats signal handling in plugins and scripts as undefined behavior,
+for two broad reasons.
+
+The first is that Shirabe consists of a Rust core process and a PHP worker
+process that runs the plugins and scripts, which makes faithful reproduction
+difficult.
+
+The second is that Composer itself does not fully account for plugins and
+scripts subscribing to signals either. The `SignalHandler` that Composer uses
+to handle signals overwrites an existing signal handler unconditionally. So
+even when a plugin or script installs a handler through Symfony Console or a
+PHP builtin, that handler is lost the moment execution reaches a place where
+Composer uses `SignalHandler`.
+
+For these two reasons, Shirabe today neither restricts plugins and scripts from
+installing signal handlers nor does anything special about it. What happens
+when they do is not guaranteed.
+
+This stance may be withdrawn if a legitimate use case turns up.