Skip to content

melt: non-zero exit when interrupted by a signal (#547)#1270

Open
Magnussmari wants to merge 1 commit into
mltframework:masterfrom
Magnussmari:fix/547-nonzero-exit-on-signal
Open

melt: non-zero exit when interrupted by a signal (#547)#1270
Magnussmari wants to merge 1 commit into
mltframework:masterfrom
Magnussmari:fix/547-nonzero-exit-on-signal

Conversation

@Magnussmari

Copy link
Copy Markdown

Fixes #547.

melt traps SIGINT/SIGTERM (and SIGHUP/SIGPIPE on non-Windows) with stop_handler, which sets the producer done property for a graceful stop. The render loop then unwinds and main() returns 0, so a Ctrl-C'd, incomplete render reports success and a calling script cannot tell the output was truncated. This is the case @mzealey raised (driving melt from a shell script).

The fix. Record the signal in a volatile sig_atomic_t, and once the output file is finalized (after mlt_consumer_stop), restore the default handler and re-raise so the exit status reflects the signal. This is @marcespie's suggested approach, and raise(sig) is the portable ISO-C equivalent of his kill(getpid(), sig). It also mirrors the abnormal_exit_handler already in this file, which does term_exit(); signal(signum, SIG_DFL); raise(signum); — including the term_exit() call, which matters: re-raising bypasses atexit, so without it a Ctrl-C during interactive SDL playback (where term_init puts the tty in raw mode) would leave the terminal needing reset.

Exit-status breadth. Any trapped termination signal now yields 128 + signum instead of 0: SIGINT 130 and SIGTERM 143 on all platforms, plus SIGHUP 129 and SIGPIPE 141 on non-Windows. In particular a broken output pipe now exits 141 instead of 0, which is standard Unix behavior and lets scripts detect truncation. Keeping it uniform (rather than special-casing SIGINT) matches abnormal_exit_handler and adds no new flag surface. On Windows the re-raise is skipped and EXIT_FAILURE is returned instead.

Unchanged. Normal completion still exits 0, and quitting interactive playback with q/ESC still exits 0 (that path sets done directly and never sets stop_signum). A genuine consumer fatal error keeps precedence over the signal status via the !error guard.

Testing. Built from source and verified on macOS (Apple Silicon): a render interrupted by SIGINT now exits 130 (was 0), SIGTERM exits 143, and a normal render still exits 0.

melt caught SIGINT/SIGTERM (and SIGHUP/SIGPIPE on non-Windows) via
stop_handler, which sets the producer "done" property for a graceful
stop but then let main() return 0 — so a Ctrl-C'd, incomplete render
reported success and callers could not detect it.

Record the signal in a volatile sig_atomic_t and, after the output is
finalized, restore the default handler and re-raise it so the exit
status reflects the signal (130 for SIGINT, 143 for SIGTERM, etc.),
mirroring the existing abnormal_exit_handler in this file (term_exit
then signal(SIG_DFL) then raise). On Windows, surface EXIT_FAILURE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ddennedy

Copy link
Copy Markdown
Member

Besides what was already written, why do you want to change it? Is this just fodder for your AI?

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the melt CLI to return a non-zero exit status when interrupted by termination signals (e.g., Ctrl-C), so calling scripts can reliably detect incomplete/truncated renders (fixes #547).

Changes:

  • Record the terminating signal in a volatile sig_atomic_t (stop_signum) inside stop_handler.
  • After the consumer stops and output is flushed, restore the default handler and re-raise the signal on non-Windows to produce standard 128 + signum exit codes (Windows returns EXIT_FAILURE instead).
Comments suppressed due to low confidence (1)

src/melt/melt.c:56

  • stop_handler runs in a signal-handler context, but it calls mlt_properties_set_int(), which is not guaranteed to be async-signal-safe. That can lead to undefined behavior (e.g., deadlocks/crashes if the signal interrupts code holding internal locks). Now that stop_signum exists, consider making the handler only record the signal (and possibly set a simple sig_atomic_t "stop requested" flag), and have the main/render loop perform the MLT property update in a safe context.
    stop_signum = signum;
    if (melt) {
        mlt_properties properties = MLT_PRODUCER_PROPERTIES(melt);
        mlt_properties_set_int(properties, "done", 1);
    }

Comment thread src/melt/melt.c
Comment on lines +1146 to +1153
if (stop_signum && !error) {
#ifndef _WIN32
term_exit();
signal(stop_signum, SIG_DFL);
raise(stop_signum);
#endif
error = EXIT_FAILURE; // Windows: no re-raise, so surface a non-zero status
}
Comment thread src/melt/melt.c
Comment on lines +1148 to +1152
term_exit();
signal(stop_signum, SIG_DFL);
raise(stop_signum);
#endif
error = EXIT_FAILURE; // Windows: no re-raise, so surface a non-zero status

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After further reviewing the referenced issue discussion and considering your use case for headless rendering, I can understand how this might affect you.

Based on both the PR description and the copilot review comment, I think this should be like this instead of the copilot suggestion:

Suggested change
term_exit();
signal(stop_signum, SIG_DFL);
raise(stop_signum);
#endif
error = EXIT_FAILURE; // Windows: no re-raise, so surface a non-zero status
abnormal_exit_handler(stop_signum);
#else
error = EXIT_FAILURE; // Windows: no re-raise, so surface a non-zero status
#endif

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Melt should non-zero exit on SIGINT/SIGTERM

3 participants