about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2014-05-09 01:29:39 +0000
committerEric Wong <e@80x24.org>2014-05-09 06:30:19 +0000
commit0c20f34c3d54e1c3d7b7a811c401e6858196e8a7 (patch)
tree1675a6e5211082b8829bec6ae33903db8241fc7d
parentdc55ac157cd8f7e55339e8b1e36d2a3978934a15 (diff)
downloadyahns-0c20f34c3d54e1c3d7b7a811c401e6858196e8a7.tar.gz
This prevents potential errors and compatibility problems with
buggy libraries which do not respond well to signal delivery.

Based on unicorn commit 6f6e4115b4bb03e5e7c55def91527799190566f2
-rw-r--r--lib/yahns/server.rb2
-rw-r--r--lib/yahns/server_mp.rb13
-rw-r--r--lib/yahns/worker.rb37
3 files changed, 41 insertions, 11 deletions
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