about summary refs log tree commit homepage
diff options
context:
space:
mode:
-rw-r--r--SIGNALS6
-rw-r--r--lib/rainbows/event_machine.rb10
-rw-r--r--t/app_deferred.ru8
-rwxr-xr-xt/t0700-app-deferred.sh18
4 files changed, 34 insertions, 8 deletions
diff --git a/SIGNALS b/SIGNALS
index e53c3e4..fa95a1c 100644
--- a/SIGNALS
+++ b/SIGNALS
@@ -24,9 +24,9 @@ between \Rainbows!, unicorn and nginx.
 * INT/TERM - quick shutdown, kills all workers immediately
 
 * QUIT - graceful shutdown, waits for workers to finish their
-  current request before finishing.  This currently does not
-  wait for requests deferred to a separate thread when using
-  EventMachine (when app.deferred?(env) => true)
+  current request before finishing.  Since Rainbows 5.1.0 (Jan 2017),
+  this waits requests deferred to a separate thread with
+  EventMachine (app.deferred?(env) => true).
 
 * USR1 - reopen all logs owned by the master and all workers
   See Unicorn::Util.reopen_logs for what is considered a log.
diff --git a/lib/rainbows/event_machine.rb b/lib/rainbows/event_machine.rb
index b326e26..896fdac 100644
--- a/lib/rainbows/event_machine.rb
+++ b/lib/rainbows/event_machine.rb
@@ -65,6 +65,11 @@ module Rainbows::EventMachine
     end
   end
 
+  def defers_finished?
+    # EventMachine 1.0.0+ has defers_finished?
+    EM.respond_to?(:defers_finished?) ? EM.defers_finished? : true
+  end
+
   # runs inside each forked worker, this sits around and waits
   # for connections and doesn't die until the parent dies (or is
   # given a INT, QUIT, or TERM signal)
@@ -101,7 +106,10 @@ module Rainbows::EventMachine
         end
       end
       EM.add_periodic_timer(1) do
-        EM.stop if ! Rainbows.tick && conns.empty? && EM.reactor_running?
+        if ! Rainbows.tick && conns.empty? && defers_finished? &&
+            EM.reactor_running?
+          EM.stop
+        end
       end
       LISTENERS.map! do |s|
         EM.watch(s, Rainbows::EventMachine::Server) do |c|
diff --git a/t/app_deferred.ru b/t/app_deferred.ru
index a70b33b..b3d7ff1 100644
--- a/t/app_deferred.ru
+++ b/t/app_deferred.ru
@@ -6,12 +6,18 @@
 
 class DeferredApp < Struct.new(:app)
   def deferred?(env)
-    env["PATH_INFO"] == "/deferred"
+    env["PATH_INFO"] =~ %r{\A/deferred}
   end
 
   def call(env)
     env["rack.multithread"] or raise RuntimeError, "rack.multithread not true"
     body = "#{Thread.current.inspect}\n"
+    if env["PATH_INFO"] =~ %r{\A/deferred(\d+)}
+      delay = $1.to_i
+      File.open(ENV['fifo'], 'w') { |fp| fp.write "sleeping #{delay}s\n" }
+      body = "deferred sleep\n"
+      sleep(delay)
+    end
     headers = {
       "Content-Type" => "text/plain",
       "Content-Length" => body.size.to_s,
diff --git a/t/t0700-app-deferred.sh b/t/t0700-app-deferred.sh
index 90614b2..188fdde 100755
--- a/t/t0700-app-deferred.sh
+++ b/t/t0700-app-deferred.sh
@@ -15,7 +15,7 @@ CONFIG_RU=app_deferred.ru
 t_begin "setup and start" && {
         rainbows_setup
         rtmpfiles deferred_err deferred_out sync_err sync_out
-        rainbows -D -c $unicorn_config $CONFIG_RU
+        fifo=$fifo rainbows -D -c $unicorn_config $CONFIG_RU
         rainbows_wait_start
 }
 
@@ -36,8 +36,20 @@ t_begin "deferred requests run in a different thread" && {
         test x"$(uniq < $deferred_out)" != x"$sync_thread"
 }
 
-t_begin "termination signal sent" && {
-        kill $rainbows_pid
+t_begin "deferred requests run after graceful shutdown" && {
+        # XXX sleeping 5s ought to be enough for SIGQUIT to arrive,
+        # hard to tell with overloaded systems...
+        s=5
+        curl -sSf --no-buffer http://$listen/deferred$s \
+                >$deferred_out 2>$deferred_err &
+        curl_pid=$!
+        msg="$(cat $fifo)"
+        kill -QUIT $rainbows_pid
+        test x"$msg" = x"sleeping ${s}s"
+        wait $curl_pid # for curl to finish
+        test $? -eq 0
+        test ! -s $deferred_err
+        test x"$(cat $deferred_out)" = 'xdeferred sleep'
 }
 
 t_begin "no errors in stderr" && check_stderr