Skip to content

Give the Puma plugin the pid of the litestream process - #77

Open
cole-robertson wants to merge 1 commit into
fractaledmind:mainfrom
Rebulk:puma-plugin-pid
Open

Give the Puma plugin the pid of the litestream process#77
cole-robertson wants to merge 1 commit into
fractaledmind:mainfrom
Rebulk:puma-plugin-pid

Conversation

@cole-robertson

Copy link
Copy Markdown

Standalone fix for a bug that exists on the current 0.14.0 release. Independent of the 0.5 work.

Problem

Since 0.14 (#62) the Puma plugin forks a Ruby child that calls Commands.replicate(async: true), which forks again and execs the binary. The plugin holds the outer pid, which exits immediately as a zombie, while the daemon is reparented to init. On Puma stop the hook fires, sends INT to the dead pid, and replication keeps running.

Reproduced on upstream main in a fresh Rails 8 app with Puma 8.0.2: after boot the tree under Puma was [ruby] <defunct> and the daemon's parent was init; after kill -INT on Puma the log said Stopping Litestream... and the daemon was still running. Three runs, same result.

Change

Commands.replicate(async: true) uses Process.spawn and returns the daemon's own pid. The plugin holds that pid, so its existing INT-then-wait works. The monitor_puma thread goes with the Ruby child: it could only run inside a Ruby process, and it only covered Puma dying without running its stop hooks.

Verified

  • 90 tests, 0 failures; standardrb clean.
  • New test: async replicate returns the pid of the spawned executable, and after TERM the child is reaped.
  • Same fresh app on this branch: the daemon is a direct child of the Puma master; on kill -INT the daemon logs signal received, litestream shutting down before Puma's Goodbye!, and no litestream replicate process survives.

Puma 8 deprecates the on_booted/on_stopped/on_restart hook names in favour of after_booted/after_stopped/before_restart; they still fire, so that is left alone here.

Since 0.14 the plugin forked a Ruby child that called
Commands.replicate(async: true), which forked again and exec'd the
binary. The plugin held the outer pid, which exited as a zombie while
the daemon was reparented to init. On Puma stop the hook fired, sent
INT to the dead pid, and replication kept running.

Commands.replicate(async: true) now uses Process.spawn and returns the
daemon's pid; the plugin holds that pid and its INT-then-wait works.
The extra monitor_puma thread went with the Ruby child; it could only
run inside a Ruby process, and it only covered the case where Puma
died without running its stop hooks.
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