fix(summer): exit 128+n when a delegated binary dies by a signal (WR-10, quick-261028-rqd)

This commit is contained in:
Jakub Zych
2026-10-28 20:52:57 +01:00
parent 4914cade5a
commit bb7e14bce9
4 changed files with 94 additions and 16 deletions

View File

@@ -162,22 +162,26 @@ func appStreams(out bonfire.Output, terminal bool) (stdout, stderr io.Writer) {
// runAppCmd runs cmd, the app binary started with argv, forwarding SIGINT
// and SIGTERM to it. When the binary exits with a code, the error carries
// that code (bonfire.WithExitCode) and is marked as already reported. The
// error names the command through argmask.Join, with the values of
// secret-looking flags masked, on every branch: a binary that fails to
// start or is ended by a signal gets summer's error block, and a signal
// death keeps exit code 1.
// that code (bonfire.WithExitCode) and is marked as already reported. A
// binary ended by a signal makes summer exit 128 plus the signal number
// (signalExit). The error names the command through argmask.Join, with the
// values of secret-looking flags masked, on every branch that prints it: a
// binary that fails to start gets summer's error block and exit code 1.
func runAppCmd(cmd *exec.Cmd, argv []string) error {
sigs := make(chan os.Signal, 2)
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)
defer signal.Stop(sigs)
if err := runChild(cmd, sigs); err != nil {
err = fmt.Errorf("summer: %s: %w", argmask.Join(argv), err)
var exit *exec.ExitError
if errors.As(err, &exit) {
if ws, ok := exit.Sys().(syscall.WaitStatus); ok && ws.Signaled() {
return signalExit(argv, err, ws.Signal())
}
}
err = fmt.Errorf("summer: %s: %w", argmask.Join(argv), err)
if errors.As(err, &exit) && exit.Exited() {
// The binary printed its own error; summer exits with its
// code and prints nothing more. A binary ended by a signal
// printed nothing, so it gets summer's error block.
// code and prints nothing more.
return bonfire.Reported(bonfire.WithExitCode(err, exit.ExitCode()))
}
return err
@@ -185,6 +189,21 @@ func runAppCmd(cmd *exec.Cmd, argv []string) error {
return nil
}
// signalExit is the error for an app binary, started with argv, that died
// by sig: it carries exit code 128 plus the signal number, as a shell
// reports such a death (130 for Ctrl+C, 143 for SIGTERM, 137 for SIGKILL).
// SIGINT and SIGTERM are what stopping summer or the binary sends, so they
// count as already reported and print nothing more; any other signal gets
// summer's error block, naming the command through argmask.Join with the
// values of secret-looking flags masked.
func signalExit(argv []string, err error, sig syscall.Signal) error {
err = bonfire.WithExitCode(fmt.Errorf("summer: %s: %w", argmask.Join(argv), err), 128+int(sig))
if sig == syscall.SIGINT || sig == syscall.SIGTERM {
return bonfire.Reported(err)
}
return err
}
// appShutdownGrace is how long a cancelled app binary may take to exit after
// SIGTERM before it is killed: the grace summer dev gives serve as well,
// longer than the 10-second shutdown timeout of serve and the workers.