unicorn 5.0.1 → 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 +6 -4
- data/Documentation/unicorn.1.txt +2 -3
- data/Documentation/unicorn_rails.1.txt +3 -3
- data/FAQ +1 -1
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +3 -3
- data/HACKING +1 -2
- data/ISSUES +21 -17
- data/KNOWN_ISSUES +2 -2
- data/LATEST +82 -28
- data/Links +3 -3
- data/NEWS +270 -0
- data/README +7 -19
- data/SIGNALS +1 -1
- data/Sandbox +6 -5
- data/TODO +0 -2
- data/TUNING +19 -1
- data/examples/big_app_gc.rb +1 -1
- data/examples/init.sh +36 -8
- data/examples/logrotate.conf +16 -1
- data/examples/nginx.conf +1 -1
- data/examples/unicorn.conf.minimal.rb +2 -2
- data/examples/unicorn.conf.rb +2 -2
- data/examples/unicorn@.service +7 -0
- 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 +673 -358
- data/ext/unicorn_http/unicorn_http.rl +32 -16
- data/ext/unicorn_http/unicorn_http_common.rl +1 -1
- data/lib/unicorn/configurator.rb +71 -12
- data/lib/unicorn/http_request.rb +89 -9
- data/lib/unicorn/http_response.rb +5 -2
- data/lib/unicorn/http_server.rb +66 -22
- data/lib/unicorn/oob_gc.rb +4 -4
- 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/lib/unicorn.rb +31 -20
- data/man/man1/unicorn.1 +54 -55
- data/man/man1/unicorn_rails.1 +47 -46
- 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 +31 -17
- data/test/unit/test_socket_helper.rb +8 -4
- data/test/unit/test_util.rb +2 -2
- data/unicorn.gemspec +15 -17
- metadata +6 -19
|
@@ -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
|
|
@@ -442,11 +442,31 @@ post_exec: /* "_out:" also goes here */
|
|
|
442
442
|
assert(hp->offset <= len && "offset longer than length");
|
|
443
443
|
}
|
|
444
444
|
|
|
445
|
+
static void hp_mark(void *ptr)
|
|
446
|
+
{
|
|
447
|
+
struct http_parser *hp = ptr;
|
|
448
|
+
|
|
449
|
+
rb_gc_mark(hp->buf);
|
|
450
|
+
rb_gc_mark(hp->env);
|
|
451
|
+
rb_gc_mark(hp->cont);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
static size_t hp_memsize(const void *ptr)
|
|
455
|
+
{
|
|
456
|
+
return sizeof(struct http_parser);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
static const rb_data_type_t hp_type = {
|
|
460
|
+
"unicorn_http",
|
|
461
|
+
{ hp_mark, RUBY_TYPED_DEFAULT_FREE, hp_memsize, /* reserved */ },
|
|
462
|
+
/* parent, data, [ flags ] */
|
|
463
|
+
};
|
|
464
|
+
|
|
445
465
|
static struct http_parser *data_get(VALUE self)
|
|
446
466
|
{
|
|
447
467
|
struct http_parser *hp;
|
|
448
468
|
|
|
449
|
-
|
|
469
|
+
TypedData_Get_Struct(self, struct http_parser, &hp_type, hp);
|
|
450
470
|
assert(hp && "failed to extract http_parser struct");
|
|
451
471
|
return hp;
|
|
452
472
|
}
|
|
@@ -552,21 +572,12 @@ static void finalize_header(struct http_parser *hp)
|
|
|
552
572
|
rb_hash_aset(hp->env, g_query_string, rb_str_new(NULL, 0));
|
|
553
573
|
}
|
|
554
574
|
|
|
555
|
-
static void hp_mark(void *ptr)
|
|
556
|
-
{
|
|
557
|
-
struct http_parser *hp = ptr;
|
|
558
|
-
|
|
559
|
-
rb_gc_mark(hp->buf);
|
|
560
|
-
rb_gc_mark(hp->env);
|
|
561
|
-
rb_gc_mark(hp->cont);
|
|
562
|
-
}
|
|
563
|
-
|
|
564
575
|
static VALUE HttpParser_alloc(VALUE klass)
|
|
565
576
|
{
|
|
566
577
|
struct http_parser *hp;
|
|
567
|
-
return Data_Make_Struct(klass, struct http_parser, hp_mark, -1, hp);
|
|
568
|
-
}
|
|
569
578
|
|
|
579
|
+
return TypedData_Make_Struct(klass, struct http_parser, &hp_type, hp);
|
|
580
|
+
}
|
|
570
581
|
|
|
571
582
|
/**
|
|
572
583
|
* call-seq:
|
|
@@ -906,8 +917,10 @@ static VALUE HttpParser_rssget(VALUE self)
|
|
|
906
917
|
|
|
907
918
|
void Init_unicorn_http(void)
|
|
908
919
|
{
|
|
920
|
+
static VALUE mark_ary;
|
|
909
921
|
VALUE mUnicorn, cHttpParser;
|
|
910
922
|
|
|
923
|
+
mark_ary = rb_ary_new();
|
|
911
924
|
mUnicorn = rb_define_module("Unicorn");
|
|
912
925
|
cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
|
|
913
926
|
eHttpParserError =
|
|
@@ -917,7 +930,7 @@ void Init_unicorn_http(void)
|
|
|
917
930
|
e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError",
|
|
918
931
|
eHttpParserError);
|
|
919
932
|
|
|
920
|
-
init_globals();
|
|
933
|
+
init_globals(mark_ary);
|
|
921
934
|
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
|
922
935
|
rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
|
|
923
936
|
rb_define_method(cHttpParser, "clear", HttpParser_clear, 0);
|
|
@@ -953,14 +966,17 @@ void Init_unicorn_http(void)
|
|
|
953
966
|
|
|
954
967
|
rb_define_singleton_method(cHttpParser, "max_header_len=", set_maxhdrlen, 1);
|
|
955
968
|
|
|
956
|
-
init_common_fields();
|
|
969
|
+
init_common_fields(mark_ary);
|
|
957
970
|
SET_GLOBAL(g_http_host, "HOST");
|
|
958
971
|
SET_GLOBAL(g_http_trailer, "TRAILER");
|
|
959
972
|
SET_GLOBAL(g_http_transfer_encoding, "TRANSFER_ENCODING");
|
|
960
973
|
SET_GLOBAL(g_content_length, "CONTENT_LENGTH");
|
|
961
974
|
SET_GLOBAL(g_http_connection, "CONNECTION");
|
|
962
975
|
id_set_backtrace = rb_intern("set_backtrace");
|
|
963
|
-
init_unicorn_httpdate();
|
|
976
|
+
init_unicorn_httpdate(mark_ary);
|
|
977
|
+
|
|
978
|
+
OBJ_FREEZE(mark_ary);
|
|
979
|
+
rb_global_variable(&mark_ary);
|
|
964
980
|
|
|
965
981
|
#ifndef HAVE_RB_HASH_CLEAR
|
|
966
982
|
id_clear = rb_intern("clear");
|
data/lib/unicorn/configurator.rb
CHANGED
|
@@ -3,11 +3,11 @@ require 'logger'
|
|
|
3
3
|
|
|
4
4
|
# Implements a simple DSL for configuring a unicorn server.
|
|
5
5
|
#
|
|
6
|
-
# See
|
|
7
|
-
#
|
|
6
|
+
# See https://bogomips.org/unicorn/examples/unicorn.conf.rb and
|
|
7
|
+
# https://bogomips.org/unicorn/examples/unicorn.conf.minimal.rb
|
|
8
8
|
# example configuration files. An example config file for use with
|
|
9
9
|
# nginx is also available at
|
|
10
|
-
#
|
|
10
|
+
# https://bogomips.org/unicorn/examples/nginx.conf
|
|
11
11
|
#
|
|
12
12
|
# See the link:/TUNING.html document for more information on tuning unicorn.
|
|
13
13
|
class Unicorn::Configurator
|
|
@@ -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 @@ class Unicorn::Configurator
|
|
|
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.
|
|
@@ -181,8 +225,6 @@ class Unicorn::Configurator
|
|
|
181
225
|
# to have nginx always retry backends that may have had workers
|
|
182
226
|
# SIGKILL-ed due to timeouts.
|
|
183
227
|
#
|
|
184
|
-
# # See http://wiki.nginx.org/NginxHttpUpstreamModule for more details
|
|
185
|
-
# # on nginx upstream configuration:
|
|
186
228
|
# upstream unicorn_backend {
|
|
187
229
|
# # for UNIX domain socket setups:
|
|
188
230
|
# server unix:/path/to/.unicorn.sock fail_timeout=0;
|
|
@@ -192,6 +234,9 @@ class Unicorn::Configurator
|
|
|
192
234
|
# server 192.168.0.8:8080 fail_timeout=0;
|
|
193
235
|
# server 192.168.0.9:8080 fail_timeout=0;
|
|
194
236
|
# }
|
|
237
|
+
#
|
|
238
|
+
# See http://nginx.org/en/docs/http/ngx_http_upstream_module.html
|
|
239
|
+
# for more details on nginx upstream configuration.
|
|
195
240
|
def timeout(seconds)
|
|
196
241
|
set_int(:timeout, seconds, 3)
|
|
197
242
|
# POSIX says 31 days is the smallest allowed maximum timeout for select()
|
|
@@ -199,6 +244,17 @@ class Unicorn::Configurator
|
|
|
199
244
|
set[:timeout] = seconds > max ? max : seconds
|
|
200
245
|
end
|
|
201
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
|
+
|
|
202
258
|
# sets the current number of worker_processes to +nr+. Each worker
|
|
203
259
|
# process will serve exactly one client at a time. You can
|
|
204
260
|
# increment or decrement this value at runtime by sending SIGTTIN
|
|
@@ -465,13 +521,12 @@ class Unicorn::Configurator
|
|
|
465
521
|
# Disabling rewindability can improve performance by lowering
|
|
466
522
|
# I/O and memory usage for applications that accept uploads.
|
|
467
523
|
# Keep in mind that the Rack 1.x spec requires
|
|
468
|
-
# \env[\"rack.input\"] to be rewindable,
|
|
469
|
-
#
|
|
524
|
+
# \env[\"rack.input\"] to be rewindable,
|
|
525
|
+
# but the Rack 2.x spec does not.
|
|
470
526
|
#
|
|
471
|
-
# +rewindable_input+ defaults to +true+
|
|
472
|
-
#
|
|
473
|
-
#
|
|
474
|
-
# (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.
|
|
475
530
|
def rewindable_input(bool)
|
|
476
531
|
set_bool(:rewindable_input, bool)
|
|
477
532
|
end
|
|
@@ -547,6 +602,10 @@ class Unicorn::Configurator
|
|
|
547
602
|
# This switch will occur after calling the after_fork hook, and only
|
|
548
603
|
# if the Worker#user method is not called in the after_fork hook
|
|
549
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.
|
|
550
609
|
def user(user, group = nil)
|
|
551
610
|
# raises ArgumentError on invalid user/group
|
|
552
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 @@ class Unicorn::HttpParser
|
|
|
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 @@ class Unicorn::HttpParser
|
|
|
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
|
|
@@ -10,10 +10,13 @@
|
|
|
10
10
|
# is the job of Rack, with the exception of the "Date" and "Status" header.
|
|
11
11
|
module Unicorn::HttpResponse
|
|
12
12
|
|
|
13
|
+
STATUS_CODES = defined?(Rack::Utils::HTTP_STATUS_CODES) ?
|
|
14
|
+
Rack::Utils::HTTP_STATUS_CODES : {}
|
|
15
|
+
|
|
13
16
|
# internal API, code will always be common-enough-for-even-old-Rack
|
|
14
17
|
def err_response(code, response_start_sent)
|
|
15
18
|
"#{response_start_sent ? '' : 'HTTP/1.1 '}" \
|
|
16
|
-
"#{code} #{
|
|
19
|
+
"#{code} #{STATUS_CODES[code]}\r\n\r\n"
|
|
17
20
|
end
|
|
18
21
|
|
|
19
22
|
# writes the rack_response to socket as an HTTP response
|
|
@@ -23,7 +26,7 @@ module Unicorn::HttpResponse
|
|
|
23
26
|
|
|
24
27
|
if headers
|
|
25
28
|
code = status.to_i
|
|
26
|
-
msg =
|
|
29
|
+
msg = STATUS_CODES[code]
|
|
27
30
|
start = response_start_sent ? ''.freeze : 'HTTP/1.1 '.freeze
|
|
28
31
|
buf = "#{start}#{msg ? %Q(#{code} #{msg}) : status}\r\n" \
|
|
29
32
|
"Date: #{httpdate}\r\n" \
|
data/lib/unicorn/http_server.rb
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# forked worker children.
|
|
7
7
|
#
|
|
8
8
|
# Users do not need to know the internals of this class, but reading the
|
|
9
|
-
# {source}[
|
|
9
|
+
# {source}[https://bogomips.org/unicorn.git/tree/lib/unicorn/http_server.rb]
|
|
10
10
|
# is education for programmers wishing to learn how unicorn works.
|
|
11
11
|
# See Unicorn::Configurator for information on how to configure unicorn.
|
|
12
12
|
class Unicorn::HttpServer
|
|
@@ -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
|
|
@@ -36,7 +37,7 @@ class Unicorn::HttpServer
|
|
|
36
37
|
# or even different installations of the same applications without
|
|
37
38
|
# downtime. Keys of this constant Hash are described as follows:
|
|
38
39
|
#
|
|
39
|
-
# * 0 - the path to the unicorn
|
|
40
|
+
# * 0 - the path to the unicorn executable
|
|
40
41
|
# * :argv - a deep copy of the ARGV array the executable originally saw
|
|
41
42
|
# * :cwd - the working directory of the application, this is where
|
|
42
43
|
# you originally started Unicorn.
|
|
@@ -45,7 +46,7 @@ class Unicorn::HttpServer
|
|
|
45
46
|
# you can set the following in your Unicorn config file, HUP and then
|
|
46
47
|
# continue with the traditional USR2 + QUIT upgrade steps:
|
|
47
48
|
#
|
|
48
|
-
# Unicorn::HttpServer::START_CTX[0] = "/home/bofh/2.
|
|
49
|
+
# Unicorn::HttpServer::START_CTX[0] = "/home/bofh/2.3.0/bin/unicorn"
|
|
49
50
|
START_CTX = {
|
|
50
51
|
:argv => ARGV.map(&:dup),
|
|
51
52
|
0 => $0.dup,
|
|
@@ -88,6 +89,7 @@ class Unicorn::HttpServer
|
|
|
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 @@ class Unicorn::HttpServer
|
|
|
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 @@ class Unicorn::HttpServer
|
|
|
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 @@ class Unicorn::HttpServer
|
|
|
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 @@ class Unicorn::HttpServer
|
|
|
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 @@ class Unicorn::HttpServer
|
|
|
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 @@ class Unicorn::HttpServer
|
|
|
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 @@ class Unicorn::HttpServer
|
|
|
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 @@ class Unicorn::HttpServer
|
|
|
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)
|
data/lib/unicorn/oob_gc.rb
CHANGED
|
@@ -43,8 +43,9 @@
|
|
|
43
43
|
# use Unicorn::OobGC, 2, %r{\A/(?:expensive/foo|more_expensive/foo)}
|
|
44
44
|
#
|
|
45
45
|
# Feedback from users of early implementations of this module:
|
|
46
|
-
# *
|
|
47
|
-
# *
|
|
46
|
+
# * https://bogomips.org/unicorn-public/0BFC98E9-072B-47EE-9A70-05478C20141B@lukemelia.com/
|
|
47
|
+
# * https://bogomips.org/unicorn-public/AANLkTilUbgdyDv9W1bi-s_W6kq9sOhWfmuYkKLoKGOLj@mail.gmail.com/
|
|
48
|
+
|
|
48
49
|
module Unicorn::OobGC
|
|
49
50
|
|
|
50
51
|
# this pretends to be Rack middleware because it used to be
|
|
@@ -65,10 +66,9 @@ module Unicorn::OobGC
|
|
|
65
66
|
end
|
|
66
67
|
|
|
67
68
|
#:stopdoc:
|
|
68
|
-
PATH_INFO = "PATH_INFO"
|
|
69
69
|
def process_client(client)
|
|
70
70
|
super(client) # Unicorn::HttpServer#process_client
|
|
71
|
-
if OOBGC_PATH =~ OOBGC_ENV[PATH_INFO] && ((@@nr -= 1) <= 0)
|
|
71
|
+
if OOBGC_PATH =~ OOBGC_ENV['PATH_INFO'] && ((@@nr -= 1) <= 0)
|
|
72
72
|
@@nr = OOBGC_INTERVAL
|
|
73
73
|
OOBGC_ENV.clear
|
|
74
74
|
disabled = GC.enable
|
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
require 'socket'
|
|
4
4
|
|
|
5
5
|
module Unicorn
|
|
6
|
+
|
|
7
|
+
# Instead of using a generic Kgio::Socket for everything,
|
|
8
|
+
# tag TCP sockets so we can use TCP_INFO under Linux without
|
|
9
|
+
# incurring extra syscalls for Unix domain sockets.
|
|
10
|
+
# TODO: remove these when we remove kgio
|
|
11
|
+
TCPClient = Class.new(Kgio::Socket) # :nodoc:
|
|
12
|
+
class TCPSrv < Kgio::TCPServer # :nodoc:
|
|
13
|
+
def kgio_tryaccept # :nodoc:
|
|
14
|
+
super(TCPClient)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
6
18
|
module SocketHelper
|
|
7
19
|
|
|
8
20
|
# internal interface
|
|
@@ -63,12 +75,15 @@ module Unicorn
|
|
|
63
75
|
elsif respond_to?(:accf_arg)
|
|
64
76
|
name = opt[:accept_filter]
|
|
65
77
|
name = DEFAULTS[:accept_filter] if name.nil?
|
|
78
|
+
sock.listen(opt[:backlog])
|
|
79
|
+
got = (sock.getsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER) rescue nil).to_s
|
|
80
|
+
arg = accf_arg(name)
|
|
66
81
|
begin
|
|
67
|
-
sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER,
|
|
82
|
+
sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER, arg)
|
|
68
83
|
rescue => e
|
|
69
84
|
logger.error("#{sock_name(sock)} " \
|
|
70
85
|
"failed to set accept_filter=#{name} (#{e.inspect})")
|
|
71
|
-
end
|
|
86
|
+
end if arg != got
|
|
72
87
|
end
|
|
73
88
|
end
|
|
74
89
|
|
|
@@ -148,7 +163,7 @@ module Unicorn
|
|
|
148
163
|
end
|
|
149
164
|
sock.bind(Socket.pack_sockaddr_in(port, addr))
|
|
150
165
|
sock.autoclose = false
|
|
151
|
-
|
|
166
|
+
TCPSrv.for_fd(sock.fileno)
|
|
152
167
|
end
|
|
153
168
|
|
|
154
169
|
# returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
|
|
@@ -185,7 +200,7 @@ module Unicorn
|
|
|
185
200
|
def server_cast(sock)
|
|
186
201
|
begin
|
|
187
202
|
Socket.unpack_sockaddr_in(sock.getsockname)
|
|
188
|
-
|
|
203
|
+
TCPSrv.for_fd(sock.fileno)
|
|
189
204
|
rescue ArgumentError
|
|
190
205
|
Kgio::UNIXServer.for_fd(sock.fileno)
|
|
191
206
|
end
|
data/lib/unicorn/stream_input.rb
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
# -*- encoding: binary -*-
|
|
2
2
|
|
|
3
|
-
# When processing uploads,
|
|
4
|
-
# "rack.input" of the
|
|
3
|
+
# When processing uploads, unicorn may expose a StreamInput object under
|
|
4
|
+
# "rack.input" of the Rack environment when
|
|
5
|
+
# Unicorn::Configurator#rewindable_input is set to +false+
|
|
5
6
|
class Unicorn::StreamInput
|
|
6
7
|
# The I/O chunk size (in +bytes+) for I/O operations where
|
|
7
8
|
# the size cannot be user-specified when a method is called.
|
|
8
9
|
# The default is 16 kilobytes.
|
|
9
|
-
@@io_chunk_size = Unicorn::Const::CHUNK_SIZE
|
|
10
|
+
@@io_chunk_size = Unicorn::Const::CHUNK_SIZE # :nodoc:
|
|
10
11
|
|
|
11
12
|
# Initializes a new StreamInput object. You normally do not have to call
|
|
12
13
|
# this unless you are writing an HTTP server.
|
|
13
|
-
def initialize(socket, request)
|
|
14
|
+
def initialize(socket, request) # :nodoc:
|
|
14
15
|
@chunked = request.content_length.nil?
|
|
15
16
|
@socket = socket
|
|
16
17
|
@parser = request
|