From 0c20f34c3d54e1c3d7b7a811c401e6858196e8a7 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 9 May 2014 01:29:39 +0000 Subject: rework master-to-worker signaling to use a pipe This prevents potential errors and compatibility problems with buggy libraries which do not respond well to signal delivery. Based on unicorn commit 6f6e4115b4bb03e5e7c55def91527799190566f2 --- lib/yahns/server.rb | 2 +- lib/yahns/server_mp.rb | 13 +++++++------ lib/yahns/worker.rb | 37 +++++++++++++++++++++++++++++++++---- 3 files changed, 41 insertions(+), 11 deletions(-) (limited to 'lib') diff --git a/lib/yahns/server.rb b/lib/yahns/server.rb index a6462df..3f1f6ab 100644 --- a/lib/yahns/server.rb +++ b/lib/yahns/server.rb @@ -285,7 +285,7 @@ class Yahns::Server # :nodoc: @logger.info "reloading config_file=#{@config.config_file}" @config.config_reload! @config.commit!(self) - kill_each_worker(:QUIT) + soft_kill_each_worker("QUIT") Yahns::Log.reopen_all @logger.info "done reloading config_file=#{@config.config_file}" rescue StandardError, LoadError, SyntaxError => e diff --git a/lib/yahns/server_mp.rb b/lib/yahns/server_mp.rb index 5c9aaa1..d234709 100644 --- a/lib/yahns/server_mp.rb +++ b/lib/yahns/server_mp.rb @@ -12,9 +12,10 @@ module Yahns::ServerMP # :nodoc: end end - # delivers a signal to each worker - def kill_each_worker(signal) - @workers.each_key { |wpid| Process.kill(signal, wpid) } + # fakes delivery of a signal to each worker + def soft_kill_each_worker(sig) + sig = Signal.list[sig] + @workers.each_value { |worker| worker.soft_kill(sig) } end # this is the first thing that runs after forking in a child @@ -94,18 +95,18 @@ module Yahns::ServerMP # :nodoc: case @sig_queue.shift when *EXIT_SIGS # graceful shutdown (twice for non graceful) @listeners.each(&:close).clear - kill_each_worker(:QUIT) + soft_kill_each_worker("QUIT") state = :QUIT when :USR1 # rotate logs usr1_reopen("master ") - kill_each_worker(:USR1) + soft_kill_each_worker("USR1") when :USR2 # exec binary, stay alive in case something went wrong reexec when :WINCH if @daemon_pipe state = :WINCH @logger.info "gracefully stopping all workers" - kill_each_worker(:QUIT) + soft_kill_each_worker("QUIT") @worker_processes = 0 else @logger.info "SIGWINCH ignored because we're not daemonized" diff --git a/lib/yahns/worker.rb b/lib/yahns/worker.rb index 9b1bb8a..4183c63 100644 --- a/lib/yahns/worker.rb +++ b/lib/yahns/worker.rb @@ -23,15 +23,44 @@ class Yahns::Worker # :nodoc: # This causes the worker to gracefully exit if the master # dies unexpectedly. def yahns_step - if @to_io.kgio_tryread(11) == nil - Process.kill(:QUIT, $$) + case buf = @to_io.kgio_tryread(4) + when String + # unpack the buffer and trigger the signal handler + signum = buf.unpack('l') + fake_sig(signum[0]) + # keep looping, more signals may be queued + when nil # EOF: master died, but we are at a safe place to exit + fake_sig(:QUIT) @to_io.close - end - :ignore + return :ignore + when :wait_readable # keep waiting + return :ignore + end while true # loop, as multiple signals may be sent end # worker objects may be compared to just plain Integers def ==(other_nr) # :nodoc: @nr == other_nr end + + # call a signal handler immediately without triggering EINTR + # We do not use the more obvious Process.kill(sig, $$) here since + # that signal delivery may be deferred. We want to avoid signal delivery + # while the Rack app.call is running because some database drivers + # (e.g. ruby-pg) may cancel pending requests. + def fake_sig(sig) # :nodoc: + old_cb = trap(sig, "IGNORE") + old_cb.call + ensure + trap(sig, old_cb) + end + + # master sends fake signals to children + def soft_kill(signum) # :nodoc: + # writing and reading 4 bytes on a pipe is atomic on all POSIX platforms + # Do not care in the odd case the buffer is full, here. + @wr.kgio_trywrite([signum].pack('l')) + rescue Errno::EPIPE + # worker will be reaped soon + end end -- cgit v1.2.3-24-ge0c7