diff options
-rw-r--r-- | Documentation/yahns_config.txt | 11 | ||||
-rw-r--r-- | lib/yahns/config.rb | 7 | ||||
-rw-r--r-- | lib/yahns/fdmap.rb | 1 | ||||
-rw-r--r-- | lib/yahns/server.rb | 15 | ||||
-rw-r--r-- | lib/yahns/server_mp.rb | 2 | ||||
-rw-r--r-- | test/test_server.rb | 33 |
6 files changed, 66 insertions, 3 deletions
diff --git a/Documentation/yahns_config.txt b/Documentation/yahns_config.txt index db2634c..b49617b 100644 --- a/Documentation/yahns_config.txt +++ b/Documentation/yahns_config.txt @@ -419,6 +419,17 @@ Ruby it is running under. Default: uses the global, :default queue if none is specified +* shutdown_timeout SECONDS + + This defines the timeout for gracefully exiting the process if there + are still connected clients. This should generally be higher or equal + to the app with the highest client_timeout value. + + Increase this if your application has slow endpoints which may take + longer than the default timeout. + + Default: max client_timeout value of all apps in the process + * worker_processes INTEGER [&BLOCK] This directive allows yahns to use a master/worker configuration to diff --git a/lib/yahns/config.rb b/lib/yahns/config.rb index c4c1b41..e48ba4e 100644 --- a/lib/yahns/config.rb +++ b/lib/yahns/config.rb @@ -73,6 +73,11 @@ class Yahns::Config # :nodoc: end end + def shutdown_timeout(sec) + var = _check_in_block(nil, :shutdown_timeout) + @set[var] = _check_int(var, sec, 0) + end + def worker_processes(nr, &blk) var =_check_in_block(nil, :worker_processes) @set[var] = _check_int(var, nr, 1) @@ -392,7 +397,7 @@ class Yahns::Config # :nodoc: io.sync = true end - [ :logger, :pid, :worker_processes, :user, + [ :logger, :pid, :worker_processes, :user, :shutdown_timeout, :worker_atfork_prepare, :worker_atfork_parent, :worker_atfork_child ].each do |var| val = @set[var] diff --git a/lib/yahns/fdmap.rb b/lib/yahns/fdmap.rb index 8dda4e6..97e9f44 100644 --- a/lib/yahns/fdmap.rb +++ b/lib/yahns/fdmap.rb @@ -56,6 +56,7 @@ class Yahns::Fdmap # :nodoc: end # this is only called in Errno::EMFILE/Errno::ENFILE situations + # and graceful shutdown def desperate_expire_for(io, timeout) @fdmap_mtx.synchronize { __expire_for(io, timeout) } end diff --git a/lib/yahns/server.rb b/lib/yahns/server.rb index 4bd523f..6439918 100644 --- a/lib/yahns/server.rb +++ b/lib/yahns/server.rb @@ -12,12 +12,14 @@ class Yahns::Server # :nodoc: attr_accessor :logger attr_writer :user attr_writer :worker_processes + attr_writer :shutdown_timeout attr_writer :worker_atfork_prepare attr_writer :worker_atfork_parent attr_writer :worker_atfork_child include Yahns::SocketHelper def initialize(config) + @shutdown_timeout = nil @reexec_pid = 0 @daemon_pipe = nil # writable IO or true @config = config @@ -339,6 +341,8 @@ class Yahns::Server # :nodoc: # spin up applications (which are preload: false) @config.app_ctx.each(&:after_fork_init) + @shutdown_timeout ||= @config.app_ctx.map(&:client_timeout).max + # spin up acceptor threads, clients flow into worker queues after this @listeners.each do |l| opts = sock_opts(l) @@ -413,6 +417,15 @@ class Yahns::Server # :nodoc: alive end + def dropping(fdmap) + if drop_acceptors[0] || fdmap.size > 0 + fdmap.desperate_expire_for(nil, @shutdown_timeout) + true + else + false + end + end + # single-threaded only, this is overriden if @worker_processes is non-nil def join daemon_ready @@ -422,7 +435,7 @@ class Yahns::Server # :nodoc: alive = sp_sig_handle(alive) rescue => e Yahns::Log.exception(@logger, "main loop", e) - end while alive || drop_acceptors[0] || fdmap.size > 0 + end while alive || dropping(fdmap) unlink_pid_safe(@pid) if @pid ensure quit_finish diff --git a/lib/yahns/server_mp.rb b/lib/yahns/server_mp.rb index 551cf69..4957c22 100644 --- a/lib/yahns/server_mp.rb +++ b/lib/yahns/server_mp.rb @@ -142,7 +142,7 @@ module Yahns::ServerMP # :nodoc: alive = mp_sig_handle(worker, alive) rescue => e Yahns::Log.exception(@logger, "main worker loop", e) - end while alive || drop_acceptors[0] || fdmap.size > 0 + end while alive || dropping(fdmap) exit ensure quit_finish diff --git a/test/test_server.rb b/test/test_server.rb index da57f8c..2cfee09 100644 --- a/test/test_server.rb +++ b/test/test_server.rb @@ -622,4 +622,37 @@ class TestServer < Testcase quit_wait(pid) FileUtils.rm_rf(tmpdir) end + + def test_persistent_shutdown_timeout; _persistent_shutdown(nil); end + def test_persistent_shutdown_timeout_mp; _persistent_shutdown(1); end + + def _persistent_shutdown(nr_workers) + err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1] + pid = mkserver(cfg) do + ru = lambda { |e| [ 200, {'Content-Length'=>'2'}, %w(OK) ] } + cfg.instance_eval do + app(:rack, ru) { listen "#{host}:#{port}" } + stderr_path err.path + shutdown_timeout 1 + worker_processes(nr_workers) if nr_workers + end + end + c = get_tcp_client(host, port) + c.write "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n" + assert_equal c, c.wait(30) + buf = "" + re = /\r\n\r\nOK\z/ + Timeout.timeout(30) do + begin + buf << c.readpartial(666) + end until re =~ buf + end + refute_match %r{Connection: close}, buf + assert_nil c.wait(0.001), "connection should still be alive" + Process.kill(:QUIT, pid) + _, status = Timeout.timeout(5) { Process.waitpid2(pid) } + assert status.success?, status.inspect + assert_nil c.wait(1) + assert_nil c.read(666) + end end |