diff options
author | Eric Wong <e@80x24.org> | 2013-10-19 02:47:58 +0000 |
---|---|---|
committer | Eric Wong <e@80x24.org> | 2013-10-19 02:47:58 +0000 |
commit | 9cd4a50c275bbda9ee23f0351f1eba2289af075f (patch) | |
tree | e7051daa5f7fd691133a9c48e047468df39b9e63 /test | |
parent | 2377d5a1cafa518313b0b597e4c3af65bb57f887 (diff) | |
download | yahns-9cd4a50c275bbda9ee23f0351f1eba2289af075f.tar.gz |
Rack hijacking may close the socket before it yields control back to our epoll event loop. So it's not safe to attempt to use the socket (or even get the .fileno) with :delete/EPOLL_CTL_DEL. So change :delete to :ignore, and only decrement the FD counter to allow yahns to do graceful shutdown. This means we'll potentially waste ~200 bytes of kernel memory due to epoll overhead until the FD is closed by the app hijacking. I'm not sure how Rack servers handle graceful shutdown when hijacking, but maybe they just retrap SIGQUIT...
Diffstat (limited to 'test')
-rw-r--r-- | test/test_queue.rb | 13 | ||||
-rw-r--r-- | test/test_rack_hijack.rb | 74 |
2 files changed, 82 insertions, 5 deletions
diff --git a/test/test_queue.rb b/test/test_queue.rb index 6d61aef..cdb9ade 100644 --- a/test/test_queue.rb +++ b/test/test_queue.rb @@ -23,8 +23,8 @@ class TestQueue < Testcase def r.yahns_step begin case read_nonblock(11) - when "delete" - return :delete + when "ignore" + return :ignore end rescue Errno::EAGAIN return :wait_readable @@ -38,13 +38,16 @@ class TestQueue < Testcase @q.spawn_worker_threads(@logger, 1, 1) Thread.pass until r.nread == 0 - w.write("delete") + assert_equal 1, @fdmap.size + w.write("ignore") Thread.pass until r.nread == 0 Thread.pass until @fdmap.size == 0 - # should not raise - @q.queue_add(r, Yahns::Queue::QEV_RD) + assert_raises(Errno::EEXIST) { + @q.queue_add(r, Yahns::Queue::QEV_RD) + } assert_equal 1, @fdmap.size + @q.epoll_ctl(SleepyPenguin::Epoll::CTL_MOD, r, Yahns::Queue::QEV_RD) w.close Thread.pass until @fdmap.size == 0 end diff --git a/test/test_rack_hijack.rb b/test/test_rack_hijack.rb new file mode 100644 index 0000000..ba2bd78 --- /dev/null +++ b/test/test_rack_hijack.rb @@ -0,0 +1,74 @@ +# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors +# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt) +require_relative 'server_helper' + +class TestRackHijack < Testcase + parallelize_me! + include ServerHelper + alias setup server_helper_setup + alias teardown server_helper_teardown + + class DieIfUsed + def each + abort "body.each called after response hijack\n" + end + + def close + abort "body.close called after response hijack\n" + end + end + + HIJACK_APP = lambda { |env| + case env["PATH_INFO"] + when "/hijack_req" + io = env["rack.hijack"].call + if io.respond_to?(:read_nonblock) && + env["rack.hijack_io"].respond_to?(:read_nonblock) + + # exercise both, since we Rack::Lint may use different objects + env["rack.hijack_io"].write("HTTP/1.0 200 OK\r\n\r\n") + io.write("request.hijacked") + io.close + return [ 500, {}, DieIfUsed.new ] + end + [ 500, {}, [ "hijack BAD\n" ] ] + when "/hijack_res" + r = "response.hijacked" + [ 200, + { + "X-Test" => "zzz", + "Content-Length" => r.bytesize.to_s, + "rack.hijack" => proc { |x| x.write(r); x.close } + }, + DieIfUsed.new + ] + end + } + + def test_hijack + err = @err + cfg = Yahns::Config.new + host, port = @srv.addr[3], @srv.addr[1] + cfg.instance_eval do + GTL.synchronize { app(:rack, HIJACK_APP) { listen "#{host}:#{port}" } } + logger(Logger.new(err.path)) + end + srv = Yahns::Server.new(cfg) + pid = fork do + ENV["YAHNS_FD"] = @srv.fileno.to_s + srv.start.join + end + res = Net::HTTP.start(host, port) { |h| h.get("/hijack_req") } + assert_equal "request.hijacked", res.body + assert_equal 200, res.code.to_i + assert_equal "1.0", res.http_version + + res = Net::HTTP.start(host, port) { |h| h.get("/hijack_res") } + assert_equal "response.hijacked", res.body + assert_equal 200, res.code.to_i + assert_equal "zzz", res["X-Test"] + assert_equal "1.1", res.http_version + ensure + quit_wait(pid) + end +end |