unicorn-fork 6.1.1

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 (145) 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 +144 -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 +318 -0
  20. data/HACKING +117 -0
  21. data/ISSUES +102 -0
  22. data/KNOWN_ISSUES +79 -0
  23. data/LICENSE +67 -0
  24. data/Links +58 -0
  25. data/PHILOSOPHY +139 -0
  26. data/README +165 -0
  27. data/Rakefile +17 -0
  28. data/SIGNALS +123 -0
  29. data/Sandbox +104 -0
  30. data/TODO +1 -0
  31. data/TUNING +119 -0
  32. data/archive/.gitignore +3 -0
  33. data/archive/slrnpull.conf +4 -0
  34. data/bin/unicorn +129 -0
  35. data/bin/unicorn_rails +210 -0
  36. data/examples/big_app_gc.rb +3 -0
  37. data/examples/echo.ru +27 -0
  38. data/examples/init.sh +102 -0
  39. data/examples/logger_mp_safe.rb +26 -0
  40. data/examples/logrotate.conf +44 -0
  41. data/examples/nginx.conf +156 -0
  42. data/examples/unicorn.conf.minimal.rb +14 -0
  43. data/examples/unicorn.conf.rb +111 -0
  44. data/examples/unicorn.socket +11 -0
  45. data/examples/unicorn@.service +40 -0
  46. data/ext/unicorn_http/CFLAGS +13 -0
  47. data/ext/unicorn_http/c_util.h +115 -0
  48. data/ext/unicorn_http/common_field_optimization.h +128 -0
  49. data/ext/unicorn_http/epollexclusive.h +128 -0
  50. data/ext/unicorn_http/ext_help.h +38 -0
  51. data/ext/unicorn_http/extconf.rb +40 -0
  52. data/ext/unicorn_http/global_variables.h +97 -0
  53. data/ext/unicorn_http/httpdate.c +91 -0
  54. data/ext/unicorn_http/unicorn_http.c +4348 -0
  55. data/ext/unicorn_http/unicorn_http.rl +1054 -0
  56. data/ext/unicorn_http/unicorn_http_common.rl +76 -0
  57. data/lib/unicorn/app/old_rails/static.rb +60 -0
  58. data/lib/unicorn/app/old_rails.rb +36 -0
  59. data/lib/unicorn/cgi_wrapper.rb +148 -0
  60. data/lib/unicorn/configurator.rb +749 -0
  61. data/lib/unicorn/const.rb +22 -0
  62. data/lib/unicorn/http_request.rb +180 -0
  63. data/lib/unicorn/http_response.rb +95 -0
  64. data/lib/unicorn/http_server.rb +860 -0
  65. data/lib/unicorn/launcher.rb +63 -0
  66. data/lib/unicorn/oob_gc.rb +82 -0
  67. data/lib/unicorn/preread_input.rb +34 -0
  68. data/lib/unicorn/select_waiter.rb +7 -0
  69. data/lib/unicorn/socket_helper.rb +186 -0
  70. data/lib/unicorn/stream_input.rb +152 -0
  71. data/lib/unicorn/tee_input.rb +132 -0
  72. data/lib/unicorn/tmpio.rb +34 -0
  73. data/lib/unicorn/util.rb +91 -0
  74. data/lib/unicorn/version.rb +1 -0
  75. data/lib/unicorn/worker.rb +166 -0
  76. data/lib/unicorn.rb +137 -0
  77. data/man/man1/unicorn.1 +222 -0
  78. data/man/man1/unicorn_rails.1 +207 -0
  79. data/setup.rb +1587 -0
  80. data/t/.gitignore +4 -0
  81. data/t/GNUmakefile +5 -0
  82. data/t/README +49 -0
  83. data/t/active-unix-socket.t +110 -0
  84. data/t/back-out-of-upgrade.t +44 -0
  85. data/t/bin/unused_listen +40 -0
  86. data/t/client_body_buffer_size.ru +15 -0
  87. data/t/client_body_buffer_size.t +79 -0
  88. data/t/detach.ru +12 -0
  89. data/t/env.ru +4 -0
  90. data/t/fails-rack-lint.ru +6 -0
  91. data/t/heartbeat-timeout.ru +13 -0
  92. data/t/heartbeat-timeout.t +60 -0
  93. data/t/integration.ru +129 -0
  94. data/t/integration.t +509 -0
  95. data/t/lib.perl +309 -0
  96. data/t/listener_names.ru +5 -0
  97. data/t/my-tap-lib.sh +201 -0
  98. data/t/oob_gc.ru +18 -0
  99. data/t/oob_gc_path.ru +18 -0
  100. data/t/pid.ru +4 -0
  101. data/t/preread_input.ru +23 -0
  102. data/t/reload-bad-config.t +49 -0
  103. data/t/reopen-logs.ru +14 -0
  104. data/t/reopen-logs.t +36 -0
  105. data/t/t0010-reap-logging.sh +55 -0
  106. data/t/t0012-reload-empty-config.sh +86 -0
  107. data/t/t0013-rewindable-input-false.sh +24 -0
  108. data/t/t0013.ru +13 -0
  109. data/t/t0014-rewindable-input-true.sh +24 -0
  110. data/t/t0014.ru +13 -0
  111. data/t/t0015-configurator-internals.sh +25 -0
  112. data/t/t0020-at_exit-handler.sh +49 -0
  113. data/t/t0021-process_detach.sh +29 -0
  114. data/t/t0022-listener_names-preload_app.sh +32 -0
  115. data/t/t0300-no-default-middleware.sh +20 -0
  116. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  117. data/t/t0301.ru +14 -0
  118. data/t/t9001-oob_gc.sh +47 -0
  119. data/t/t9002-oob_gc-path.sh +75 -0
  120. data/t/test-lib.sh +125 -0
  121. data/t/winch_ttin.t +64 -0
  122. data/t/working_directory.t +86 -0
  123. data/test/aggregate.rb +16 -0
  124. data/test/benchmark/README +60 -0
  125. data/test/benchmark/dd.ru +19 -0
  126. data/test/benchmark/ddstream.ru +51 -0
  127. data/test/benchmark/readinput.ru +41 -0
  128. data/test/benchmark/stack.ru +9 -0
  129. data/test/benchmark/uconnect.perl +66 -0
  130. data/test/exec/README +5 -0
  131. data/test/exec/test_exec.rb +1030 -0
  132. data/test/test_helper.rb +307 -0
  133. data/test/unit/test_configurator.rb +176 -0
  134. data/test/unit/test_droplet.rb +29 -0
  135. data/test/unit/test_http_parser.rb +885 -0
  136. data/test/unit/test_http_parser_ng.rb +715 -0
  137. data/test/unit/test_server.rb +245 -0
  138. data/test/unit/test_signals.rb +189 -0
  139. data/test/unit/test_socket_helper.rb +160 -0
  140. data/test/unit/test_stream_input.rb +211 -0
  141. data/test/unit/test_tee_input.rb +304 -0
  142. data/test/unit/test_util.rb +132 -0
  143. data/test/unit/test_waiter.rb +35 -0
  144. data/unicorn.gemspec +49 -0
  145. metadata +266 -0
