Skip to content

host: watch BEAM child and re-spawn it on any exit - #4

Merged
dominicletz merged 1 commit into
masterfrom
host-driven-beam-restart
Aug 10, 2026
Merged

host: watch BEAM child and re-spawn it on any exit#4
dominicletz merged 1 commit into
masterfrom
host-driven-beam-restart

Conversation

@dominicletz

Copy link
Copy Markdown
Contributor

Summary

The native webview host now owns BEAM restart for packaged apps instead of heart. This unblocks the host-first macOS bundle (and the equivalent Linux layout going forward) where the host — not the BEAM release — is the long-lived process.

A child-exit watcher on both the Swift (macOS) and C++/GTK (Linux) controllers catches BEAM termination, applies an exponential backoff, and respawns. The host distinguishes a user-initiated shutdown from a crash using the existing system.prepare_quit RPC, which marks a 3-second expected-exit window.

Configurable via CLI flags and INI keys:

  • --edw-restart-beam= / [lifetime] restart_beam — turn respawn on/off (default: on)
  • --edw-max-restart-attempts= / [lifetime] restart_max_attempts — 0 = forever (default)
  • --edw-restart-backoff-ms= / [lifetime] restart_backoff_ms — base backoff in ms, exponential 1×→16× capped at 5 s

When the attempt cap is hit, the host quits instead of looping forever on a persistently broken release.

Why this change

Heart watches the launcher shell script and re-execs it. That makes BEAM the long-lived process and the host a transient one, which is the opposite of what we want for the host-first bundle. With this change:

  • Host sees BEAM die → host decides what to do (respawn, throttle, or quit)
  • system.prepare_quit from Elixir tells the host "this exit is intentional" so it doesn't respawn
  • Update flow (Updater.apply_updateOS.restartkill -9) keeps working because the host picks up the kill and respawns

Test plan

  • Build the macOS host; DesktopWebView.ini with restart_beam = false disables respawn
  • Force-kill the BEAM child (kill -9 $BEAM_PID) while DesktopWebView is running — host respawns BEAM within ~500 ms; verify prepare_quit was not sent
  • Trigger OS.restart/0 from Elixir — host respawns BEAM after the kill
  • Run :init.stop() from Elixir, observe host does NOT respawn (prepare_quit is sent before stop)
  • Cmd+Q the macOS app — host exits, no respawn attempt
  • Set restart_max_attempts = 2, repeatedly kill BEAM; after 2 restarts host itself quits
  • Confirm --edw-restart-beam=false disables respawn entirely

The host owns BEAM in packaged apps (host-first macOS layout, future
Linux parity). Heart watches the launcher script and re-execs it; that
fights the host's own process model where the host should drive restart.

Install a child-exit handler in both the Swift (NSApp) macOS host and
the C++ GTK Linux host. On any exit:

- If quit is already in flight (Cmd+Q, prepare_quit RPC, or force-quit),
  do not respawn.
- Otherwise, after an exponential backoff (500 ms -> 1 s -> 2 s -> 4 s ->
  5 s cap), spawn the BEAM script again.
- Optional restart_max_attempts cap (0 = forever). The host quits once
  the cap is reached, instead of looping forever on a persistent crash.

Configurable via CLI flags --edw-restart-beam=, --edw-max-restart-
attempts=, --edw-restart-backoff-ms= and INI keys [lifetime]
restart_beam / restart_max_attempts / restart_backoff_ms.

The existing system.prepare_quit RPC now also marks a 3 s "expected
exit" window so user-driven shutdowns are not misread as crashes.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@dominicletz
dominicletz merged commit f2050bc into master Aug 10, 2026
3 checks passed
@dominicletz
dominicletz deleted the host-driven-beam-restart branch August 10, 2026 12:27
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.

1 participant