about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2013-10-19 22:14:59 +0000
committerEric Wong <normalperson@yhbt.net>2013-10-19 22:14:59 +0000
commitd973de0d6dfdf799e111d9f9a71170b61a0ac100 (patch)
tree35fbb97fa5697eb398e243d0325523f24d903358
parent9cd4a50c275bbda9ee23f0351f1eba2289af075f (diff)
downloadyahns-d973de0d6dfdf799e111d9f9a71170b61a0ac100.tar.gz
We no longer have to worry about 1.8 compatibility, so use
Process.spawn and shorten our code.  Also, add tests for this
functionality.
-rw-r--r--lib/yahns/server.rb21
-rw-r--r--test/test_bin.rb79
2 files changed, 87 insertions, 13 deletions
diff --git a/lib/yahns/server.rb b/lib/yahns/server.rb
index e8f1213..e664293 100644
--- a/lib/yahns/server.rb
+++ b/lib/yahns/server.rb
@@ -173,19 +173,14 @@ class Yahns::Server # :nodoc:
       end
     end
 
-    @reexec_pid = fork do
-      redirects = {}
-      listeners.each do |sock|
-        sock.close_on_exec = false
-        redirects[sock.fileno] = sock
-      end
-      ENV['YAHNS_FD'] = redirects.keys.map(&:to_s).join(',')
-      Dir.chdir(@config.value(:working_directory) || Yahns::START[:cwd])
-      cmd = [ Yahns::START[0] ].concat(Yahns::START[:argv])
-      @logger.info "executing #{cmd.inspect} (in #{Dir.pwd})"
-      cmd << redirects
-      exec(*cmd)
-    end
+    opts = {}
+    @listeners.each { |sock| opts[sock.fileno] = sock }
+    env = { "YAHNS_FD" => opts.keys.map(&:to_s).join(',') }
+    opts[:chdir] = @config.value(:working_directory) || Yahns::START[:cwd]
+    cmd = [ Yahns::START[0] ].concat(Yahns::START[:argv])
+    @logger.info "spawning #{cmd.inspect} (in #{opts[:chdir]})"
+    cmd << opts
+    @reexec_pid = Process.spawn(env, *cmd)
     proc_name 'master (old)'
   end
 
diff --git a/test/test_bin.rb b/test/test_bin.rb
index aab4616..d21e8b9 100644
--- a/test/test_bin.rb
+++ b/test/test_bin.rb
@@ -94,4 +94,83 @@ class TestBin < Testcase
     end
     @pid.close! if @pid
   end
+
+  def test_usr2_preload_noworker; usr2(true, false); end
+  def test_usr2_preload_worker; usr2(true, true); end
+  def test_usr2_nopreload_worker; usr2(false, true); end
+  def test_usr2_nopreload_noworker; usr2(false, false); end
+
+  def usr2(preload, worker)
+    Dir.mktmpdir { |tmpdir| usr2_dir(tmpdir, preload, worker) }
+  end
+
+  def usr2_dir(tmpdir, preload, worker)
+    exe = "#{tmpdir}/yahns"
+
+    # need to fork here since tests are MT and the FD can leak out and go to
+    # other processes which fork (but do not exec), causing ETXTBUSY on
+    # Process.spawn
+    pid = fork do
+      ruby = "#!#{`which ruby`}"
+      File.open(exe, "w") { |y|
+        lines = File.readlines("bin/yahns")
+        lines[0] = ruby
+        y.chmod(0755)
+        y.syswrite(lines.join)
+      }
+    end
+    _, status = Process.waitpid2(pid)
+    assert status.success?, status.inspect
+
+    @pid = tmpfile(%w(test_bin_daemon .pid))
+    host, port = @srv.addr[3], @srv.addr[1]
+    @ru = tmpfile(%w(test_bin_daemon .ru))
+    @ru.puts("use Rack::ContentLength")
+    @ru.puts("use Rack::ContentType, 'text/plain'")
+    @ru.puts("run lambda { |_| [ 200, {}, [ Process.pid.to_s ] ] }")
+    cfg = tmpfile(%w(test_bin_daemon_conf .rb))
+    cfg.puts "pid '#{@pid.path}'"
+    cfg.puts "stderr_path '#{@err.path}'"
+    cfg.puts "worker_processes 1" if worker
+    cfg.puts "app(:rack, '#{@ru.path}', preload: #{preload}) do"
+    cfg.puts "  listen '#{host}:#{port}'"
+    cfg.puts "end"
+    env = {
+      "YAHNS_FD" => @srv.fileno.to_s,
+      "PATH" => "#{tmpdir}:#{ENV['PATH']}",
+      "RUBYLIB" => "#{Dir.pwd}/lib",
+    }
+    cmd = %W(#{exe} -D -c #{cfg.path})
+    cmd << { @srv => @srv, close_others: true }
+    pid = GTL.synchronize { Process.spawn(env, *cmd) }
+    res = Net::HTTP.start(host, port) { |h| h.get("/") }
+    assert_equal 200, res.code.to_i
+    orig = res.body
+    Process.kill(:USR2, pid)
+    Timeout.timeout(10) do
+      begin
+        sleep 0.01
+        newpid = File.read(@pid.path)
+      rescue Errno::ENOENT
+      end until newpid.to_i != pid
+    end
+    Process.kill(:QUIT, pid)
+    _, status = Timeout.timeout(10) { Process.waitpid2(pid) }
+    assert status.success?, status
+    res = Net::HTTP.start(host, port) { |h| h.get("/") }
+    assert_equal 200, res.code.to_i
+    refute_equal orig, res.body
+  rescue => e
+    Yahns::Log.exception(Logger.new($stderr), "test", e)
+    raise
+  ensure
+    File.unlink(exe) if exe
+    cfg.close! if cfg
+    pid = File.read(@pid.path)
+    pid = pid.to_i
+    assert_operator pid, :>, 0
+    Process.kill(:QUIT, pid)
+    poke_until_dead pid
+    @pid.close!
+  end
 end