@@ -0,0 +1,245 @@
1
+ # -*- encoding: binary -*-
2
+ # frozen_string_literal: false
3
+
4
+ # Copyright (c) 2005 Zed A. Shaw
5
+ # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
6
+ # the GPLv2+ (GPLv3+ preferred)
7
+ #
8
+ # Additional work donated by contributors. See git history
9
+ # for more information.
10
+
11
+ require './test/test_helper'
12
+
13
+ include Unicorn
14
+
15
+ class TestHandler
16
+
17
+ def call(env)
18
+ while env['rack.input'].read(4096)
19
+ end
20
+ [200, { 'content-type' => 'text/plain' }, ['hello!\n']]
21
+ rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
22
+ $stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
23
+ raise e
24
+ end
25
+ end
26
+
27
+ class TestRackAfterReply
28
+ def initialize
29
+ @called = false
30
+ end
31
+
32
+ def call(env)
33
+ while env['rack.input'].read(4096)
34
+ end
35
+
36
+ env["rack.after_reply"] << -> { @called = true }
37
+
38
+ [200, { 'content-type' => 'text/plain' }, ["after_reply_called: #{@called}"]]
39
+ rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
40
+ $stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
41
+ raise e
42
+ end
43
+ end
44
+
45
+ class WebServerTest < Test::Unit::TestCase
46
+
47
+ def setup
48
+ @valid_request = "GET / HTTP/1.1\r\nHost: www.zedshaw.com\r\nContent-Type: text/plain\r\n\r\n"
49
+ @port = unused_port
50
+ @tester = TestHandler.new
51
+ redirect_test_io do
52
+ @server = HttpServer.new(@tester, :listeners => [ "127.0.0.1:#{@port}" ] )
53
+ @server.start
54
+ end
55
+ end
56
+
57
+ def teardown
58
+ redirect_test_io do
59
+ wait_workers_ready("test_stderr.#$$.log", 1)
60
+ File.truncate("test_stderr.#$$.log", 0)
61
+ @server.stop(false)
62
+ end
63
+ reset_sig_handlers
64
+ end
65
+
66
+ def test_preload_app_config
67
+ teardown
68
+ tmp = Tempfile.new('test_preload_app_config')
69
+ ObjectSpace.undefine_finalizer(tmp)
70
+ app = lambda { ||
71
+ tmp.sysseek(0)
72
+ tmp.truncate(0)
73
+ tmp.syswrite($$)
74
+ lambda { |env| [ 200, { 'content-type' => 'text/plain' }, [ "#$$\n" ] ] }
75
+ }
76
+ redirect_test_io do
77
+ @server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"] )
78
+ @server.start
79
+ end
80
+ results = hit(["http://localhost:#@port/"])
81
+ worker_pid = results[0].to_i
82
+ assert worker_pid != 0
83
+ tmp.sysseek(0)
84
+ loader_pid = tmp.sysread(4096).to_i
85
+ assert loader_pid != 0
86
+ assert_equal worker_pid, loader_pid
87
+ teardown
88
+
89
+ redirect_test_io do
90
+ @server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"],
91
+ :preload_app => true)
92
+ @server.start
93
+ end
94
+ results = hit(["http://localhost:#@port/"])
95
+ worker_pid = results[0].to_i
96
+ assert worker_pid != 0
97
+ tmp.sysseek(0)
98
+ loader_pid = tmp.sysread(4096).to_i
99
+ assert_equal $$, loader_pid
100
+ assert worker_pid != loader_pid
101
+ ensure
102
+ tmp.close!
103
+ end
104
+
105
+ def test_after_reply
106
+ teardown
107
+
108
+ redirect_test_io do
109
+ @server = HttpServer.new(TestRackAfterReply.new,
110
+ :listeners => [ "127.0.0.1:#@port"])
111
+ @server.start
112
+ end
113
+
114
+ sock = tcp_socket('127.0.0.1', @port)
115
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
116
+
117
+ responses = sock.read(4096)
118
+ assert_match %r{\AHTTP/1.[01] 200\b}, responses
119
+ assert_match %r{^after_reply_called: false}, responses
120
+
121
+ sock = tcp_socket('127.0.0.1', @port)
122
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
123
+
124
+ responses = sock.read(4096)
125
+ assert_match %r{\AHTTP/1.[01] 200\b}, responses
126
+ assert_match %r{^after_reply_called: true}, responses
127
+
128
+ sock.close
129
+ end
130
+
131
+ def test_simple_server
132
+ results = hit(["http://localhost:#{@port}/test"])
133
+ assert_equal 'hello!\n', results[0], "Handler didn't really run"
134
+ end
135
+
136
+ def test_client_malformed_body
137
+ bs = 15653984
138
+ sock = tcp_socket('127.0.0.1', @port)
139
+ sock.syswrite("PUT /hello HTTP/1.1\r\n")
140
+ sock.syswrite("Host: example.com\r\n")
141
+ sock.syswrite("Transfer-Encoding: chunked\r\n")
142
+ sock.syswrite("Trailer: X-Foo\r\n")
143
+ sock.syswrite("\r\n")
144
+ sock.syswrite("%x\r\n" % [ bs ])
145
+ sock.syswrite("F" * bs)
146
+ begin
147
+ File.open("/dev/urandom", "rb") { |fp| sock.syswrite(fp.sysread(16384)) }
148
+ rescue
149
+ end
150
+ assert_nil sock.close
151
+ next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
152
+ assert_equal 'hello!\n', next_client
153
+ lines = File.readlines("test_stderr.#$$.log")
154
+ lines = lines.grep(/^Unicorn::HttpParserError: .* true$/)
155
+ assert_equal 1, lines.size
156
+ end
157
+
158
+ def do_test(string, chunk, close_after=nil, shutdown_delay=0)
159
+ # Do not use instance variables here, because it needs to be thread safe
160
+ socket = tcp_socket("127.0.0.1", @port);
161
+ request = StringIO.new(string)
162
+ chunks_out = 0
163
+
164
+ while data = request.read(chunk)
165
+ chunks_out += socket.write(data)
166
+ socket.flush
167
+ sleep 0.2
168
+ if close_after and chunks_out > close_after
169
+ socket.close
170
+ sleep 1
171
+ end
172
+ end
173
+ sleep(shutdown_delay)
174
+ socket.write(" ") # Some platforms only raise the exception on attempted write
175
+ socket.flush
176
+ end
177
+
178
+ def test_trickle_attack
179
+ do_test(@valid_request, 3)
180
+ end
181
+
182
+ def test_close_client
183
+ assert_raises IOError do
184
+ do_test(@valid_request, 10, 20)
185
+ end
186
+ end
187
+
188
+ def test_bad_client
189
+ redirect_test_io do
190
+ do_test("GET /test HTTP/BAD", 3)
191
+ end
192
+ end
193
+
194
+ def test_logger_set
195
+ assert_equal @server.logger, Unicorn::HttpRequest::DEFAULTS["rack.logger"]
196
+ end
197
+
198
+ def test_logger_changed
199
+ tmp = Logger.new($stdout)
200
+ @server.logger = tmp
201
+ assert_equal tmp, Unicorn::HttpRequest::DEFAULTS["rack.logger"]
202
+ end
203
+
204
+ def test_bad_client_400
205
+ sock = tcp_socket('127.0.0.1', @port)
206
+ sock.syswrite("GET / HTTP/1.0\r\nHost: foo\rbar\r\n\r\n")
207
+ assert_match %r{\AHTTP/1.[01] 400\b}, sock.sysread(4096)
208
+ assert_nil sock.close
209
+ end
210
+
211
+ def test_http_0_9
212
+ sock = tcp_socket('127.0.0.1', @port)
213
+ sock.syswrite("GET /hello\r\n")
214
+ assert_match 'hello!\n', sock.sysread(4096)
215
+ assert_nil sock.close
216
+ end
217
+
218
+ def test_header_is_too_long
219
+ redirect_test_io do
220
+ long = "GET /test HTTP/1.1\r\n" + ("X-Big: stuff\r\n" * 15000) + "\r\n"
221
+ assert_raises Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EINVAL, IOError do
222
+ do_test(long, long.length/2, 10)
223
+ end
224
+ end
225
+ end
226
+
227
+ def test_file_streamed_request
228
+ body = "a" * (Unicorn::Const::MAX_BODY * 2)
229
+ long = "PUT /test HTTP/1.1\r\nContent-length: #{body.length}\r\n\r\n" + body
230
+ do_test(long, Unicorn::Const::CHUNK_SIZE * 2 - 400)
231
+ end
232
+
233
+ def test_file_streamed_request_bad_body
234
+ body = "a" * (Unicorn::Const::MAX_BODY * 2)
235
+ long = "GET /test HTTP/1.1\r\nContent-ength: #{body.length}\r\n\r\n" + body
236
+ assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
237
+ Errno::EBADF) {
238
+ do_test(long, Unicorn::Const::CHUNK_SIZE * 2 - 400)
239
+ }
240
+ end
241
+
242
+ def test_listener_names
243
+ assert_equal [ "127.0.0.1:#@port" ], Unicorn.listener_names
244
+ end
245
+ end
@@ -0,0 +1,189 @@
1
+ # -*- encoding: binary -*-
2
+ # frozen_string_literal: false
3
+
4
+ # Copyright (c) 2009 Eric Wong
5
+ # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
6
+ # the GPLv2+ (GPLv3+ preferred)
7
+ #
8
+ # Ensure we stay sane in the face of signals being sent to us
9
+
10
+ require './test/test_helper'
11
+
12
+ include Unicorn
13
+
14
+ class Dd
15
+ def initialize(bs, count)
16
+ @count = count
17
+ @buf = ' ' * bs
18
+ end
19
+
20
+ def each(&block)
21
+ @count.times { yield @buf }
22
+ end
23
+ end
24
+
25
+ class SignalsTest < Test::Unit::TestCase
26
+
27
+ def setup
28
+ @bs = 1 * 1024 * 1024
29
+ @count = 100
30
+ @port = unused_port
31
+ @sock = Tempfile.new('unicorn.sock')
32
+ @tmp = Tempfile.new('unicorn.write')
33
+ @tmp.sync = true
34
+ File.unlink(@sock.path)
35
+ File.unlink(@tmp.path)
36
+ @server_opts = {
37
+ :listeners => [ "127.0.0.1:#@port", @sock.path ],
38
+ :after_fork => lambda { |server,worker|
39
+ trap(:HUP) { @tmp.syswrite('.') }
40
+ },
41
+ }
42
+ @server = nil
43
+ end
44
+
45
+ def teardown
46
+ reset_sig_handlers
47
+ end
48
+
49
+ def test_worker_dies_on_dead_master
50
+ pid = fork {
51
+ app = lambda { |env| [ 200, {'x-pid' => "#$$" }, [] ] }
52
+ opts = @server_opts.merge(:timeout => 3)
53
+ redirect_test_io { HttpServer.new(app, opts).start.join }
54
+ }
55
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
56
+ sock = tcp_socket('127.0.0.1', @port)
57
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
58
+ buf = sock.readpartial(4096)
59
+ assert_nil sock.close
60
+ buf =~ /\bx-pid: (\d+)\b/ or raise Exception
61
+ child = $1.to_i
62
+ wait_master_ready("test_stderr.#{pid}.log")
63
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
64
+ Process.kill(:KILL, pid)
65
+ Process.waitpid(pid)
66
+ File.unlink("test_stderr.#{pid}.log", "test_stdout.#{pid}.log")
67
+ t0 = Time.now
68
+ assert child
69
+ assert t0
70
+ assert_raises(Errno::ESRCH) { loop { Process.kill(0, child); sleep 0.2 } }
71
+ assert((Time.now - t0) < 60)
72
+ end
73
+
74
+ def test_sleepy_kill
75
+ rd, wr = IO.pipe
76
+ pid = fork {
77
+ rd.close
78
+ app = lambda { |env| wr.syswrite('.'); sleep; [ 200, {}, [] ] }
79
+ redirect_test_io { HttpServer.new(app, @server_opts).start.join }
80
+ }
81
+ wr.close
82
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
83
+ sock = tcp_socket('127.0.0.1', @port)
84
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
85
+ buf = rd.readpartial(1)
86
+ wait_master_ready("test_stderr.#{pid}.log")
87
+ Process.kill(:INT, pid)
88
+ Process.waitpid(pid)
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
+ end
97
+
98
+ def test_timeout_slow_response
99
+ pid = fork {
100
+ app = lambda { |env| sleep }
101
+ opts = @server_opts.merge(:timeout => 3)
102
+ redirect_test_io { HttpServer.new(app, opts).start.join }
103
+ }
104
+ t0 = Time.now
105
+ wait_workers_ready("test_stderr.#{pid}.log", 1)
106
+ sock = tcp_socket('127.0.0.1', @port)
107
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
108
+
109
+ buf = nil
110
+ assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
111
+ Errno::EBADF) do
112
+ buf = sock.sysread(4096)
113
+ end
114
+ diff = Time.now - t0
115
+ assert_nil buf
116
+ assert diff > 1.0, "diff was #{diff.inspect}"
117
+ assert diff < 60.0
118
+ ensure
119
+ Process.kill(:TERM, pid) rescue nil
120
+ end
121
+
122
+ def test_response_write
123
+ app = lambda { |env|
124
+ [ 200, { 'content-type' => 'text/plain', 'x-pid' => Process.pid.to_s },
125
+ Dd.new(@bs, @count) ]
126
+ }
127
+ redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
128
+ wait_workers_ready("test_stderr.#{$$}.log", 1)
129
+ sock = tcp_socket('127.0.0.1', @port)
130
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
131
+ buf = ''
132
+ header_len = pid = nil
133
+ buf = sock.sysread(16384, buf)
134
+ pid = buf[/\r\nx-pid: (\d+)\r\n/, 1].to_i
135
+ header_len = buf[/\A(.+?\r\n\r\n)/m, 1].size
136
+ assert pid > 0, "pid not positive: #{pid.inspect}"
137
+ read = buf.size
138
+ size_before = @tmp.stat.size
139
+ assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
140
+ Errno::EBADF) do
141
+ loop do
142
+ 3.times { Process.kill(:HUP, pid) }
143
+ sock.sysread(16384, buf)
144
+ read += buf.size
145
+ 3.times { Process.kill(:HUP, pid) }
146
+ end
147
+ end
148
+
149
+ redirect_test_io { @server.stop(true) }
150
+ # can't check for == since pending signals get merged
151
+ assert size_before < @tmp.stat.size
152
+ got = read - header_len
153
+ expect = @bs * @count
154
+ assert_equal(expect, got, "expect=#{expect} got=#{got}")
155
+ assert_nil sock.close
156
+ end
157
+
158
+ def test_request_read
159
+ app = lambda { |env|
160
+ while env['rack.input'].read(4096)
161
+ end
162
+ [ 200, {'content-type'=>'text/plain', 'x-pid'=>Process.pid.to_s}, [] ]
163
+ }
164
+ redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
165
+
166
+ wait_workers_ready("test_stderr.#{$$}.log", 1)
167
+ sock = tcp_socket('127.0.0.1', @port)
168
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
169
+ pid = sock.sysread(4096)[/\r\nx-pid: (\d+)\r\n/, 1].to_i
170
+ assert_nil sock.close
171
+
172
+ assert pid > 0, "pid not positive: #{pid.inspect}"
173
+ sock = tcp_socket('127.0.0.1', @port)
174
+ sock.syswrite("PUT / HTTP/1.0\r\n")
175
+ sock.syswrite("Content-Length: #{@bs * @count}\r\n\r\n")
176
+ 1000.times { Process.kill(:HUP, pid) }
177
+ size_before = @tmp.stat.size
178
+ killer = fork { loop { Process.kill(:HUP, pid); sleep(0.01) } }
179
+ buf = ' ' * @bs
180
+ @count.times { sock.syswrite(buf) }
181
+ Process.kill(:KILL, killer)
182
+ Process.waitpid2(killer)
183
+ redirect_test_io { @server.stop(true) }
184
+ # can't check for == since pending signals get merged
185
+ assert size_before < @tmp.stat.size
186
+ assert_equal pid, sock.sysread(4096)[/\r\nx-pid: (\d+)\r\n/, 1].to_i
187
+ assert_nil sock.close
188
+ end
189
+ end
@@ -0,0 +1,160 @@
1
+ # -*- encoding: binary -*-
2
+ # frozen_string_literal: false
3
+
4
+ require './test/test_helper'
5
+ require 'tempfile'
6
+
7
+ class TestSocketHelper < Test::Unit::TestCase
8
+ include Unicorn::SocketHelper
9
+ attr_reader :logger
10
+ GET_SLASH = "GET / HTTP/1.0\r\n\r\n".freeze
11
+
12
+ def setup
13
+ @log_tmp = Tempfile.new 'logger'
14
+ @logger = Logger.new(@log_tmp.path)
15
+ @test_addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
16
+ @test6_addr = ENV['UNICORN_TEST6_ADDR'] || '::1'
17
+ GC.disable
18
+ end
19
+
20
+ def teardown
21
+ GC.enable
22
+ end
23
+
24
+ def test_bind_listen_tcp
25
+ port = unused_port @test_addr
26
+ @tcp_listener_name = "#@test_addr:#{port}"
27
+ @tcp_listener = bind_listen(@tcp_listener_name)
28
+ assert Socket === @tcp_listener
29
+ assert @tcp_listener.local_address.ip?
30
+ assert_equal @tcp_listener_name, sock_name(@tcp_listener)
31
+ end
32
+
33
+ def test_bind_listen_options
34
+ port = unused_port @test_addr
35
+ tcp_listener_name = "#@test_addr:#{port}"
36
+ tmp = Tempfile.new 'unix.sock'
37
+ unix_listener_name = tmp.path
38
+ File.unlink(tmp.path)
39
+ [ { :backlog => 5 }, { :sndbuf => 4096 }, { :rcvbuf => 4096 },
40
+ { :backlog => 16, :rcvbuf => 4096, :sndbuf => 4096 }
41
+ ].each do |opts|
42
+ tcp_listener = bind_listen(tcp_listener_name, opts)
43
+ assert tcp_listener.local_address.ip?
44
+ tcp_listener.close
45
+ unix_listener = bind_listen(unix_listener_name, opts)
46
+ assert unix_listener.local_address.unix?
47
+ unix_listener.close
48
+ end
49
+ end
50
+
51
+ def test_bind_listen_unix
52
+ old_umask = File.umask(0777)
53
+ tmp = Tempfile.new 'unix.sock'
54
+ @unix_listener_path = tmp.path
55
+ File.unlink(@unix_listener_path)
56
+ @unix_listener = bind_listen(@unix_listener_path)
57
+ assert Socket === @unix_listener
58
+ assert @unix_listener.local_address.unix?
59
+ assert_equal @unix_listener_path, sock_name(@unix_listener)
60
+ assert File.readable?(@unix_listener_path), "not readable"
61
+ assert File.writable?(@unix_listener_path), "not writable"
62
+ assert_equal 0777, File.umask
63
+ assert_equal @unix_listener, bind_listen(@unix_listener)
64
+ ensure
65
+ File.umask(old_umask)
66
+ end
67
+
68
+ def test_bind_listen_unix_umask
69
+ old_umask = File.umask(0777)
70
+ tmp = Tempfile.new 'unix.sock'
71
+ @unix_listener_path = tmp.path
72
+ File.unlink(@unix_listener_path)
73
+ @unix_listener = bind_listen(@unix_listener_path, :umask => 077)
74
+ assert_equal @unix_listener_path, sock_name(@unix_listener)
75
+ assert_equal 0140700, File.stat(@unix_listener_path).mode
76
+ assert_equal 0777, File.umask
77
+ ensure
78
+ File.umask(old_umask)
79
+ end
80
+
81
+ def test_bind_listen_unix_rebind
82
+ test_bind_listen_unix
83
+ new_listener = nil
84
+ assert_raises(Errno::EADDRINUSE) do
85
+ new_listener = bind_listen(@unix_listener_path)
86
+ end
87
+
88
+ File.unlink(@unix_listener_path)
89
+ new_listener = bind_listen(@unix_listener_path)
90
+
91
+ assert new_listener.fileno != @unix_listener.fileno
92
+ assert_equal sock_name(new_listener), sock_name(@unix_listener)
93
+ assert_equal @unix_listener_path, sock_name(new_listener)
94
+ pid = fork do
95
+ begin
96
+ client, _ = new_listener.accept
97
+ client.syswrite('abcde')
98
+ exit 0
99
+ rescue => e
100
+ warn "#{e.message} (#{e.class})"
101
+ exit 1
102
+ end
103
+ end
104
+ s = unix_socket(@unix_listener_path)
105
+ IO.select([s])
106
+ assert_equal 'abcde', s.sysread(5)
107
+ pid, status = Process.waitpid2(pid)
108
+ assert status.success?
109
+ end
110
+
111
+ def test_tcp_defer_accept_default
112
+ return unless defined?(TCP_DEFER_ACCEPT)
113
+ port = unused_port @test_addr
114
+ name = "#@test_addr:#{port}"
115
+ sock = bind_listen(name)
116
+ cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
117
+ assert cur >= 1
118
+ end
119
+
120
+ def test_tcp_defer_accept_disable
121
+ return unless defined?(TCP_DEFER_ACCEPT)
122
+ port = unused_port @test_addr
123
+ name = "#@test_addr:#{port}"
124
+ sock = bind_listen(name, :tcp_defer_accept => false)
125
+ cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
126
+ assert_equal 0, cur
127
+ end
128
+
129
+ def test_tcp_defer_accept_nr
130
+ return unless defined?(TCP_DEFER_ACCEPT)
131
+ port = unused_port @test_addr
132
+ name = "#@test_addr:#{port}"
133
+ sock = bind_listen(name, :tcp_defer_accept => 60)
134
+ cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
135
+ assert cur > 1
136
+ end
137
+
138
+ def test_ipv6only
139
+ port = begin
140
+ unused_port "#@test6_addr"
141
+ rescue Errno::EINVAL
142
+ return
143
+ end
144
+ sock = bind_listen "[#@test6_addr]:#{port}", :ipv6only => true
145
+ cur = sock.getsockopt(:IPPROTO_IPV6, :IPV6_V6ONLY).unpack('i')[0]
146
+ assert_equal 1, cur
147
+ rescue Errno::EAFNOSUPPORT
148
+ end
149
+
150
+ def test_reuseport
151
+ return unless defined?(Socket::SO_REUSEPORT)
152
+ port = unused_port @test_addr
153
+ name = "#@test_addr:#{port}"
154
+ sock = bind_listen(name, :reuseport => true)
155
+ cur = sock.getsockopt(:SOL_SOCKET, :SO_REUSEPORT).int
156
+ assert_operator cur, :>, 0
157
+ rescue Errno::ENOPROTOOPT
158
+ # kernel does not support SO_REUSEPORT (older Linux)
159
+ end
160
+ end