unicorn-maintained 6.2.0

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 (151) hide show
  1. checksums.yaml +7 -0
  2. data/.CHANGELOG.old +25 -0
  3. data/.document +28 -0
  4. data/.gitattributes +5 -0
  5. data/.gitignore +25 -0
  6. data/.mailmap +26 -0
  7. data/.manifest +149 -0
  8. data/.olddoc.yml +25 -0
  9. data/Application_Timeouts +77 -0
  10. data/CONTRIBUTORS +39 -0
  11. data/COPYING +674 -0
  12. data/DESIGN +99 -0
  13. data/Documentation/.gitignore +3 -0
  14. data/Documentation/unicorn.1 +222 -0
  15. data/Documentation/unicorn_rails.1 +207 -0
  16. data/FAQ +70 -0
  17. data/GIT-VERSION-FILE +1 -0
  18. data/GIT-VERSION-GEN +39 -0
  19. data/GNUmakefile +317 -0
  20. data/HACKING +112 -0
  21. data/ISSUES +102 -0
  22. data/KNOWN_ISSUES +79 -0
  23. data/LATEST +1 -0
  24. data/LICENSE +67 -0
  25. data/Links +58 -0
  26. data/NEWS +1 -0
  27. data/PHILOSOPHY +139 -0
  28. data/README +156 -0
  29. data/Rakefile +16 -0
  30. data/SIGNALS +123 -0
  31. data/Sandbox +104 -0
  32. data/TODO +3 -0
  33. data/TUNING +119 -0
  34. data/archive/.gitignore +3 -0
  35. data/archive/slrnpull.conf +4 -0
  36. data/bin/unicorn +128 -0
  37. data/bin/unicorn_rails +209 -0
  38. data/examples/big_app_gc.rb +2 -0
  39. data/examples/echo.ru +26 -0
  40. data/examples/init.sh +102 -0
  41. data/examples/logger_mp_safe.rb +25 -0
  42. data/examples/logrotate.conf +44 -0
  43. data/examples/nginx.conf +156 -0
  44. data/examples/unicorn.conf.minimal.rb +13 -0
  45. data/examples/unicorn.conf.rb +110 -0
  46. data/examples/unicorn.socket +11 -0
  47. data/examples/unicorn@.service +40 -0
  48. data/ext/unicorn_http/CFLAGS +13 -0
  49. data/ext/unicorn_http/c_util.h +116 -0
  50. data/ext/unicorn_http/common_field_optimization.h +128 -0
  51. data/ext/unicorn_http/epollexclusive.h +128 -0
  52. data/ext/unicorn_http/ext_help.h +38 -0
  53. data/ext/unicorn_http/extconf.rb +39 -0
  54. data/ext/unicorn_http/global_variables.h +97 -0
  55. data/ext/unicorn_http/httpdate.c +91 -0
  56. data/ext/unicorn_http/unicorn_http.c +4334 -0
  57. data/ext/unicorn_http/unicorn_http.rl +1040 -0
  58. data/ext/unicorn_http/unicorn_http_common.rl +76 -0
  59. data/lib/unicorn/app/old_rails/static.rb +59 -0
  60. data/lib/unicorn/app/old_rails.rb +35 -0
  61. data/lib/unicorn/cgi_wrapper.rb +147 -0
  62. data/lib/unicorn/configurator.rb +748 -0
  63. data/lib/unicorn/const.rb +21 -0
  64. data/lib/unicorn/http_request.rb +201 -0
  65. data/lib/unicorn/http_response.rb +93 -0
  66. data/lib/unicorn/http_server.rb +859 -0
  67. data/lib/unicorn/launcher.rb +62 -0
  68. data/lib/unicorn/oob_gc.rb +81 -0
  69. data/lib/unicorn/preread_input.rb +33 -0
  70. data/lib/unicorn/select_waiter.rb +6 -0
  71. data/lib/unicorn/socket_helper.rb +185 -0
  72. data/lib/unicorn/stream_input.rb +151 -0
  73. data/lib/unicorn/tee_input.rb +131 -0
  74. data/lib/unicorn/tmpio.rb +33 -0
  75. data/lib/unicorn/util.rb +90 -0
  76. data/lib/unicorn/version.rb +1 -0
  77. data/lib/unicorn/worker.rb +165 -0
  78. data/lib/unicorn.rb +136 -0
  79. data/man/man1/unicorn.1 +222 -0
  80. data/man/man1/unicorn_rails.1 +207 -0
  81. data/setup.rb +1586 -0
  82. data/t/.gitignore +4 -0
  83. data/t/GNUmakefile +5 -0
  84. data/t/README +49 -0
  85. data/t/active-unix-socket.t +117 -0
  86. data/t/bin/unused_listen +40 -0
  87. data/t/broken-app.ru +12 -0
  88. data/t/client_body_buffer_size.ru +14 -0
  89. data/t/client_body_buffer_size.t +80 -0
  90. data/t/detach.ru +11 -0
  91. data/t/env.ru +3 -0
  92. data/t/fails-rack-lint.ru +5 -0
  93. data/t/heartbeat-timeout.ru +12 -0
  94. data/t/heartbeat-timeout.t +62 -0
  95. data/t/integration.ru +115 -0
  96. data/t/integration.t +356 -0
  97. data/t/lib.perl +258 -0
  98. data/t/listener_names.ru +4 -0
  99. data/t/my-tap-lib.sh +201 -0
  100. data/t/oob_gc.ru +17 -0
  101. data/t/oob_gc_path.ru +17 -0
  102. data/t/pid.ru +3 -0
  103. data/t/preread_input.ru +22 -0
  104. data/t/reload-bad-config.t +54 -0
  105. data/t/reopen-logs.ru +13 -0
  106. data/t/reopen-logs.t +39 -0
  107. data/t/t0008-back_out_of_upgrade.sh +110 -0
  108. data/t/t0009-broken-app.sh +56 -0
  109. data/t/t0010-reap-logging.sh +55 -0
  110. data/t/t0012-reload-empty-config.sh +86 -0
  111. data/t/t0013-rewindable-input-false.sh +24 -0
  112. data/t/t0013.ru +12 -0
  113. data/t/t0014-rewindable-input-true.sh +24 -0
  114. data/t/t0014.ru +12 -0
  115. data/t/t0015-configurator-internals.sh +25 -0
  116. data/t/t0020-at_exit-handler.sh +49 -0
  117. data/t/t0021-process_detach.sh +29 -0
  118. data/t/t0022-listener_names-preload_app.sh +32 -0
  119. data/t/t0300-no-default-middleware.sh +20 -0
  120. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  121. data/t/t0301.ru +13 -0
  122. data/t/t9001-oob_gc.sh +47 -0
  123. data/t/t9002-oob_gc-path.sh +75 -0
  124. data/t/test-lib.sh +125 -0
  125. data/t/winch_ttin.t +67 -0
  126. data/t/working_directory.t +94 -0
  127. data/test/aggregate.rb +15 -0
  128. data/test/benchmark/README +60 -0
  129. data/test/benchmark/dd.ru +18 -0
  130. data/test/benchmark/ddstream.ru +50 -0
  131. data/test/benchmark/readinput.ru +40 -0
  132. data/test/benchmark/stack.ru +8 -0
  133. data/test/benchmark/uconnect.perl +66 -0
  134. data/test/exec/README +5 -0
  135. data/test/exec/test_exec.rb +1029 -0
  136. data/test/test_helper.rb +306 -0
  137. data/test/unit/test_ccc.rb +91 -0
  138. data/test/unit/test_configurator.rb +175 -0
  139. data/test/unit/test_droplet.rb +28 -0
  140. data/test/unit/test_http_parser.rb +884 -0
  141. data/test/unit/test_http_parser_ng.rb +714 -0
  142. data/test/unit/test_request.rb +169 -0
  143. data/test/unit/test_server.rb +244 -0
  144. data/test/unit/test_signals.rb +188 -0
  145. data/test/unit/test_socket_helper.rb +159 -0
  146. data/test/unit/test_stream_input.rb +210 -0
  147. data/test/unit/test_tee_input.rb +303 -0
  148. data/test/unit/test_util.rb +131 -0
  149. data/test/unit/test_waiter.rb +34 -0
  150. data/unicorn.gemspec +48 -0
  151. metadata +275 -0
