diff options
author | Eric Wong <e@80x24.org> | 2013-10-22 22:46:25 +0000 |
---|---|---|
committer | Eric Wong <e@80x24.org> | 2013-10-22 23:08:46 +0000 |
commit | 28163a34ceff8334b2af4c5c30ec1e642efb9be1 (patch) | |
tree | 6219d20af729502cd20b942fb8700bffea741f16 | |
parent | b79ba40ff559f9676c7bad530eeab6b7eb6a91f1 (diff) | |
download | yahns-28163a34ceff8334b2af4c5c30ec1e642efb9be1.tar.gz |
These can be used to disconnect/reconnect to databases and other external connections. These are named to match the documentation of pthread_atfork(3)
-rw-r--r-- | examples/yahns_rack_basic.conf.rb | 16 | ||||
-rw-r--r-- | lib/yahns/config.rb | 40 | ||||
-rw-r--r-- | lib/yahns/server.rb | 4 | ||||
-rw-r--r-- | lib/yahns/server_mp.rb | 9 | ||||
-rw-r--r-- | test/test_server.rb | 46 |
5 files changed, 108 insertions, 7 deletions
diff --git a/examples/yahns_rack_basic.conf.rb b/examples/yahns_rack_basic.conf.rb index ea367cd..3364587 100644 --- a/examples/yahns_rack_basic.conf.rb +++ b/examples/yahns_rack_basic.conf.rb @@ -3,7 +3,21 @@ # A typical Rack example for hosting a single Rack application with yahns # and only frequently-useful config values -worker_processes 1 +worker_processes(1) do + # these names are based on pthread_atfork(3) documentation + atfork_child do + defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection + puts "#$$ yahns worker is running" + end + atfork_prepare do + defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect! + puts "#$$ yahns parent about to spawn" + end + atfork_parent do + puts "#$$ this is probably not useful" + end +end + # working_directory "/path/to/my_app" stdout_path "/path/to/my_logs/out.log" stderr_path "/path/to/my_logs/err.log" diff --git a/lib/yahns/config.rb b/lib/yahns/config.rb index f1b3612..c955e0f 100644 --- a/lib/yahns/config.rb +++ b/lib/yahns/config.rb @@ -73,10 +73,40 @@ class Yahns::Config # :nodoc: end end - def worker_processes(nr) - # TODO: allow zero - var = _check_in_block(nil, :worker_processes) + def worker_processes(nr, &blk) + var =_check_in_block(nil, :worker_processes) @set[var] = _check_int(var, nr, 1) + if block_given? + @block = CfgBlock.new(var, nil) + instance_eval(&blk) + @block = nil + end + end + + %w(prepare parent child).each do |x| + fn = "atfork_#{x}" + eval( + %Q(def #{fn}(*args, &blk);) << + %Q( _check_in_block(:worker_processes, :#{fn});) << + %Q( _add_hook("worker_", :#{fn}, block_given? ? blk : args[0]);) << + %Q(end) + ) + end + + def _add_hook(pfx, var, my_proc) + case my_proc + when Proc + my_proc.arity == 0 or raise ArgumentError, + "#{var}=#{my_proc.inspect} should not take arguments" + else + raise ArgumentError, "invalid type: #{var}=#{my_proc.inspect}" + end + + # this sets: + # :worker_atfork_prepare, :worker_atfork_parent, :worker_atfork_child + key = :"#{pfx}#{var}" + @set[key] = [] unless @set.include?(key) + @set[key] << my_proc end # sets the +path+ for the PID file of the yahns master process @@ -338,7 +368,9 @@ class Yahns::Config # :nodoc: io.sync = true end - [ :logger, :pid, :worker_processes ].each do |var| + [ :logger, :pid, :worker_processes, + :worker_atfork_prepare, :worker_atfork_parent, :worker_atfork_child + ].each do |var| val = @set[var] server.__send__("#{var}=", val) if val != :unset end diff --git a/lib/yahns/server.rb b/lib/yahns/server.rb index 4663c36..5532666 100644 --- a/lib/yahns/server.rb +++ b/lib/yahns/server.rb @@ -8,6 +8,9 @@ class Yahns::Server # :nodoc: attr_accessor :daemon_pipe attr_accessor :logger attr_writer :worker_processes + attr_writer :worker_atfork_prepare + attr_writer :worker_atfork_parent + attr_writer :worker_atfork_child include Yahns::SocketHelper def initialize(config) @@ -21,6 +24,7 @@ class Yahns::Server # :nodoc: @listeners = [] @pid = nil @worker_processes = nil + @worker_atfork_prepare = @worker_atfork_parent = @worker_atfork_child = nil @user = nil @queues = [] @wthr = [] diff --git a/lib/yahns/server_mp.rb b/lib/yahns/server_mp.rb index 4c1d9e1..640f1b2 100644 --- a/lib/yahns/server_mp.rb +++ b/lib/yahns/server_mp.rb @@ -47,7 +47,7 @@ module Yahns::ServerMP # :nodoc: # to free some resources and drops all sig handlers. # traps for USR1, USR2, and HUP may be set in the after_fork Proc # by the user. - def after_fork_internal(worker) + def worker_atfork_internal(worker) worker.atfork_child # daemon_pipe may be true for non-initial workers @@ -73,6 +73,8 @@ module Yahns::ServerMP # :nodoc: @sev = Yahns::Sigevent.new worker.user(*@user) if @user @user = @workers = nil + @worker_atfork_child.each(&:call) if @worker_atfork_child + @worker_atfork_child = @worker_atfork_parent = @worker_atfork_prepare = nil end def spawn_missing_workers @@ -81,10 +83,13 @@ module Yahns::ServerMP # :nodoc: @workers.value?(worker_nr) and next worker = Yahns::Worker.new(worker_nr) @logger.info("worker=#{worker_nr} spawning...") + @worker_atfork_prepare.each(&:call) if @worker_atfork_parent if pid = fork @workers[pid] = worker.atfork_parent + # XXX is this useful? + @worker_atfork_parent.each(&:call) if @worker_atfork_parent else - after_fork_internal(worker) + worker_atfork_internal(worker) run_mp_worker(worker) end end diff --git a/test/test_server.rb b/test/test_server.rb index 25f4b44..c277896 100644 --- a/test/test_server.rb +++ b/test/test_server.rb @@ -379,4 +379,50 @@ class TestServer < Testcase ensure quit_wait(pid) end + + def test_mp_hooks + err = @err + out = tmpfile(%w(mp_hooks .out)) + cfg = Yahns::Config.new + host, port = @srv.addr[3], @srv.addr[1] + cfg.instance_eval do + ru = lambda {|_|x="#$$";[200,{'Content-Length'=>x.size.to_s },[x]]} + GTL.synchronize { + app(:rack, ru) { + listen "#{host}:#{port}" + persistent_connections false + } + worker_processes(1) do + atfork_child { puts "af #$$ worker is running" } + atfork_prepare { puts "af #$$ parent about to spawn" } + atfork_parent { puts "af #$$ this is probably not useful" } + end + } + stderr_path err.path + stdout_path out.path + end + pid = fork do + ENV["YAHNS_FD"] = @srv.fileno.to_s + Yahns::Server.new(cfg).start.join + end + c = get_tcp_client(host, port) + c.write("GET / HTTP/1.0\r\nHost: example.com\r\n\r\n") + buf = Timeout.timeout(10) { c.read } + c.close + head, body = buf.split(/\r\n\r\n/) + assert_match(/200 OK/, head) + assert_match(/\A\d+\z/, body) + worker_pid = body.to_i + lines = out.readlines.map!(&:chomp!) + out.close! + + assert_equal 3, lines.size + assert_equal("af #{pid} parent about to spawn", lines.shift) + + # child/parent ordering is not guaranteed + assert_equal 1, lines.grep(/\Aaf #{pid} this is probably not useful\z/).size + assert_equal 1, lines.grep(/\Aaf #{worker_pid} worker is running\z/).size + ensure + quit_wait(pid) + end end |