unicorn 5.2.0 → 5.3.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.olddoc.yml +0 -1
- data/GIT-VERSION-GEN +1 -1
- data/HACKING +0 -1
- data/ISSUES +12 -6
- data/Links +1 -1
- 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.rl +9 -4
- data/lib/unicorn.rb +1 -1
- data/lib/unicorn/configurator.rb +63 -7
- data/lib/unicorn/http_request.rb +90 -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/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 +5 -4
- 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 +6 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6625aff20e7f744a10b6f9724b1ddb30f60e9b51
|
4
|
+
data.tar.gz: 0e33bab2037c0dbfdb91bc463bf41dbb502e6f16
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 18a1e1143713bc8f4d7491ef3684dc0154bc5e1e335d5d2b8ace3f2ca99cbd57d686c7f03601919d8ebfcb68d4472ad1cc8c5802479a45951af5808fa6c53572
|
7
|
+
data.tar.gz: '08a6ab6905c240e6fd1e99d26d004b697c76e535ff37832bf71a342ea29fc424a10f790f65ed5496cc10312c3323339dcfe545d3b539130dc6310e6ba81c3d0d'
|
data/.olddoc.yml
CHANGED
@@ -12,7 +12,6 @@ noindex:
|
|
12
12
|
- TODO
|
13
13
|
- unicorn_rails_1
|
14
14
|
public_email: unicorn-public@bogomips.org
|
15
|
-
private_email: unicorn@bogomips.org
|
16
15
|
nntp_url:
|
17
16
|
- nntp://news.public-inbox.org/inbox.comp.lang.ruby.unicorn
|
18
17
|
- nntp://news.gmane.org/gmane.comp.lang.ruby.unicorn.general
|
data/GIT-VERSION-GEN
CHANGED
data/HACKING
CHANGED
@@ -104,7 +104,6 @@ don't email the git mailing list or maintainer with Unicorn patches :)
|
|
104
104
|
|
105
105
|
In order to build the gem, you must install the following components:
|
106
106
|
|
107
|
-
* olddoc (RubyGem)
|
108
107
|
* pandoc
|
109
108
|
|
110
109
|
You can build the Unicorn gem with the following command:
|
data/ISSUES
CHANGED
@@ -9,14 +9,16 @@ submit patches and/or obtain support after you have searched the
|
|
9
9
|
* Cc: all participants in a thread or commit, as subscription is optional
|
10
10
|
* Do not {top post}[http://catb.org/jargon/html/T/top-post.html] in replies
|
11
11
|
* Quote as little as possible of the message you're replying to
|
12
|
-
* Do not send HTML mail or images,
|
13
|
-
|
12
|
+
* Do not send HTML mail or images,
|
13
|
+
they hurt reader privacy and will be flagged as spam
|
14
|
+
* Anonymous and pseudonymous messages will ALWAYS be welcome
|
14
15
|
* The email submission port (587) is enabled on the bogomips.org MX:
|
15
16
|
https://bogomips.org/unicorn-public/20141004232241.GA23908@dcvr.yhbt.net/t/
|
16
17
|
|
17
18
|
If your issue is of a sensitive nature or you're just shy in public,
|
18
|
-
|
19
|
-
|
19
|
+
use anonymity tools such as Tor or Mixmaster; and rely on the public
|
20
|
+
mail archives for responses. Be sure to scrub sensitive log messages
|
21
|
+
and such.
|
20
22
|
|
21
23
|
If you don't get a response within a few days, we may have forgotten
|
22
24
|
about it so feel free to ask again.
|
@@ -64,14 +66,14 @@ document distributed with git) on guidelines for patch submission.
|
|
64
66
|
== Contact Info
|
65
67
|
|
66
68
|
* public: mailto:unicorn-public@bogomips.org
|
67
|
-
* private: mailto:unicorn@bogomips.org
|
68
69
|
* nntp://news.gmane.org/gmane.comp.lang.ruby.unicorn.general
|
69
70
|
* nntp://news.public-inbox.org/inbox.comp.lang.ruby.unicorn
|
70
71
|
* https://bogomips.org/unicorn-public/
|
72
|
+
* http://ou63pmih66umazou.onion/unicorn-public/
|
71
73
|
|
72
74
|
Mailing list subscription is optional, so Cc: all participants.
|
73
75
|
|
74
|
-
You can follow along via NNTP:
|
76
|
+
You can follow along via NNTP (read-only):
|
75
77
|
|
76
78
|
nntp://news.public-inbox.org/inbox.comp.lang.ruby.unicorn
|
77
79
|
nntp://news.gmane.org/gmane.comp.lang.ruby.unicorn.general
|
@@ -79,6 +81,7 @@ You can follow along via NNTP:
|
|
79
81
|
Or Atom feeds:
|
80
82
|
|
81
83
|
https://bogomips.org/unicorn-public/new.atom
|
84
|
+
http://ou63pmih66umazou.onion/unicorn-public/new.atom
|
82
85
|
|
83
86
|
The HTML archives at https://bogomips.org/unicorn-public/
|
84
87
|
also has links to per-thread Atom feeds and downloadable
|
@@ -88,3 +91,6 @@ You may optionally subscribe via plain-text email:
|
|
88
91
|
|
89
92
|
mailto:unicorn-public+subscribe@bogomips.org
|
90
93
|
(and confirming the auto-reply)
|
94
|
+
|
95
|
+
Just keep in mind we suck at delivering email, so using NNTP,
|
96
|
+
or Atom feeds might be a better bet...
|
data/Links
CHANGED
@@ -23,7 +23,7 @@ or services behind them.
|
|
23
23
|
* {golden_brindle}[https://github.com/simonoff/golden_brindle] - tool to
|
24
24
|
manage multiple unicorn instances/applications on a single server
|
25
25
|
|
26
|
-
* {raindrops}[
|
26
|
+
* {raindrops}[https://bogomips.org/raindrops/] - real-time stats for
|
27
27
|
preforking Rack servers
|
28
28
|
|
29
29
|
* {UnXF}[https://bogomips.org/unxf/] Un-X-Forward* the Rack environment,
|
data/TUNING
CHANGED
@@ -72,10 +72,28 @@ See Unicorn::Configurator for details on the config file format.
|
|
72
72
|
have them unbuffered (File#sync = true) or they are
|
73
73
|
record(line)-buffered in userspace before any writes.
|
74
74
|
|
75
|
-
== Kernel Parameters (Linux sysctl)
|
75
|
+
== Kernel Parameters (Linux sysctl and sysfs)
|
76
76
|
|
77
77
|
WARNING: Do not change system parameters unless you know what you're doing!
|
78
78
|
|
79
|
+
* Transparent hugepages (THP) improves performance in many cases,
|
80
|
+
but can also increase memory use when relying on a
|
81
|
+
copy-on-write(CoW)-friendly GC (Ruby 2.0+) with "preload_app true".
|
82
|
+
CoW operates at the page level, so writing to a huge page would
|
83
|
+
trigger a 2 MB copy (x86-64), as opposed to a 4 KB copy on a
|
84
|
+
regular (non-huge) page.
|
85
|
+
|
86
|
+
Consider only allowing THP to be used when it is requested via the
|
87
|
+
madvise(2) syscall:
|
88
|
+
|
89
|
+
echo madvise >/sys/kernel/mm/transparent_hugepage/enabled
|
90
|
+
|
91
|
+
Or disabling it system-wide, via "never".
|
92
|
+
|
93
|
+
n.b. "page" in this context only applies to the OS kernel,
|
94
|
+
Ruby GC implementations also use this term for the same concept
|
95
|
+
in a way that is agnostic to the OS.
|
96
|
+
|
79
97
|
* net.core.rmem_max and net.core.wmem_max can increase the allowed
|
80
98
|
size of :rcvbuf and :sndbuf respectively. This is mostly only useful
|
81
99
|
for UNIX domain sockets which do not have auto-tuning buffer sizes.
|
@@ -60,7 +60,7 @@ static struct common_field common_http_fields[] = {
|
|
60
60
|
#define HTTP_PREFIX_LEN (sizeof(HTTP_PREFIX) - 1)
|
61
61
|
|
62
62
|
/* this function is not performance-critical, called only at load time */
|
63
|
-
static void init_common_fields(
|
63
|
+
static void init_common_fields(VALUE mark_ary)
|
64
64
|
{
|
65
65
|
int i;
|
66
66
|
struct common_field *cf = common_http_fields;
|
@@ -77,7 +77,7 @@ static void init_common_fields(void)
|
|
77
77
|
cf->value = rb_str_new(tmp, HTTP_PREFIX_LEN + cf->len);
|
78
78
|
}
|
79
79
|
cf->value = rb_obj_freeze(cf->value);
|
80
|
-
|
80
|
+
rb_ary_push(mark_ary, cf->value);
|
81
81
|
}
|
82
82
|
}
|
83
83
|
|
data/ext/unicorn_http/ext_help.h
CHANGED
@@ -1,26 +1,6 @@
|
|
1
1
|
#ifndef ext_help_h
|
2
2
|
#define ext_help_h
|
3
3
|
|
4
|
-
#ifndef RSTRING_PTR
|
5
|
-
#define RSTRING_PTR(s) (RSTRING(s)->ptr)
|
6
|
-
#endif /* !defined(RSTRING_PTR) */
|
7
|
-
#ifndef RSTRING_LEN
|
8
|
-
#define RSTRING_LEN(s) (RSTRING(s)->len)
|
9
|
-
#endif /* !defined(RSTRING_LEN) */
|
10
|
-
|
11
|
-
#ifndef HAVE_RB_STR_SET_LEN
|
12
|
-
# ifdef RUBINIUS
|
13
|
-
# error we should never get here with current Rubinius (1.x)
|
14
|
-
# endif
|
15
|
-
/* this is taken from Ruby 1.8.7, 1.8.6 may not have it */
|
16
|
-
static void rb_18_str_set_len(VALUE str, long len)
|
17
|
-
{
|
18
|
-
RSTRING(str)->len = len;
|
19
|
-
RSTRING(str)->ptr[len] = '\0';
|
20
|
-
}
|
21
|
-
# define rb_str_set_len(str,len) rb_18_str_set_len(str,len)
|
22
|
-
#endif /* !defined(HAVE_RB_STR_SET_LEN) */
|
23
|
-
|
24
4
|
/* not all Ruby implementations support frozen objects (Rubinius does not) */
|
25
5
|
#if defined(OBJ_FROZEN)
|
26
6
|
# define assert_frozen(f) assert(OBJ_FROZEN(f) && "unfrozen object")
|
data/ext/unicorn_http/extconf.rb
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
have_macro("SIZEOF_OFF_T", "ruby.h") or check_sizeof("off_t", "sys/types.h")
|
5
5
|
have_macro("SIZEOF_SIZE_T", "ruby.h") or check_sizeof("size_t", "sys/types.h")
|
6
6
|
have_macro("SIZEOF_LONG", "ruby.h") or check_sizeof("long", "sys/types.h")
|
7
|
-
have_func("rb_str_set_len", "ruby.h")
|
7
|
+
have_func("rb_str_set_len", "ruby.h") or abort 'Ruby 1.9.3+ required'
|
8
8
|
have_func("rb_hash_clear", "ruby.h") # Ruby 2.0+
|
9
9
|
have_func("gmtime_r", "time.h")
|
10
10
|
|
@@ -56,7 +56,7 @@ NORETURN(static void parser_raise(VALUE klass, const char *));
|
|
56
56
|
/** Defines global strings in the init method. */
|
57
57
|
#define DEF_GLOBAL(N, val) do { \
|
58
58
|
g_##N = rb_obj_freeze(rb_str_new(val, sizeof(val) - 1)); \
|
59
|
-
|
59
|
+
rb_ary_push(mark_ary, g_##N); \
|
60
60
|
} while (0)
|
61
61
|
|
62
62
|
/* Defines the maximum allowed lengths for various input elements.*/
|
@@ -67,7 +67,7 @@ DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewh
|
|
67
67
|
DEF_MAX_LENGTH(REQUEST_PATH, 4096); /* common PATH_MAX on modern systems */
|
68
68
|
DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
|
69
69
|
|
70
|
-
static void init_globals(
|
70
|
+
static void init_globals(VALUE mark_ary)
|
71
71
|
{
|
72
72
|
DEF_GLOBAL(rack_url_scheme, "rack.url_scheme");
|
73
73
|
DEF_GLOBAL(request_method, "REQUEST_METHOD");
|
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
|
|
@@ -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,36 @@ 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
|
+
def after_worker_exit(*args, &block)
|
176
|
+
set_hook(:after_worker_exit, block_given? ? block : args[0], 3)
|
177
|
+
end
|
178
|
+
|
179
|
+
# sets after_worker_ready hook to a given block. This block will be called
|
180
|
+
# by a worker process after it has been fully loaded, directly before it
|
181
|
+
# starts responding to requests:
|
182
|
+
#
|
183
|
+
# after_worker_ready do |server,worker|
|
184
|
+
# server.logger.info("worker #{worker.nr} ready, dropping privileges")
|
185
|
+
# worker.user('username', 'groupname')
|
186
|
+
# end
|
187
|
+
#
|
188
|
+
# Do not use Configurator#user if you rely on changing users in the
|
189
|
+
# after_worker_ready hook.
|
190
|
+
#
|
191
|
+
# after_worker_ready is only available in unicorn 5.3.0+
|
192
|
+
def after_worker_ready(*args, &block)
|
193
|
+
set_hook(:after_worker_ready, block_given? ? block : args[0])
|
194
|
+
end
|
195
|
+
|
154
196
|
# sets before_fork got be a given Proc object. This Proc
|
155
197
|
# object will be called by the master process before forking
|
156
198
|
# each worker.
|
@@ -200,6 +242,17 @@ def timeout(seconds)
|
|
200
242
|
set[:timeout] = seconds > max ? max : seconds
|
201
243
|
end
|
202
244
|
|
245
|
+
# Whether to exec in each worker process after forking. This changes the
|
246
|
+
# memory layout of each worker process, which is a security feature designed
|
247
|
+
# to defeat possible address space discovery attacks. Note that using
|
248
|
+
# worker_exec only makes sense if you are not preloading the application,
|
249
|
+
# and will result in higher memory usage.
|
250
|
+
#
|
251
|
+
# worker_exec is only available in unicorn 5.3.0+
|
252
|
+
def worker_exec(bool)
|
253
|
+
set_bool(:worker_exec, bool)
|
254
|
+
end
|
255
|
+
|
203
256
|
# sets the current number of worker_processes to +nr+. Each worker
|
204
257
|
# process will serve exactly one client at a time. You can
|
205
258
|
# increment or decrement this value at runtime by sending SIGTTIN
|
@@ -466,13 +519,12 @@ def preload_app(bool)
|
|
466
519
|
# Disabling rewindability can improve performance by lowering
|
467
520
|
# I/O and memory usage for applications that accept uploads.
|
468
521
|
# Keep in mind that the Rack 1.x spec requires
|
469
|
-
# \env[\"rack.input\"] to be rewindable,
|
470
|
-
#
|
522
|
+
# \env[\"rack.input\"] to be rewindable,
|
523
|
+
# but the Rack 2.x spec does not.
|
471
524
|
#
|
472
|
-
# +rewindable_input+ defaults to +true+
|
473
|
-
#
|
474
|
-
#
|
475
|
-
# (less demanding) spec.
|
525
|
+
# +rewindable_input+ defaults to +true+ for compatibility.
|
526
|
+
# Setting it to +false+ may be safe for applications and
|
527
|
+
# frameworks developed for Rack 2.x and later.
|
476
528
|
def rewindable_input(bool)
|
477
529
|
set_bool(:rewindable_input, bool)
|
478
530
|
end
|
@@ -548,6 +600,10 @@ def working_directory(path)
|
|
548
600
|
# This switch will occur after calling the after_fork hook, and only
|
549
601
|
# if the Worker#user method is not called in the after_fork hook
|
550
602
|
# +group+ is optional and will not change if unspecified.
|
603
|
+
#
|
604
|
+
# Do not use Configurator#user if you rely on changing users in the
|
605
|
+
# after_worker_ready hook. Instead, you need to call Worker#user
|
606
|
+
# directly in after_worker_ready.
|
551
607
|
def user(user, group = nil)
|
552
608
|
# raises ArgumentError on invalid user/group
|
553
609
|
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 = true
|
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,89 @@ 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(:IPPROTO_TCP, :TCP_INFO).inspect
|
159
|
+
if opt =~ /\bstate=(\S+)/
|
160
|
+
@@tcpi_inspect_ok = true
|
161
|
+
raise Errno::EPIPE, "client closed connection".freeze,
|
162
|
+
EMPTY_ARRAY if closed_state_str?($1)
|
163
|
+
else
|
164
|
+
@@tcpi_inspect_ok = false
|
165
|
+
write_http_header(socket)
|
166
|
+
end
|
167
|
+
opt.clear
|
168
|
+
else
|
169
|
+
write_http_header(socket)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def closed_state_str?(state)
|
174
|
+
case state
|
175
|
+
when 'ESTABLISHED'
|
176
|
+
false
|
177
|
+
# not a typo, ruby maps TCP_CLOSE (no 'D') to state=CLOSED (w/ 'D')
|
178
|
+
when 'CLOSE_WAIT', 'TIME_WAIT', 'CLOSED', 'LAST_ACK', 'CLOSING'
|
179
|
+
true
|
180
|
+
else
|
181
|
+
false
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def write_http_header(socket) # :nodoc:
|
187
|
+
if headers?
|
188
|
+
self.response_start_sent = true
|
189
|
+
HTTP_RESPONSE_START.each { |c| socket.write(c) }
|
190
|
+
end
|
191
|
+
end
|
111
192
|
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)
|
data/lib/unicorn/oob_gc.rb
CHANGED
@@ -66,10 +66,9 @@ def self.new(app, interval = 5, path = %r{\A/})
|
|
66
66
|
end
|
67
67
|
|
68
68
|
#:stopdoc:
|
69
|
-
PATH_INFO = "PATH_INFO"
|
70
69
|
def process_client(client)
|
71
70
|
super(client) # Unicorn::HttpServer#process_client
|
72
|
-
if OOBGC_PATH =~ OOBGC_ENV[PATH_INFO] && ((@@nr -= 1) <= 0)
|
71
|
+
if OOBGC_PATH =~ OOBGC_ENV['PATH_INFO'] && ((@@nr -= 1) <= 0)
|
73
72
|
@@nr = OOBGC_INTERVAL
|
74
73
|
OOBGC_ENV.clear
|
75
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 @@ def set_tcp_sockopt(sock, opt)
|
|
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 @@ def new_tcp_server(addr, port, opt)
|
|
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 @@ def sock_name(sock)
|
|
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
|
data/lib/unicorn/tee_input.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
|
3
|
-
#
|
3
|
+
# Acts like tee(1) on an input input to provide a input-like stream
|
4
4
|
# while providing rewindable semantics through a File/StringIO backing
|
5
5
|
# store. On the first pass, the input is only read on demand so your
|
6
6
|
# Rack application can use input notification (upload progress and
|
@@ -9,22 +9,22 @@
|
|
9
9
|
# strict interpretation of Rack::Lint::InputWrapper functionality and
|
10
10
|
# will not support any deviations from it.
|
11
11
|
#
|
12
|
-
# When processing uploads,
|
13
|
-
# "rack.input" of the Rack environment.
|
12
|
+
# When processing uploads, unicorn exposes a TeeInput object under
|
13
|
+
# "rack.input" of the Rack environment by default.
|
14
14
|
class Unicorn::TeeInput < Unicorn::StreamInput
|
15
15
|
# The maximum size (in +bytes+) to buffer in memory before
|
16
16
|
# resorting to a temporary file. Default is 112 kilobytes.
|
17
|
-
@@client_body_buffer_size = Unicorn::Const::MAX_BODY
|
17
|
+
@@client_body_buffer_size = Unicorn::Const::MAX_BODY # :nodoc:
|
18
18
|
|
19
19
|
# sets the maximum size of request bodies to buffer in memory,
|
20
20
|
# amounts larger than this are buffered to the filesystem
|
21
|
-
def self.client_body_buffer_size=(bytes)
|
21
|
+
def self.client_body_buffer_size=(bytes) # :nodoc:
|
22
22
|
@@client_body_buffer_size = bytes
|
23
23
|
end
|
24
24
|
|
25
25
|
# returns the maximum size of request bodies to buffer in memory,
|
26
26
|
# amounts larger than this are buffered to the filesystem
|
27
|
-
def self.client_body_buffer_size
|
27
|
+
def self.client_body_buffer_size # :nodoc:
|
28
28
|
@@client_body_buffer_size
|
29
29
|
end
|
30
30
|
|
@@ -37,7 +37,7 @@ def new_tmpio # :nodoc:
|
|
37
37
|
|
38
38
|
# Initializes a new TeeInput object. You normally do not have to call
|
39
39
|
# this unless you are writing an HTTP server.
|
40
|
-
def initialize(socket, request)
|
40
|
+
def initialize(socket, request) # :nodoc:
|
41
41
|
@len = request.content_length
|
42
42
|
super
|
43
43
|
@tmp = @len && @len <= @@client_body_buffer_size ?
|
@@ -125,9 +125,7 @@ def consume!
|
|
125
125
|
end
|
126
126
|
|
127
127
|
def tee(buffer)
|
128
|
-
|
129
|
-
@tmp.write(buffer)
|
130
|
-
end
|
128
|
+
@tmp.write(buffer) if buffer
|
131
129
|
buffer
|
132
130
|
end
|
133
131
|
end
|
data/lib/unicorn/worker.rb
CHANGED
@@ -12,18 +12,19 @@ class Unicorn::Worker
|
|
12
12
|
# :stopdoc:
|
13
13
|
attr_accessor :nr, :switched
|
14
14
|
attr_reader :to_io # IO.select-compatible
|
15
|
+
attr_reader :master
|
15
16
|
|
16
17
|
PER_DROP = Raindrops::PAGE_SIZE / Raindrops::SIZE
|
17
18
|
DROPS = []
|
18
19
|
|
19
|
-
def initialize(nr)
|
20
|
+
def initialize(nr, pipe=nil)
|
20
21
|
drop_index = nr / PER_DROP
|
21
22
|
@raindrop = DROPS[drop_index] ||= Raindrops.new(PER_DROP)
|
22
23
|
@offset = nr % PER_DROP
|
23
24
|
@raindrop[@offset] = 0
|
24
25
|
@nr = nr
|
25
26
|
@switched = false
|
26
|
-
@to_io, @master = Unicorn.pipe
|
27
|
+
@to_io, @master = pipe || Unicorn.pipe
|
27
28
|
end
|
28
29
|
|
29
30
|
def atfork_child # :nodoc:
|
@@ -111,9 +112,11 @@ def close # :nodoc:
|
|
111
112
|
# In most cases, you should be using the Unicorn::Configurator#user
|
112
113
|
# directive instead. This method should only be used if you need
|
113
114
|
# fine-grained control of exactly when you want to change permissions
|
114
|
-
# in your after_fork hooks
|
115
|
+
# in your after_fork or after_worker_ready hooks, or if you want to
|
116
|
+
# use the chroot support.
|
115
117
|
#
|
116
|
-
# Changes the worker process to the specified +user+ and +group
|
118
|
+
# Changes the worker process to the specified +user+ and +group+,
|
119
|
+
# and chroots to the current working directory if +chroot+ is set.
|
117
120
|
# This is only intended to be called from within the worker
|
118
121
|
# process from the +after_fork+ hook. This should be called in
|
119
122
|
# the +after_fork+ hook after any privileged functions need to be
|
@@ -122,8 +125,11 @@ def close # :nodoc:
|
|
122
125
|
# Any and all errors raised within this method will be propagated
|
123
126
|
# directly back to the caller (usually the +after_fork+ hook.
|
124
127
|
# These errors commonly include ArgumentError for specifying an
|
125
|
-
# invalid user/group and Errno::EPERM for insufficient privileges
|
126
|
-
|
128
|
+
# invalid user/group and Errno::EPERM for insufficient privileges.
|
129
|
+
#
|
130
|
+
# chroot support is only available in unicorn 5.3.0+
|
131
|
+
# user and group switching appeared in unicorn 0.94.0 (2009-11-05)
|
132
|
+
def user(user, group = nil, chroot = false)
|
127
133
|
# we do not protect the caller, checking Process.euid == 0 is
|
128
134
|
# insufficient because modern systems have fine-grained
|
129
135
|
# capabilities. Let the caller handle any and all errors.
|
@@ -134,6 +140,11 @@ def user(user, group = nil)
|
|
134
140
|
Process.initgroups(user, gid)
|
135
141
|
Process::GID.change_privilege(gid)
|
136
142
|
end
|
143
|
+
if chroot
|
144
|
+
chroot = Dir.pwd if chroot == true
|
145
|
+
Dir.chroot(chroot)
|
146
|
+
Dir.chdir('/')
|
147
|
+
end
|
137
148
|
Process.euid != uid and Process::UID.change_privilege(uid)
|
138
149
|
@switched = true
|
139
150
|
end
|
@@ -18,7 +18,8 @@ after_fork { |s,w| }
|
|
18
18
|
next if key =~ %r{\Astd(?:err|out)_path\z}
|
19
19
|
key = key.to_sym
|
20
20
|
def_value = defaults[key]
|
21
|
-
srv_value = srv.__send__(key)
|
21
|
+
srv_value = srv.respond_to?(key) ? srv.__send__(key)
|
22
|
+
: srv.instance_variable_get("@#{key}")
|
22
23
|
fp << "#{key}|#{srv_value}|#{def_value}\\n"
|
23
24
|
end
|
24
25
|
}
|
data/t/test-lib.sh
CHANGED
@@ -106,8 +106,8 @@ check_stderr () {
|
|
106
106
|
# unicorn_setup
|
107
107
|
unicorn_setup () {
|
108
108
|
eval $(unused_listen)
|
109
|
-
port=$(expr $listen : '[^:]*:\([0-9]
|
110
|
-
host=$(expr $listen : '\([^:]*\):[0-9]
|
109
|
+
port=$(expr $listen : '[^:]*:\([0-9]*\)')
|
110
|
+
host=$(expr $listen : '\([^:][^:]*\):[0-9][0-9]*')
|
111
111
|
|
112
112
|
rtmpfiles unicorn_config pid r_err r_out fifo tmp ok
|
113
113
|
cat > $unicorn_config <<EOF
|
data/test/exec/test_exec.rb
CHANGED
@@ -97,6 +97,9 @@ def teardown
|
|
97
97
|
end
|
98
98
|
|
99
99
|
def test_sd_listen_fds_emulation
|
100
|
+
# [ruby-core:69895] [Bug #11336] fixed by r51576
|
101
|
+
return if RUBY_VERSION.to_f < 2.3
|
102
|
+
|
100
103
|
File.open("config.ru", "wb") { |fp| fp.write(HI) }
|
101
104
|
sock = TCPServer.new(@addr, @port)
|
102
105
|
|
@@ -124,9 +127,7 @@ def test_sd_listen_fds_emulation
|
|
124
127
|
end
|
125
128
|
ensure
|
126
129
|
sock.close if sock
|
127
|
-
|
128
|
-
# [ruby-core:69895] [Bug #11336] fixed by r51576
|
129
|
-
end if RUBY_VERSION.to_f >= 2.3
|
130
|
+
end
|
130
131
|
|
131
132
|
def test_inherit_listener_unspecified
|
132
133
|
File.open("config.ru", "wb") { |fp| fp.write(HI) }
|
@@ -142,7 +143,7 @@ def test_inherit_listener_unspecified
|
|
142
143
|
res = hit(["http://#@addr:#@port/"])
|
143
144
|
assert_equal [ "HI\n" ], res
|
144
145
|
assert_shutdown(pid)
|
145
|
-
|
146
|
+
assert sock.getsockopt(:SOL_SOCKET, :SO_KEEPALIVE).bool,
|
146
147
|
'unicorn should always set SO_KEEPALIVE on inherited sockets'
|
147
148
|
ensure
|
148
149
|
sock.close if sock
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'unicorn'
|
3
|
+
require 'io/wait'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'test/unit'
|
6
|
+
|
7
|
+
class TestCccTCPI < Test::Unit::TestCase
|
8
|
+
def test_ccc_tcpi
|
9
|
+
start_pid = $$
|
10
|
+
host = '127.0.0.1'
|
11
|
+
srv = TCPServer.new(host, 0)
|
12
|
+
port = srv.addr[1]
|
13
|
+
err = Tempfile.new('unicorn_ccc')
|
14
|
+
rd, wr = IO.pipe
|
15
|
+
sleep_pipe = IO.pipe
|
16
|
+
pid = fork do
|
17
|
+
sleep_pipe[1].close
|
18
|
+
reqs = 0
|
19
|
+
rd.close
|
20
|
+
worker_pid = nil
|
21
|
+
app = lambda do |env|
|
22
|
+
worker_pid ||= begin
|
23
|
+
at_exit { wr.write(reqs.to_s) if worker_pid == $$ }
|
24
|
+
$$
|
25
|
+
end
|
26
|
+
reqs += 1
|
27
|
+
|
28
|
+
# will wake up when writer closes
|
29
|
+
sleep_pipe[0].read if env['PATH_INFO'] == '/sleep'
|
30
|
+
|
31
|
+
[ 200, [ %w(Content-Length 0), %w(Content-Type text/plain) ], [] ]
|
32
|
+
end
|
33
|
+
ENV['UNICORN_FD'] = srv.fileno.to_s
|
34
|
+
opts = {
|
35
|
+
listeners: [ "#{host}:#{port}" ],
|
36
|
+
stderr_path: err.path,
|
37
|
+
check_client_connection: true,
|
38
|
+
}
|
39
|
+
uni = Unicorn::HttpServer.new(app, opts)
|
40
|
+
uni.start.join
|
41
|
+
end
|
42
|
+
wr.close
|
43
|
+
|
44
|
+
# make sure the server is running, at least
|
45
|
+
client = TCPSocket.new(host, port)
|
46
|
+
client.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
47
|
+
assert client.wait_readable(10), 'never got response from server'
|
48
|
+
res = client.read
|
49
|
+
assert_match %r{\AHTTP/1\.1 200}, res, 'got part of first response'
|
50
|
+
assert_match %r{\r\n\r\n\z}, res, 'got end of response, server is ready'
|
51
|
+
client.close
|
52
|
+
|
53
|
+
# start a slow request...
|
54
|
+
sleeper = TCPSocket.new(host, port)
|
55
|
+
sleeper.write("GET /sleep HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
56
|
+
|
57
|
+
# and a bunch of aborted ones
|
58
|
+
nr = 100
|
59
|
+
nr.times do |i|
|
60
|
+
client = TCPSocket.new(host, port)
|
61
|
+
client.write("GET /collections/#{rand(10000)} HTTP/1.1\r\n" \
|
62
|
+
"Host: example.com\r\n\r\n")
|
63
|
+
client.close
|
64
|
+
end
|
65
|
+
sleep_pipe[1].close # wake up the reader in the worker
|
66
|
+
res = sleeper.read
|
67
|
+
assert_match %r{\AHTTP/1\.1 200}, res, 'got part of first sleeper response'
|
68
|
+
assert_match %r{\r\n\r\n\z}, res, 'got end of sleeper response'
|
69
|
+
sleeper.close
|
70
|
+
kpid = pid
|
71
|
+
pid = nil
|
72
|
+
Process.kill(:QUIT, kpid)
|
73
|
+
_, status = Process.waitpid2(kpid)
|
74
|
+
assert status.success?
|
75
|
+
reqs = rd.read.to_i
|
76
|
+
warn "server got #{reqs} requests with #{nr} CCC aborted\n" if $DEBUG
|
77
|
+
assert_operator reqs, :<, nr
|
78
|
+
assert_operator reqs, :>=, 2, 'first 2 requests got through, at least'
|
79
|
+
ensure
|
80
|
+
return if start_pid != $$
|
81
|
+
srv.close if srv
|
82
|
+
if pid
|
83
|
+
Process.kill(:QUIT, pid)
|
84
|
+
_, status = Process.waitpid2(pid)
|
85
|
+
assert status.success?
|
86
|
+
end
|
87
|
+
err.close! if err
|
88
|
+
rd.close if rd
|
89
|
+
end
|
90
|
+
end
|
@@ -851,24 +851,6 @@ def test_empty_header
|
|
851
851
|
assert_equal '', parser.env['HTTP_HOST']
|
852
852
|
end
|
853
853
|
|
854
|
-
# so we don't care about the portability of this test
|
855
|
-
# if it doesn't leak on Linux, it won't leak anywhere else
|
856
|
-
# unless your C compiler or platform is otherwise broken
|
857
|
-
LINUX_PROC_PID_STATUS = "/proc/self/status"
|
858
|
-
def test_memory_leak
|
859
|
-
match_rss = /^VmRSS:\s+(\d+)/
|
860
|
-
if File.read(LINUX_PROC_PID_STATUS) =~ match_rss
|
861
|
-
before = $1.to_i
|
862
|
-
1000000.times { Unicorn::HttpParser.new }
|
863
|
-
File.read(LINUX_PROC_PID_STATUS) =~ match_rss
|
864
|
-
after = $1.to_i
|
865
|
-
diff = after - before
|
866
|
-
assert(diff < 10000, "memory grew more than 10M: #{diff}")
|
867
|
-
end
|
868
|
-
end if RUBY_PLATFORM =~ /linux/ &&
|
869
|
-
File.readable?(LINUX_PROC_PID_STATUS) &&
|
870
|
-
!defined?(RUBY_ENGINE)
|
871
|
-
|
872
854
|
def test_memsize
|
873
855
|
require 'objspace'
|
874
856
|
if ObjectSpace.respond_to?(:memsize_of)
|
@@ -150,28 +150,31 @@ def test_sock_name
|
|
150
150
|
end
|
151
151
|
|
152
152
|
def test_tcp_defer_accept_default
|
153
|
+
return unless defined?(TCP_DEFER_ACCEPT)
|
153
154
|
port = unused_port @test_addr
|
154
155
|
name = "#@test_addr:#{port}"
|
155
156
|
sock = bind_listen(name)
|
156
157
|
cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
|
157
158
|
assert cur >= 1
|
158
|
-
end
|
159
|
+
end
|
159
160
|
|
160
161
|
def test_tcp_defer_accept_disable
|
162
|
+
return unless defined?(TCP_DEFER_ACCEPT)
|
161
163
|
port = unused_port @test_addr
|
162
164
|
name = "#@test_addr:#{port}"
|
163
165
|
sock = bind_listen(name, :tcp_defer_accept => false)
|
164
166
|
cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
|
165
167
|
assert_equal 0, cur
|
166
|
-
end
|
168
|
+
end
|
167
169
|
|
168
170
|
def test_tcp_defer_accept_nr
|
171
|
+
return unless defined?(TCP_DEFER_ACCEPT)
|
169
172
|
port = unused_port @test_addr
|
170
173
|
name = "#@test_addr:#{port}"
|
171
174
|
sock = bind_listen(name, :tcp_defer_accept => 60)
|
172
175
|
cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
|
173
176
|
assert cur > 1
|
174
|
-
end
|
177
|
+
end
|
175
178
|
|
176
179
|
def test_ipv6only
|
177
180
|
port = begin
|
@@ -186,6 +189,7 @@ def test_ipv6only
|
|
186
189
|
end
|
187
190
|
|
188
191
|
def test_reuseport
|
192
|
+
return unless defined?(Socket::SO_REUSEPORT)
|
189
193
|
port = unused_port @test_addr
|
190
194
|
name = "#@test_addr:#{port}"
|
191
195
|
sock = bind_listen(name, :reuseport => true)
|
@@ -193,5 +197,5 @@ def test_reuseport
|
|
193
197
|
assert_operator cur, :>, 0
|
194
198
|
rescue Errno::ENOPROTOOPT
|
195
199
|
# kernel does not support SO_REUSEPORT (older Linux)
|
196
|
-
end
|
200
|
+
end
|
197
201
|
end
|
data/test/unit/test_util.rb
CHANGED
@@ -69,7 +69,7 @@ def test_reopen_logs_renamed_with_encoding
|
|
69
69
|
}
|
70
70
|
}
|
71
71
|
tmp.close!
|
72
|
-
end
|
72
|
+
end
|
73
73
|
|
74
74
|
def test_reopen_logs_renamed_with_internal_encoding
|
75
75
|
tmp = Tempfile.new('')
|
@@ -101,5 +101,5 @@ def test_reopen_logs_renamed_with_internal_encoding
|
|
101
101
|
}
|
102
102
|
}
|
103
103
|
tmp.close!
|
104
|
-
end
|
104
|
+
end
|
105
105
|
end
|
data/unicorn.gemspec
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
-
|
3
|
-
|
4
|
-
require 'olddoc'
|
5
|
-
extend Olddoc::Gemspec
|
6
|
-
name, summary, title = readme_metadata
|
2
|
+
manifest = File.exist?('.manifest') ?
|
3
|
+
IO.readlines('.manifest').map!(&:chomp!) : `git ls-files`.split("\n")
|
7
4
|
|
8
5
|
# don't bother with tests that fork, not worth our time to get working
|
9
6
|
# with `gem check -t` ... (of course we care for them when testing with
|
@@ -14,16 +11,18 @@
|
|
14
11
|
|
15
12
|
Gem::Specification.new do |s|
|
16
13
|
s.name = %q{unicorn}
|
17
|
-
s.version = ENV[
|
18
|
-
s.authors = [
|
19
|
-
s.summary =
|
20
|
-
s.description =
|
14
|
+
s.version = (ENV['VERSION'] || '5.3.0.pre1').dup
|
15
|
+
s.authors = ['unicorn hackers']
|
16
|
+
s.summary = 'Rack HTTP server for fast clients and Unix'
|
17
|
+
s.description = File.read('README').split("\n\n")[1]
|
21
18
|
s.email = %q{unicorn-public@bogomips.org}
|
22
19
|
s.executables = %w(unicorn unicorn_rails)
|
23
20
|
s.extensions = %w(ext/unicorn_http/extconf.rb)
|
24
|
-
s.extra_rdoc_files =
|
21
|
+
s.extra_rdoc_files = IO.readlines('.document').map!(&:chomp!).keep_if do |f|
|
22
|
+
File.exist?(f)
|
23
|
+
end
|
25
24
|
s.files = manifest
|
26
|
-
s.homepage =
|
25
|
+
s.homepage = 'https://bogomips.org/unicorn/'
|
27
26
|
s.test_files = test_files
|
28
27
|
|
29
28
|
# technically we need ">= 1.9.3", too, but avoid the array here since
|
@@ -40,7 +39,6 @@
|
|
40
39
|
s.add_dependency(%q<raindrops>, '~> 0.7')
|
41
40
|
|
42
41
|
s.add_development_dependency('test-unit', '~> 3.0')
|
43
|
-
s.add_development_dependency('olddoc', '~> 1.2')
|
44
42
|
|
45
43
|
# Note: To avoid ambiguity, we intentionally avoid the SPDX-compatible
|
46
44
|
# 'Ruby' here since Ruby 1.9.3 switched to BSD-2-Clause, but we
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unicorn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.3.0.pre1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- unicorn hackers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-03-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -66,20 +66,6 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '3.0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: olddoc
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '1.2'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '1.2'
|
83
69
|
description: |-
|
84
70
|
unicorn is an HTTP server for Rack applications designed to only serve
|
85
71
|
fast clients on low-latency, high-bandwidth connections and take
|
@@ -264,6 +250,7 @@ files:
|
|
264
250
|
- test/exec/README
|
265
251
|
- test/exec/test_exec.rb
|
266
252
|
- test/test_helper.rb
|
253
|
+
- test/unit/test_ccc.rb
|
267
254
|
- test/unit/test_configurator.rb
|
268
255
|
- test/unit/test_droplet.rb
|
269
256
|
- test/unit/test_http_parser.rb
|
@@ -296,12 +283,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
296
283
|
version: '3.0'
|
297
284
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
298
285
|
requirements:
|
299
|
-
- - "
|
286
|
+
- - ">"
|
300
287
|
- !ruby/object:Gem::Version
|
301
|
-
version:
|
288
|
+
version: 1.3.1
|
302
289
|
requirements: []
|
303
290
|
rubyforge_project:
|
304
|
-
rubygems_version: 2.6.
|
291
|
+
rubygems_version: 2.6.10
|
305
292
|
signing_key:
|
306
293
|
specification_version: 4
|
307
294
|
summary: Rack HTTP server for fast clients and Unix
|