unicorn 5.4.1 → 6.1.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 +10 -5
- data/.olddoc.yml +15 -7
- data/Application_Timeouts +4 -4
- data/CONTRIBUTORS +6 -2
- data/Documentation/.gitignore +1 -3
- data/Documentation/unicorn.1 +222 -0
- data/Documentation/unicorn_rails.1 +207 -0
- data/FAQ +1 -1
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +112 -57
- data/HACKING +2 -9
- data/ISSUES +29 -33
- data/KNOWN_ISSUES +2 -2
- data/LATEST +16 -21
- data/LICENSE +2 -2
- data/Links +13 -11
- data/NEWS +197 -0
- data/README +27 -14
- data/SIGNALS +1 -1
- data/Sandbox +5 -5
- data/archive/slrnpull.conf +1 -1
- data/bin/unicorn +3 -1
- data/bin/unicorn_rails +2 -2
- data/examples/big_app_gc.rb +1 -1
- data/examples/logrotate.conf +3 -3
- data/examples/nginx.conf +4 -3
- 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/c_util.h +5 -13
- data/ext/unicorn_http/common_field_optimization.h +23 -6
- data/ext/unicorn_http/epollexclusive.h +124 -0
- data/ext/unicorn_http/ext_help.h +0 -24
- data/ext/unicorn_http/extconf.rb +32 -6
- data/ext/unicorn_http/global_variables.h +3 -3
- data/ext/unicorn_http/httpdate.c +3 -2
- data/ext/unicorn_http/unicorn_http.c +262 -237
- data/ext/unicorn_http/unicorn_http.rl +52 -27
- data/lib/unicorn/configurator.rb +25 -4
- data/lib/unicorn/http_request.rb +12 -3
- data/lib/unicorn/http_server.rb +62 -36
- data/lib/unicorn/launcher.rb +1 -1
- data/lib/unicorn/oob_gc.rb +5 -5
- data/lib/unicorn/select_waiter.rb +6 -0
- data/lib/unicorn/socket_helper.rb +1 -0
- data/lib/unicorn/tmpio.rb +8 -2
- data/lib/unicorn/util.rb +1 -1
- data/lib/unicorn/version.rb +1 -1
- data/lib/unicorn/worker.rb +16 -2
- data/lib/unicorn.rb +21 -9
- data/man/man1/unicorn.1 +89 -88
- data/man/man1/unicorn_rails.1 +78 -83
- data/t/GNUmakefile +3 -72
- data/t/README +4 -4
- data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
- data/t/t0301.ru +13 -0
- data/t/test-lib.sh +2 -1
- 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 +14 -12
- data/test/test_helper.rb +38 -30
- data/test/unit/test_ccc.rb +4 -3
- data/test/unit/test_http_parser.rb +16 -0
- data/test/unit/test_http_parser_ng.rb +81 -0
- data/test/unit/test_server.rb +81 -7
- data/test/unit/test_signals.rb +6 -6
- data/test/unit/test_socket_helper.rb +1 -1
- data/test/unit/test_upload.rb +9 -14
- data/test/unit/test_util.rb +29 -3
- data/test/unit/test_waiter.rb +34 -0
- data/unicorn.gemspec +8 -7
- metadata +19 -13
- data/Documentation/GNUmakefile +0 -30
- data/Documentation/unicorn.1.txt +0 -187
- data/Documentation/unicorn_rails.1.txt +0 -175
- data/t/hijack.ru +0 -55
- data/t/t0200-rack-hijack.sh +0 -51
@@ -12,8 +12,9 @@
|
|
12
12
|
#include "common_field_optimization.h"
|
13
13
|
#include "global_variables.h"
|
14
14
|
#include "c_util.h"
|
15
|
+
#include "epollexclusive.h"
|
15
16
|
|
16
|
-
void init_unicorn_httpdate(
|
17
|
+
void init_unicorn_httpdate(void);
|
17
18
|
|
18
19
|
#define UH_FL_CHUNKED 0x1
|
19
20
|
#define UH_FL_HASBODY 0x2
|
@@ -62,19 +63,8 @@ struct http_parser {
|
|
62
63
|
} len;
|
63
64
|
};
|
64
65
|
|
65
|
-
static ID id_set_backtrace;
|
66
|
-
|
67
|
-
#ifdef HAVE_RB_HASH_CLEAR /* Ruby >= 2.0 */
|
68
|
-
# define my_hash_clear(h) (void)rb_hash_clear(h)
|
69
|
-
#else /* !HAVE_RB_HASH_CLEAR - Ruby <= 1.9.3 */
|
70
|
-
|
71
|
-
static ID id_clear;
|
72
|
-
|
73
|
-
static void my_hash_clear(VALUE h)
|
74
|
-
{
|
75
|
-
rb_funcall(h, id_clear, 0);
|
76
|
-
}
|
77
|
-
#endif /* HAVE_RB_HASH_CLEAR */
|
66
|
+
static ID id_set_backtrace, id_is_chunked_p;
|
67
|
+
static VALUE cHttpParser;
|
78
68
|
|
79
69
|
static void finalize_header(struct http_parser *hp);
|
80
70
|
|
@@ -220,6 +210,19 @@ static void write_cont_value(struct http_parser *hp,
|
|
220
210
|
rb_str_buf_cat(hp->cont, vptr, end + 1);
|
221
211
|
}
|
222
212
|
|
213
|
+
static int is_chunked(VALUE v)
|
214
|
+
{
|
215
|
+
/* common case first */
|
216
|
+
if (STR_CSTR_CASE_EQ(v, "chunked"))
|
217
|
+
return 1;
|
218
|
+
|
219
|
+
/*
|
220
|
+
* call Ruby function in unicorn/http_request.rb to deal with unlikely
|
221
|
+
* comma-delimited case
|
222
|
+
*/
|
223
|
+
return rb_funcall(cHttpParser, id_is_chunked_p, 1, v) != Qfalse;
|
224
|
+
}
|
225
|
+
|
223
226
|
static void write_value(struct http_parser *hp,
|
224
227
|
const char *buffer, const char *p)
|
225
228
|
{
|
@@ -246,7 +249,9 @@ static void write_value(struct http_parser *hp,
|
|
246
249
|
f = uncommon_field(field, flen);
|
247
250
|
} else if (f == g_http_connection) {
|
248
251
|
hp_keepalive_connection(hp, v);
|
249
|
-
} else if (f == g_content_length) {
|
252
|
+
} else if (f == g_content_length && !HP_FL_TEST(hp, CHUNKED)) {
|
253
|
+
if (hp->len.content)
|
254
|
+
parser_raise(eHttpParserError, "Content-Length already set");
|
250
255
|
hp->len.content = parse_length(RSTRING_PTR(v), RSTRING_LEN(v));
|
251
256
|
if (hp->len.content < 0)
|
252
257
|
parser_raise(eHttpParserError, "invalid Content-Length");
|
@@ -254,9 +259,30 @@ static void write_value(struct http_parser *hp,
|
|
254
259
|
HP_FL_SET(hp, HASBODY);
|
255
260
|
hp_invalid_if_trailer(hp);
|
256
261
|
} else if (f == g_http_transfer_encoding) {
|
257
|
-
if (
|
262
|
+
if (is_chunked(v)) {
|
263
|
+
if (HP_FL_TEST(hp, CHUNKED))
|
264
|
+
/*
|
265
|
+
* RFC 7230 3.3.1:
|
266
|
+
* A sender MUST NOT apply chunked more than once to a message body
|
267
|
+
* (i.e., chunking an already chunked message is not allowed).
|
268
|
+
*/
|
269
|
+
parser_raise(eHttpParserError, "Transfer-Encoding double chunked");
|
270
|
+
|
258
271
|
HP_FL_SET(hp, CHUNKED);
|
259
272
|
HP_FL_SET(hp, HASBODY);
|
273
|
+
|
274
|
+
/* RFC 7230 3.3.3, 3: favor chunked if Content-Length exists */
|
275
|
+
hp->len.content = 0;
|
276
|
+
} else if (HP_FL_TEST(hp, CHUNKED)) {
|
277
|
+
/*
|
278
|
+
* RFC 7230 3.3.3, point 3 states:
|
279
|
+
* If a Transfer-Encoding header field is present in a request and
|
280
|
+
* the chunked transfer coding is not the final encoding, the
|
281
|
+
* message body length cannot be determined reliably; the server
|
282
|
+
* MUST respond with the 400 (Bad Request) status code and then
|
283
|
+
* close the connection.
|
284
|
+
*/
|
285
|
+
parser_raise(eHttpParserError, "invalid Transfer-Encoding");
|
260
286
|
}
|
261
287
|
hp_invalid_if_trailer(hp);
|
262
288
|
} else if (f == g_http_trailer) {
|
@@ -487,7 +513,7 @@ static void set_url_scheme(VALUE env, VALUE *server_port)
|
|
487
513
|
* and X-Forwarded-Proto handling from this parser? We've had it
|
488
514
|
* forever and nobody has said anything against it, either.
|
489
515
|
* Anyways, please send comments to our public mailing list:
|
490
|
-
* unicorn-public@
|
516
|
+
* unicorn-public@yhbt.net (no HTML mail, no subscription necessary)
|
491
517
|
*/
|
492
518
|
scheme = rb_hash_aref(env, g_http_x_forwarded_ssl);
|
493
519
|
if (!NIL_P(scheme) && STR_CSTR_EQ(scheme, "on")) {
|
@@ -613,7 +639,7 @@ static VALUE HttpParser_clear(VALUE self)
|
|
613
639
|
return HttpParser_init(self);
|
614
640
|
|
615
641
|
http_parser_init(hp);
|
616
|
-
|
642
|
+
rb_hash_clear(hp->env);
|
617
643
|
|
618
644
|
return self;
|
619
645
|
}
|
@@ -931,11 +957,8 @@ static VALUE HttpParser_rssget(VALUE self)
|
|
931
957
|
|
932
958
|
void Init_unicorn_http(void)
|
933
959
|
{
|
934
|
-
|
935
|
-
VALUE mUnicorn, cHttpParser;
|
960
|
+
VALUE mUnicorn;
|
936
961
|
|
937
|
-
mark_ary = rb_ary_new();
|
938
|
-
rb_global_variable(&mark_ary);
|
939
962
|
mUnicorn = rb_define_module("Unicorn");
|
940
963
|
cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
|
941
964
|
eHttpParserError =
|
@@ -945,7 +968,8 @@ void Init_unicorn_http(void)
|
|
945
968
|
e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError",
|
946
969
|
eHttpParserError);
|
947
970
|
|
948
|
-
|
971
|
+
id_uminus = rb_intern("-@");
|
972
|
+
init_globals();
|
949
973
|
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
950
974
|
rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
|
951
975
|
rb_define_method(cHttpParser, "clear", HttpParser_clear, 0);
|
@@ -982,19 +1006,20 @@ void Init_unicorn_http(void)
|
|
982
1006
|
|
983
1007
|
rb_define_singleton_method(cHttpParser, "max_header_len=", set_maxhdrlen, 1);
|
984
1008
|
|
985
|
-
init_common_fields(
|
1009
|
+
init_common_fields();
|
986
1010
|
SET_GLOBAL(g_http_host, "HOST");
|
987
1011
|
SET_GLOBAL(g_http_trailer, "TRAILER");
|
988
1012
|
SET_GLOBAL(g_http_transfer_encoding, "TRANSFER_ENCODING");
|
989
1013
|
SET_GLOBAL(g_content_length, "CONTENT_LENGTH");
|
990
1014
|
SET_GLOBAL(g_http_connection, "CONNECTION");
|
991
1015
|
id_set_backtrace = rb_intern("set_backtrace");
|
992
|
-
init_unicorn_httpdate(
|
993
|
-
|
994
|
-
OBJ_FREEZE(mark_ary);
|
1016
|
+
init_unicorn_httpdate();
|
995
1017
|
|
996
1018
|
#ifndef HAVE_RB_HASH_CLEAR
|
997
1019
|
id_clear = rb_intern("clear");
|
998
1020
|
#endif
|
1021
|
+
id_is_chunked_p = rb_intern("is_chunked?");
|
1022
|
+
|
1023
|
+
init_epollexclusive(mUnicorn);
|
999
1024
|
}
|
1000
1025
|
#undef SET_GLOBAL
|
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 https://
|
7
|
-
# https://
|
6
|
+
# See https://yhbt.net/unicorn/examples/unicorn.conf.rb and
|
7
|
+
# https://yhbt.net/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
|
-
# https://
|
10
|
+
# https://yhbt.net/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
|
@@ -53,6 +53,7 @@ class Unicorn::Configurator
|
|
53
53
|
server.logger.info("worker=#{worker.nr} ready")
|
54
54
|
},
|
55
55
|
:pid => nil,
|
56
|
+
:early_hints => false,
|
56
57
|
:worker_exec => false,
|
57
58
|
:preload_app => false,
|
58
59
|
:check_client_connection => false,
|
@@ -88,6 +89,9 @@ class Unicorn::Configurator
|
|
88
89
|
RACKUP[:set_listener] and
|
89
90
|
set[:listeners] << "#{RACKUP[:host]}:#{RACKUP[:port]}"
|
90
91
|
|
92
|
+
RACKUP[:no_default_middleware] and
|
93
|
+
set[:default_middleware] = false
|
94
|
+
|
91
95
|
# unicorn_rails creates dirs here after working_directory is bound
|
92
96
|
after_reload.call if after_reload
|
93
97
|
|
@@ -235,7 +239,7 @@ class Unicorn::Configurator
|
|
235
239
|
# server 192.168.0.9:8080 fail_timeout=0;
|
236
240
|
# }
|
237
241
|
#
|
238
|
-
# See
|
242
|
+
# See https://nginx.org/en/docs/http/ngx_http_upstream_module.html
|
239
243
|
# for more details on nginx upstream configuration.
|
240
244
|
def timeout(seconds)
|
241
245
|
set_int(:timeout, seconds, 3)
|
@@ -265,6 +269,23 @@ class Unicorn::Configurator
|
|
265
269
|
set_int(:worker_processes, nr, 1)
|
266
270
|
end
|
267
271
|
|
272
|
+
# sets whether to add default middleware in the development and
|
273
|
+
# deployment RACK_ENVs.
|
274
|
+
#
|
275
|
+
# default_middleware is only available in unicorn 5.5.0+
|
276
|
+
def default_middleware(bool)
|
277
|
+
set_bool(:default_middleware, bool)
|
278
|
+
end
|
279
|
+
|
280
|
+
# sets whether to enable the proposed early hints Rack API.
|
281
|
+
# If enabled, Rails 5.2+ will automatically send a 103 Early Hint
|
282
|
+
# for all the `javascript_include_tag` and `stylesheet_link_tag`
|
283
|
+
# in your response. See: https://api.rubyonrails.org/v5.2/classes/ActionDispatch/Request.html#method-i-send_early_hints
|
284
|
+
# See also https://tools.ietf.org/html/rfc8297
|
285
|
+
def early_hints(bool)
|
286
|
+
set_bool(:early_hints, bool)
|
287
|
+
end
|
288
|
+
|
268
289
|
# sets listeners to the given +addresses+, replacing or augmenting the
|
269
290
|
# current set. This is for the global listener pool shared by all
|
270
291
|
# worker processes. For per-worker listeners, see the after_fork example
|
data/lib/unicorn/http_request.rb
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
# :enddoc:
|
3
3
|
# no stable API here
|
4
4
|
require 'unicorn_http'
|
5
|
-
require 'raindrops'
|
6
5
|
|
7
6
|
# TODO: remove redundant names
|
8
7
|
Unicorn.const_set(:HttpRequest, Unicorn::HttpParser)
|
@@ -63,10 +62,9 @@ class Unicorn::HttpParser
|
|
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
|
@@ -189,4 +187,15 @@ class Unicorn::HttpParser
|
|
189
187
|
HTTP_RESPONSE_START.each { |c| socket.write(c) }
|
190
188
|
end
|
191
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', []
|
197
|
+
end
|
198
|
+
return false unless vals.include?('chunked'.freeze)
|
199
|
+
raise Unicorn::HttpParserError, 'chunked not last', []
|
200
|
+
end
|
192
201
|
end
|
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}[https://
|
9
|
+
# {source}[https://yhbt.net/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
|
@@ -14,7 +14,8 @@ class Unicorn::HttpServer
|
|
14
14
|
attr_accessor :app, :timeout, :worker_processes,
|
15
15
|
:before_fork, :after_fork, :before_exec,
|
16
16
|
:listener_opts, :preload_app,
|
17
|
-
:orig_app, :config, :ready_pipe, :user
|
17
|
+
:orig_app, :config, :ready_pipe, :user,
|
18
|
+
:default_middleware, :early_hints
|
18
19
|
attr_writer :after_worker_exit, :after_worker_ready, :worker_exec
|
19
20
|
|
20
21
|
attr_reader :pid, :logger
|
@@ -68,8 +69,8 @@ class Unicorn::HttpServer
|
|
68
69
|
# incoming requests on the socket.
|
69
70
|
def initialize(app, options = {})
|
70
71
|
@app = app
|
71
|
-
@request = Unicorn::HttpRequest.new
|
72
72
|
@reexec_pid = 0
|
73
|
+
@default_middleware = true
|
73
74
|
options = options.dup
|
74
75
|
@ready_pipe = options.delete(:ready_pipe)
|
75
76
|
@init_listeners = options[:listeners] ? options[:listeners].dup : []
|
@@ -82,7 +83,7 @@ class Unicorn::HttpServer
|
|
82
83
|
# * The master process never closes or reinitializes this once
|
83
84
|
# initialized. Signal handlers in the master process will write to
|
84
85
|
# it to wake up the master from IO.select in exactly the same manner
|
85
|
-
# djb describes in
|
86
|
+
# djb describes in https://cr.yp.to/docs/selfpipe.html
|
86
87
|
#
|
87
88
|
# * The workers immediately close the pipe they inherit. See the
|
88
89
|
# Unicorn::Worker class for the pipe workers use.
|
@@ -380,7 +381,7 @@ class Unicorn::HttpServer
|
|
380
381
|
|
381
382
|
# wait for a signal hander to wake us up and then consume the pipe
|
382
383
|
def master_sleep(sec)
|
383
|
-
@self_pipe[0].
|
384
|
+
@self_pipe[0].wait(sec) or return
|
384
385
|
# 11 bytes is the maximum string length which can be embedded within
|
385
386
|
# the Ruby itself and not require a separate malloc (on 32-bit MRI 1.9+).
|
386
387
|
# Most reads are only one byte here and uncommon, so it's not worth a
|
@@ -520,9 +521,6 @@ class Unicorn::HttpServer
|
|
520
521
|
Unicorn::Configurator::RACKUP.clear
|
521
522
|
@ready_pipe = @init_listeners = @before_exec = @before_fork = nil
|
522
523
|
|
523
|
-
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/36450
|
524
|
-
srand # remove in unicorn 6
|
525
|
-
|
526
524
|
# The OpenSSL PRNG is seeded with only the pid, and apps with frequently
|
527
525
|
# dying workers can recycle pids
|
528
526
|
OpenSSL::Random.seed(rand.to_s) if defined?(OpenSSL::Random)
|
@@ -589,10 +587,29 @@ class Unicorn::HttpServer
|
|
589
587
|
rescue
|
590
588
|
end
|
591
589
|
|
590
|
+
def e103_response_write(client, headers)
|
591
|
+
response = if @request.response_start_sent
|
592
|
+
"103 Early Hints\r\n"
|
593
|
+
else
|
594
|
+
"HTTP/1.1 103 Early Hints\r\n"
|
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
|
+
|
592
609
|
def e100_response_write(client, env)
|
593
610
|
# We use String#freeze to avoid allocations under Ruby 2.1+
|
594
611
|
# Not many users hit this code path, so it's better to reduce the
|
595
|
-
# constant table sizes even for
|
612
|
+
# constant table sizes even for Ruby 2.0 users who'll hit extra
|
596
613
|
# allocations here.
|
597
614
|
client.write(@request.response_start_sent ?
|
598
615
|
"100 Continue\r\n\r\nHTTP/1.1 ".freeze :
|
@@ -603,7 +620,18 @@ class Unicorn::HttpServer
|
|
603
620
|
# once a client is accepted, it is processed in its entirety here
|
604
621
|
# in 3 easy steps: read request, call app, write app response
|
605
622
|
def process_client(client)
|
606
|
-
|
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
|
631
|
+
|
632
|
+
env["rack.after_reply"] = []
|
633
|
+
|
634
|
+
status, headers, body = @app.call(env)
|
607
635
|
|
608
636
|
begin
|
609
637
|
return if @request.hijacked?
|
@@ -625,6 +653,8 @@ class Unicorn::HttpServer
|
|
625
653
|
end
|
626
654
|
rescue => e
|
627
655
|
handle_error(client, e)
|
656
|
+
ensure
|
657
|
+
env["rack.after_reply"].each(&:call) if env
|
628
658
|
end
|
629
659
|
|
630
660
|
def nuke_listeners!(readers)
|
@@ -655,7 +685,6 @@ class Unicorn::HttpServer
|
|
655
685
|
LISTENERS.each { |sock| sock.close_on_exec = true }
|
656
686
|
|
657
687
|
worker.user(*user) if user.kind_of?(Array) && ! worker.switched
|
658
|
-
self.timeout /= 2.0 # halve it for select()
|
659
688
|
@config = nil
|
660
689
|
build_app! unless preload_app
|
661
690
|
@after_fork = @listener_opts = @orig_app = nil
|
@@ -669,58 +698,55 @@ class Unicorn::HttpServer
|
|
669
698
|
logger.info "worker=#{worker_nr} reopening logs..."
|
670
699
|
Unicorn::Util.reopen_logs
|
671
700
|
logger.info "worker=#{worker_nr} done reopening logs"
|
701
|
+
false
|
672
702
|
rescue => e
|
673
703
|
logger.error(e) rescue nil
|
674
704
|
exit!(77) # EX_NOPERM in sysexits.h
|
675
705
|
end
|
676
706
|
|
707
|
+
def prep_readers(readers)
|
708
|
+
wtr = Unicorn::Waiter.prep_readers(readers)
|
709
|
+
@timeout *= 500 # to milliseconds for epoll, but halved
|
710
|
+
wtr
|
711
|
+
rescue
|
712
|
+
require_relative 'select_waiter'
|
713
|
+
@timeout /= 2.0 # halved for IO.select
|
714
|
+
Unicorn::SelectWaiter.new
|
715
|
+
end
|
716
|
+
|
677
717
|
# runs inside each forked worker, this sits around and waits
|
678
718
|
# for connections and doesn't die until the parent dies (or is
|
679
719
|
# given a INT, QUIT, or TERM signal)
|
680
720
|
def worker_loop(worker)
|
681
|
-
ppid = @master_pid
|
682
721
|
readers = init_worker_process(worker)
|
683
|
-
|
722
|
+
waiter = prep_readers(readers)
|
723
|
+
reopen = false
|
684
724
|
|
685
725
|
# this only works immediately if the master sent us the signal
|
686
726
|
# (which is the normal case)
|
687
|
-
trap(:USR1) {
|
727
|
+
trap(:USR1) { reopen = true }
|
688
728
|
|
689
729
|
ready = readers.dup
|
690
730
|
@after_worker_ready.call(self, worker)
|
691
731
|
|
692
732
|
begin
|
693
|
-
|
694
|
-
nr = 0
|
733
|
+
reopen = reopen_worker_logs(worker.nr) if reopen
|
695
734
|
worker.tick = time_now.to_i
|
696
|
-
|
697
|
-
while sock = tmp.shift
|
735
|
+
while sock = ready.shift
|
698
736
|
# Unicorn::Worker#kgio_tryaccept is not like accept(2) at all,
|
699
737
|
# but that will return false
|
700
738
|
if client = sock.kgio_tryaccept
|
701
739
|
process_client(client)
|
702
|
-
nr += 1
|
703
740
|
worker.tick = time_now.to_i
|
704
741
|
end
|
705
|
-
break if
|
742
|
+
break if reopen
|
706
743
|
end
|
707
744
|
|
708
|
-
#
|
709
|
-
# we're probably reasonably busy, so avoid calling select()
|
710
|
-
# and do a speculative non-blocking accept() on ready listeners
|
711
|
-
# before we sleep again in select().
|
712
|
-
unless nr == 0
|
713
|
-
tmp = ready.dup
|
714
|
-
redo
|
715
|
-
end
|
716
|
-
|
717
|
-
ppid == Process.ppid or return
|
718
|
-
|
719
|
-
# timeout used so we can detect parent death:
|
745
|
+
# timeout so we can .tick and keep parent from SIGKILL-ing us
|
720
746
|
worker.tick = time_now.to_i
|
721
|
-
|
747
|
+
waiter.get_readers(ready, readers, @timeout)
|
722
748
|
rescue => e
|
723
|
-
redo if
|
749
|
+
redo if reopen && readers[0]
|
724
750
|
Unicorn.log_error(@logger, "listen loop error", e) if readers[0]
|
725
751
|
end while readers[0]
|
726
752
|
end
|
@@ -787,12 +813,12 @@ class Unicorn::HttpServer
|
|
787
813
|
end
|
788
814
|
|
789
815
|
def build_app!
|
790
|
-
if app.respond_to?(:arity) && app.arity == 0
|
816
|
+
if app.respond_to?(:arity) && (app.arity == 0 || app.arity == 2)
|
791
817
|
if defined?(Gem) && Gem.respond_to?(:refresh)
|
792
818
|
logger.info "Refreshing Gem list"
|
793
819
|
Gem.refresh
|
794
820
|
end
|
795
|
-
self.app = app.call
|
821
|
+
self.app = app.arity == 0 ? app.call : app.call(nil, self)
|
796
822
|
end
|
797
823
|
end
|
798
824
|
|
data/lib/unicorn/launcher.rb
CHANGED
data/lib/unicorn/oob_gc.rb
CHANGED
@@ -43,8 +43,8 @@
|
|
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
|
-
# * https://
|
47
|
-
# * https://
|
46
|
+
# * https://yhbt.net/unicorn-public/0BFC98E9-072B-47EE-9A70-05478C20141B@lukemelia.com/
|
47
|
+
# * https://yhbt.net/unicorn-public/AANLkTilUbgdyDv9W1bi-s_W6kq9sOhWfmuYkKLoKGOLj@mail.gmail.com/
|
48
48
|
|
49
49
|
module Unicorn::OobGC
|
50
50
|
|
@@ -60,7 +60,6 @@ module Unicorn::OobGC
|
|
60
60
|
self.const_set :OOBGC_INTERVAL, interval
|
61
61
|
ObjectSpace.each_object(Unicorn::HttpServer) do |s|
|
62
62
|
s.extend(self)
|
63
|
-
self.const_set :OOBGC_ENV, s.instance_variable_get(:@request).env
|
64
63
|
end
|
65
64
|
app # pretend to be Rack middleware since it was in the past
|
66
65
|
end
|
@@ -68,9 +67,10 @@ module Unicorn::OobGC
|
|
68
67
|
#:stopdoc:
|
69
68
|
def process_client(client)
|
70
69
|
super(client) # Unicorn::HttpServer#process_client
|
71
|
-
|
70
|
+
env = instance_variable_get(:@request).env
|
71
|
+
if OOBGC_PATH =~ env['PATH_INFO'] && ((@@nr -= 1) <= 0)
|
72
72
|
@@nr = OOBGC_INTERVAL
|
73
|
-
|
73
|
+
env.clear
|
74
74
|
disabled = GC.enable
|
75
75
|
GC.start
|
76
76
|
GC.disable if disabled
|
data/lib/unicorn/tmpio.rb
CHANGED
@@ -11,12 +11,18 @@ class Unicorn::TmpIO < File
|
|
11
11
|
# immediately, switched to binary mode, and userspace output
|
12
12
|
# buffering is disabled
|
13
13
|
def self.new
|
14
|
+
path = nil
|
15
|
+
|
16
|
+
# workaround File#path being tainted:
|
17
|
+
# https://bugs.ruby-lang.org/issues/14485
|
14
18
|
fp = begin
|
15
|
-
|
19
|
+
path = "#{Dir::tmpdir}/#{rand}"
|
20
|
+
super(path, RDWR|CREAT|EXCL, 0600)
|
16
21
|
rescue Errno::EEXIST
|
17
22
|
retry
|
18
23
|
end
|
19
|
-
|
24
|
+
|
25
|
+
unlink(path)
|
20
26
|
fp.binmode
|
21
27
|
fp.sync = true
|
22
28
|
fp
|
data/lib/unicorn/util.rb
CHANGED
@@ -64,7 +64,7 @@ module Unicorn::Util # :nodoc:
|
|
64
64
|
fp.reopen(fp.path, "a")
|
65
65
|
else
|
66
66
|
# We should not need this workaround, Ruby can be fixed:
|
67
|
-
#
|
67
|
+
# https://bugs.ruby-lang.org/issues/9036
|
68
68
|
# MRI will not call call fclose(3) or freopen(3) here
|
69
69
|
# since there's no associated std{in,out,err} FILE * pointer
|
70
70
|
# This should atomically use dup3(2) (or dup2(2)) syscall
|
data/lib/unicorn/version.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
Unicorn::Const::UNICORN_VERSION = '
|
1
|
+
Unicorn::Const::UNICORN_VERSION = '6.1.0'
|
data/lib/unicorn/worker.rb
CHANGED
@@ -122,6 +122,11 @@ class Unicorn::Worker
|
|
122
122
|
# the +after_fork+ hook after any privileged functions need to be
|
123
123
|
# run (e.g. to set per-worker CPU affinity, niceness, etc)
|
124
124
|
#
|
125
|
+
# +group+ can be specified as a string, or as an array of two
|
126
|
+
# strings. If an array of two strings is given, the first string
|
127
|
+
# is used as the primary group of the process, and the second is
|
128
|
+
# used as the group of the log files.
|
129
|
+
#
|
125
130
|
# Any and all errors raised within this method will be propagated
|
126
131
|
# directly back to the caller (usually the +after_fork+ hook.
|
127
132
|
# These errors commonly include ArgumentError for specifying an
|
@@ -134,8 +139,17 @@ class Unicorn::Worker
|
|
134
139
|
# insufficient because modern systems have fine-grained
|
135
140
|
# capabilities. Let the caller handle any and all errors.
|
136
141
|
uid = Etc.getpwnam(user).uid
|
137
|
-
|
138
|
-
|
142
|
+
|
143
|
+
if group
|
144
|
+
if group.is_a?(Array)
|
145
|
+
group, log_group = group
|
146
|
+
log_gid = Etc.getgrnam(log_group).gid
|
147
|
+
end
|
148
|
+
gid = Etc.getgrnam(group).gid
|
149
|
+
log_gid ||= gid
|
150
|
+
end
|
151
|
+
|
152
|
+
Unicorn::Util.chown_logs(uid, log_gid)
|
139
153
|
if gid && Process.egid != gid
|
140
154
|
Process.initgroups(user, gid)
|
141
155
|
Process::GID.change_privilege(gid)
|
data/lib/unicorn.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
require 'etc'
|
3
3
|
require 'stringio'
|
4
4
|
require 'kgio'
|
5
|
+
require 'raindrops'
|
6
|
+
require 'io/wait'
|
5
7
|
|
6
8
|
begin
|
7
9
|
require 'rack'
|
@@ -43,12 +45,8 @@ module Unicorn
|
|
43
45
|
abort "rack and Rack::Builder must be available for processing #{ru}"
|
44
46
|
end
|
45
47
|
|
46
|
-
# Op is going to get cleared before the returned lambda is called, so
|
47
|
-
# save this value so that it's still there when we need it:
|
48
|
-
no_default_middleware = op[:no_default_middleware]
|
49
|
-
|
50
48
|
# always called after config file parsing, may be called after forking
|
51
|
-
lambda do
|
49
|
+
lambda do |_, server|
|
52
50
|
inner_app = case ru
|
53
51
|
when /\.ru$/
|
54
52
|
raw = File.read(ru)
|
@@ -64,7 +62,7 @@ module Unicorn
|
|
64
62
|
pp({ :inner_app => inner_app })
|
65
63
|
end
|
66
64
|
|
67
|
-
return inner_app
|
65
|
+
return inner_app unless server.default_middleware
|
68
66
|
|
69
67
|
middleware = { # order matters
|
70
68
|
ContentLength: nil,
|
@@ -98,7 +96,7 @@ module Unicorn
|
|
98
96
|
|
99
97
|
# returns an array of strings representing TCP listen socket addresses
|
100
98
|
# and Unix domain socket paths. This is useful for use with
|
101
|
-
# Raindrops::Middleware under Linux: https://
|
99
|
+
# Raindrops::Middleware under Linux: https://yhbt.net/raindrops/
|
102
100
|
def self.listener_names
|
103
101
|
Unicorn::HttpServer::LISTENERS.map do |io|
|
104
102
|
Unicorn::SocketHelper.sock_name(io)
|
@@ -112,9 +110,23 @@ module Unicorn
|
|
112
110
|
exc.backtrace.each { |line| logger.error(line) }
|
113
111
|
end
|
114
112
|
|
115
|
-
|
113
|
+
F_SETPIPE_SZ = 1031 if RUBY_PLATFORM =~ /linux/
|
114
|
+
|
116
115
|
def self.pipe # :nodoc:
|
117
|
-
Kgio::Pipe.new.each
|
116
|
+
Kgio::Pipe.new.each do |io|
|
117
|
+
# shrink pipes to minimize impact on /proc/sys/fs/pipe-user-pages-soft
|
118
|
+
# limits.
|
119
|
+
if defined?(F_SETPIPE_SZ)
|
120
|
+
begin
|
121
|
+
io.fcntl(F_SETPIPE_SZ, Raindrops::PAGE_SIZE)
|
122
|
+
rescue Errno::EINVAL
|
123
|
+
# old kernel
|
124
|
+
rescue Errno::EPERM
|
125
|
+
# resizes fail if Linux is close to the pipe limit for the user
|
126
|
+
# or if the user does not have permissions to resize
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
118
130
|
end
|
119
131
|
# :startdoc:
|
120
132
|
end
|