@@ -0,0 +1,169 @@
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 1.8 or
5
+ # the GPLv2+ (GPLv3+ preferred)
6
+
7
+ require './test/test_helper'
8
+
9
+ include Unicorn
10
+
11
+ class RequestTest < Test::Unit::TestCase
12
+
13
+ MockRequest = Class.new(StringIO)
14
+
15
+ AI = Addrinfo.new(Socket.sockaddr_un('/unicorn/sucks'))
16
+
17
+ def setup
18
+ @request = HttpRequest.new
19
+ @app = lambda do |env|
20
+ [ 200, { 'content-length' => '0', 'content-type' => 'text/plain' }, [] ]
21
+ end
22
+ @lint = Rack::Lint.new(@app)
23
+ end
24
+
25
+ def test_options
26
+ client = MockRequest.new("OPTIONS * HTTP/1.1\r\n" \
27
+ "Host: foo\r\n\r\n")
28
+ env = @request.read_headers(client, AI)
29
+ assert_equal '', env['REQUEST_PATH']
30
+ assert_equal '', env['PATH_INFO']
31
+ assert_equal '*', env['REQUEST_URI']
32
+ assert_kind_of Array, @lint.call(env)
33
+ end
34
+
35
+ def test_absolute_uri_with_query
36
+ client = MockRequest.new("GET http://e:3/x?y=z HTTP/1.1\r\n" \
37
+ "Host: foo\r\n\r\n")
38
+ env = @request.read_headers(client, AI)
39
+ assert_equal '/x', env['REQUEST_PATH']
40
+ assert_equal '/x', env['PATH_INFO']
41
+ assert_equal 'y=z', env['QUERY_STRING']
42
+ assert_kind_of Array, @lint.call(env)
43
+ end
44
+
45
+ def test_absolute_uri_with_fragment
46
+ client = MockRequest.new("GET http://e:3/x#frag HTTP/1.1\r\n" \
47
+ "Host: foo\r\n\r\n")
48
+ env = @request.read_headers(client, AI)
49
+ assert_equal '/x', env['REQUEST_PATH']
50
+ assert_equal '/x', env['PATH_INFO']
51
+ assert_equal '', env['QUERY_STRING']
52
+ assert_equal 'frag', env['FRAGMENT']
53
+ assert_kind_of Array, @lint.call(env)
54
+ end
55
+
56
+ def test_absolute_uri_with_query_and_fragment
57
+ client = MockRequest.new("GET http://e:3/x?a=b#frag HTTP/1.1\r\n" \
58
+ "Host: foo\r\n\r\n")
59
+ env = @request.read_headers(client, AI)
60
+ assert_equal '/x', env['REQUEST_PATH']
61
+ assert_equal '/x', env['PATH_INFO']
62
+ assert_equal 'a=b', env['QUERY_STRING']
63
+ assert_equal 'frag', env['FRAGMENT']
64
+ assert_kind_of Array, @lint.call(env)
65
+ end
66
+
67
+ def test_absolute_uri_unsupported_schemes
68
+ %w(ssh+http://e/ ftp://e/x http+ssh://e/x).each do |abs_uri|
69
+ client = MockRequest.new("GET #{abs_uri} HTTP/1.1\r\n" \
70
+ "Host: foo\r\n\r\n")
71
+ assert_raises(HttpParserError) { @request.read_headers(client, AI) }
72
+ end
73
+ end
74
+
75
+ def test_x_forwarded_proto_https
76
+ client = MockRequest.new("GET / HTTP/1.1\r\n" \
77
+ "X-Forwarded-Proto: https\r\n" \
78
+ "Host: foo\r\n\r\n")
79
+ env = @request.read_headers(client, AI)
80
+ assert_equal "https", env['rack.url_scheme']
81
+ assert_kind_of Array, @lint.call(env)
82
+ end
83
+
84
+ def test_x_forwarded_proto_http
85
+ client = MockRequest.new("GET / HTTP/1.1\r\n" \
86
+ "X-Forwarded-Proto: http\r\n" \
87
+ "Host: foo\r\n\r\n")
88
+ env = @request.read_headers(client, AI)
89
+ assert_equal "http", env['rack.url_scheme']
90
+ assert_kind_of Array, @lint.call(env)
91
+ end
92
+
93
+ def test_x_forwarded_proto_invalid
94
+ client = MockRequest.new("GET / HTTP/1.1\r\n" \
95
+ "X-Forwarded-Proto: ftp\r\n" \
96
+ "Host: foo\r\n\r\n")
97
+ env = @request.read_headers(client, AI)
98
+ assert_equal "http", env['rack.url_scheme']
99
+ assert_kind_of Array, @lint.call(env)
100
+ end
101
+
102
+ def test_rack_lint_get
103
+ client = MockRequest.new("GET / HTTP/1.1\r\nHost: foo\r\n\r\n")
104
+ env = @request.read_headers(client, AI)
105
+ assert_equal "http", env['rack.url_scheme']
106
+ assert_equal '127.0.0.1', env['REMOTE_ADDR']
107
+ assert_kind_of Array, @lint.call(env)
108
+ end
109
+
110
+ def test_no_content_stringio
111
+ client = MockRequest.new("GET / HTTP/1.1\r\nHost: foo\r\n\r\n")
112
+ env = @request.read_headers(client, AI)
113
+ assert_equal StringIO, env['rack.input'].class
114
+ end
115
+
116
+ def test_zero_content_stringio
117
+ client = MockRequest.new("PUT / HTTP/1.1\r\n" \
118
+ "Content-Length: 0\r\n" \
119
+ "Host: foo\r\n\r\n")
120
+ env = @request.read_headers(client, AI)
121
+ assert_equal StringIO, env['rack.input'].class
122
+ end
123
+
124
+ def test_real_content_not_stringio
125
+ client = MockRequest.new("PUT / HTTP/1.1\r\n" \
126
+ "Content-Length: 1\r\n" \
127
+ "Host: foo\r\n\r\n")
128
+ env = @request.read_headers(client, AI)
129
+ assert_equal Unicorn::TeeInput, env['rack.input'].class
130
+ end
131
+
132
+ def test_rack_lint_put
133
+ client = MockRequest.new(
134
+ "PUT / HTTP/1.1\r\n" \
135
+ "Host: foo\r\n" \
136
+ "Content-Length: 5\r\n" \
137
+ "\r\n" \
138
+ "abcde")
139
+ env = @request.read_headers(client, AI)
140
+ assert ! env.include?(:http_body)
141
+ assert_kind_of Array, @lint.call(env)
142
+ end
143
+
144
+ def test_rack_lint_big_put
145
+ count = 100
146
+ bs = 0x10000
147
+ buf = (' ' * bs).freeze
148
+ length = bs * count
149
+ client = Tempfile.new('big_put')
150
+ client.syswrite(
151
+ "PUT / HTTP/1.1\r\n" \
152
+ "Host: foo\r\n" \
153
+ "Content-Length: #{length}\r\n" \
154
+ "\r\n")
155
+ count.times { assert_equal bs, client.syswrite(buf) }
156
+ assert_equal 0, client.sysseek(0)
157
+ env = @request.read_headers(client, AI)
158
+ assert ! env.include?(:http_body)
159
+ assert_equal length, env['rack.input'].size
160
+ count.times {
161
+ tmp = env['rack.input'].read(bs)
162
+ tmp << env['rack.input'].read(bs - tmp.size) if tmp.size != bs
163
+ assert_equal buf, tmp
164
+ }
165
+ assert_nil env['rack.input'].read(bs)
166
+ env['rack.input'].rewind
167
+ assert_kind_of Array, @lint.call(env)
168
+ end
169
+ end
@@ -0,0 +1,244 @@
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 1.8 or
5
+ # the GPLv2+ (GPLv3+ preferred)
6
+ #
7
+ # Additional work donated by contributors. See git history
8
+ # for more information.
9
+
10
+ require './test/test_helper'
11
+
12
+ include Unicorn
13
+
14
+ class TestHandler
15
+
16
+ def call(env)
17
+ while env['rack.input'].read(4096)
18
+ end
19
+ [200, { 'content-type' => 'text/plain' }, ['hello!\n']]
20
+ rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
21
+ $stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
22
+ raise e
23
+ end
24
+ end
25
+
26
+ class TestRackAfterReply
27
+ def initialize
28
+ @called = false
29
+ end
30
+
31
+ def call(env)
32
+ while env['rack.input'].read(4096)
33
+ end
34
+
35
+ env["rack.after_reply"] << -> { @called = true }
36
+
37
+ [200, { 'content-type' => 'text/plain' }, ["after_reply_called: #{@called}"]]
38
+ rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
39
+ $stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
40
+ raise e
41
+ end
42
+ end
43
+
44
+ class WebServerTest < Test::Unit::TestCase
45
+
46
+ def setup
47
+ @valid_request = "GET / HTTP/1.1\r\nHost: www.zedshaw.com\r\nContent-Type: text/plain\r\n\r\n"
48
+ @port = unused_port
49
+ @tester = TestHandler.new
50
+ redirect_test_io do
51
+ @server = HttpServer.new(@tester, :listeners => [ "127.0.0.1:#{@port}" ] )
52
+ @server.start
53
+ end
54
+ end
55
+
56
+ def teardown
57
+ redirect_test_io do
58
+ wait_workers_ready("test_stderr.#$$.log", 1)
59
+ File.truncate("test_stderr.#$$.log", 0)
60
+ @server.stop(false)
61
+ end
62
+ reset_sig_handlers
63
+ end
64
+
65
+ def test_preload_app_config
66
+ teardown
67
+ tmp = Tempfile.new('test_preload_app_config')
68
+ ObjectSpace.undefine_finalizer(tmp)
69
+ app = lambda { ||
70
+ tmp.sysseek(0)
71
+ tmp.truncate(0)
72
+ tmp.syswrite($$)
73
+ lambda { |env| [ 200, { 'content-type' => 'text/plain' }, [ "#$$\n" ] ] }
74
+ }
75
+ redirect_test_io do
76
+ @server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"] )
77
+ @server.start
78
+ end
79
+ results = hit(["http://localhost:#@port/"])
80
+ worker_pid = results[0].to_i
81
+ assert worker_pid != 0
82
+ tmp.sysseek(0)
83
+ loader_pid = tmp.sysread(4096).to_i
84
+ assert loader_pid != 0
85
+ assert_equal worker_pid, loader_pid
86
+ teardown
87
+
88
+ redirect_test_io do
89
+ @server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"],
90
+ :preload_app => true)
91
+ @server.start
92
+ end
93
+ results = hit(["http://localhost:#@port/"])
94
+ worker_pid = results[0].to_i
95
+ assert worker_pid != 0
96
+ tmp.sysseek(0)
97
+ loader_pid = tmp.sysread(4096).to_i
98
+ assert_equal $$, loader_pid
99
+ assert worker_pid != loader_pid
100
+ ensure
101
+ tmp.close!
102
+ end
103
+
104
+ def test_after_reply
105
+ teardown
106
+
107
+ redirect_test_io do
108
+ @server = HttpServer.new(TestRackAfterReply.new,
109
+ :listeners => [ "127.0.0.1:#@port"])
110
+ @server.start
111
+ end
112
+
113
+ sock = tcp_socket('127.0.0.1', @port)
114
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
115
+
116
+ responses = sock.read(4096)
117
+ assert_match %r{\AHTTP/1.[01] 200\b}, responses
118
+ assert_match %r{^after_reply_called: false}, responses
119
+
120
+ sock = tcp_socket('127.0.0.1', @port)
121
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
122
+
123
+ responses = sock.read(4096)
124
+ assert_match %r{\AHTTP/1.[01] 200\b}, responses
125
+ assert_match %r{^after_reply_called: true}, responses
126
+
127
+ sock.close
128
+ end
129
+
130
+ def test_simple_server
131
+ results = hit(["http://localhost:#{@port}/test"])
132
+ assert_equal 'hello!\n', results[0], "Handler didn't really run"
133
+ end
134
+
135
+ def test_client_malformed_body
136
+ bs = 15653984
137
+ sock = tcp_socket('127.0.0.1', @port)
138
+ sock.syswrite("PUT /hello HTTP/1.1\r\n")
139
+ sock.syswrite("Host: example.com\r\n")
140
+ sock.syswrite("Transfer-Encoding: chunked\r\n")
141
+ sock.syswrite("Trailer: X-Foo\r\n")
142
+ sock.syswrite("\r\n")
143
+ sock.syswrite("%x\r\n" % [ bs ])
144
+ sock.syswrite("F" * bs)
145
+ begin
146
+ File.open("/dev/urandom", "rb") { |fp| sock.syswrite(fp.sysread(16384)) }
147
+ rescue
148
+ end
149
+ assert_nil sock.close
150
+ next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
151
+ assert_equal 'hello!\n', next_client
152
+ lines = File.readlines("test_stderr.#$$.log")
153
+ lines = lines.grep(/^Unicorn::HttpParserError: .* true$/)
154
+ assert_equal 1, lines.size
155
+ end
156
+
157
+ def do_test(string, chunk, close_after=nil, shutdown_delay=0)
158
+ # Do not use instance variables here, because it needs to be thread safe
159
+ socket = tcp_socket("127.0.0.1", @port);
160
+ request = StringIO.new(string)
161
+ chunks_out = 0
162
+
163
+ while data = request.read(chunk)
164
+ chunks_out += socket.write(data)
165
+ socket.flush
166
+ sleep 0.2
167
+ if close_after and chunks_out > close_after
168
+ socket.close
169
+ sleep 1
170
+ end
171
+ end
172
+ sleep(shutdown_delay)
173
+ socket.write(" ") # Some platforms only raise the exception on attempted write
174
+ socket.flush
175
+ end
176
+
177
+ def test_trickle_attack
178
+ do_test(@valid_request, 3)
179
+ end
180
+
181
+ def test_close_client
182
+ assert_raises IOError do
183
+ do_test(@valid_request, 10, 20)
184
+ end
185
+ end
186
+
187
+ def test_bad_client
188
+ redirect_test_io do
189
+ do_test("GET /test HTTP/BAD", 3)
190
+ end
191
+ end
192
+
193
+ def test_logger_set
194
+ assert_equal @server.logger, Unicorn::HttpRequest::DEFAULTS["rack.logger"]
195
+ end
196
+
197
+ def test_logger_changed
198
+ tmp = Logger.new($stdout)
199
+ @server.logger = tmp
200
+ assert_equal tmp, Unicorn::HttpRequest::DEFAULTS["rack.logger"]
201
+ end
202
+
203
+ def test_bad_client_400
204
+ sock = tcp_socket('127.0.0.1', @port)
205
+ sock.syswrite("GET / HTTP/1.0\r\nHost: foo\rbar\r\n\r\n")
206
+ assert_match %r{\AHTTP/1.[01] 400\b}, sock.sysread(4096)
207
+ assert_nil sock.close
208
+ end
209
+
210
+ def test_http_0_9
211
+ sock = tcp_socket('127.0.0.1', @port)
212
+ sock.syswrite("GET /hello\r\n")
213
+ assert_match 'hello!\n', sock.sysread(4096)
214
+ assert_nil sock.close
215
+ end
216
+
217
+ def test_header_is_too_long
218
+ redirect_test_io do
219
+ long = "GET /test HTTP/1.1\r\n" + ("X-Big: stuff\r\n" * 15000) + "\r\n"
220
+ assert_raises Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EINVAL, IOError do
221
+ do_test(long, long.length/2, 10)
222
+ end
223
+ end
224
+ end
225
+
226
+ def test_file_streamed_request
227
+ body = "a" * (Unicorn::Const::MAX_BODY * 2)
228
+ long = "PUT /test HTTP/1.1\r\nContent-length: #{body.length}\r\n\r\n" + body
229
+ do_test(long, Unicorn::Const::CHUNK_SIZE * 2 - 400)
230
+ end
231
+
232
+ def test_file_streamed_request_bad_body
233
+ body = "a" * (Unicorn::Const::MAX_BODY * 2)
234
+ long = "GET /test HTTP/1.1\r\nContent-ength: #{body.length}\r\n\r\n" + body
235
+ assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
236
+ Errno::EBADF) {
237
+ do_test(long, Unicorn::Const::CHUNK_SIZE * 2 - 400)
238
+ }
239
+ end
240
+
241
+ def test_listener_names
242
+ assert_equal [ "127.0.0.1:#@port" ], Unicorn.listener_names
243
+ end
244
+ end
@@ -0,0 +1,188 @@
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 1.8 or
5
+ # the GPLv2+ (GPLv3+ preferred)
6
+ #
7
+ # Ensure we stay sane in the face of signals being sent to us
8
+
9
+ require './test/test_helper'
10
+
11
+ include Unicorn
12
+
13
+ class Dd
14
+ def initialize(bs, count)
15
+ @count = count
16
+ @buf = ' ' * bs
17
+ end
18
+
19
+ def each(&block)
20
+ @count.times { yield @buf }
21
+ end
22
+ end
23
+
24
+ class SignalsTest < Test::Unit::TestCase
25
+
26
+ def setup
27
+ @bs = 1 * 1024 * 1024
28
+ @count = 100
29
+ @port = unused_port
30
+ @sock = Tempfile.new('unicorn.sock')
31
+ @tmp = Tempfile.new('unicorn.write')
32
+ @tmp.sync = true
33
+ File.unlink(@sock.path)
34
+ File.unlink(@tmp.path)
35
+ @server_opts = {
36
+ :listeners => [ "127.0.0.1:#@port", @sock.path ],
37
+ :after_fork => lambda { |server,worker|
38
+ trap(:HUP) { @tmp.syswrite('.') }
39
+ },
40
+ }
41
+ @server = nil
42
+ end
43
+
44
+ def teardown
45
+ reset_sig_handlers
46
+ end
47
+
48
+ def test_worker_dies_on_dead_master
49
+ pid = fork {
50
+ app = lambda { |env| [ 200, {'x-pid' => "#$$" }, [] ] }
51
+ opts = @server_opts.merge(:timeout => 3)
52
+ redirect_test_io { HttpServer.new(app, opts).start.join }
53
+ }
54
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
55
+ sock = tcp_socket('127.0.0.1', @port)
56
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
57
+ buf = sock.readpartial(4096)
58
+ assert_nil sock.close
59
+ buf =~ /\bx-pid: (\d+)\b/ or raise Exception
60
+ child = $1.to_i
61
+ wait_master_ready("test_stderr.#{pid}.log")
62
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
63
+ Process.kill(:KILL, pid)
64
+ Process.waitpid(pid)
65
+ File.unlink("test_stderr.#{pid}.log", "test_stdout.#{pid}.log")
66
+ t0 = Time.now
67
+ assert child
68
+ assert t0
69
+ assert_raises(Errno::ESRCH) { loop { Process.kill(0, child); sleep 0.2 } }
70
+ assert((Time.now - t0) < 60)
71
+ end
72
+
73
+ def test_sleepy_kill
74
+ rd, wr = IO.pipe
75
+ pid = fork {
76
+ rd.close
77
+ app = lambda { |env| wr.syswrite('.'); sleep; [ 200, {}, [] ] }
78
+ redirect_test_io { HttpServer.new(app, @server_opts).start.join }
79
+ }
80
+ wr.close
81
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
82
+ sock = tcp_socket('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
+ assert_equal '.', buf
89
+ buf = nil
90
+ assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
91
+ Errno::EBADF) do
92
+ buf = sock.sysread(4096)
93
+ end
94
+ assert_nil buf
95
+ end
96
+
97
+ def test_timeout_slow_response
98
+ pid = fork {
99
+ app = lambda { |env| sleep }
100
+ opts = @server_opts.merge(:timeout => 3)
101
+ redirect_test_io { HttpServer.new(app, opts).start.join }
102
+ }
103
+ t0 = Time.now
104
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
105
+ sock = tcp_socket('127.0.0.1', @port)
106
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
107
+
108
+ buf = nil
109
+ assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
110
+ Errno::EBADF) do
111
+ buf = sock.sysread(4096)
112
+ end
113
+ diff = Time.now - t0
114
+ assert_nil buf
115
+ assert diff > 1.0, "diff was #{diff.inspect}"
116
+ assert diff < 60.0
117
+ ensure
118
+ Process.kill(:TERM, pid) rescue nil
119
+ end
120
+
121
+ def test_response_write
122
+ app = lambda { |env|
123
+ [ 200, { 'content-type' => 'text/plain', 'x-pid' => Process.pid.to_s },
124
+ Dd.new(@bs, @count) ]
125
+ }
126
+ redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
127
+ wait_workers_ready("test_stderr.#{$$}.log", 1)
128
+ sock = tcp_socket('127.0.0.1', @port)
129
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
130
+ buf = ''
131
+ header_len = pid = nil
132
+ buf = sock.sysread(16384, buf)
133
+ pid = buf[/\r\nx-pid: (\d+)\r\n/, 1].to_i
134
+ header_len = buf[/\A(.+?\r\n\r\n)/m, 1].size
135
+ assert pid > 0, "pid not positive: #{pid.inspect}"
136
+ read = buf.size
137
+ size_before = @tmp.stat.size
138
+ assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
139
+ Errno::EBADF) do
140
+ loop do
141
+ 3.times { Process.kill(:HUP, pid) }
142
+ sock.sysread(16384, buf)
143
+ read += buf.size
144
+ 3.times { Process.kill(:HUP, pid) }
145
+ end
146
+ end
147
+
148
+ redirect_test_io { @server.stop(true) }
149
+ # can't check for == since pending signals get merged
150
+ assert size_before < @tmp.stat.size
151
+ got = read - header_len
152
+ expect = @bs * @count
153
+ assert_equal(expect, got, "expect=#{expect} got=#{got}")
154
+ assert_nil sock.close
155
+ end
156
+
157
+ def test_request_read
158
+ app = lambda { |env|
159
+ while env['rack.input'].read(4096)
160
+ end
161
+ [ 200, {'content-type'=>'text/plain', 'x-pid'=>Process.pid.to_s}, [] ]
162
+ }
163
+ redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
164
+
165
+ wait_workers_ready("test_stderr.#{$$}.log", 1)
166
+ sock = tcp_socket('127.0.0.1', @port)
167
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
168
+ pid = sock.sysread(4096)[/\r\nx-pid: (\d+)\r\n/, 1].to_i
169
+ assert_nil sock.close
170
+
171
+ assert pid > 0, "pid not positive: #{pid.inspect}"
172
+ sock = tcp_socket('127.0.0.1', @port)
173
+ sock.syswrite("PUT / HTTP/1.0\r\n")
174
+ sock.syswrite("Content-Length: #{@bs * @count}\r\n\r\n")
175
+ 1000.times { Process.kill(:HUP, pid) }
176
+ size_before = @tmp.stat.size
177
+ killer = fork { loop { Process.kill(:HUP, pid); sleep(0.01) } }
178
+ buf = ' ' * @bs
179
+ @count.times { sock.syswrite(buf) }
180
+ Process.kill(:KILL, killer)
181
+ Process.waitpid2(killer)
182
+ redirect_test_io { @server.stop(true) }
183
+ # can't check for == since pending signals get merged
184
+ assert size_before < @tmp.stat.size
185
+ assert_equal pid, sock.sysread(4096)[/\r\nx-pid: (\d+)\r\n/, 1].to_i
186
+ assert_nil sock.close
187
+ end
188
+ end