unicorn 4.9.0 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitattributes +5 -0
- data/.olddoc.yml +13 -6
- data/Application_Timeouts +7 -7
- data/DESIGN +2 -4
- data/Documentation/.gitignore +1 -3
- data/Documentation/unicorn.1 +222 -0
- data/Documentation/unicorn_rails.1 +207 -0
- data/FAQ +17 -8
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +121 -56
- data/HACKING +1 -2
- data/ISSUES +40 -41
- data/KNOWN_ISSUES +11 -11
- data/LICENSE +2 -2
- data/Links +24 -25
- data/PHILOSOPHY +0 -6
- data/README +46 -39
- data/SIGNALS +2 -2
- data/Sandbox +10 -9
- data/TODO +0 -2
- data/TUNING +30 -9
- data/archive/slrnpull.conf +1 -1
- data/bin/unicorn +4 -2
- data/bin/unicorn_rails +3 -3
- data/examples/big_app_gc.rb +1 -1
- data/examples/init.sh +36 -8
- data/examples/logrotate.conf +17 -2
- data/examples/nginx.conf +14 -14
- data/examples/unicorn.conf.minimal.rb +2 -2
- data/examples/unicorn.conf.rb +3 -6
- data/examples/unicorn.socket +11 -0
- data/examples/unicorn@.service +40 -0
- data/ext/unicorn_http/common_field_optimization.h +23 -5
- data/ext/unicorn_http/ext_help.h +0 -20
- data/ext/unicorn_http/extconf.rb +37 -1
- data/ext/unicorn_http/global_variables.h +1 -1
- data/ext/unicorn_http/httpdate.c +2 -2
- data/ext/unicorn_http/unicorn_http.rl +167 -170
- data/ext/unicorn_http/unicorn_http_common.rl +1 -1
- data/lib/unicorn.rb +66 -46
- data/lib/unicorn/configurator.rb +110 -44
- data/lib/unicorn/const.rb +2 -25
- data/lib/unicorn/http_request.rb +110 -31
- data/lib/unicorn/http_response.rb +17 -31
- data/lib/unicorn/http_server.rb +238 -157
- data/lib/unicorn/launcher.rb +1 -1
- data/lib/unicorn/oob_gc.rb +6 -6
- data/lib/unicorn/socket_helper.rb +58 -78
- data/lib/unicorn/stream_input.rb +8 -7
- data/lib/unicorn/tee_input.rb +8 -10
- data/lib/unicorn/tmpio.rb +8 -7
- data/lib/unicorn/util.rb +5 -4
- data/lib/unicorn/worker.rb +36 -23
- data/t/GNUmakefile +3 -72
- data/t/README +4 -4
- data/t/t0011-active-unix-socket.sh +1 -1
- data/t/t0012-reload-empty-config.sh +2 -1
- data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
- data/t/t0301.ru +13 -0
- data/t/test-lib.sh +2 -2
- data/test/benchmark/README +14 -4
- data/test/benchmark/ddstream.ru +50 -0
- data/test/benchmark/readinput.ru +40 -0
- data/test/benchmark/uconnect.perl +66 -0
- data/test/exec/test_exec.rb +73 -19
- data/test/test_helper.rb +40 -31
- data/test/unit/test_ccc.rb +91 -0
- data/test/unit/test_droplet.rb +1 -1
- data/test/unit/test_http_parser.rb +46 -16
- data/test/unit/test_http_parser_ng.rb +97 -114
- data/test/unit/test_request.rb +10 -10
- data/test/unit/test_response.rb +28 -16
- data/test/unit/test_server.rb +86 -12
- data/test/unit/test_signals.rb +8 -8
- data/test/unit/test_socket_helper.rb +14 -10
- data/test/unit/test_upload.rb +9 -14
- data/test/unit/test_util.rb +27 -2
- data/unicorn.gemspec +27 -19
- metadata +24 -45
- data/Documentation/GNUmakefile +0 -30
- data/Documentation/unicorn.1.txt +0 -185
- data/Documentation/unicorn_rails.1.txt +0 -175
- data/examples/git.ru +0 -13
- data/lib/unicorn/app/exec_cgi.rb +0 -154
- data/lib/unicorn/app/inetd.rb +0 -109
- data/lib/unicorn/ssl_client.rb +0 -11
- data/lib/unicorn/ssl_configurator.rb +0 -104
- data/lib/unicorn/ssl_server.rb +0 -42
- data/t/hijack.ru +0 -42
- data/t/t0016-trust-x-forwarded-false.sh +0 -30
- data/t/t0017-trust-x-forwarded-true.sh +0 -30
- data/t/t0200-rack-hijack.sh +0 -27
- data/test/unit/test_http_parser_xftrust.rb +0 -38
- data/test/unit/test_sni_hostnames.rb +0 -47
data/lib/unicorn/const.rb
CHANGED
@@ -1,12 +1,6 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
|
3
|
-
# :
|
4
|
-
# Frequently used constants when constructing requests or responses.
|
5
|
-
# Many times the constant just refers to a string with the same
|
6
|
-
# contents. Using these constants gave about a 3% to 10% performance
|
7
|
-
# improvement over using the strings directly. Symbols did not really
|
8
|
-
# improve things much compared to constants.
|
9
|
-
module Unicorn::Const
|
3
|
+
module Unicorn::Const # :nodoc:
|
10
4
|
# default TCP listen host address (0.0.0.0, all interfaces)
|
11
5
|
DEFAULT_HOST = "0.0.0.0"
|
12
6
|
|
@@ -23,22 +17,5 @@ module Unicorn::Const
|
|
23
17
|
# temporary file for reading (112 kilobytes). This is the default
|
24
18
|
# value of client_body_buffer_size.
|
25
19
|
MAX_BODY = 1024 * 112
|
26
|
-
|
27
|
-
# :stopdoc:
|
28
|
-
# common errors we'll send back
|
29
|
-
# (N.B. these are not used by unicorn, but we won't drop them until
|
30
|
-
# unicorn 5.x to avoid breaking Rainbows!).
|
31
|
-
ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n"
|
32
|
-
ERROR_414_RESPONSE = "HTTP/1.1 414 Request-URI Too Long\r\n\r\n"
|
33
|
-
ERROR_413_RESPONSE = "HTTP/1.1 413 Request Entity Too Large\r\n\r\n"
|
34
|
-
ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n"
|
35
|
-
|
36
|
-
EXPECT_100_RESPONSE = "HTTP/1.1 100 Continue\r\n\r\n"
|
37
|
-
EXPECT_100_RESPONSE_SUFFIXED = "100 Continue\r\n\r\nHTTP/1.1 "
|
38
|
-
|
39
|
-
HTTP_RESPONSE_START = ['HTTP', '/1.1 ']
|
40
|
-
HTTP_EXPECT = "HTTP_EXPECT"
|
41
|
-
|
42
|
-
# :startdoc:
|
43
20
|
end
|
44
|
-
|
21
|
+
require_relative 'version'
|
data/lib/unicorn/http_request.rb
CHANGED
@@ -13,7 +13,8 @@ class Unicorn::HttpParser
|
|
13
13
|
"rack.multiprocess" => true,
|
14
14
|
"rack.multithread" => false,
|
15
15
|
"rack.run_once" => false,
|
16
|
-
"rack.version" => [1,
|
16
|
+
"rack.version" => [1, 2],
|
17
|
+
"rack.hijack?" => true,
|
17
18
|
"SCRIPT_NAME" => "",
|
18
19
|
|
19
20
|
# this is not in the Rack spec, but some apps may rely on it
|
@@ -22,14 +23,12 @@ class Unicorn::HttpParser
|
|
22
23
|
|
23
24
|
NULL_IO = StringIO.new("")
|
24
25
|
|
25
|
-
attr_accessor :response_start_sent
|
26
|
-
|
27
26
|
# :stopdoc:
|
28
|
-
|
29
|
-
|
30
|
-
RACK_INPUT = 'rack.input'.freeze
|
27
|
+
HTTP_RESPONSE_START = [ 'HTTP'.freeze, '/1.1 '.freeze ]
|
28
|
+
EMPTY_ARRAY = [].freeze
|
31
29
|
@@input_class = Unicorn::TeeInput
|
32
30
|
@@check_client_connection = false
|
31
|
+
@@tcpi_inspect_ok = Socket.const_defined?(:TCP_INFO)
|
33
32
|
|
34
33
|
def self.input_class
|
35
34
|
@@input_class
|
@@ -63,17 +62,16 @@ def self.check_client_connection=(bool)
|
|
63
62
|
# This does minimal exception trapping and it is up to the caller
|
64
63
|
# to handle any socket errors (e.g. user aborted upload).
|
65
64
|
def read(socket)
|
66
|
-
clear
|
67
65
|
e = env
|
68
66
|
|
69
|
-
# From
|
67
|
+
# From https://www.ietf.org/rfc/rfc3875:
|
70
68
|
# "Script authors should be aware that the REMOTE_ADDR and
|
71
69
|
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
72
70
|
# may not identify the ultimate source of the request. They
|
73
71
|
# identify the client for the immediate request to the server;
|
74
72
|
# that client may be a proxy, gateway, or other intermediary
|
75
73
|
# acting on behalf of the actual source client."
|
76
|
-
e[REMOTE_ADDR] = socket.kgio_addr
|
74
|
+
e['REMOTE_ADDR'] = socket.kgio_addr
|
77
75
|
|
78
76
|
# short circuit the common case with small GET requests first
|
79
77
|
socket.kgio_read!(16384, buf)
|
@@ -83,40 +81,121 @@ def read(socket)
|
|
83
81
|
false until add_parse(socket.kgio_read!(16384))
|
84
82
|
end
|
85
83
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
84
|
+
check_client_connection(socket) if @@check_client_connection
|
85
|
+
|
86
|
+
e['rack.input'] = 0 == content_length ?
|
87
|
+
NULL_IO : @@input_class.new(socket, self)
|
88
|
+
|
89
|
+
# for Rack hijacking in Rack 1.5 and later
|
90
|
+
e['unicorn.socket'] = socket
|
91
|
+
e['rack.hijack'] = self
|
91
92
|
|
92
|
-
e[RACK_INPUT] = 0 == content_length ?
|
93
|
-
NULL_IO : @@input_class.new(socket, self)
|
94
|
-
hijack_setup(e, socket)
|
95
93
|
e.merge!(DEFAULTS)
|
96
94
|
end
|
97
95
|
|
98
|
-
#
|
99
|
-
|
100
|
-
|
101
|
-
|
96
|
+
# for rack.hijack, we respond to this method so no extra allocation
|
97
|
+
# of a proc object
|
98
|
+
def call
|
99
|
+
hijacked!
|
100
|
+
env['rack.hijack_io'] = env['unicorn.socket']
|
101
|
+
end
|
102
102
|
|
103
|
-
|
104
|
-
|
103
|
+
def hijacked?
|
104
|
+
env.include?('rack.hijack_io'.freeze)
|
105
|
+
end
|
105
106
|
|
106
|
-
|
107
|
-
|
107
|
+
if Raindrops.const_defined?(:TCP_Info)
|
108
|
+
TCPI = Raindrops::TCP_Info.allocate
|
109
|
+
|
110
|
+
def check_client_connection(socket) # :nodoc:
|
111
|
+
if Unicorn::TCPClient === socket
|
112
|
+
# Raindrops::TCP_Info#get!, #state (reads struct tcp_info#tcpi_state)
|
113
|
+
raise Errno::EPIPE, "client closed connection".freeze,
|
114
|
+
EMPTY_ARRAY if closed_state?(TCPI.get!(socket).state)
|
115
|
+
else
|
116
|
+
write_http_header(socket)
|
117
|
+
end
|
108
118
|
end
|
109
119
|
|
110
|
-
|
111
|
-
|
120
|
+
if Raindrops.const_defined?(:TCP)
|
121
|
+
# raindrops 0.18.0+ supports FreeBSD + Linux using the same names
|
122
|
+
# Evaluate these hash lookups at load time so we can
|
123
|
+
# generate an opt_case_dispatch instruction
|
124
|
+
eval <<-EOS
|
125
|
+
def closed_state?(state) # :nodoc:
|
126
|
+
case state
|
127
|
+
when #{Raindrops::TCP[:ESTABLISHED]}
|
128
|
+
false
|
129
|
+
when #{Raindrops::TCP.values_at(
|
130
|
+
:CLOSE_WAIT, :TIME_WAIT, :CLOSE, :LAST_ACK, :CLOSING).join(',')}
|
131
|
+
true
|
132
|
+
else
|
133
|
+
false
|
134
|
+
end
|
135
|
+
end
|
136
|
+
EOS
|
137
|
+
else
|
138
|
+
# raindrops before 0.18 only supported TCP_INFO under Linux
|
139
|
+
def closed_state?(state) # :nodoc:
|
140
|
+
case state
|
141
|
+
when 1 # ESTABLISHED
|
142
|
+
false
|
143
|
+
when 8, 6, 7, 9, 11 # CLOSE_WAIT, TIME_WAIT, CLOSE, LAST_ACK, CLOSING
|
144
|
+
true
|
145
|
+
else
|
146
|
+
false
|
147
|
+
end
|
148
|
+
end
|
112
149
|
end
|
113
150
|
else
|
114
|
-
|
115
|
-
|
151
|
+
|
152
|
+
# Ruby 2.2+ can show struct tcp_info as a string Socket::Option#inspect.
|
153
|
+
# Not that efficient, but probably still better than doing unnecessary
|
154
|
+
# work after a client gives up.
|
155
|
+
def check_client_connection(socket) # :nodoc:
|
156
|
+
if Unicorn::TCPClient === socket && @@tcpi_inspect_ok
|
157
|
+
opt = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO).inspect
|
158
|
+
if opt =~ /\bstate=(\S+)/
|
159
|
+
raise Errno::EPIPE, "client closed connection".freeze,
|
160
|
+
EMPTY_ARRAY if closed_state_str?($1)
|
161
|
+
else
|
162
|
+
@@tcpi_inspect_ok = false
|
163
|
+
write_http_header(socket)
|
164
|
+
end
|
165
|
+
opt.clear
|
166
|
+
else
|
167
|
+
write_http_header(socket)
|
168
|
+
end
|
116
169
|
end
|
117
170
|
|
118
|
-
def
|
119
|
-
|
171
|
+
def closed_state_str?(state)
|
172
|
+
case state
|
173
|
+
when 'ESTABLISHED'
|
174
|
+
false
|
175
|
+
# not a typo, ruby maps TCP_CLOSE (no 'D') to state=CLOSED (w/ 'D')
|
176
|
+
when 'CLOSE_WAIT', 'TIME_WAIT', 'CLOSED', 'LAST_ACK', 'CLOSING'
|
177
|
+
true
|
178
|
+
else
|
179
|
+
false
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def write_http_header(socket) # :nodoc:
|
185
|
+
if headers?
|
186
|
+
self.response_start_sent = true
|
187
|
+
HTTP_RESPONSE_START.each { |c| socket.write(c) }
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# called by ext/unicorn_http/unicorn_http.rl via rb_funcall
|
192
|
+
def self.is_chunked?(v) # :nodoc:
|
193
|
+
vals = v.split(/[ \t]*,[ \t]*/).map!(&:downcase)
|
194
|
+
if vals.pop == 'chunked'.freeze
|
195
|
+
return true unless vals.include?('chunked'.freeze)
|
196
|
+
raise Unicorn::HttpParserError, 'double chunked', []
|
120
197
|
end
|
198
|
+
return false unless vals.include?('chunked'.freeze)
|
199
|
+
raise Unicorn::HttpParserError, 'chunked not last', []
|
121
200
|
end
|
122
201
|
end
|
@@ -10,66 +10,52 @@
|
|
10
10
|
# is the job of Rack, with the exception of the "Date" and "Status" header.
|
11
11
|
module Unicorn::HttpResponse
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
hash[code] = "#{code} #{msg}"
|
16
|
-
hash
|
17
|
-
}
|
18
|
-
CRLF = "\r\n"
|
13
|
+
STATUS_CODES = defined?(Rack::Utils::HTTP_STATUS_CODES) ?
|
14
|
+
Rack::Utils::HTTP_STATUS_CODES : {}
|
19
15
|
|
16
|
+
# internal API, code will always be common-enough-for-even-old-Rack
|
20
17
|
def err_response(code, response_start_sent)
|
21
|
-
"#{response_start_sent ? '' : 'HTTP/1.1 '}
|
18
|
+
"#{response_start_sent ? '' : 'HTTP/1.1 '}" \
|
19
|
+
"#{code} #{STATUS_CODES[code]}\r\n\r\n"
|
22
20
|
end
|
23
21
|
|
24
22
|
# writes the rack_response to socket as an HTTP response
|
25
23
|
def http_response_write(socket, status, headers, body,
|
26
|
-
|
27
|
-
status = CODES[status.to_i] || status
|
24
|
+
req = Unicorn::HttpRequest.new)
|
28
25
|
hijack = nil
|
29
26
|
|
30
|
-
http_response_start = response_start_sent ? '' : 'HTTP/1.1 '
|
31
27
|
if headers
|
32
|
-
|
28
|
+
code = status.to_i
|
29
|
+
msg = STATUS_CODES[code]
|
30
|
+
start = req.response_start_sent ? ''.freeze : 'HTTP/1.1 '.freeze
|
31
|
+
buf = "#{start}#{msg ? %Q(#{code} #{msg}) : status}\r\n" \
|
33
32
|
"Date: #{httpdate}\r\n" \
|
34
|
-
"Status: #{status}\r\n" \
|
35
33
|
"Connection: close\r\n"
|
36
34
|
headers.each do |key, value|
|
37
35
|
case key
|
38
|
-
when %r{\A(?:Date
|
36
|
+
when %r{\A(?:Date|Connection)\z}i
|
39
37
|
next
|
40
38
|
when "rack.hijack"
|
41
|
-
#
|
42
|
-
#
|
43
|
-
hijack =
|
39
|
+
# This should only be hit under Rack >= 1.5, as this was an illegal
|
40
|
+
# key in Rack < 1.5
|
41
|
+
hijack = value
|
44
42
|
else
|
45
43
|
if value =~ /\n/
|
46
44
|
# avoiding blank, key-only cookies with /\n+/
|
47
|
-
|
45
|
+
value.split(/\n+/).each { |v| buf << "#{key}: #{v}\r\n" }
|
48
46
|
else
|
49
47
|
buf << "#{key}: #{value}\r\n"
|
50
48
|
end
|
51
49
|
end
|
52
50
|
end
|
53
|
-
socket.write(buf <<
|
51
|
+
socket.write(buf << "\r\n".freeze)
|
54
52
|
end
|
55
53
|
|
56
54
|
if hijack
|
57
|
-
|
55
|
+
req.hijacked!
|
58
56
|
hijack.call(socket)
|
59
57
|
else
|
60
58
|
body.each { |chunk| socket.write(chunk) }
|
61
59
|
end
|
62
|
-
ensure
|
63
|
-
body.respond_to?(:close) and body.close
|
64
|
-
end
|
65
|
-
|
66
|
-
# Rack 1.5.0 (protocol version 1.2) adds response hijacking support
|
67
|
-
if ((Rack::VERSION[0] << 8) | Rack::VERSION[1]) >= 0x0102
|
68
|
-
def hijack_prepare(value)
|
69
|
-
value
|
70
|
-
end
|
71
|
-
else
|
72
|
-
def hijack_prepare(_)
|
73
|
-
end
|
74
60
|
end
|
75
61
|
end
|
data/lib/unicorn/http_server.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
-
require "unicorn/ssl_server"
|
3
2
|
|
4
3
|
# This is the process manager of Unicorn. This manages worker
|
5
4
|
# processes which in turn handle the I/O and application process.
|
@@ -7,51 +6,30 @@
|
|
7
6
|
# forked worker children.
|
8
7
|
#
|
9
8
|
# Users do not need to know the internals of this class, but reading the
|
10
|
-
# {source}[
|
11
|
-
# is education for programmers wishing to learn how
|
12
|
-
# See Unicorn::Configurator for information on how to configure
|
9
|
+
# {source}[https://yhbt.net/unicorn.git/tree/lib/unicorn/http_server.rb]
|
10
|
+
# is education for programmers wishing to learn how unicorn works.
|
11
|
+
# See Unicorn::Configurator for information on how to configure unicorn.
|
13
12
|
class Unicorn::HttpServer
|
14
13
|
# :stopdoc:
|
15
|
-
attr_accessor :app, :
|
14
|
+
attr_accessor :app, :timeout, :worker_processes,
|
16
15
|
:before_fork, :after_fork, :before_exec,
|
17
16
|
:listener_opts, :preload_app,
|
18
|
-
:
|
19
|
-
:
|
17
|
+
:orig_app, :config, :ready_pipe, :user,
|
18
|
+
:default_middleware, :early_hints
|
19
|
+
attr_writer :after_worker_exit, :after_worker_ready, :worker_exec
|
20
20
|
|
21
21
|
attr_reader :pid, :logger
|
22
22
|
include Unicorn::SocketHelper
|
23
23
|
include Unicorn::HttpResponse
|
24
|
-
include Unicorn::SSLServer
|
25
|
-
|
26
|
-
# backwards compatibility with 1.x
|
27
|
-
Worker = Unicorn::Worker
|
28
24
|
|
29
25
|
# all bound listener sockets
|
26
|
+
# note: this is public used by raindrops, but not recommended for use
|
27
|
+
# in new projects
|
30
28
|
LISTENERS = []
|
31
29
|
|
32
30
|
# listeners we have yet to bind
|
33
31
|
NEW_LISTENERS = []
|
34
32
|
|
35
|
-
# This hash maps PIDs to Workers
|
36
|
-
WORKERS = {}
|
37
|
-
|
38
|
-
# We use SELF_PIPE differently in the master and worker processes:
|
39
|
-
#
|
40
|
-
# * The master process never closes or reinitializes this once
|
41
|
-
# initialized. Signal handlers in the master process will write to
|
42
|
-
# it to wake up the master from IO.select in exactly the same manner
|
43
|
-
# djb describes in http://cr.yp.to/docs/selfpipe.html
|
44
|
-
#
|
45
|
-
# * The workers immediately close the pipe they inherit. See the
|
46
|
-
# Unicorn::Worker class for the pipe workers use.
|
47
|
-
SELF_PIPE = []
|
48
|
-
|
49
|
-
# signal queue used for self-piping
|
50
|
-
SIG_QUEUE = []
|
51
|
-
|
52
|
-
# list of signals we care about and trap in master.
|
53
|
-
QUEUE_SIGS = [ :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ]
|
54
|
-
|
55
33
|
# :startdoc:
|
56
34
|
# We populate this at startup so we can figure out how to reexecute
|
57
35
|
# and upgrade the currently running instance of Unicorn
|
@@ -60,7 +38,7 @@ class Unicorn::HttpServer
|
|
60
38
|
# or even different installations of the same applications without
|
61
39
|
# downtime. Keys of this constant Hash are described as follows:
|
62
40
|
#
|
63
|
-
# * 0 - the path to the unicorn
|
41
|
+
# * 0 - the path to the unicorn executable
|
64
42
|
# * :argv - a deep copy of the ARGV array the executable originally saw
|
65
43
|
# * :cwd - the working directory of the application, this is where
|
66
44
|
# you originally started Unicorn.
|
@@ -69,9 +47,9 @@ class Unicorn::HttpServer
|
|
69
47
|
# you can set the following in your Unicorn config file, HUP and then
|
70
48
|
# continue with the traditional USR2 + QUIT upgrade steps:
|
71
49
|
#
|
72
|
-
# Unicorn::HttpServer::START_CTX[0] = "/home/bofh/2.
|
50
|
+
# Unicorn::HttpServer::START_CTX[0] = "/home/bofh/2.3.0/bin/unicorn"
|
73
51
|
START_CTX = {
|
74
|
-
:argv => ARGV.map
|
52
|
+
:argv => ARGV.map(&:dup),
|
75
53
|
0 => $0.dup,
|
76
54
|
}
|
77
55
|
# We favor ENV['PWD'] since it is (usually) symlink aware for Capistrano
|
@@ -91,8 +69,8 @@ class Unicorn::HttpServer
|
|
91
69
|
# incoming requests on the socket.
|
92
70
|
def initialize(app, options = {})
|
93
71
|
@app = app
|
94
|
-
@
|
95
|
-
|
72
|
+
@reexec_pid = 0
|
73
|
+
@default_middleware = true
|
96
74
|
options = options.dup
|
97
75
|
@ready_pipe = options.delete(:ready_pipe)
|
98
76
|
@init_listeners = options[:listeners] ? options[:listeners].dup : []
|
@@ -100,6 +78,20 @@ def initialize(app, options = {})
|
|
100
78
|
self.config = Unicorn::Configurator.new(options)
|
101
79
|
self.listener_opts = {}
|
102
80
|
|
81
|
+
# We use @self_pipe differently in the master and worker processes:
|
82
|
+
#
|
83
|
+
# * The master process never closes or reinitializes this once
|
84
|
+
# initialized. Signal handlers in the master process will write to
|
85
|
+
# it to wake up the master from IO.select in exactly the same manner
|
86
|
+
# djb describes in https://cr.yp.to/docs/selfpipe.html
|
87
|
+
#
|
88
|
+
# * The workers immediately close the pipe they inherit. See the
|
89
|
+
# Unicorn::Worker class for the pipe workers use.
|
90
|
+
@self_pipe = []
|
91
|
+
@workers = {} # hash maps PIDs to Workers
|
92
|
+
@sig_queue = [] # signal queue used for self-piping
|
93
|
+
@pid = nil
|
94
|
+
|
103
95
|
# we try inheriting listeners first, so we bind them later.
|
104
96
|
# we don't write the pid file until we've bound listeners in case
|
105
97
|
# unicorn was started twice by mistake. Even though our #pid= method
|
@@ -111,7 +103,18 @@ def initialize(app, options = {})
|
|
111
103
|
# monitoring tools may also rely on pid files existing before we
|
112
104
|
# attempt to connect to the listener(s)
|
113
105
|
config.commit!(self, :skip => [:listeners, :pid])
|
114
|
-
|
106
|
+
@orig_app = app
|
107
|
+
# list of signals we care about and trap in master.
|
108
|
+
@queue_sigs = [
|
109
|
+
:WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ]
|
110
|
+
|
111
|
+
@worker_data = if worker_data = ENV['UNICORN_WORKER']
|
112
|
+
worker_data = worker_data.split(',').map!(&:to_i)
|
113
|
+
worker_data[1] = worker_data.slice!(1..2).map do |i|
|
114
|
+
Kgio::Pipe.for_fd(i)
|
115
|
+
end
|
116
|
+
worker_data
|
117
|
+
end
|
115
118
|
end
|
116
119
|
|
117
120
|
# Runs the thing. Returns self so you can run join on it
|
@@ -119,13 +122,13 @@ def start
|
|
119
122
|
inherit_listeners!
|
120
123
|
# this pipe is used to wake us up from select(2) in #join when signals
|
121
124
|
# are trapped. See trap_deferred.
|
122
|
-
|
123
|
-
@master_pid = $$
|
125
|
+
@self_pipe.replace(Unicorn.pipe)
|
126
|
+
@master_pid = @worker_data ? Process.ppid : $$
|
124
127
|
|
125
128
|
# setup signal handlers before writing pid file in case people get
|
126
129
|
# trigger happy and send signals as soon as the pid file exists.
|
127
130
|
# Note that signals don't actually get handled until the #join method
|
128
|
-
|
131
|
+
@queue_sigs.each { |sig| trap(sig) { @sig_queue << sig; awaken_master } }
|
129
132
|
trap(:CHLD) { awaken_master }
|
130
133
|
|
131
134
|
# write pid early for Mongrel compatibility if we're not inheriting sockets
|
@@ -146,7 +149,7 @@ def start
|
|
146
149
|
def listeners=(listeners)
|
147
150
|
cur_names, dead_names = [], []
|
148
151
|
listener_names.each do |name|
|
149
|
-
if
|
152
|
+
if name.start_with?('/')
|
150
153
|
# mark unlinked sockets as dead so we can rebind them
|
151
154
|
(File.socket?(name) ? cur_names : dead_names) << name
|
152
155
|
else
|
@@ -158,9 +161,6 @@ def listeners=(listeners)
|
|
158
161
|
|
159
162
|
LISTENERS.delete_if do |io|
|
160
163
|
if dead_names.include?(sock_name(io))
|
161
|
-
IO_PURGATORY.delete_if do |pio|
|
162
|
-
pio.fileno == io.fileno && (pio.close rescue nil).nil? # true
|
163
|
-
end
|
164
164
|
(io.close rescue nil).nil? # true
|
165
165
|
else
|
166
166
|
set_server_sockopt(io, listener_opts[sock_name(io)])
|
@@ -198,7 +198,7 @@ def pid=(path)
|
|
198
198
|
if path
|
199
199
|
if x = valid_pid?(path)
|
200
200
|
return path if pid && path == pid && x == $$
|
201
|
-
if x == reexec_pid && pid
|
201
|
+
if x == @reexec_pid && pid.end_with?('.oldbin')
|
202
202
|
logger.warn("will not set pid=#{path} while reexec-ed "\
|
203
203
|
"child is running PID:#{x}")
|
204
204
|
return
|
@@ -241,7 +241,7 @@ def listen(address, opt = {}.merge(listener_opts[address] || {}))
|
|
241
241
|
begin
|
242
242
|
io = bind_listen(address, opt)
|
243
243
|
unless Kgio::TCPServer === io || Kgio::UNIXServer === io
|
244
|
-
|
244
|
+
io.autoclose = false
|
245
245
|
io = server_cast(io)
|
246
246
|
end
|
247
247
|
logger.info "listening on addr=#{sock_name(io)} fd=#{io.fileno}"
|
@@ -267,7 +267,7 @@ def listen(address, opt = {}.merge(listener_opts[address] || {}))
|
|
267
267
|
# is signalling us too often.
|
268
268
|
def join
|
269
269
|
respawn = true
|
270
|
-
last_check =
|
270
|
+
last_check = time_now
|
271
271
|
|
272
272
|
proc_name 'master'
|
273
273
|
logger.info "master process ready" # test_exec.rb relies on this message
|
@@ -281,11 +281,11 @@ def join
|
|
281
281
|
end
|
282
282
|
begin
|
283
283
|
reap_all_workers
|
284
|
-
case
|
284
|
+
case @sig_queue.shift
|
285
285
|
when nil
|
286
286
|
# avoid murdering workers after our master process (or the
|
287
287
|
# machine) comes out of suspend/hibernation
|
288
|
-
if (last_check + @timeout) >= (last_check =
|
288
|
+
if (last_check + @timeout) >= (last_check = time_now)
|
289
289
|
sleep_time = murder_lazy_workers
|
290
290
|
else
|
291
291
|
sleep_time = @timeout/2.0 + 1
|
@@ -306,13 +306,13 @@ def join
|
|
306
306
|
when :USR2 # exec binary, stay alive in case something went wrong
|
307
307
|
reexec
|
308
308
|
when :WINCH
|
309
|
-
if
|
309
|
+
if $stdin.tty?
|
310
|
+
logger.info "SIGWINCH ignored because we're not daemonized"
|
311
|
+
else
|
310
312
|
respawn = false
|
311
313
|
logger.info "gracefully stopping all workers"
|
312
314
|
soft_kill_each_worker(:QUIT)
|
313
315
|
self.worker_processes = 0
|
314
|
-
else
|
315
|
-
logger.info "SIGWINCH ignored because we're not daemonized"
|
316
316
|
end
|
317
317
|
when :TTIN
|
318
318
|
respawn = true
|
@@ -339,8 +339,8 @@ def join
|
|
339
339
|
# Terminates all workers, but does not exit master process
|
340
340
|
def stop(graceful = true)
|
341
341
|
self.listeners = []
|
342
|
-
limit =
|
343
|
-
until
|
342
|
+
limit = time_now + timeout
|
343
|
+
until @workers.empty? || time_now > limit
|
344
344
|
if graceful
|
345
345
|
soft_kill_each_worker(:QUIT)
|
346
346
|
else
|
@@ -369,14 +369,6 @@ def client_body_buffer_size=(bytes)
|
|
369
369
|
Unicorn::TeeInput.client_body_buffer_size = bytes
|
370
370
|
end
|
371
371
|
|
372
|
-
def trust_x_forwarded
|
373
|
-
Unicorn::HttpParser.trust_x_forwarded?
|
374
|
-
end
|
375
|
-
|
376
|
-
def trust_x_forwarded=(bool)
|
377
|
-
Unicorn::HttpParser.trust_x_forwarded = bool
|
378
|
-
end
|
379
|
-
|
380
372
|
def check_client_connection
|
381
373
|
Unicorn::HttpRequest.check_client_connection
|
382
374
|
end
|
@@ -389,17 +381,17 @@ def check_client_connection=(bool)
|
|
389
381
|
|
390
382
|
# wait for a signal hander to wake us up and then consume the pipe
|
391
383
|
def master_sleep(sec)
|
384
|
+
@self_pipe[0].wait(sec) or return
|
392
385
|
# 11 bytes is the maximum string length which can be embedded within
|
393
386
|
# the Ruby itself and not require a separate malloc (on 32-bit MRI 1.9+).
|
394
387
|
# Most reads are only one byte here and uncommon, so it's not worth a
|
395
388
|
# persistent buffer, either:
|
396
|
-
|
397
|
-
SELF_PIPE[0].kgio_tryread(11)
|
389
|
+
@self_pipe[0].kgio_tryread(11)
|
398
390
|
end
|
399
391
|
|
400
392
|
def awaken_master
|
401
393
|
return if $$ != @master_pid
|
402
|
-
|
394
|
+
@self_pipe[1].kgio_trywrite('.') # wakeup master process from select
|
403
395
|
end
|
404
396
|
|
405
397
|
# reaps all unreaped workers
|
@@ -407,15 +399,14 @@ def reap_all_workers
|
|
407
399
|
begin
|
408
400
|
wpid, status = Process.waitpid2(-1, Process::WNOHANG)
|
409
401
|
wpid or return
|
410
|
-
if reexec_pid == wpid
|
402
|
+
if @reexec_pid == wpid
|
411
403
|
logger.error "reaped #{status.inspect} exec()-ed"
|
412
|
-
|
404
|
+
@reexec_pid = 0
|
413
405
|
self.pid = pid.chomp('.oldbin') if pid
|
414
406
|
proc_name 'master'
|
415
407
|
else
|
416
|
-
worker =
|
417
|
-
|
418
|
-
status.success? ? logger.info(m) : logger.error(m)
|
408
|
+
worker = @workers.delete(wpid) and worker.close rescue nil
|
409
|
+
@after_worker_exit.call(self, worker, status)
|
419
410
|
end
|
420
411
|
rescue Errno::ECHILD
|
421
412
|
break
|
@@ -424,13 +415,13 @@ def reap_all_workers
|
|
424
415
|
|
425
416
|
# reexecutes the START_CTX with a new binary
|
426
417
|
def reexec
|
427
|
-
if reexec_pid > 0
|
418
|
+
if @reexec_pid > 0
|
428
419
|
begin
|
429
|
-
Process.kill(0, reexec_pid)
|
430
|
-
logger.error "reexec-ed child already running PID
|
420
|
+
Process.kill(0, @reexec_pid)
|
421
|
+
logger.error "reexec-ed child already running PID:#@reexec_pid"
|
431
422
|
return
|
432
423
|
rescue Errno::ESRCH
|
433
|
-
|
424
|
+
@reexec_pid = 0
|
434
425
|
end
|
435
426
|
end
|
436
427
|
|
@@ -448,15 +439,8 @@ def reexec
|
|
448
439
|
end
|
449
440
|
end
|
450
441
|
|
451
|
-
|
452
|
-
listener_fds =
|
453
|
-
LISTENERS.each do |sock|
|
454
|
-
# IO#close_on_exec= will be available on any future version of
|
455
|
-
# Ruby that sets FD_CLOEXEC by default on new file descriptors
|
456
|
-
# ref: http://redmine.ruby-lang.org/issues/5041
|
457
|
-
sock.close_on_exec = false if sock.respond_to?(:close_on_exec=)
|
458
|
-
listener_fds[sock.fileno] = sock
|
459
|
-
end
|
442
|
+
@reexec_pid = fork do
|
443
|
+
listener_fds = listener_sockets
|
460
444
|
ENV['UNICORN_FD'] = listener_fds.keys.join(',')
|
461
445
|
Dir.chdir(START_CTX[:cwd])
|
462
446
|
cmd = [ START_CTX[0] ].concat(START_CTX[:argv])
|
@@ -464,16 +448,11 @@ def reexec
|
|
464
448
|
# avoid leaking FDs we don't know about, but let before_exec
|
465
449
|
# unset FD_CLOEXEC, if anything else in the app eventually
|
466
450
|
# relies on FD inheritence.
|
467
|
-
(
|
468
|
-
next if listener_fds.include?(io)
|
469
|
-
io = IO.for_fd(io) rescue next
|
470
|
-
prevent_autoclose(io)
|
471
|
-
io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
472
|
-
end
|
451
|
+
close_sockets_on_exec(listener_fds)
|
473
452
|
|
474
453
|
# exec(command, hash) works in at least 1.9.1+, but will only be
|
475
454
|
# required in 1.9.4/2.0.0 at earliest.
|
476
|
-
cmd << listener_fds
|
455
|
+
cmd << listener_fds
|
477
456
|
logger.info "executing #{cmd.inspect} (in #{Dir.pwd})"
|
478
457
|
before_exec.call(self)
|
479
458
|
exec(*cmd)
|
@@ -481,11 +460,45 @@ def reexec
|
|
481
460
|
proc_name 'master (old)'
|
482
461
|
end
|
483
462
|
|
463
|
+
def worker_spawn(worker)
|
464
|
+
listener_fds = listener_sockets
|
465
|
+
env = {}
|
466
|
+
env['UNICORN_FD'] = listener_fds.keys.join(',')
|
467
|
+
|
468
|
+
listener_fds[worker.to_io.fileno] = worker.to_io
|
469
|
+
listener_fds[worker.master.fileno] = worker.master
|
470
|
+
|
471
|
+
worker_info = [worker.nr, worker.to_io.fileno, worker.master.fileno]
|
472
|
+
env['UNICORN_WORKER'] = worker_info.join(',')
|
473
|
+
|
474
|
+
close_sockets_on_exec(listener_fds)
|
475
|
+
|
476
|
+
Process.spawn(env, START_CTX[0], *START_CTX[:argv], listener_fds)
|
477
|
+
end
|
478
|
+
|
479
|
+
def listener_sockets
|
480
|
+
listener_fds = {}
|
481
|
+
LISTENERS.each do |sock|
|
482
|
+
sock.close_on_exec = false
|
483
|
+
listener_fds[sock.fileno] = sock
|
484
|
+
end
|
485
|
+
listener_fds
|
486
|
+
end
|
487
|
+
|
488
|
+
def close_sockets_on_exec(sockets)
|
489
|
+
(3..1024).each do |io|
|
490
|
+
next if sockets.include?(io)
|
491
|
+
io = IO.for_fd(io) rescue next
|
492
|
+
io.autoclose = false
|
493
|
+
io.close_on_exec = true
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
484
497
|
# forcibly terminate all workers that haven't checked in in timeout seconds. The timeout is implemented using an unlinked File
|
485
498
|
def murder_lazy_workers
|
486
499
|
next_sleep = @timeout - 1
|
487
|
-
now =
|
488
|
-
|
500
|
+
now = time_now.to_i
|
501
|
+
@workers.dup.each_pair do |wpid, worker|
|
489
502
|
tick = worker.tick
|
490
503
|
0 == tick and next # skip workers that haven't processed any clients
|
491
504
|
diff = now - tick
|
@@ -503,42 +516,50 @@ def murder_lazy_workers
|
|
503
516
|
end
|
504
517
|
|
505
518
|
def after_fork_internal
|
506
|
-
|
519
|
+
@self_pipe.each(&:close).clear # this is master-only, now
|
507
520
|
@ready_pipe.close if @ready_pipe
|
508
521
|
Unicorn::Configurator::RACKUP.clear
|
509
522
|
@ready_pipe = @init_listeners = @before_exec = @before_fork = nil
|
510
523
|
|
511
|
-
srand # http://redmine.ruby-lang.org/issues/4338
|
512
|
-
|
513
524
|
# The OpenSSL PRNG is seeded with only the pid, and apps with frequently
|
514
525
|
# dying workers can recycle pids
|
515
526
|
OpenSSL::Random.seed(rand.to_s) if defined?(OpenSSL::Random)
|
516
527
|
end
|
517
528
|
|
518
529
|
def spawn_missing_workers
|
530
|
+
if @worker_data
|
531
|
+
worker = Unicorn::Worker.new(*@worker_data)
|
532
|
+
after_fork_internal
|
533
|
+
worker_loop(worker)
|
534
|
+
exit
|
535
|
+
end
|
536
|
+
|
519
537
|
worker_nr = -1
|
520
538
|
until (worker_nr += 1) == @worker_processes
|
521
|
-
|
522
|
-
worker = Worker.new(worker_nr)
|
539
|
+
@workers.value?(worker_nr) and next
|
540
|
+
worker = Unicorn::Worker.new(worker_nr)
|
523
541
|
before_fork.call(self, worker)
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
542
|
+
|
543
|
+
pid = @worker_exec ? worker_spawn(worker) : fork
|
544
|
+
|
545
|
+
unless pid
|
528
546
|
after_fork_internal
|
529
547
|
worker_loop(worker)
|
530
548
|
exit
|
531
549
|
end
|
550
|
+
|
551
|
+
@workers[pid] = worker
|
552
|
+
worker.atfork_parent
|
532
553
|
end
|
533
|
-
|
534
|
-
|
535
|
-
|
554
|
+
rescue => e
|
555
|
+
@logger.error(e) rescue nil
|
556
|
+
exit!
|
536
557
|
end
|
537
558
|
|
538
559
|
def maintain_worker_count
|
539
|
-
(off =
|
560
|
+
(off = @workers.size - worker_processes) == 0 and return
|
540
561
|
off < 0 and return spawn_missing_workers
|
541
|
-
|
562
|
+
@workers.each_value { |w| w.nr >= worker_processes and w.soft_kill(:QUIT) }
|
542
563
|
end
|
543
564
|
|
544
565
|
# if we get any error, try to write something back to the client
|
@@ -563,43 +584,79 @@ def handle_error(client, e)
|
|
563
584
|
client.kgio_trywrite(err_response(code, @request.response_start_sent))
|
564
585
|
end
|
565
586
|
client.close
|
566
|
-
|
587
|
+
rescue
|
567
588
|
end
|
568
589
|
|
569
|
-
def
|
570
|
-
if @request.response_start_sent
|
571
|
-
|
590
|
+
def e103_response_write(client, headers)
|
591
|
+
response = if @request.response_start_sent
|
592
|
+
"103 Early Hints\r\n"
|
572
593
|
else
|
573
|
-
|
594
|
+
"HTTP/1.1 103 Early Hints\r\n"
|
574
595
|
end
|
596
|
+
|
597
|
+
headers.each_pair do |k, vs|
|
598
|
+
next if !vs || vs.empty?
|
599
|
+
values = vs.to_s.split("\n".freeze)
|
600
|
+
values.each do |v|
|
601
|
+
response << "#{k}: #{v}\r\n"
|
602
|
+
end
|
603
|
+
end
|
604
|
+
response << "\r\n".freeze
|
605
|
+
response << "HTTP/1.1 ".freeze if @request.response_start_sent
|
606
|
+
client.write(response)
|
607
|
+
end
|
608
|
+
|
609
|
+
def e100_response_write(client, env)
|
610
|
+
# We use String#freeze to avoid allocations under Ruby 2.1+
|
611
|
+
# Not many users hit this code path, so it's better to reduce the
|
612
|
+
# constant table sizes even for 1.9.3-2.0 users who'll hit extra
|
613
|
+
# allocations here.
|
614
|
+
client.write(@request.response_start_sent ?
|
615
|
+
"100 Continue\r\n\r\nHTTP/1.1 ".freeze :
|
616
|
+
"HTTP/1.1 100 Continue\r\n\r\n".freeze)
|
617
|
+
env.delete('HTTP_EXPECT'.freeze)
|
575
618
|
end
|
576
619
|
|
577
620
|
# once a client is accepted, it is processed in its entirety here
|
578
621
|
# in 3 easy steps: read request, call app, write app response
|
579
622
|
def process_client(client)
|
580
|
-
|
581
|
-
|
623
|
+
@request = Unicorn::HttpRequest.new
|
624
|
+
env = @request.read(client)
|
625
|
+
|
626
|
+
if early_hints
|
627
|
+
env["rack.early_hints"] = lambda do |headers|
|
628
|
+
e103_response_write(client, headers)
|
629
|
+
end
|
630
|
+
end
|
582
631
|
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
632
|
+
env["rack.after_reply"] = []
|
633
|
+
|
634
|
+
status, headers, body = @app.call(env)
|
635
|
+
|
636
|
+
begin
|
587
637
|
return if @request.hijacked?
|
638
|
+
|
639
|
+
if 100 == status.to_i
|
640
|
+
e100_response_write(client, env)
|
641
|
+
status, headers, body = @app.call(env)
|
642
|
+
return if @request.hijacked?
|
643
|
+
end
|
644
|
+
@request.headers? or headers = nil
|
645
|
+
http_response_write(client, status, headers, body, @request)
|
646
|
+
ensure
|
647
|
+
body.respond_to?(:close) and body.close
|
588
648
|
end
|
589
|
-
|
590
|
-
http_response_write(client, status, headers, body,
|
591
|
-
@request.response_start_sent)
|
649
|
+
|
592
650
|
unless client.closed? # rack.hijack may've close this for us
|
593
651
|
client.shutdown # in case of fork() in Rack app
|
594
652
|
client.close # flush and uncork socket immediately, no keepalive
|
595
653
|
end
|
596
654
|
rescue => e
|
597
655
|
handle_error(client, e)
|
656
|
+
ensure
|
657
|
+
env["rack.after_reply"].each(&:call) if env
|
598
658
|
end
|
599
659
|
|
600
|
-
EXIT_SIGS = [ :QUIT, :TERM, :INT ]
|
601
|
-
WORKER_QUEUE_SIGS = QUEUE_SIGS - EXIT_SIGS
|
602
|
-
|
603
660
|
def nuke_listeners!(readers)
|
604
661
|
# only called from the worker, ordering is important here
|
605
662
|
tmp = readers.dup
|
@@ -614,23 +671,23 @@ def nuke_listeners!(readers)
|
|
614
671
|
def init_worker_process(worker)
|
615
672
|
worker.atfork_child
|
616
673
|
# we'll re-trap :QUIT later for graceful shutdown iff we accept clients
|
617
|
-
|
618
|
-
exit!(0)
|
619
|
-
|
674
|
+
exit_sigs = [ :QUIT, :TERM, :INT ]
|
675
|
+
exit_sigs.each { |sig| trap(sig) { exit!(0) } }
|
676
|
+
exit!(0) if (@sig_queue & exit_sigs)[0]
|
677
|
+
(@queue_sigs - exit_sigs).each { |sig| trap(sig, nil) }
|
620
678
|
trap(:CHLD, 'DEFAULT')
|
621
|
-
|
679
|
+
@sig_queue.clear
|
622
680
|
proc_name "worker[#{worker.nr}]"
|
623
681
|
START_CTX.clear
|
624
|
-
|
682
|
+
@workers.clear
|
625
683
|
|
626
684
|
after_fork.call(self, worker) # can drop perms and create listeners
|
627
|
-
LISTENERS.each { |sock| sock.
|
685
|
+
LISTENERS.each { |sock| sock.close_on_exec = true }
|
628
686
|
|
629
687
|
worker.user(*user) if user.kind_of?(Array) && ! worker.switched
|
630
688
|
self.timeout /= 2.0 # halve it for select()
|
631
689
|
@config = nil
|
632
690
|
build_app! unless preload_app
|
633
|
-
ssl_enable!
|
634
691
|
@after_fork = @listener_opts = @orig_app = nil
|
635
692
|
readers = LISTENERS.dup
|
636
693
|
readers << worker
|
@@ -642,16 +699,16 @@ def reopen_worker_logs(worker_nr)
|
|
642
699
|
logger.info "worker=#{worker_nr} reopening logs..."
|
643
700
|
Unicorn::Util.reopen_logs
|
644
701
|
logger.info "worker=#{worker_nr} done reopening logs"
|
645
|
-
|
646
|
-
|
647
|
-
|
702
|
+
rescue => e
|
703
|
+
logger.error(e) rescue nil
|
704
|
+
exit!(77) # EX_NOPERM in sysexits.h
|
648
705
|
end
|
649
706
|
|
650
707
|
# runs inside each forked worker, this sits around and waits
|
651
708
|
# for connections and doesn't die until the parent dies (or is
|
652
709
|
# given a INT, QUIT, or TERM signal)
|
653
710
|
def worker_loop(worker)
|
654
|
-
ppid = master_pid
|
711
|
+
ppid = @master_pid
|
655
712
|
readers = init_worker_process(worker)
|
656
713
|
nr = 0 # this becomes negative if we need to reopen logs
|
657
714
|
|
@@ -660,12 +717,13 @@ def worker_loop(worker)
|
|
660
717
|
trap(:USR1) { nr = -65536 }
|
661
718
|
|
662
719
|
ready = readers.dup
|
663
|
-
|
720
|
+
nr_listeners = readers.size
|
721
|
+
@after_worker_ready.call(self, worker)
|
664
722
|
|
665
723
|
begin
|
666
724
|
nr < 0 and reopen_worker_logs(worker.nr)
|
667
725
|
nr = 0
|
668
|
-
worker.tick =
|
726
|
+
worker.tick = time_now.to_i
|
669
727
|
tmp = ready.dup
|
670
728
|
while sock = tmp.shift
|
671
729
|
# Unicorn::Worker#kgio_tryaccept is not like accept(2) at all,
|
@@ -673,7 +731,7 @@ def worker_loop(worker)
|
|
673
731
|
if client = sock.kgio_tryaccept
|
674
732
|
process_client(client)
|
675
733
|
nr += 1
|
676
|
-
worker.tick =
|
734
|
+
worker.tick = time_now.to_i
|
677
735
|
end
|
678
736
|
break if nr < 0
|
679
737
|
end
|
@@ -682,7 +740,7 @@ def worker_loop(worker)
|
|
682
740
|
# we're probably reasonably busy, so avoid calling select()
|
683
741
|
# and do a speculative non-blocking accept() on ready listeners
|
684
742
|
# before we sleep again in select().
|
685
|
-
|
743
|
+
if nr == nr_listeners
|
686
744
|
tmp = ready.dup
|
687
745
|
redo
|
688
746
|
end
|
@@ -690,7 +748,7 @@ def worker_loop(worker)
|
|
690
748
|
ppid == Process.ppid or return
|
691
749
|
|
692
750
|
# timeout used so we can detect parent death:
|
693
|
-
worker.tick =
|
751
|
+
worker.tick = time_now.to_i
|
694
752
|
ret = IO.select(readers, nil, nil, @timeout) and ready = ret[0]
|
695
753
|
rescue => e
|
696
754
|
redo if nr < 0 && readers[0]
|
@@ -702,17 +760,17 @@ def worker_loop(worker)
|
|
702
760
|
# is no longer running.
|
703
761
|
def kill_worker(signal, wpid)
|
704
762
|
Process.kill(signal, wpid)
|
705
|
-
|
706
|
-
|
763
|
+
rescue Errno::ESRCH
|
764
|
+
worker = @workers.delete(wpid) and worker.close rescue nil
|
707
765
|
end
|
708
766
|
|
709
767
|
# delivers a signal to each worker
|
710
768
|
def kill_each_worker(signal)
|
711
|
-
|
769
|
+
@workers.keys.each { |wpid| kill_worker(signal, wpid) }
|
712
770
|
end
|
713
771
|
|
714
772
|
def soft_kill_each_worker(signal)
|
715
|
-
|
773
|
+
@workers.each_value { |worker| worker.soft_kill(signal) }
|
716
774
|
end
|
717
775
|
|
718
776
|
# unlinks a PID file at given +path+ if it contains the current PID
|
@@ -730,11 +788,11 @@ def valid_pid?(path)
|
|
730
788
|
wpid <= 0 and return
|
731
789
|
Process.kill(0, wpid)
|
732
790
|
wpid
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
791
|
+
rescue Errno::EPERM
|
792
|
+
logger.info "pid=#{path} possibly stale, got EPERM signalling PID:#{wpid}"
|
793
|
+
nil
|
794
|
+
rescue Errno::ESRCH, Errno::ENOENT
|
795
|
+
# don't unlink stale pid files, racy without non-portable locking...
|
738
796
|
end
|
739
797
|
|
740
798
|
def load_config!
|
@@ -745,7 +803,7 @@ def load_config!
|
|
745
803
|
config.commit!(self)
|
746
804
|
soft_kill_each_worker(:QUIT)
|
747
805
|
Unicorn::Util.reopen_logs
|
748
|
-
self.app = orig_app
|
806
|
+
self.app = @orig_app
|
749
807
|
build_app! if preload_app
|
750
808
|
logger.info "done reloading config_file=#{config.config_file}"
|
751
809
|
rescue StandardError, LoadError, SyntaxError => e
|
@@ -760,12 +818,12 @@ def listener_names(listeners = LISTENERS)
|
|
760
818
|
end
|
761
819
|
|
762
820
|
def build_app!
|
763
|
-
if app.respond_to?(:arity) && app.arity == 0
|
821
|
+
if app.respond_to?(:arity) && (app.arity == 0 || app.arity == 2)
|
764
822
|
if defined?(Gem) && Gem.respond_to?(:refresh)
|
765
823
|
logger.info "Refreshing Gem list"
|
766
824
|
Gem.refresh
|
767
825
|
end
|
768
|
-
self.app = app.call
|
826
|
+
self.app = app.arity == 0 ? app.call : app.call(nil, self)
|
769
827
|
end
|
770
828
|
end
|
771
829
|
|
@@ -782,12 +840,23 @@ def redirect_io(io, path)
|
|
782
840
|
def inherit_listeners!
|
783
841
|
# inherit sockets from parents, they need to be plain Socket objects
|
784
842
|
# before they become Kgio::UNIXServer or Kgio::TCPServer
|
785
|
-
inherited = ENV['UNICORN_FD'].to_s.split(
|
843
|
+
inherited = ENV['UNICORN_FD'].to_s.split(',')
|
844
|
+
|
845
|
+
# emulate sd_listen_fds() for systemd
|
846
|
+
sd_pid, sd_fds = ENV.values_at('LISTEN_PID', 'LISTEN_FDS')
|
847
|
+
if sd_pid.to_i == $$ # n.b. $$ can never be zero
|
848
|
+
# 3 = SD_LISTEN_FDS_START
|
849
|
+
inherited.concat((3...(3 + sd_fds.to_i)).to_a)
|
850
|
+
end
|
851
|
+
# to ease debugging, we will not unset LISTEN_PID and LISTEN_FDS
|
852
|
+
|
853
|
+
inherited.map! do |fd|
|
786
854
|
io = Socket.for_fd(fd.to_i)
|
855
|
+
io.autoclose = false
|
856
|
+
io = server_cast(io)
|
787
857
|
set_server_sockopt(io, listener_opts[sock_name(io)])
|
788
|
-
|
789
|
-
|
790
|
-
server_cast(io)
|
858
|
+
logger.info "inherited addr=#{sock_name(io)} fd=#{io.fileno}"
|
859
|
+
io
|
791
860
|
end
|
792
861
|
|
793
862
|
config_listeners = config[:listeners].dup
|
@@ -810,8 +879,20 @@ def inherit_listeners!
|
|
810
879
|
# call only after calling inherit_listeners!
|
811
880
|
# This binds any listeners we did NOT inherit from the parent
|
812
881
|
def bind_new_listeners!
|
813
|
-
NEW_LISTENERS.each { |addr| listen(addr) }
|
882
|
+
NEW_LISTENERS.each { |addr| listen(addr) }.clear
|
814
883
|
raise ArgumentError, "no listeners" if LISTENERS.empty?
|
815
|
-
|
884
|
+
end
|
885
|
+
|
886
|
+
# try to use the monotonic clock in Ruby >= 2.1, it is immune to clock
|
887
|
+
# offset adjustments and generates less garbage (Float vs Time object)
|
888
|
+
begin
|
889
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
890
|
+
def time_now
|
891
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
892
|
+
end
|
893
|
+
rescue NameError, NoMethodError
|
894
|
+
def time_now # Ruby <= 2.0
|
895
|
+
Time.now
|
896
|
+
end
|
816
897
|
end
|
817
898
|
end
|