unicorn 4.9.0 → 6.0.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.
- 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
|