diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d8773c9..1071d53 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 @@ -24,7 +24,7 @@ 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 @@ -32,6 +32,7 @@ jobs: 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 @@ -40,6 +41,8 @@ jobs: - '3.0' - '3.1' - '3.2' + - '3.3' + - '3.4' steps: - uses: actions/checkout@v3 diff --git a/.rubocop.yml b/.rubocop.yml deleted file mode 100644 index 9b7fff4..0000000 --- a/.rubocop.yml +++ /dev/null @@ -1,14 +0,0 @@ -require: - - rubocop-performance - -Layout/SpaceAroundEqualsInParameterDefault: - EnforcedStyle: no_space - -Style/StringLiterals: - EnforcedStyle: double_quotes - -Style/SafeNavigation: - Enabled: false - -Style/NegatedIf: - Enabled: false diff --git a/README.md b/README.md index b0b239f..10a12c7 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/lib/sd_notify.rb b/lib/sd_notify.rb index 66d73ed..7c82480 100644 --- a/lib/sd_notify.rb +++ b/lib/sd_notify.rb @@ -59,9 +59,13 @@ 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. @@ -69,23 +73,30 @@ def self.fdstore(unset_env=false) # @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. # diff --git a/sd_notify.gemspec b/sd_notify.gemspec index 46dae4f..5690583 100644 --- a/sd_notify.gemspec +++ b/sd_notify.gemspec @@ -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 diff --git a/test/sd_notify_test.rb b/test/sd_notify_test.rb index 535fb39..a46065f 100644 --- a/test/sd_notify_test.rb +++ b/test/sd_notify_test.rb @@ -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