about summary refs log tree commit homepage
diff options
context:
space:
mode:
-rw-r--r--examples/yahns_rack_basic.conf.rb16
-rw-r--r--lib/yahns/config.rb40
-rw-r--r--lib/yahns/server.rb4
-rw-r--r--lib/yahns/server_mp.rb9
-rw-r--r--test/test_server.rb46
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