unicorn 5.4.0 → 5.6.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 +7 -3
- data/.olddoc.yml +12 -7
- data/Application_Timeouts +4 -4
- 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 +16 -6
- data/HACKING +1 -1
- data/ISSUES +17 -22
- data/KNOWN_ISSUES +2 -2
- data/LATEST +18 -8
- data/LICENSE +2 -2
- data/Links +13 -11
- data/NEWS +94 -0
- data/README +18 -11
- data/SIGNALS +1 -1
- data/Sandbox +4 -4
- 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/common_field_optimization.h +24 -6
- data/ext/unicorn_http/extconf.rb +30 -0
- data/ext/unicorn_http/global_variables.h +2 -2
- data/ext/unicorn_http/httpdate.c +2 -2
- data/ext/unicorn_http/unicorn_http.c +257 -224
- data/ext/unicorn_http/unicorn_http.rl +47 -14
- data/lib/unicorn/configurator.rb +25 -4
- data/lib/unicorn/http_request.rb +12 -2
- data/lib/unicorn/http_server.rb +50 -23
- data/lib/unicorn/launcher.rb +1 -1
- data/lib/unicorn/oob_gc.rb +2 -2
- data/lib/unicorn/socket_helper.rb +3 -2
- data/lib/unicorn/tmpio.rb +8 -2
- data/lib/unicorn/util.rb +3 -3
- data/lib/unicorn/version.rb +1 -1
- data/lib/unicorn/worker.rb +16 -2
- data/lib/unicorn.rb +23 -9
- data/man/man1/unicorn.1 +88 -85
- data/man/man1/unicorn_rails.1 +79 -81
- data/t/README +4 -4
- data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
- data/t/t0301.ru +13 -0
- 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 +15 -14
- data/test/test_helper.rb +0 -26
- data/test/unit/test_ccc.rb +1 -1
- 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 +35 -5
- data/test/unit/test_signals.rb +2 -2
- data/test/unit/test_socket_helper.rb +4 -4
- data/test/unit/test_upload.rb +4 -9
- data/test/unit/test_util.rb +25 -0
- data/unicorn.gemspec +3 -3
- metadata +13 -9
- data/Documentation/GNUmakefile +0 -30
- data/Documentation/unicorn.1.txt +0 -187
- data/Documentation/unicorn_rails.1.txt +0 -175
@@ -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(void);
|
17
17
|
|
18
18
|
#define UH_FL_CHUNKED 0x1
|
19
19
|
#define UH_FL_HASBODY 0x2
|
@@ -62,7 +62,8 @@ struct http_parser {
|
|
62
62
|
} len;
|
63
63
|
};
|
64
64
|
|
65
|
-
static ID id_set_backtrace;
|
65
|
+
static ID id_set_backtrace, id_is_chunked_p;
|
66
|
+
static VALUE cHttpParser;
|
66
67
|
|
67
68
|
#ifdef HAVE_RB_HASH_CLEAR /* Ruby >= 2.0 */
|
68
69
|
# define my_hash_clear(h) (void)rb_hash_clear(h)
|
@@ -220,6 +221,19 @@ static void write_cont_value(struct http_parser *hp,
|
|
220
221
|
rb_str_buf_cat(hp->cont, vptr, end + 1);
|
221
222
|
}
|
222
223
|
|
224
|
+
static int is_chunked(VALUE v)
|
225
|
+
{
|
226
|
+
/* common case first */
|
227
|
+
if (STR_CSTR_CASE_EQ(v, "chunked"))
|
228
|
+
return 1;
|
229
|
+
|
230
|
+
/*
|
231
|
+
* call Ruby function in unicorn/http_request.rb to deal with unlikely
|
232
|
+
* comma-delimited case
|
233
|
+
*/
|
234
|
+
return rb_funcall(cHttpParser, id_is_chunked_p, 1, v) != Qfalse;
|
235
|
+
}
|
236
|
+
|
223
237
|
static void write_value(struct http_parser *hp,
|
224
238
|
const char *buffer, const char *p)
|
225
239
|
{
|
@@ -246,7 +260,9 @@ static void write_value(struct http_parser *hp,
|
|
246
260
|
f = uncommon_field(field, flen);
|
247
261
|
} else if (f == g_http_connection) {
|
248
262
|
hp_keepalive_connection(hp, v);
|
249
|
-
} else if (f == g_content_length) {
|
263
|
+
} else if (f == g_content_length && !HP_FL_TEST(hp, CHUNKED)) {
|
264
|
+
if (hp->len.content)
|
265
|
+
parser_raise(eHttpParserError, "Content-Length already set");
|
250
266
|
hp->len.content = parse_length(RSTRING_PTR(v), RSTRING_LEN(v));
|
251
267
|
if (hp->len.content < 0)
|
252
268
|
parser_raise(eHttpParserError, "invalid Content-Length");
|
@@ -254,9 +270,30 @@ static void write_value(struct http_parser *hp,
|
|
254
270
|
HP_FL_SET(hp, HASBODY);
|
255
271
|
hp_invalid_if_trailer(hp);
|
256
272
|
} else if (f == g_http_transfer_encoding) {
|
257
|
-
if (
|
273
|
+
if (is_chunked(v)) {
|
274
|
+
if (HP_FL_TEST(hp, CHUNKED))
|
275
|
+
/*
|
276
|
+
* RFC 7230 3.3.1:
|
277
|
+
* A sender MUST NOT apply chunked more than once to a message body
|
278
|
+
* (i.e., chunking an already chunked message is not allowed).
|
279
|
+
*/
|
280
|
+
parser_raise(eHttpParserError, "Transfer-Encoding double chunked");
|
281
|
+
|
258
282
|
HP_FL_SET(hp, CHUNKED);
|
259
283
|
HP_FL_SET(hp, HASBODY);
|
284
|
+
|
285
|
+
/* RFC 7230 3.3.3, 3: favor chunked if Content-Length exists */
|
286
|
+
hp->len.content = 0;
|
287
|
+
} else if (HP_FL_TEST(hp, CHUNKED)) {
|
288
|
+
/*
|
289
|
+
* RFC 7230 3.3.3, point 3 states:
|
290
|
+
* If a Transfer-Encoding header field is present in a request and
|
291
|
+
* the chunked transfer coding is not the final encoding, the
|
292
|
+
* message body length cannot be determined reliably; the server
|
293
|
+
* MUST respond with the 400 (Bad Request) status code and then
|
294
|
+
* close the connection.
|
295
|
+
*/
|
296
|
+
parser_raise(eHttpParserError, "invalid Transfer-Encoding");
|
260
297
|
}
|
261
298
|
hp_invalid_if_trailer(hp);
|
262
299
|
} else if (f == g_http_trailer) {
|
@@ -487,7 +524,7 @@ static void set_url_scheme(VALUE env, VALUE *server_port)
|
|
487
524
|
* and X-Forwarded-Proto handling from this parser? We've had it
|
488
525
|
* forever and nobody has said anything against it, either.
|
489
526
|
* Anyways, please send comments to our public mailing list:
|
490
|
-
* unicorn-public@
|
527
|
+
* unicorn-public@yhbt.net (no HTML mail, no subscription necessary)
|
491
528
|
*/
|
492
529
|
scheme = rb_hash_aref(env, g_http_x_forwarded_ssl);
|
493
530
|
if (!NIL_P(scheme) && STR_CSTR_EQ(scheme, "on")) {
|
@@ -931,11 +968,8 @@ static VALUE HttpParser_rssget(VALUE self)
|
|
931
968
|
|
932
969
|
void Init_unicorn_http(void)
|
933
970
|
{
|
934
|
-
|
935
|
-
VALUE mUnicorn, cHttpParser;
|
971
|
+
VALUE mUnicorn;
|
936
972
|
|
937
|
-
mark_ary = rb_ary_new();
|
938
|
-
rb_global_variable(&mark_ary);
|
939
973
|
mUnicorn = rb_define_module("Unicorn");
|
940
974
|
cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
|
941
975
|
eHttpParserError =
|
@@ -945,7 +979,7 @@ void Init_unicorn_http(void)
|
|
945
979
|
e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError",
|
946
980
|
eHttpParserError);
|
947
981
|
|
948
|
-
init_globals(
|
982
|
+
init_globals();
|
949
983
|
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
950
984
|
rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
|
951
985
|
rb_define_method(cHttpParser, "clear", HttpParser_clear, 0);
|
@@ -982,19 +1016,18 @@ void Init_unicorn_http(void)
|
|
982
1016
|
|
983
1017
|
rb_define_singleton_method(cHttpParser, "max_header_len=", set_maxhdrlen, 1);
|
984
1018
|
|
985
|
-
init_common_fields(
|
1019
|
+
init_common_fields();
|
986
1020
|
SET_GLOBAL(g_http_host, "HOST");
|
987
1021
|
SET_GLOBAL(g_http_trailer, "TRAILER");
|
988
1022
|
SET_GLOBAL(g_http_transfer_encoding, "TRANSFER_ENCODING");
|
989
1023
|
SET_GLOBAL(g_content_length, "CONTENT_LENGTH");
|
990
1024
|
SET_GLOBAL(g_http_connection, "CONNECTION");
|
991
1025
|
id_set_backtrace = rb_intern("set_backtrace");
|
992
|
-
init_unicorn_httpdate(
|
993
|
-
|
994
|
-
OBJ_FREEZE(mark_ary);
|
1026
|
+
init_unicorn_httpdate();
|
995
1027
|
|
996
1028
|
#ifndef HAVE_RB_HASH_CLEAR
|
997
1029
|
id_clear = rb_intern("clear");
|
998
1030
|
#endif
|
1031
|
+
id_is_chunked_p = rb_intern("is_chunked?");
|
999
1032
|
}
|
1000
1033
|
#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)
|
@@ -66,7 +65,7 @@ class Unicorn::HttpParser
|
|
66
65
|
clear
|
67
66
|
e = env
|
68
67
|
|
69
|
-
# From
|
68
|
+
# From https://www.ietf.org/rfc/rfc3875:
|
70
69
|
# "Script authors should be aware that the REMOTE_ADDR and
|
71
70
|
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
72
71
|
# may not identify the ultimate source of the request. They
|
@@ -189,4 +188,15 @@ class Unicorn::HttpParser
|
|
189
188
|
HTTP_RESPONSE_START.each { |c| socket.write(c) }
|
190
189
|
end
|
191
190
|
end
|
191
|
+
|
192
|
+
# called by ext/unicorn_http/unicorn_http.rl via rb_funcall
|
193
|
+
def self.is_chunked?(v) # :nodoc:
|
194
|
+
vals = v.split(/[ \t]*,[ \t]*/).map!(&:downcase)
|
195
|
+
if vals.pop == 'chunked'.freeze
|
196
|
+
return true unless vals.include?('chunked'.freeze)
|
197
|
+
raise Unicorn::HttpParserError, 'double chunked', []
|
198
|
+
end
|
199
|
+
return false unless vals.include?('chunked'.freeze)
|
200
|
+
raise Unicorn::HttpParserError, 'chunked not last', []
|
201
|
+
end
|
192
202
|
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
|
@@ -70,6 +71,7 @@ class Unicorn::HttpServer
|
|
70
71
|
@app = app
|
71
72
|
@request = Unicorn::HttpRequest.new
|
72
73
|
@reexec_pid = 0
|
74
|
+
@default_middleware = true
|
73
75
|
options = options.dup
|
74
76
|
@ready_pipe = options.delete(:ready_pipe)
|
75
77
|
@init_listeners = options[:listeners] ? options[:listeners].dup : []
|
@@ -82,7 +84,7 @@ class Unicorn::HttpServer
|
|
82
84
|
# * The master process never closes or reinitializes this once
|
83
85
|
# initialized. Signal handlers in the master process will write to
|
84
86
|
# it to wake up the master from IO.select in exactly the same manner
|
85
|
-
# djb describes in
|
87
|
+
# djb describes in https://cr.yp.to/docs/selfpipe.html
|
86
88
|
#
|
87
89
|
# * The workers immediately close the pipe they inherit. See the
|
88
90
|
# Unicorn::Worker class for the pipe workers use.
|
@@ -380,7 +382,7 @@ class Unicorn::HttpServer
|
|
380
382
|
|
381
383
|
# wait for a signal hander to wake us up and then consume the pipe
|
382
384
|
def master_sleep(sec)
|
383
|
-
@self_pipe[0].
|
385
|
+
@self_pipe[0].wait(sec) or return
|
384
386
|
# 11 bytes is the maximum string length which can be embedded within
|
385
387
|
# the Ruby itself and not require a separate malloc (on 32-bit MRI 1.9+).
|
386
388
|
# Most reads are only one byte here and uncommon, so it's not worth a
|
@@ -520,9 +522,6 @@ class Unicorn::HttpServer
|
|
520
522
|
Unicorn::Configurator::RACKUP.clear
|
521
523
|
@ready_pipe = @init_listeners = @before_exec = @before_fork = nil
|
522
524
|
|
523
|
-
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/36450
|
524
|
-
srand # remove in unicorn 6
|
525
|
-
|
526
525
|
# The OpenSSL PRNG is seeded with only the pid, and apps with frequently
|
527
526
|
# dying workers can recycle pids
|
528
527
|
OpenSSL::Random.seed(rand.to_s) if defined?(OpenSSL::Random)
|
@@ -553,9 +552,9 @@ class Unicorn::HttpServer
|
|
553
552
|
@workers[pid] = worker
|
554
553
|
worker.atfork_parent
|
555
554
|
end
|
556
|
-
|
557
|
-
|
558
|
-
|
555
|
+
rescue => e
|
556
|
+
@logger.error(e) rescue nil
|
557
|
+
exit!
|
559
558
|
end
|
560
559
|
|
561
560
|
def maintain_worker_count
|
@@ -586,7 +585,26 @@ class Unicorn::HttpServer
|
|
586
585
|
client.kgio_trywrite(err_response(code, @request.response_start_sent))
|
587
586
|
end
|
588
587
|
client.close
|
589
|
-
|
588
|
+
rescue
|
589
|
+
end
|
590
|
+
|
591
|
+
def e103_response_write(client, headers)
|
592
|
+
response = if @request.response_start_sent
|
593
|
+
"103 Early Hints\r\n"
|
594
|
+
else
|
595
|
+
"HTTP/1.1 103 Early Hints\r\n"
|
596
|
+
end
|
597
|
+
|
598
|
+
headers.each_pair do |k, vs|
|
599
|
+
next if !vs || vs.empty?
|
600
|
+
values = vs.to_s.split("\n".freeze)
|
601
|
+
values.each do |v|
|
602
|
+
response << "#{k}: #{v}\r\n"
|
603
|
+
end
|
604
|
+
end
|
605
|
+
response << "\r\n".freeze
|
606
|
+
response << "HTTP/1.1 ".freeze if @request.response_start_sent
|
607
|
+
client.write(response)
|
590
608
|
end
|
591
609
|
|
592
610
|
def e100_response_write(client, env)
|
@@ -603,7 +621,15 @@ class Unicorn::HttpServer
|
|
603
621
|
# once a client is accepted, it is processed in its entirety here
|
604
622
|
# in 3 easy steps: read request, call app, write app response
|
605
623
|
def process_client(client)
|
606
|
-
|
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
|
+
status, headers, body = @app.call(env)
|
607
633
|
|
608
634
|
begin
|
609
635
|
return if @request.hijacked?
|
@@ -669,9 +695,9 @@ class Unicorn::HttpServer
|
|
669
695
|
logger.info "worker=#{worker_nr} reopening logs..."
|
670
696
|
Unicorn::Util.reopen_logs
|
671
697
|
logger.info "worker=#{worker_nr} done reopening logs"
|
672
|
-
|
673
|
-
|
674
|
-
|
698
|
+
rescue => e
|
699
|
+
logger.error(e) rescue nil
|
700
|
+
exit!(77) # EX_NOPERM in sysexits.h
|
675
701
|
end
|
676
702
|
|
677
703
|
# runs inside each forked worker, this sits around and waits
|
@@ -687,6 +713,7 @@ class Unicorn::HttpServer
|
|
687
713
|
trap(:USR1) { nr = -65536 }
|
688
714
|
|
689
715
|
ready = readers.dup
|
716
|
+
nr_listeners = readers.size
|
690
717
|
@after_worker_ready.call(self, worker)
|
691
718
|
|
692
719
|
begin
|
@@ -709,7 +736,7 @@ class Unicorn::HttpServer
|
|
709
736
|
# we're probably reasonably busy, so avoid calling select()
|
710
737
|
# and do a speculative non-blocking accept() on ready listeners
|
711
738
|
# before we sleep again in select().
|
712
|
-
|
739
|
+
if nr == nr_listeners
|
713
740
|
tmp = ready.dup
|
714
741
|
redo
|
715
742
|
end
|
@@ -757,11 +784,11 @@ class Unicorn::HttpServer
|
|
757
784
|
wpid <= 0 and return
|
758
785
|
Process.kill(0, wpid)
|
759
786
|
wpid
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
787
|
+
rescue Errno::EPERM
|
788
|
+
logger.info "pid=#{path} possibly stale, got EPERM signalling PID:#{wpid}"
|
789
|
+
nil
|
790
|
+
rescue Errno::ESRCH, Errno::ENOENT
|
791
|
+
# don't unlink stale pid files, racy without non-portable locking...
|
765
792
|
end
|
766
793
|
|
767
794
|
def load_config!
|
@@ -787,12 +814,12 @@ class Unicorn::HttpServer
|
|
787
814
|
end
|
788
815
|
|
789
816
|
def build_app!
|
790
|
-
if app.respond_to?(:arity) && app.arity == 0
|
817
|
+
if app.respond_to?(:arity) && (app.arity == 0 || app.arity == 2)
|
791
818
|
if defined?(Gem) && Gem.respond_to?(:refresh)
|
792
819
|
logger.info "Refreshing Gem list"
|
793
820
|
Gem.refresh
|
794
821
|
end
|
795
|
-
self.app = app.call
|
822
|
+
self.app = app.arity == 0 ? app.call : app.call(nil, self)
|
796
823
|
end
|
797
824
|
end
|
798
825
|
|
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
|
|
@@ -83,6 +83,7 @@ module Unicorn
|
|
83
83
|
rescue => e
|
84
84
|
logger.error("#{sock_name(sock)} " \
|
85
85
|
"failed to set accept_filter=#{name} (#{e.inspect})")
|
86
|
+
logger.error("perhaps accf_http(9) needs to be loaded".freeze)
|
86
87
|
end if arg != got
|
87
88
|
end
|
88
89
|
end
|
@@ -100,8 +101,8 @@ module Unicorn
|
|
100
101
|
log_buffer_sizes(sock, " after: ")
|
101
102
|
end
|
102
103
|
sock.listen(opt[:backlog])
|
103
|
-
|
104
|
-
|
104
|
+
rescue => e
|
105
|
+
Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
|
105
106
|
end
|
106
107
|
|
107
108
|
def log_buffer_sizes(sock, pfx = '')
|
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
@@ -11,8 +11,8 @@ module Unicorn::Util # :nodoc:
|
|
11
11
|
fp.stat.file? &&
|
12
12
|
fp.sync &&
|
13
13
|
(fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
|
14
|
-
|
15
|
-
|
14
|
+
rescue IOError, Errno::EBADF
|
15
|
+
false
|
16
16
|
end
|
17
17
|
|
18
18
|
def self.chown_logs(uid, gid)
|
@@ -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 = '5.
|
1
|
+
Unicorn::Const::UNICORN_VERSION = '5.6.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,25 @@ 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
|
+
io.close_on_exec = true # remove this when we only support Ruby >= 2.0
|
118
|
+
|
119
|
+
# shrink pipes to minimize impact on /proc/sys/fs/pipe-user-pages-soft
|
120
|
+
# limits.
|
121
|
+
if defined?(F_SETPIPE_SZ)
|
122
|
+
begin
|
123
|
+
io.fcntl(F_SETPIPE_SZ, Raindrops::PAGE_SIZE)
|
124
|
+
rescue Errno::EINVAL
|
125
|
+
# old kernel
|
126
|
+
rescue Errno::EPERM
|
127
|
+
# resizes fail if Linux is close to the pipe limit for the user
|
128
|
+
# or if the user does not have permissions to resize
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
118
132
|
end
|
119
133
|
# :startdoc:
|
120
134
|
end
|