unicorn 5.2.0 → 5.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.manifest +1 -0
- data/.olddoc.yml +0 -1
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/HACKING +0 -1
- data/ISSUES +12 -6
- data/LATEST +73 -27
- data/Links +1 -1
- data/NEWS +113 -0
- data/TUNING +19 -1
- data/ext/unicorn_http/common_field_optimization.h +2 -2
- data/ext/unicorn_http/ext_help.h +0 -20
- data/ext/unicorn_http/extconf.rb +1 -1
- data/ext/unicorn_http/global_variables.h +2 -2
- data/ext/unicorn_http/httpdate.c +2 -2
- data/ext/unicorn_http/unicorn_http.c +9 -4
- data/ext/unicorn_http/unicorn_http.rl +9 -4
- data/lib/unicorn.rb +1 -1
- data/lib/unicorn/configurator.rb +65 -7
- data/lib/unicorn/http_request.rb +89 -9
- data/lib/unicorn/http_server.rb +63 -19
- data/lib/unicorn/oob_gc.rb +1 -2
- data/lib/unicorn/socket_helper.rb +19 -4
- data/lib/unicorn/stream_input.rb +5 -4
- data/lib/unicorn/tee_input.rb +8 -10
- data/lib/unicorn/version.rb +1 -1
- data/lib/unicorn/worker.rb +17 -6
- data/t/t0011-active-unix-socket.sh +1 -1
- data/t/t0012-reload-empty-config.sh +2 -1
- data/t/test-lib.sh +2 -2
- data/test/exec/test_exec.rb +6 -5
- data/test/unit/test_ccc.rb +90 -0
- data/test/unit/test_http_parser.rb +0 -18
- data/test/unit/test_socket_helper.rb +8 -4
- data/test/unit/test_util.rb +2 -2
- data/unicorn.gemspec +10 -12
- metadata +4 -17
data/ext/unicorn_http/httpdate.c
CHANGED
@@ -64,13 +64,13 @@ static VALUE httpdate(VALUE self)
|
|
64
64
|
return buf;
|
65
65
|
}
|
66
66
|
|
67
|
-
void init_unicorn_httpdate(
|
67
|
+
void init_unicorn_httpdate(VALUE mark_ary)
|
68
68
|
{
|
69
69
|
VALUE mod = rb_define_module("Unicorn");
|
70
70
|
mod = rb_define_module_under(mod, "HttpResponse");
|
71
71
|
|
72
72
|
buf = rb_str_new(0, buf_capa - 1);
|
73
|
-
|
73
|
+
rb_ary_push(mark_ary, buf);
|
74
74
|
buf_ptr = RSTRING_PTR(buf);
|
75
75
|
httpdate(Qnil);
|
76
76
|
|
@@ -15,7 +15,7 @@
|
|
15
15
|
#include "global_variables.h"
|
16
16
|
#include "c_util.h"
|
17
17
|
|
18
|
-
void init_unicorn_httpdate(
|
18
|
+
void init_unicorn_httpdate(VALUE mark_ary);
|
19
19
|
|
20
20
|
#define UH_FL_CHUNKED 0x1
|
21
21
|
#define UH_FL_HASBODY 0x2
|
@@ -4211,8 +4211,10 @@ static VALUE HttpParser_rssget(VALUE self)
|
|
4211
4211
|
|
4212
4212
|
void Init_unicorn_http(void)
|
4213
4213
|
{
|
4214
|
+
static VALUE mark_ary;
|
4214
4215
|
VALUE mUnicorn, cHttpParser;
|
4215
4216
|
|
4217
|
+
mark_ary = rb_ary_new();
|
4216
4218
|
mUnicorn = rb_define_module("Unicorn");
|
4217
4219
|
cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
|
4218
4220
|
eHttpParserError =
|
@@ -4222,7 +4224,7 @@ void Init_unicorn_http(void)
|
|
4222
4224
|
e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError",
|
4223
4225
|
eHttpParserError);
|
4224
4226
|
|
4225
|
-
init_globals();
|
4227
|
+
init_globals(mark_ary);
|
4226
4228
|
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
4227
4229
|
rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
|
4228
4230
|
rb_define_method(cHttpParser, "clear", HttpParser_clear, 0);
|
@@ -4258,14 +4260,17 @@ void Init_unicorn_http(void)
|
|
4258
4260
|
|
4259
4261
|
rb_define_singleton_method(cHttpParser, "max_header_len=", set_maxhdrlen, 1);
|
4260
4262
|
|
4261
|
-
init_common_fields();
|
4263
|
+
init_common_fields(mark_ary);
|
4262
4264
|
SET_GLOBAL(g_http_host, "HOST");
|
4263
4265
|
SET_GLOBAL(g_http_trailer, "TRAILER");
|
4264
4266
|
SET_GLOBAL(g_http_transfer_encoding, "TRANSFER_ENCODING");
|
4265
4267
|
SET_GLOBAL(g_content_length, "CONTENT_LENGTH");
|
4266
4268
|
SET_GLOBAL(g_http_connection, "CONNECTION");
|
4267
4269
|
id_set_backtrace = rb_intern("set_backtrace");
|
4268
|
-
init_unicorn_httpdate();
|
4270
|
+
init_unicorn_httpdate(mark_ary);
|
4271
|
+
|
4272
|
+
OBJ_FREEZE(mark_ary);
|
4273
|
+
rb_global_variable(&mark_ary);
|
4269
4274
|
|
4270
4275
|
#ifndef HAVE_RB_HASH_CLEAR
|
4271
4276
|
id_clear = rb_intern("clear");
|
@@ -13,7 +13,7 @@
|
|
13
13
|
#include "global_variables.h"
|
14
14
|
#include "c_util.h"
|
15
15
|
|
16
|
-
void init_unicorn_httpdate(
|
16
|
+
void init_unicorn_httpdate(VALUE mark_ary);
|
17
17
|
|
18
18
|
#define UH_FL_CHUNKED 0x1
|
19
19
|
#define UH_FL_HASBODY 0x2
|
@@ -917,8 +917,10 @@ static VALUE HttpParser_rssget(VALUE self)
|
|
917
917
|
|
918
918
|
void Init_unicorn_http(void)
|
919
919
|
{
|
920
|
+
static VALUE mark_ary;
|
920
921
|
VALUE mUnicorn, cHttpParser;
|
921
922
|
|
923
|
+
mark_ary = rb_ary_new();
|
922
924
|
mUnicorn = rb_define_module("Unicorn");
|
923
925
|
cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
|
924
926
|
eHttpParserError =
|
@@ -928,7 +930,7 @@ void Init_unicorn_http(void)
|
|
928
930
|
e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError",
|
929
931
|
eHttpParserError);
|
930
932
|
|
931
|
-
init_globals();
|
933
|
+
init_globals(mark_ary);
|
932
934
|
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
933
935
|
rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
|
934
936
|
rb_define_method(cHttpParser, "clear", HttpParser_clear, 0);
|
@@ -964,14 +966,17 @@ void Init_unicorn_http(void)
|
|
964
966
|
|
965
967
|
rb_define_singleton_method(cHttpParser, "max_header_len=", set_maxhdrlen, 1);
|
966
968
|
|
967
|
-
init_common_fields();
|
969
|
+
init_common_fields(mark_ary);
|
968
970
|
SET_GLOBAL(g_http_host, "HOST");
|
969
971
|
SET_GLOBAL(g_http_trailer, "TRAILER");
|
970
972
|
SET_GLOBAL(g_http_transfer_encoding, "TRANSFER_ENCODING");
|
971
973
|
SET_GLOBAL(g_content_length, "CONTENT_LENGTH");
|
972
974
|
SET_GLOBAL(g_http_connection, "CONNECTION");
|
973
975
|
id_set_backtrace = rb_intern("set_backtrace");
|
974
|
-
init_unicorn_httpdate();
|
976
|
+
init_unicorn_httpdate(mark_ary);
|
977
|
+
|
978
|
+
OBJ_FREEZE(mark_ary);
|
979
|
+
rb_global_variable(&mark_ary);
|
975
980
|
|
976
981
|
#ifndef HAVE_RB_HASH_CLEAR
|
977
982
|
id_clear = rb_intern("clear");
|
data/lib/unicorn.rb
CHANGED
@@ -95,7 +95,7 @@ def self.builder(ru, op)
|
|
95
95
|
|
96
96
|
# returns an array of strings representing TCP listen socket addresses
|
97
97
|
# and Unix domain socket paths. This is useful for use with
|
98
|
-
# Raindrops::Middleware under Linux:
|
98
|
+
# Raindrops::Middleware under Linux: https://bogomips.org/raindrops/
|
99
99
|
def self.listener_names
|
100
100
|
Unicorn::HttpServer::LISTENERS.map do |io|
|
101
101
|
Unicorn::SocketHelper.sock_name(io)
|
data/lib/unicorn/configurator.rb
CHANGED
@@ -41,10 +41,22 @@ class Unicorn::Configurator
|
|
41
41
|
:before_exec => lambda { |server|
|
42
42
|
server.logger.info("forked child re-executing...")
|
43
43
|
},
|
44
|
+
:after_worker_exit => lambda { |server, worker, status|
|
45
|
+
m = "reaped #{status.inspect} worker=#{worker.nr rescue 'unknown'}"
|
46
|
+
if status.success?
|
47
|
+
server.logger.info(m)
|
48
|
+
else
|
49
|
+
server.logger.error(m)
|
50
|
+
end
|
51
|
+
},
|
52
|
+
:after_worker_ready => lambda { |server, worker|
|
53
|
+
server.logger.info("worker=#{worker.nr} ready")
|
54
|
+
},
|
44
55
|
:pid => nil,
|
56
|
+
:worker_exec => false,
|
45
57
|
:preload_app => false,
|
46
58
|
:check_client_connection => false,
|
47
|
-
:rewindable_input => true,
|
59
|
+
:rewindable_input => true,
|
48
60
|
:client_body_buffer_size => Unicorn::Const::MAX_BODY,
|
49
61
|
}
|
50
62
|
#:startdoc:
|
@@ -151,6 +163,38 @@ def after_fork(*args, &block)
|
|
151
163
|
set_hook(:after_fork, block_given? ? block : args[0])
|
152
164
|
end
|
153
165
|
|
166
|
+
# sets after_worker_exit hook to a given block. This block will be called
|
167
|
+
# by the master process after a worker exits:
|
168
|
+
#
|
169
|
+
# after_worker_exit do |server,worker,status|
|
170
|
+
# # status is a Process::Status instance for the exited worker process
|
171
|
+
# unless status.success?
|
172
|
+
# server.logger.error("worker process failure: #{status.inspect}")
|
173
|
+
# end
|
174
|
+
# end
|
175
|
+
#
|
176
|
+
# after_worker_exit is only available in unicorn 5.3.0+
|
177
|
+
def after_worker_exit(*args, &block)
|
178
|
+
set_hook(:after_worker_exit, block_given? ? block : args[0], 3)
|
179
|
+
end
|
180
|
+
|
181
|
+
# sets after_worker_ready hook to a given block. This block will be called
|
182
|
+
# by a worker process after it has been fully loaded, directly before it
|
183
|
+
# starts responding to requests:
|
184
|
+
#
|
185
|
+
# after_worker_ready do |server,worker|
|
186
|
+
# server.logger.info("worker #{worker.nr} ready, dropping privileges")
|
187
|
+
# worker.user('username', 'groupname')
|
188
|
+
# end
|
189
|
+
#
|
190
|
+
# Do not use Configurator#user if you rely on changing users in the
|
191
|
+
# after_worker_ready hook.
|
192
|
+
#
|
193
|
+
# after_worker_ready is only available in unicorn 5.3.0+
|
194
|
+
def after_worker_ready(*args, &block)
|
195
|
+
set_hook(:after_worker_ready, block_given? ? block : args[0])
|
196
|
+
end
|
197
|
+
|
154
198
|
# sets before_fork got be a given Proc object. This Proc
|
155
199
|
# object will be called by the master process before forking
|
156
200
|
# each worker.
|
@@ -200,6 +244,17 @@ def timeout(seconds)
|
|
200
244
|
set[:timeout] = seconds > max ? max : seconds
|
201
245
|
end
|
202
246
|
|
247
|
+
# Whether to exec in each worker process after forking. This changes the
|
248
|
+
# memory layout of each worker process, which is a security feature designed
|
249
|
+
# to defeat possible address space discovery attacks. Note that using
|
250
|
+
# worker_exec only makes sense if you are not preloading the application,
|
251
|
+
# and will result in higher memory usage.
|
252
|
+
#
|
253
|
+
# worker_exec is only available in unicorn 5.3.0+
|
254
|
+
def worker_exec(bool)
|
255
|
+
set_bool(:worker_exec, bool)
|
256
|
+
end
|
257
|
+
|
203
258
|
# sets the current number of worker_processes to +nr+. Each worker
|
204
259
|
# process will serve exactly one client at a time. You can
|
205
260
|
# increment or decrement this value at runtime by sending SIGTTIN
|
@@ -466,13 +521,12 @@ def preload_app(bool)
|
|
466
521
|
# Disabling rewindability can improve performance by lowering
|
467
522
|
# I/O and memory usage for applications that accept uploads.
|
468
523
|
# Keep in mind that the Rack 1.x spec requires
|
469
|
-
# \env[\"rack.input\"] to be rewindable,
|
470
|
-
#
|
524
|
+
# \env[\"rack.input\"] to be rewindable,
|
525
|
+
# but the Rack 2.x spec does not.
|
471
526
|
#
|
472
|
-
# +rewindable_input+ defaults to +true+
|
473
|
-
#
|
474
|
-
#
|
475
|
-
# (less demanding) spec.
|
527
|
+
# +rewindable_input+ defaults to +true+ for compatibility.
|
528
|
+
# Setting it to +false+ may be safe for applications and
|
529
|
+
# frameworks developed for Rack 2.x and later.
|
476
530
|
def rewindable_input(bool)
|
477
531
|
set_bool(:rewindable_input, bool)
|
478
532
|
end
|
@@ -548,6 +602,10 @@ def working_directory(path)
|
|
548
602
|
# This switch will occur after calling the after_fork hook, and only
|
549
603
|
# if the Worker#user method is not called in the after_fork hook
|
550
604
|
# +group+ is optional and will not change if unspecified.
|
605
|
+
#
|
606
|
+
# Do not use Configurator#user if you rely on changing users in the
|
607
|
+
# after_worker_ready hook. Instead, you need to call Worker#user
|
608
|
+
# directly in after_worker_ready.
|
551
609
|
def user(user, group = nil)
|
552
610
|
# raises ArgumentError on invalid user/group
|
553
611
|
Etc.getpwnam(user)
|
data/lib/unicorn/http_request.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
# :enddoc:
|
3
3
|
# no stable API here
|
4
4
|
require 'unicorn_http'
|
5
|
+
require 'raindrops'
|
5
6
|
|
6
7
|
# TODO: remove redundant names
|
7
8
|
Unicorn.const_set(:HttpRequest, Unicorn::HttpParser)
|
@@ -24,12 +25,11 @@ class Unicorn::HttpParser
|
|
24
25
|
NULL_IO = StringIO.new("")
|
25
26
|
|
26
27
|
# :stopdoc:
|
27
|
-
|
28
|
-
|
29
|
-
# 2.2+ optimizes hash assignments when used with literal string keys
|
30
|
-
HTTP_RESPONSE_START = [ 'HTTP', '/1.1 ']
|
28
|
+
HTTP_RESPONSE_START = [ 'HTTP'.freeze, '/1.1 '.freeze ]
|
29
|
+
EMPTY_ARRAY = [].freeze
|
31
30
|
@@input_class = Unicorn::TeeInput
|
32
31
|
@@check_client_connection = false
|
32
|
+
@@tcpi_inspect_ok = Socket.const_defined?(:TCP_INFO)
|
33
33
|
|
34
34
|
def self.input_class
|
35
35
|
@@input_class
|
@@ -83,11 +83,7 @@ def read(socket)
|
|
83
83
|
false until add_parse(socket.kgio_read!(16384))
|
84
84
|
end
|
85
85
|
|
86
|
-
|
87
|
-
if @@check_client_connection && headers?
|
88
|
-
self.response_start_sent = true
|
89
|
-
HTTP_RESPONSE_START.each { |c| socket.write(c) }
|
90
|
-
end
|
86
|
+
check_client_connection(socket) if @@check_client_connection
|
91
87
|
|
92
88
|
e['rack.input'] = 0 == content_length ?
|
93
89
|
NULL_IO : @@input_class.new(socket, self)
|
@@ -108,4 +104,88 @@ def call
|
|
108
104
|
def hijacked?
|
109
105
|
env.include?('rack.hijack_io'.freeze)
|
110
106
|
end
|
107
|
+
|
108
|
+
if Raindrops.const_defined?(:TCP_Info)
|
109
|
+
TCPI = Raindrops::TCP_Info.allocate
|
110
|
+
|
111
|
+
def check_client_connection(socket) # :nodoc:
|
112
|
+
if Unicorn::TCPClient === socket
|
113
|
+
# Raindrops::TCP_Info#get!, #state (reads struct tcp_info#tcpi_state)
|
114
|
+
raise Errno::EPIPE, "client closed connection".freeze,
|
115
|
+
EMPTY_ARRAY if closed_state?(TCPI.get!(socket).state)
|
116
|
+
else
|
117
|
+
write_http_header(socket)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
if Raindrops.const_defined?(:TCP)
|
122
|
+
# raindrops 0.18.0+ supports FreeBSD + Linux using the same names
|
123
|
+
# Evaluate these hash lookups at load time so we can
|
124
|
+
# generate an opt_case_dispatch instruction
|
125
|
+
eval <<-EOS
|
126
|
+
def closed_state?(state) # :nodoc:
|
127
|
+
case state
|
128
|
+
when #{Raindrops::TCP[:ESTABLISHED]}
|
129
|
+
false
|
130
|
+
when #{Raindrops::TCP.values_at(
|
131
|
+
:CLOSE_WAIT, :TIME_WAIT, :CLOSE, :LAST_ACK, :CLOSING).join(',')}
|
132
|
+
true
|
133
|
+
else
|
134
|
+
false
|
135
|
+
end
|
136
|
+
end
|
137
|
+
EOS
|
138
|
+
else
|
139
|
+
# raindrops before 0.18 only supported TCP_INFO under Linux
|
140
|
+
def closed_state?(state) # :nodoc:
|
141
|
+
case state
|
142
|
+
when 1 # ESTABLISHED
|
143
|
+
false
|
144
|
+
when 8, 6, 7, 9, 11 # CLOSE_WAIT, TIME_WAIT, CLOSE, LAST_ACK, CLOSING
|
145
|
+
true
|
146
|
+
else
|
147
|
+
false
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
else
|
152
|
+
|
153
|
+
# Ruby 2.2+ can show struct tcp_info as a string Socket::Option#inspect.
|
154
|
+
# Not that efficient, but probably still better than doing unnecessary
|
155
|
+
# work after a client gives up.
|
156
|
+
def check_client_connection(socket) # :nodoc:
|
157
|
+
if Unicorn::TCPClient === socket && @@tcpi_inspect_ok
|
158
|
+
opt = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO).inspect
|
159
|
+
if opt =~ /\bstate=(\S+)/
|
160
|
+
raise Errno::EPIPE, "client closed connection".freeze,
|
161
|
+
EMPTY_ARRAY if closed_state_str?($1)
|
162
|
+
else
|
163
|
+
@@tcpi_inspect_ok = false
|
164
|
+
write_http_header(socket)
|
165
|
+
end
|
166
|
+
opt.clear
|
167
|
+
else
|
168
|
+
write_http_header(socket)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def closed_state_str?(state)
|
173
|
+
case state
|
174
|
+
when 'ESTABLISHED'
|
175
|
+
false
|
176
|
+
# not a typo, ruby maps TCP_CLOSE (no 'D') to state=CLOSED (w/ 'D')
|
177
|
+
when 'CLOSE_WAIT', 'TIME_WAIT', 'CLOSED', 'LAST_ACK', 'CLOSING'
|
178
|
+
true
|
179
|
+
else
|
180
|
+
false
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def write_http_header(socket) # :nodoc:
|
186
|
+
if headers?
|
187
|
+
self.response_start_sent = true
|
188
|
+
HTTP_RESPONSE_START.each { |c| socket.write(c) }
|
189
|
+
end
|
190
|
+
end
|
111
191
|
end
|
data/lib/unicorn/http_server.rb
CHANGED
@@ -15,6 +15,7 @@ class Unicorn::HttpServer
|
|
15
15
|
:before_fork, :after_fork, :before_exec,
|
16
16
|
:listener_opts, :preload_app,
|
17
17
|
:orig_app, :config, :ready_pipe, :user
|
18
|
+
attr_writer :after_worker_exit, :after_worker_ready, :worker_exec
|
18
19
|
|
19
20
|
attr_reader :pid, :logger
|
20
21
|
include Unicorn::SocketHelper
|
@@ -88,6 +89,7 @@ def initialize(app, options = {})
|
|
88
89
|
@self_pipe = []
|
89
90
|
@workers = {} # hash maps PIDs to Workers
|
90
91
|
@sig_queue = [] # signal queue used for self-piping
|
92
|
+
@pid = nil
|
91
93
|
|
92
94
|
# we try inheriting listeners first, so we bind them later.
|
93
95
|
# we don't write the pid file until we've bound listeners in case
|
@@ -104,6 +106,14 @@ def initialize(app, options = {})
|
|
104
106
|
# list of signals we care about and trap in master.
|
105
107
|
@queue_sigs = [
|
106
108
|
:WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ]
|
109
|
+
|
110
|
+
@worker_data = if worker_data = ENV['UNICORN_WORKER']
|
111
|
+
worker_data = worker_data.split(',').map!(&:to_i)
|
112
|
+
worker_data[1] = worker_data.slice!(1..2).map do |i|
|
113
|
+
Kgio::Pipe.for_fd(i)
|
114
|
+
end
|
115
|
+
worker_data
|
116
|
+
end
|
107
117
|
end
|
108
118
|
|
109
119
|
# Runs the thing. Returns self so you can run join on it
|
@@ -112,7 +122,7 @@ def start
|
|
112
122
|
# this pipe is used to wake us up from select(2) in #join when signals
|
113
123
|
# are trapped. See trap_deferred.
|
114
124
|
@self_pipe.replace(Unicorn.pipe)
|
115
|
-
@master_pid = $$
|
125
|
+
@master_pid = @worker_data ? Process.ppid : $$
|
116
126
|
|
117
127
|
# setup signal handlers before writing pid file in case people get
|
118
128
|
# trigger happy and send signals as soon as the pid file exists.
|
@@ -395,8 +405,7 @@ def reap_all_workers
|
|
395
405
|
proc_name 'master'
|
396
406
|
else
|
397
407
|
worker = @workers.delete(wpid) and worker.close rescue nil
|
398
|
-
|
399
|
-
status.success? ? logger.info(m) : logger.error(m)
|
408
|
+
@after_worker_exit.call(self, worker, status)
|
400
409
|
end
|
401
410
|
rescue Errno::ECHILD
|
402
411
|
break
|
@@ -430,11 +439,7 @@ def reexec
|
|
430
439
|
end
|
431
440
|
|
432
441
|
@reexec_pid = fork do
|
433
|
-
listener_fds =
|
434
|
-
LISTENERS.each do |sock|
|
435
|
-
sock.close_on_exec = false
|
436
|
-
listener_fds[sock.fileno] = sock
|
437
|
-
end
|
442
|
+
listener_fds = listener_sockets
|
438
443
|
ENV['UNICORN_FD'] = listener_fds.keys.join(',')
|
439
444
|
Dir.chdir(START_CTX[:cwd])
|
440
445
|
cmd = [ START_CTX[0] ].concat(START_CTX[:argv])
|
@@ -442,12 +447,7 @@ def reexec
|
|
442
447
|
# avoid leaking FDs we don't know about, but let before_exec
|
443
448
|
# unset FD_CLOEXEC, if anything else in the app eventually
|
444
449
|
# relies on FD inheritence.
|
445
|
-
(
|
446
|
-
next if listener_fds.include?(io)
|
447
|
-
io = IO.for_fd(io) rescue next
|
448
|
-
io.autoclose = false
|
449
|
-
io.close_on_exec = true
|
450
|
-
end
|
450
|
+
close_sockets_on_exec(listener_fds)
|
451
451
|
|
452
452
|
# exec(command, hash) works in at least 1.9.1+, but will only be
|
453
453
|
# required in 1.9.4/2.0.0 at earliest.
|
@@ -459,6 +459,40 @@ def reexec
|
|
459
459
|
proc_name 'master (old)'
|
460
460
|
end
|
461
461
|
|
462
|
+
def worker_spawn(worker)
|
463
|
+
listener_fds = listener_sockets
|
464
|
+
env = {}
|
465
|
+
env['UNICORN_FD'] = listener_fds.keys.join(',')
|
466
|
+
|
467
|
+
listener_fds[worker.to_io.fileno] = worker.to_io
|
468
|
+
listener_fds[worker.master.fileno] = worker.master
|
469
|
+
|
470
|
+
worker_info = [worker.nr, worker.to_io.fileno, worker.master.fileno]
|
471
|
+
env['UNICORN_WORKER'] = worker_info.join(',')
|
472
|
+
|
473
|
+
close_sockets_on_exec(listener_fds)
|
474
|
+
|
475
|
+
Process.spawn(env, START_CTX[0], *START_CTX[:argv], listener_fds)
|
476
|
+
end
|
477
|
+
|
478
|
+
def listener_sockets
|
479
|
+
listener_fds = {}
|
480
|
+
LISTENERS.each do |sock|
|
481
|
+
sock.close_on_exec = false
|
482
|
+
listener_fds[sock.fileno] = sock
|
483
|
+
end
|
484
|
+
listener_fds
|
485
|
+
end
|
486
|
+
|
487
|
+
def close_sockets_on_exec(sockets)
|
488
|
+
(3..1024).each do |io|
|
489
|
+
next if sockets.include?(io)
|
490
|
+
io = IO.for_fd(io) rescue next
|
491
|
+
io.autoclose = false
|
492
|
+
io.close_on_exec = true
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
462
496
|
# forcibly terminate all workers that haven't checked in in timeout seconds. The timeout is implemented using an unlinked File
|
463
497
|
def murder_lazy_workers
|
464
498
|
next_sleep = @timeout - 1
|
@@ -495,19 +529,29 @@ def after_fork_internal
|
|
495
529
|
end
|
496
530
|
|
497
531
|
def spawn_missing_workers
|
532
|
+
if @worker_data
|
533
|
+
worker = Unicorn::Worker.new(*@worker_data)
|
534
|
+
after_fork_internal
|
535
|
+
worker_loop(worker)
|
536
|
+
exit
|
537
|
+
end
|
538
|
+
|
498
539
|
worker_nr = -1
|
499
540
|
until (worker_nr += 1) == @worker_processes
|
500
541
|
@workers.value?(worker_nr) and next
|
501
542
|
worker = Unicorn::Worker.new(worker_nr)
|
502
543
|
before_fork.call(self, worker)
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
544
|
+
|
545
|
+
pid = @worker_exec ? worker_spawn(worker) : fork
|
546
|
+
|
547
|
+
unless pid
|
507
548
|
after_fork_internal
|
508
549
|
worker_loop(worker)
|
509
550
|
exit
|
510
551
|
end
|
552
|
+
|
553
|
+
@workers[pid] = worker
|
554
|
+
worker.atfork_parent
|
511
555
|
end
|
512
556
|
rescue => e
|
513
557
|
@logger.error(e) rescue nil
|
@@ -644,7 +688,7 @@ def worker_loop(worker)
|
|
644
688
|
trap(:USR1) { nr = -65536 }
|
645
689
|
|
646
690
|
ready = readers.dup
|
647
|
-
@
|
691
|
+
@after_worker_ready.call(self, worker)
|
648
692
|
|
649
693
|
begin
|
650
694
|
nr < 0 and reopen_worker_logs(worker.nr)
|