giraffesoft-unicorn 0.93.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. data/.CHANGELOG.old +25 -0
  2. data/.document +16 -0
  3. data/.gitignore +20 -0
  4. data/.mailmap +26 -0
  5. data/CONTRIBUTORS +31 -0
  6. data/COPYING +339 -0
  7. data/DESIGN +105 -0
  8. data/Documentation/.gitignore +5 -0
  9. data/Documentation/GNUmakefile +30 -0
  10. data/Documentation/unicorn.1.txt +167 -0
  11. data/Documentation/unicorn_rails.1.txt +169 -0
  12. data/GIT-VERSION-GEN +40 -0
  13. data/GNUmakefile +270 -0
  14. data/HACKING +113 -0
  15. data/KNOWN_ISSUES +40 -0
  16. data/LICENSE +55 -0
  17. data/PHILOSOPHY +144 -0
  18. data/README +153 -0
  19. data/Rakefile +108 -0
  20. data/SIGNALS +97 -0
  21. data/TODO +16 -0
  22. data/TUNING +70 -0
  23. data/bin/unicorn +165 -0
  24. data/bin/unicorn_rails +208 -0
  25. data/examples/echo.ru +27 -0
  26. data/examples/git.ru +13 -0
  27. data/examples/init.sh +53 -0
  28. data/ext/unicorn_http/c_util.h +107 -0
  29. data/ext/unicorn_http/common_field_optimization.h +111 -0
  30. data/ext/unicorn_http/ext_help.h +73 -0
  31. data/ext/unicorn_http/extconf.rb +14 -0
  32. data/ext/unicorn_http/global_variables.h +91 -0
  33. data/ext/unicorn_http/unicorn_http.rl +715 -0
  34. data/ext/unicorn_http/unicorn_http_common.rl +74 -0
  35. data/lib/unicorn.rb +730 -0
  36. data/lib/unicorn/app/exec_cgi.rb +150 -0
  37. data/lib/unicorn/app/inetd.rb +109 -0
  38. data/lib/unicorn/app/old_rails.rb +31 -0
  39. data/lib/unicorn/app/old_rails/static.rb +60 -0
  40. data/lib/unicorn/cgi_wrapper.rb +145 -0
  41. data/lib/unicorn/configurator.rb +403 -0
  42. data/lib/unicorn/const.rb +37 -0
  43. data/lib/unicorn/http_request.rb +74 -0
  44. data/lib/unicorn/http_response.rb +74 -0
  45. data/lib/unicorn/launcher.rb +39 -0
  46. data/lib/unicorn/socket_helper.rb +138 -0
  47. data/lib/unicorn/tee_input.rb +174 -0
  48. data/lib/unicorn/util.rb +64 -0
  49. data/local.mk.sample +53 -0
  50. data/setup.rb +1586 -0
  51. data/test/aggregate.rb +15 -0
  52. data/test/benchmark/README +50 -0
  53. data/test/benchmark/dd.ru +18 -0
  54. data/test/exec/README +5 -0
  55. data/test/exec/test_exec.rb +855 -0
  56. data/test/rails/app-1.2.3/.gitignore +2 -0
  57. data/test/rails/app-1.2.3/Rakefile +7 -0
  58. data/test/rails/app-1.2.3/app/controllers/application.rb +6 -0
  59. data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +36 -0
  60. data/test/rails/app-1.2.3/app/helpers/application_helper.rb +4 -0
  61. data/test/rails/app-1.2.3/config/boot.rb +11 -0
  62. data/test/rails/app-1.2.3/config/database.yml +12 -0
  63. data/test/rails/app-1.2.3/config/environment.rb +13 -0
  64. data/test/rails/app-1.2.3/config/environments/development.rb +9 -0
  65. data/test/rails/app-1.2.3/config/environments/production.rb +5 -0
  66. data/test/rails/app-1.2.3/config/routes.rb +6 -0
  67. data/test/rails/app-1.2.3/db/.gitignore +0 -0
  68. data/test/rails/app-1.2.3/public/404.html +1 -0
  69. data/test/rails/app-1.2.3/public/500.html +1 -0
  70. data/test/rails/app-2.0.2/.gitignore +2 -0
  71. data/test/rails/app-2.0.2/Rakefile +7 -0
  72. data/test/rails/app-2.0.2/app/controllers/application.rb +4 -0
  73. data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +36 -0
  74. data/test/rails/app-2.0.2/app/helpers/application_helper.rb +4 -0
  75. data/test/rails/app-2.0.2/config/boot.rb +11 -0
  76. data/test/rails/app-2.0.2/config/database.yml +12 -0
  77. data/test/rails/app-2.0.2/config/environment.rb +17 -0
  78. data/test/rails/app-2.0.2/config/environments/development.rb +8 -0
  79. data/test/rails/app-2.0.2/config/environments/production.rb +5 -0
  80. data/test/rails/app-2.0.2/config/routes.rb +6 -0
  81. data/test/rails/app-2.0.2/db/.gitignore +0 -0
  82. data/test/rails/app-2.0.2/public/404.html +1 -0
  83. data/test/rails/app-2.0.2/public/500.html +1 -0
  84. data/test/rails/app-2.1.2/.gitignore +2 -0
  85. data/test/rails/app-2.1.2/Rakefile +7 -0
  86. data/test/rails/app-2.1.2/app/controllers/application.rb +4 -0
  87. data/test/rails/app-2.1.2/app/controllers/foo_controller.rb +36 -0
  88. data/test/rails/app-2.1.2/app/helpers/application_helper.rb +4 -0
  89. data/test/rails/app-2.1.2/config/boot.rb +111 -0
  90. data/test/rails/app-2.1.2/config/database.yml +12 -0
  91. data/test/rails/app-2.1.2/config/environment.rb +17 -0
  92. data/test/rails/app-2.1.2/config/environments/development.rb +7 -0
  93. data/test/rails/app-2.1.2/config/environments/production.rb +5 -0
  94. data/test/rails/app-2.1.2/config/routes.rb +6 -0
  95. data/test/rails/app-2.1.2/db/.gitignore +0 -0
  96. data/test/rails/app-2.1.2/public/404.html +1 -0
  97. data/test/rails/app-2.1.2/public/500.html +1 -0
  98. data/test/rails/app-2.2.2/.gitignore +2 -0
  99. data/test/rails/app-2.2.2/Rakefile +7 -0
  100. data/test/rails/app-2.2.2/app/controllers/application.rb +4 -0
  101. data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +36 -0
  102. data/test/rails/app-2.2.2/app/helpers/application_helper.rb +4 -0
  103. data/test/rails/app-2.2.2/config/boot.rb +111 -0
  104. data/test/rails/app-2.2.2/config/database.yml +12 -0
  105. data/test/rails/app-2.2.2/config/environment.rb +17 -0
  106. data/test/rails/app-2.2.2/config/environments/development.rb +7 -0
  107. data/test/rails/app-2.2.2/config/environments/production.rb +5 -0
  108. data/test/rails/app-2.2.2/config/routes.rb +6 -0
  109. data/test/rails/app-2.2.2/db/.gitignore +0 -0
  110. data/test/rails/app-2.2.2/public/404.html +1 -0
  111. data/test/rails/app-2.2.2/public/500.html +1 -0
  112. data/test/rails/app-2.3.3.1/.gitignore +2 -0
  113. data/test/rails/app-2.3.3.1/Rakefile +7 -0
  114. data/test/rails/app-2.3.3.1/app/controllers/application_controller.rb +5 -0
  115. data/test/rails/app-2.3.3.1/app/controllers/foo_controller.rb +36 -0
  116. data/test/rails/app-2.3.3.1/app/helpers/application_helper.rb +4 -0
  117. data/test/rails/app-2.3.3.1/config/boot.rb +109 -0
  118. data/test/rails/app-2.3.3.1/config/database.yml +12 -0
  119. data/test/rails/app-2.3.3.1/config/environment.rb +17 -0
  120. data/test/rails/app-2.3.3.1/config/environments/development.rb +7 -0
  121. data/test/rails/app-2.3.3.1/config/environments/production.rb +6 -0
  122. data/test/rails/app-2.3.3.1/config/routes.rb +6 -0
  123. data/test/rails/app-2.3.3.1/db/.gitignore +0 -0
  124. data/test/rails/app-2.3.3.1/public/404.html +1 -0
  125. data/test/rails/app-2.3.3.1/public/500.html +1 -0
  126. data/test/rails/app-2.3.3.1/public/x.txt +1 -0
  127. data/test/rails/test_rails.rb +280 -0
  128. data/test/test_helper.rb +296 -0
  129. data/test/unit/test_configurator.rb +150 -0
  130. data/test/unit/test_http_parser.rb +492 -0
  131. data/test/unit/test_http_parser_ng.rb +308 -0
  132. data/test/unit/test_request.rb +184 -0
  133. data/test/unit/test_response.rb +110 -0
  134. data/test/unit/test_server.rb +188 -0
  135. data/test/unit/test_signals.rb +202 -0
  136. data/test/unit/test_socket_helper.rb +133 -0
  137. data/test/unit/test_tee_input.rb +229 -0
  138. data/test/unit/test_upload.rb +297 -0
  139. data/test/unit/test_util.rb +96 -0
  140. data/unicorn.gemspec +42 -0
  141. metadata +228 -0
