unicorn 5.2.0 → 5.3.0.pre1
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/.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
|