unicorn 5.2.0 → 5.3.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 +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)
|