@@ -0,0 +1,110 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # Copyright (c) 2005 Zed A. Shaw
4
+ # You can redistribute it and/or modify it under the same terms as Ruby.
5
+ #
6
+ # Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
7
+ # for more information.
8
+
9
+ require 'test/test_helper'
10
+
11
+ include Unicorn
12
+
13
+ class ResponseTest < Test::Unit::TestCase
14
+
15
+ def test_response_headers
16
+ out = StringIO.new
17
+ HttpResponse.write(out,[200, {"X-Whatever" => "stuff"}, ["cool"]])
18
+ assert out.closed?
19
+
20
+ assert out.length > 0, "output didn't have data"
21
+ end
22
+
23
+ def test_response_string_status
24
+ out = StringIO.new
25
+ HttpResponse.write(out,['200', {}, []])
26
+ assert out.closed?
27
+ assert out.length > 0, "output didn't have data"
28
+ assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/).size
29
+ end
30
+
31
+ def test_response_OFS_set
32
+ old_ofs = $,
33
+ $, = "\f\v"
34
+ out = StringIO.new
35
+ HttpResponse.write(out,[200, {"X-k" => "cd","X-y" => "z"}, ["cool"]])
36
+ assert out.closed?
37
+ resp = out.string
38
+ assert ! resp.include?("\f\v"), "output didn't use $, ($OFS)"
39
+ ensure
40
+ $, = old_ofs
41
+ end
42
+
43
+ def test_response_200
44
+ io = StringIO.new
45
+ HttpResponse.write(io, [200, {}, []])
46
+ assert io.closed?
47
+ assert io.length > 0, "output didn't have data"
48
+ end
49
+
50
+ def test_response_with_default_reason
51
+ code = 400
52
+ io = StringIO.new
53
+ HttpResponse.write(io, [code, {}, []])
54
+ assert io.closed?
55
+ lines = io.string.split(/\r\n/)
56
+ assert_match(/.* Bad Request$/, lines.first,
57
+ "wrong default reason phrase")
58
+ end
59
+
60
+ def test_rack_multivalue_headers
61
+ out = StringIO.new
62
+ HttpResponse.write(out,[200, {"X-Whatever" => "stuff\nbleh"}, []])
63
+ assert out.closed?
64
+ assert_match(/^X-Whatever: stuff\r\nX-Whatever: bleh\r\n/, out.string)
65
+ end
66
+
67
+ # Even though Rack explicitly forbids "Status" in the header hash,
68
+ # some broken clients still rely on it
69
+ def test_status_header_added
70
+ out = StringIO.new
71
+ HttpResponse.write(out,[200, {"X-Whatever" => "stuff"}, []])
72
+ assert out.closed?
73
+ assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/i).size
74
+ end
75
+
76
+ # we always favor the code returned by the application, since "Status"
77
+ # in the header hash is not allowed by Rack (but not every app is
78
+ # fully Rack-compliant).
79
+ def test_status_header_ignores_app_hash
80
+ out = StringIO.new
81
+ header_hash = {"X-Whatever" => "stuff", 'StaTus' => "666" }
82
+ HttpResponse.write(out,[200, header_hash, []])
83
+ assert out.closed?
84
+ assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/i).size
85
+ assert_equal 1, out.string.split(/\r\n/).grep(/^Status:/i).size
86
+ end
87
+
88
+ def test_body_closed
89
+ expect_body = %w(1 2 3 4).join("\n")
90
+ body = StringIO.new(expect_body)
91
+ body.rewind
92
+ out = StringIO.new
93
+ HttpResponse.write(out,[200, {}, body])
94
+ assert out.closed?
95
+ assert body.closed?
96
+ assert_match(expect_body, out.string.split(/\r\n/).last)
97
+ end
98
+
99
+ def test_unknown_status_pass_through
100
+ out = StringIO.new
101
+ HttpResponse.write(out,["666 I AM THE BEAST", {}, [] ])
102
+ assert out.closed?
103
+ headers = out.string.split(/\r\n\r\n/).first.split(/\r\n/)
104
+ assert %r{\AHTTP/\d\.\d 666 I AM THE BEAST\z}.match(headers[0])
105
+ status = headers.grep(/\AStatus:/i).first
106
+ assert status
107
+ assert_equal "Status: 666 I AM THE BEAST", status
108
+ end
109
+
110
+ end
@@ -0,0 +1,188 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # Copyright (c) 2005 Zed A. Shaw
4
+ # You can redistribute it and/or modify it under the same terms as Ruby.
5
+ #
6
+ # Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
7
+ # for more information.
8
+
9
+ require 'test/test_helper'
10
+
11
+ include Unicorn
12
+
13
+ class TestHandler
14
+
15
+ def call(env)
16
+ # response.socket.write("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nhello!\n")
17
+ while env['rack.input'].read(4096)
18
+ end
19
+ [200, { 'Content-Type' => 'text/plain' }, ['hello!\n']]
20
+ end
21
+ end
22
+
23
+
24
+ class WebServerTest < Test::Unit::TestCase
25
+
26
+ def setup
27
+ @valid_request = "GET / HTTP/1.1\r\nHost: www.zedshaw.com\r\nContent-Type: text/plain\r\n\r\n"
28
+ @port = unused_port
29
+ @tester = TestHandler.new
30
+ redirect_test_io do
31
+ @server = HttpServer.new(@tester, :listeners => [ "127.0.0.1:#{@port}" ] )
32
+ @server.start
33
+ end
34
+ end
35
+
36
+ def teardown
37
+ redirect_test_io do
38
+ wait_workers_ready("test_stderr.#$$.log", 1)
39
+ File.truncate("test_stderr.#$$.log", 0)
40
+ @server.stop(true)
41
+ end
42
+ end
43
+
44
+ def test_preload_app_config
45
+ teardown
46
+ tmp = Tempfile.new('test_preload_app_config')
47
+ ObjectSpace.undefine_finalizer(tmp)
48
+ app = lambda { ||
49
+ tmp.sysseek(0)
50
+ tmp.truncate(0)
51
+ tmp.syswrite($$)
52
+ lambda { |env| [ 200, { 'Content-Type' => 'text/plain' }, [ "#$$\n" ] ] }
53
+ }
54
+ redirect_test_io do
55
+ @server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"] )
56
+ @server.start
57
+ end
58
+ results = hit(["http://localhost:#@port/"])
59
+ worker_pid = results[0].to_i
60
+ assert worker_pid != 0
61
+ tmp.sysseek(0)
62
+ loader_pid = tmp.sysread(4096).to_i
63
+ assert loader_pid != 0
64
+ assert_equal worker_pid, loader_pid
65
+ teardown
66
+
67
+ redirect_test_io do
68
+ @server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"],
69
+ :preload_app => true)
70
+ @server.start
71
+ end
72
+ results = hit(["http://localhost:#@port/"])
73
+ worker_pid = results[0].to_i
74
+ assert worker_pid != 0
75
+ tmp.sysseek(0)
76
+ loader_pid = tmp.sysread(4096).to_i
77
+ assert_equal $$, loader_pid
78
+ assert worker_pid != loader_pid
79
+ ensure
80
+ tmp.close!
81
+ end
82
+
83
+ def test_broken_app
84
+ teardown
85
+ app = lambda { |env| raise RuntimeError, "hello" }
86
+ # [200, {}, []] }
87
+ redirect_test_io do
88
+ @server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"] )
89
+ @server.start
90
+ end
91
+ sock = nil
92
+ assert_nothing_raised do
93
+ sock = TCPSocket.new('127.0.0.1', @port)
94
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
95
+ end
96
+
97
+ assert_match %r{\AHTTP/1.[01] 500\b}, sock.sysread(4096)
98
+ assert_nothing_raised { sock.close }
99
+ end
100
+
101
+ def test_simple_server
102
+ results = hit(["http://localhost:#{@port}/test"])
103
+ assert_equal 'hello!\n', results[0], "Handler didn't really run"
104
+ end
105
+
106
+
107
+ def do_test(string, chunk, close_after=nil, shutdown_delay=0)
108
+ # Do not use instance variables here, because it needs to be thread safe
109
+ socket = TCPSocket.new("127.0.0.1", @port);
110
+ request = StringIO.new(string)
111
+ chunks_out = 0
112
+
113
+ while data = request.read(chunk)
114
+ chunks_out += socket.write(data)
115
+ socket.flush
116
+ sleep 0.2
117
+ if close_after and chunks_out > close_after
118
+ socket.close
119
+ sleep 1
120
+ end
121
+ end
122
+ sleep(shutdown_delay)
123
+ socket.write(" ") # Some platforms only raise the exception on attempted write
124
+ socket.flush
125
+ end
126
+
127
+ def test_trickle_attack
128
+ do_test(@valid_request, 3)
129
+ end
130
+
131
+ def test_close_client
132
+ assert_raises IOError do
133
+ do_test(@valid_request, 10, 20)
134
+ end
135
+ end
136
+
137
+ def test_bad_client
138
+ redirect_test_io do
139
+ do_test("GET /test HTTP/BAD", 3)
140
+ end
141
+ end
142
+
143
+ def test_bad_client_400
144
+ sock = nil
145
+ assert_nothing_raised do
146
+ sock = TCPSocket.new('127.0.0.1', @port)
147
+ sock.syswrite("GET / HTTP/1.0\r\nHost: foo\rbar\r\n\r\n")
148
+ end
149
+ assert_match %r{\AHTTP/1.[01] 400\b}, sock.sysread(4096)
150
+ assert_nothing_raised { sock.close }
151
+ end
152
+
153
+ def test_http_0_9
154
+ sock = nil
155
+ assert_nothing_raised do
156
+ sock = TCPSocket.new('127.0.0.1', @port)
157
+ sock.syswrite("GET /hello\r\n")
158
+ end
159
+ assert_match 'hello!\n', sock.sysread(4096)
160
+ assert_nothing_raised { sock.close }
161
+ end
162
+
163
+ def test_header_is_too_long
164
+ redirect_test_io do
165
+ long = "GET /test HTTP/1.1\r\n" + ("X-Big: stuff\r\n" * 15000) + "\r\n"
166
+ assert_raises Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EINVAL, IOError do
167
+ do_test(long, long.length/2, 10)
168
+ end
169
+ end
170
+ end
171
+
172
+ def test_file_streamed_request
173
+ body = "a" * (Unicorn::Const::MAX_BODY * 2)
174
+ long = "PUT /test HTTP/1.1\r\nContent-length: #{body.length}\r\n\r\n" + body
175
+ do_test(long, Unicorn::Const::CHUNK_SIZE * 2 -400)
176
+ end
177
+
178
+ def test_file_streamed_request_bad_body
179
+ body = "a" * (Unicorn::Const::MAX_BODY * 2)
180
+ long = "GET /test HTTP/1.1\r\nContent-ength: #{body.length}\r\n\r\n" + body
181
+ assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
182
+ Errno::EBADF) {
183
+ do_test(long, Unicorn::Const::CHUNK_SIZE * 2 -400)
184
+ }
185
+ end
186
+
187
+ end
188
+
@@ -0,0 +1,202 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # Copyright (c) 2009 Eric Wong
4
+ # You can redistribute it and/or modify it under the same terms as Ruby.
5
+ #
6
+ # Ensure we stay sane in the face of signals being sent to us
7
+
8
+ require 'test/test_helper'
9
+
10
+ include Unicorn
11
+
12
+ class Dd
13
+ def initialize(bs, count)
14
+ @count = count
15
+ @buf = ' ' * bs
16
+ end
17
+
18
+ def each(&block)
19
+ @count.times { yield @buf }
20
+ end
21
+ end
22
+
23
+ class SignalsTest < Test::Unit::TestCase
24
+
25
+ def setup
26
+ @bs = 1 * 1024 * 1024
27
+ @count = 100
28
+ @port = unused_port
29
+ @sock = Tempfile.new('unicorn.sock')
30
+ @tmp = Tempfile.new('unicorn.write')
31
+ @tmp.sync = true
32
+ File.unlink(@sock.path)
33
+ File.unlink(@tmp.path)
34
+ @server_opts = {
35
+ :listeners => [ "127.0.0.1:#@port", @sock.path ],
36
+ :after_fork => lambda { |server,worker|
37
+ trap(:HUP) { @tmp.syswrite('.') }
38
+ },
39
+ }
40
+ @server = nil
41
+ end
42
+
43
+ def test_worker_dies_on_dead_master
44
+ pid = fork {
45
+ app = lambda { |env| [ 200, {'X-Pid' => "#$$" }, [] ] }
46
+ opts = @server_opts.merge(:timeout => 3)
47
+ redirect_test_io { HttpServer.new(app, opts).start.join }
48
+ }
49
+ child = sock = buf = t0 = nil
50
+ assert_nothing_raised do
51
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
52
+ sock = TCPSocket.new('127.0.0.1', @port)
53
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
54
+ buf = sock.readpartial(4096)
55
+ sock.close
56
+ buf =~ /\bX-Pid: (\d+)\b/ or raise Exception
57
+ child = $1.to_i
58
+ wait_master_ready("test_stderr.#{pid}.log")
59
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
60
+ Process.kill(:KILL, pid)
61
+ Process.waitpid(pid)
62
+ File.unlink("test_stderr.#{pid}.log", "test_stdout.#{pid}.log")
63
+ t0 = Time.now
64
+ end
65
+ assert child
66
+ assert t0
67
+ assert_raises(Errno::ESRCH) { loop { Process.kill(0, child); sleep 0.2 } }
68
+ assert((Time.now - t0) < 60)
69
+ end
70
+
71
+ def test_sleepy_kill
72
+ rd, wr = IO.pipe
73
+ pid = fork {
74
+ rd.close
75
+ app = lambda { |env| wr.syswrite('.'); sleep; [ 200, {}, [] ] }
76
+ redirect_test_io { HttpServer.new(app, @server_opts).start.join }
77
+ }
78
+ sock = buf = nil
79
+ wr.close
80
+ assert_nothing_raised do
81
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
82
+ sock = TCPSocket.new('127.0.0.1', @port)
83
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
84
+ buf = rd.readpartial(1)
85
+ wait_master_ready("test_stderr.#{pid}.log")
86
+ Process.kill(:INT, pid)
87
+ Process.waitpid(pid)
88
+ end
89
+ assert_equal '.', buf
90
+ buf = nil
91
+ assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
92
+ Errno::EBADF) do
93
+ buf = sock.sysread(4096)
94
+ end
95
+ assert_nil buf
96
+ ensure
97
+ end
98
+
99
+ def test_timeout_slow_response
100
+ pid = fork {
101
+ app = lambda { |env| sleep }
102
+ opts = @server_opts.merge(:timeout => 3)
103
+ redirect_test_io { HttpServer.new(app, opts).start.join }
104
+ }
105
+ t0 = Time.now
106
+ sock = nil
107
+ assert_nothing_raised do
108
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
109
+ sock = TCPSocket.new('127.0.0.1', @port)
110
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
111
+ end
112
+
113
+ buf = nil
114
+ assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
115
+ Errno::EBADF) do
116
+ buf = sock.sysread(4096)
117
+ end
118
+ diff = Time.now - t0
119
+ assert_nil buf
120
+ assert diff > 1.0, "diff was #{diff.inspect}"
121
+ assert diff < 60.0
122
+ ensure
123
+ Process.kill(:QUIT, pid) rescue nil
124
+ end
125
+
126
+ def test_response_write
127
+ app = lambda { |env|
128
+ [ 200, { 'Content-Type' => 'text/plain', 'X-Pid' => Process.pid.to_s },
129
+ Dd.new(@bs, @count) ]
130
+ }
131
+ redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
132
+ sock = nil
133
+ assert_nothing_raised do
134
+ wait_workers_ready("test_stderr.#{$$}.log", 1)
135
+ sock = TCPSocket.new('127.0.0.1', @port)
136
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
137
+ end
138
+ buf = ''
139
+ header_len = pid = nil
140
+ assert_nothing_raised do
141
+ buf = sock.sysread(16384, buf)
142
+ pid = buf[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
143
+ header_len = buf[/\A(.+?\r\n\r\n)/m, 1].size
144
+ end
145
+ assert pid > 0, "pid not positive: #{pid.inspect}"
146
+ read = buf.size
147
+ size_before = @tmp.stat.size
148
+ assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
149
+ Errno::EBADF) do
150
+ loop do
151
+ 3.times { Process.kill(:HUP, pid) }
152
+ sock.sysread(16384, buf)
153
+ read += buf.size
154
+ 3.times { Process.kill(:HUP, pid) }
155
+ end
156
+ end
157
+
158
+ redirect_test_io { @server.stop(true) }
159
+ # can't check for == since pending signals get merged
160
+ assert size_before < @tmp.stat.size
161
+ got = read - header_len
162
+ expect = @bs * @count
163
+ assert_equal(expect, got, "expect=#{expect} got=#{got}")
164
+ assert_nothing_raised { sock.close }
165
+ end
166
+
167
+ def test_request_read
168
+ app = lambda { |env|
169
+ while env['rack.input'].read(4096)
170
+ end
171
+ [ 200, {'Content-Type'=>'text/plain', 'X-Pid'=>Process.pid.to_s}, [] ]
172
+ }
173
+ redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
174
+ pid = nil
175
+
176
+ assert_nothing_raised do
177
+ wait_workers_ready("test_stderr.#{$$}.log", 1)
178
+ sock = TCPSocket.new('127.0.0.1', @port)
179
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
180
+ pid = sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
181
+ sock.close
182
+ end
183
+
184
+ assert pid > 0, "pid not positive: #{pid.inspect}"
185
+ sock = TCPSocket.new('127.0.0.1', @port)
186
+ sock.syswrite("PUT / HTTP/1.0\r\n")
187
+ sock.syswrite("Content-Length: #{@bs * @count}\r\n\r\n")
188
+ 1000.times { Process.kill(:HUP, pid) }
189
+ size_before = @tmp.stat.size
190
+ killer = fork { loop { Process.kill(:HUP, pid); sleep(0.0001) } }
191
+ buf = ' ' * @bs
192
+ @count.times { sock.syswrite(buf) }
193
+ Process.kill(:TERM, killer)
194
+ Process.waitpid2(killer)
195
+ redirect_test_io { @server.stop(true) }
196
+ # can't check for == since pending signals get merged
197
+ assert size_before < @tmp.stat.size
198
+ assert_equal pid, sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
199
+ sock.close
200
+ end
201
+
202
+ end