ExTauri.ShutdownManager (ex_tauri v0.2.0)

View Source

Manages graceful shutdown of the Phoenix application when running as a Tauri sidecar.

This GenServer implements a heartbeat-based mechanism to detect when the Tauri frontend exits. The Rust frontend sends heartbeat signals every 100ms — over a Unix domain socket on macOS/Linux, or a localhost TCP socket on Windows — and if the Phoenix sidecar doesn't receive a heartbeat within 1500ms (configurable), it initiates graceful shutdown.

Usage

Add this to your application's supervision tree in application.ex:

def start(_type, _args) do
  children = [
    ExTauri.ShutdownManager,
    # ... your other children
  ]

  opts = [strategy: :one_for_one, name: MyApp.Supervisor]
  Supervisor.start_link(children, opts)
end

How it works

The heartbeat mechanism provides robust shutdown detection:

  1. ShutdownManager opens a listener. On macOS/Linux this is a Unix domain socket at <tmpdir>/tauri_heartbeat_<app_name>.sock. On Windows (where the BEAM cannot listen on Unix domain sockets) it is a TCP socket bound to 127.0.0.1 on an ephemeral port, and the port number is written to <tmpdir>/tauri_heartbeat_<app_name>.port so the Rust frontend can find it
  2. Rust frontend connects and sends a byte every 100ms
  3. The acceptor reads bytes in the same process that accepted the connection and casts each one back as a heartbeat (reading from another process would fail)
  4. Every 500ms, ShutdownManager checks if a heartbeat was received recently
  5. Once the frontend has connected at least once, no heartbeat for 1500ms initiates graceful shutdown; before the first connection the timeout is not enforced, so a slow boot can't shut the app down before the window attaches

The socket path is unique per application (based on :app_name config) to prevent collisions when multiple ExTauri applications run simultaneously.

This works even when:

  • The app is force-quit (CMD+Q on macOS)
  • The app crashes unexpectedly
  • The process is killed without cleanup

Configuration

Heartbeat timing can be configured in your config/config.exs:

config :ex_tauri,
  heartbeat_interval: 500,  # How often to check heartbeat (ms, default: 500)
  heartbeat_timeout: 1500   # Time without heartbeat before shutdown (ms, default: 1500)

The transport is selected automatically from the OS (:unix on macOS/Linux, :tcp on Windows). It can be forced with the :heartbeat_transport config key or the :transport start option, which is mainly useful for tests.

The desktop channel

Beyond liveness, the socket carries a newline-delimited JSON protocol in both directions: the Rust frontend sends heartbeats and native events (menu and tray clicks), and Elixir sends desktop commands (notifications, tray setup). Use ExTauri.Desktop rather than talking to this server directly.

Summary

Functions

Returns a specification to start this module under a supervisor.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

start_link(opts \\ [])