Skip to content

frontend: Quit on SIGTERM while outputs run or a dialog waits - #1403

Merged
zackees merged 2 commits into
obs-rust:masterfrom
rdoupe:fix/sigterm-quit-active-outputs
Oct 18, 2026
Merged

zackees merged 2 commits into
obs-rust:masterfrom
rdoupe:fix/sigterm-quit-active-outputs

Conversation

@rdoupe

@rdoupe rdoupe commented Oct 18, 2026

Copy link
Copy Markdown
Collaborator

Fixes #1402

What and why

SIGTERM now quits OBS while it records or streams, also with "Show confirmation on exit while outputs are active" on (the default), and finishes the recording. It also quits while a modal dialog waits, including during startup. Before, OBS stayed up until the sender killed it, which cut the recording off (upstream obsproject/obs-studio#10011, reported by Mikilio).

  • OBSApp::processSigTerm() called quit(), which closes the main window. With an output active, OBSBasic::closeEvent() ignored that close and opened the "exit anyway?" dialog. systemctl stop, logind at shutdown and kill send SIGTERM, and nobody answers that dialog.
  • While a modal dialog waited (for example the "encoder missing" warning the main window opens during startup), the quit ended nothing: the dialog runs its own event loop, and before the main event loop has started quit() does nothing at all.
  • processSigTerm() now calls OBSBasic::closeWithoutPrompt(), after which shouldPromptForClose() is false. closeWindow() then runs as when the dialog is answered with Yes: it stops the outputs and waits for the recording to finish (the wait from frontend: Recording shutdown waits for the stop signal instead of polling the UI thread (#1283) #1342/fix: recording shutdown waits for inactive after deactivate #1392).
  • If a modal dialog is open, processSigTerm() dismisses it and quits once its loop has returned. main() quits right after a startup that SIGTERM interrupted, as soon as the main event loop runs. Startup itself is not cut short, so nothing it still uses is freed under it.
  • Unchanged: closing the window and Exit in the menus still ask, and SIGINT is not touched.

Headless (private Xvfb, private D-Bus, fresh HOME), driven over obs-websocket: Simple output, x264, MKV, exit confirmation on.

Build Case Result Recording
v0.1.0-beta.20261014 AppImage recording 4 s, SIGTERM still running 30 s later unfinished, no duration
master 3fa2833 recording 4 s, SIGTERM still running 30 s later (the dialog is open) 0 bytes
master 3fa2833 "encoder missing" startup dialog, SIGTERM still running 30 s later none
This branch recording 4 s, SIGTERM quits after 1.2 s, clean shutdown in the log complete, ffprobe duration 4.97 s
This branch "encoder missing" startup dialog, SIGTERM quits, clean shutdown in the log none
Release and this branch idle, SIGTERM quit within 0.3 s none

RED → GREEN

  • RED: 9838f29 test: Tier 4 checks that SIGTERM while recording quits OBS. A new Tier 4 phase (run_tier4.py --only shutdown, also part of the full run) records with the exit confirmation on, sends SIGTERM and expects OBS gone within 30 s with one finished recording (ffprobe duration at least 2 s). Then it starts OBS with a profile whose encoder is missing, sends SIGTERM while that dialog is up and expects a clean shutdown. On master: 3 of 6 fail ("still running 30 s after SIGTERM (the exit confirmation dialog is open)"; a 0-byte MKV; "still running 30 s after SIGTERM" during the startup dialog). The other Tier 4 phases turn the confirmation off, so they never saw this.
  • GREEN: 64a5e33 frontend: Quit on SIGTERM while outputs run or a dialog waits. 6 of 6, three runs. Full build (C libobs lane) and full ctest -j4 (private Xvfb, private D-Bus, fresh HOME, system libobs-opengl masked): 303/303; the only skipped case is open_crashes_without_obs_core, as on master. The change is frontend-only.

Checklist

  • Base is master
  • I claimed the issue, or no one else has an open PR for it
  • Characterization test (if the unit had none), then RED, then GREEN commits
  • (Optional) Local gate passed and the pushed head is stamped; otherwise PR CI runs the full suite
  • Warning or flag changes (-W..., ratchets): none
  • Intentional differences from C listed in the parity test header and rust/README.md (ports): not a port

AI note: Claude Code (Claude Opus 5.5) wrote the code, the test, the commit messages and this text under the submitter's direction, and ran the tests above on the submitter's machines.

A new Tier 4 phase (run_tier4.py --only shutdown, also part of the full
run) checks two stop requests:

- SIGTERM while a Simple output recording runs, with "Show confirmation
  on exit while outputs are active" left on, the default. OBS must quit
  within 30 s and leave a finished recording (one file, ffprobe duration
  at least 2 s).
- SIGTERM while the "encoder missing" dialog that the main window opens
  during its startup is waiting (the profile names an encoder the build
  does not have). OBS must quit within 30 s with a clean shutdown in its
  log and no crash.

systemctl stop, logind at shutdown and kill send SIGTERM. Nobody can
answer a dialog for them, so OBS stays up until the service manager
kills it: a recording is cut off and the next start is an unclean one
(obsproject/obs-studio#10011). The other Tier 4 phases turn the exit
confirmation off, so they never saw this.

Fails on master, 3 of 6: "OBS quits on SIGTERM while recording:
recording active=True; still running 30 s after SIGTERM", the MKV is
0 bytes, and "OBS quits on SIGTERM during the startup dialog: still
running 30 s after SIGTERM".

Refs obs-rust#1402.
SIGTERM ran OBSApp::quit(), which closes the main window. Two cases
ignored it, and systemctl stop, logind at shutdown and kill send SIGTERM
with nobody there to answer a dialog, so OBS stayed up until the sender
killed it (obsproject/obs-studio#10011):

- With an output active and "Show confirmation on exit while outputs are
  active" on (the default), OBSBasic::closeEvent() ignored the close and
  opened the "exit anyway?" dialog. The recording was cut off when OBS
  was killed (0 bytes, or no duration) and the next start was an unclean
  one.
- While a modal dialog waited, for example the "encoder missing" one the
  main window opens during its startup, the quit ended nothing: the
  dialog runs its own event loop, and before the main event loop has
  started quit() does nothing at all.

processSigTerm() now marks the main window to close without the exit
confirmation, so closeWindow() runs as when it is answered with Yes: it
stops the outputs and waits for the recording to finish. If a modal
dialog is open, it dismisses the dialogs and quits once their loops have
returned; main() quits after a startup that SIGTERM interrupted, as soon
as the main event loop runs. Closing the window, Exit in the menus and
SIGINT are unchanged.

run_tier4.py --only shutdown: 6 of 6, three runs. With a recording
running, SIGTERM quits in about 1 s and leaves a complete MKV (ffprobe
duration 4.97 s).

Fixes obs-rust#1402.
@zackees
zackees added this pull request to the merge queue Oct 18, 2026
Merged via the queue into obs-rust:master with commit a60787d Oct 18, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

bug: frontend: SIGTERM is ignored while recording (exit confirmation) or while a dialog waits, and the recording is cut off (upstream #10011)

2 participants