Skip to content

Commit

Permalink
Improve systemd notification support
Browse files Browse the repository at this point in the history
This takes the work by @acmh and improves on it. This is done by
squashing all commits and rebasing it. Then the following changes were
made:

* Dropped SD_NOTIFY env var. There is aleady the NOTIFY_SOCKET env var
  presented by systemd and is redundant.
* Move code is pushed in Puma::Systemd
* on_reload now emits RELOADING=1 notification to systemd
* Drop lower bound check on usec. Systemd can only be configured in
  seconds and it's hard to misconfigure. The actual code should be safe.
  • Loading branch information
ekohl committed Oct 20, 2020
1 parent 2603c91 commit 9e8c8c3
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 34 deletions.
13 changes: 5 additions & 8 deletions docs/systemd.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,17 @@ After=network.target

[Service]
# Puma supports systemd's `Type=notify` and watchdog service
# monitoring, since you have installed [sd_notify](https://github.com/agis/ruby-sdnotify) gem.
# monitoring, since you have installed [sd_notify](https://github.com/agis/ruby-sdnotify) gem.
# If you are using an earlier version of Puma, change this to `Type=simple`
# and remove the `WatchdogSec` and `Environment="SD_NOTIFY=true` line.
# and remove the `WatchdogSec` line.
Type=notify

# Preferably configure a non-privileged user
# User=

# Allows Systemd integration
Environment="SD_NOTIFY=true"

# If your Puma process locks up, systemd's watchdog will restart it within seconds.
WatchdogSec=10

# Preferably configure a non-privileged user
# User=

# The path to the your application code root directory.
# Also replace the "<YOUR_APP_PATH>" place holders below with this path.
# Example /home/username/myapp
Expand Down
16 changes: 12 additions & 4 deletions lib/puma/events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,26 @@ def debug_error(error, req=nil, text="")
@error_logger.debug(error: error, req: req, text: text)
end

def on_stopped(&block)
register(:on_stopped, &block)
end

def on_booted(&block)
register(:on_booted, &block)
end

def on_restart(&block)
register(:on_restart, &block)
end

def on_stopped(&block)
register(:on_stopped, &block)
end

def fire_on_booted!
fire(:on_booted)
end

def fire_on_restart!
fire(:on_restart)
end

def fire_on_stopped!
fire(:on_stopped)
end
Expand Down
19 changes: 9 additions & 10 deletions lib/puma/launcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ def reload_worker_directory
end

def restart!
@events.fire_on_restart!
@config.run_hooks :on_restart, self, @events

if Puma.jruby?
Expand Down Expand Up @@ -330,22 +331,20 @@ def prune_bundler
#

def integrate_with_systemd
return unless ENV["SD_NOTIFY"]
return unless ENV["NOTIFY_SOCKET"]

require 'sd_notify'
return log "Systemd integration failed. It looks like you're trying to use systemd notify but don't have sd_notify gem installed" unless defined?(SdNotify)

require 'puma/systemd'
begin
require 'puma/systemd'
rescue LoadError
log "Systemd integration failed. It looks like you're trying to use systemd notify but don't have sd_notify gem installed"
return
end

log "* Enabling systemd notification integration"

@events.on_booted { SdNotify.ready }
@events.on_stopped { SdNotify.stopping }

systemd = Systemd.new(@events)

systemd.start_watchdog if SdNotify.watchdog?
systemd.hook_events
systemd.start_watchdog
end

def spec_for_gem(gem_name)
Expand Down
29 changes: 22 additions & 7 deletions lib/puma/systemd.rb
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
# frozen_string_literal: true

require 'puma/events'
require 'sd_notify'

module Puma
class Systemd
def initialize(events)
@events = events
end

def hook_events
@events.on_booted { SdNotify.ready }
@events.on_stopped { SdNotify.stopping }
@events.on_restart { SdNotify.reloading }
end

def start_watchdog
usec = Integer(ENV["WATCHDOG_USEC"])
return warn "systemd Watchdog too fast: #{usec}" if usec < 1_000_000
ping_f = watchdog_sleep_time
return unless ping_f

sec_f = usec / 1_000_000.0
# "It is recommended that a daemon sends a keep-alive notification message
# to the service manager every half of the time returned here."
ping_f = sec_f / 2
log "Pinging systemd watchdog every #{ping_f.round(1)} sec"
Thread.new do
loop do
Expand All @@ -25,6 +27,19 @@ def start_watchdog
end
end

private

def watchdog_sleep_time
return unless SdNotify.watchdog?

usec = Integer(ENV["WATCHDOG_USEC"])

sec_f = usec / 1_000_000.0
# "It is recommended that a daemon sends a keep-alive notification message
# to the service manager every half of the time returned here."
sec_f / 2
end

def log(str)
@events.log str
end
Expand Down
14 changes: 9 additions & 5 deletions test/test_integration_systemd.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ def setup
skip "Skipped because Systemd support is linux-only" if windows? || osx?
skip UNIX_SKT_MSG unless UNIX_SKT_EXIST

super

::Dir::Tmpname.create("puma_socket") do |sockaddr|
@sockaddr = sockaddr
@socket = Socket.new(:UNIX, :DGRAM, 0)
socket_ai = Addrinfo.unix(sockaddr)
@socket.bind(socket_ai)
ENV["NOTIFY_SOCKET"] = sockaddr
end

ENV["SD_NOTIFY"] = "1"
end

def teardown
Expand All @@ -25,13 +25,12 @@ def teardown
File.unlink(@sockaddr) if @sockaddr
@socket = nil
@sockaddr = nil
ENV["SD_NOTIFY"] = nil
ENV["NOTIFY_SOCKET"] = nil
ENV["WATCHDOG_USEC"] = nil
end

def socket_message
@socket.recvfrom(10)[0]
@socket.recvfrom(15)[0]
end

def test_systemd_integration
Expand All @@ -42,8 +41,13 @@ def test_systemd_integration
cli_server "test/rackup/hello.ru"
assert_equal(socket_message, "READY=1")

connection = connect
restart_server connection
assert_equal(socket_message, "RELOADING=1")
assert_equal(socket_message, "READY=1")

stop_server
assert_match(socket_message, "STOPPING=1")
assert_equal(socket_message, "STOPPING=1")
end

def test_systemd_watchdog
Expand Down

0 comments on commit 9e8c8c3

Please sign in to comment.