Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ on:

jobs:
build_older:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
name: Ruby ${{ matrix.ruby }}
strategy:
matrix:
ruby:
- 2.2
- 2.1

steps:
- uses: actions/checkout@v3
Expand All @@ -24,14 +24,15 @@ jobs:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- name: Run tests
run: ruby -Ilib:test test/*
run: bundle exec ruby -Ilib:test test/*

build:
runs-on: ubuntu-latest
name: Ruby ${{ matrix.ruby }}
strategy:
matrix:
ruby:
# Ruby 2.2 is not supported on Ubuntu 22+ https://github.com/ruby/setup-ruby/issues/496
- 2.3
- 2.4
- 2.5
Expand All @@ -40,6 +41,8 @@ jobs:
- '3.0'
- '3.1'
- '3.2'
- '3.3'
- '3.4'

steps:
- uses: actions/checkout@v3
Expand Down
14 changes: 0 additions & 14 deletions .rubocop.yml

This file was deleted.

53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,59 @@ sleep 2
puts "Bye"
```

If you are operating a long-running program and want to use systemd's watchdog service manager to monitor your program:

```ruby
require "sd_notify"

puts "Hello! Booting..."

# doing some initialization work...
sleep 2

# notify systemd that we're ready
SdNotify.ready

# You might have a more complicated method of keeping an eye on the internal
# health of your program, although you will usually want to do this on a
# separate thread so notifications are not held up by especially long chunks of
# work in your main working thread.
watchdog_thread = if SdNotify.watchdog?
Thread.new do
loop do
# Systemd recommends pinging the watchdog at half the configured interval
# to make sure notifications always arrive in time.
sleep SdNotify.watchdog_interval / 2
if service_is_healthy
SdNotify.watchdog
else
break
end
end
end
end

# Do our main work...
loop do
sleep 10
sum += 1
break
end

puts "Finished working. Shutting down..."

# Stop watchdog
watchdog_thread.exit

# notify systemd we're shutting down
SdNotify.stopping

# doing some cleanup work...
sleep 2

puts "Bye"
```

## License

ruby-sdnotify is licensed under MIT. See [LICENSE](LICENSE).
39 changes: 25 additions & 14 deletions lib/sd_notify.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,33 +59,44 @@ def self.fdstore(unset_env=false)
notify(FDSTORE, unset_env)
end

# If the $WATCHDOG_USEC environment variable is set,
# and the $WATCHDOG_PID variable is unset or set to the PID of the current
# process
# Determine whether the systemd's watchdog is enabled for your program. If
# enabled, systemd will restart (or take some configured action) on your
# service when it goes N seconds without receiving a `WATCHDOG` notification
# from your program.
#
# See #watchdog for sending notifications and #watchdog_interval for how
# frequently to send them.
#
# @return [Boolean] true if the service manager expects watchdog keep-alive
# notification messages to be sent from this process.
#
# @note Unlike sd_watchdog_enabled(3), this method does not mutate the
# environment.
def self.watchdog?
wd_usec = ENV["WATCHDOG_USEC"]
wd_pid = ENV["WATCHDOG_PID"]

return false if !wd_usec

begin
wd_usec = Integer(wd_usec)
rescue
return false
end
return false unless watchdog_interval

return false if wd_usec <= 0
wd_pid = ENV["WATCHDOG_PID"]
return true if !wd_pid || wd_pid == $$.to_s

false
end

# Get the expected number of seconds between watchdog notifications. If
# systemd's watchdog manager is enabled, it will take action if it does not
# receive notifications at least this often from your program. Returns +nil+
# if no watchdog interval is set.
#
# @return [Float, nil] The frequency (in seconds) at which the service
# manager expects watchdog keep-alive notification messages from this
# process.
#
# @note Unlike +sd_watchdog_enabled(3)+, this returns seconds, not
# microseconds.
def self.watchdog_interval
wd_usec = Integer(ENV["WATCHDOG_USEC"]) rescue 0
wd_usec > 0 ? wd_usec / 1e6 : nil
end

# Notify systemd with the provided state, via the notification socket, if
# any.
#
Expand Down
2 changes: 0 additions & 2 deletions sd_notify.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,4 @@ Gem::Specification.new do |s|
s.license = "MIT"

s.add_development_dependency "minitest"
s.add_development_dependency "rubocop"
s.add_development_dependency "rubocop-performance"
end
39 changes: 39 additions & 0 deletions test/sd_notify_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,50 @@ def test_sd_notify_ready_unset
assert_nil(ENV["NOTIFY_SOCKET"])
end

def test_sd_notify_watchdog_disabled
setup_socket

refute(SdNotify.watchdog?)
end

def test_sd_notify_watchdog_enabled
ENV["WATCHDOG_USEC"] = "5_000_000"
ENV["WATCHDOG_PID"] = $$.to_s
setup_socket

assert(SdNotify.watchdog?)
end

def test_sd_notify_watchdog_enabled_for_a_different_process
ENV["WATCHDOG_USEC"] = "5_000_000"
ENV["WATCHDOG_PID"] = ($$ + 1).to_s
setup_socket

refute(SdNotify.watchdog?)
end

def test_sd_notify_watchdog_interval_disabled
setup_socket

assert_nil(SdNotify.watchdog_interval)
end

def test_sd_notify_watchdog_interval_enabled
ENV["WATCHDOG_USEC"] = "5_000_000"
ENV["WATCHDOG_PID"] = $$.to_s
setup_socket

assert_equal(5.0, SdNotify.watchdog_interval)
end

def teardown
@socket.close if @socket
File.unlink(@sockaddr) if @sockaddr
@socket = nil
@sockaddr = nil
ENV.delete("NOTIFY_SOCKET")
ENV.delete("WATCHDOG_USEC")
ENV.delete("WATCHDOG_PID")
end

private
Expand Down