unicorn 5.2.0 → 5.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.manifest +1 -0
- data/.olddoc.yml +0 -1
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/HACKING +0 -1
- data/ISSUES +12 -6
- data/LATEST +93 -27
- data/Links +1 -1
- data/NEWS +216 -0
- 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.c +9 -4
- data/ext/unicorn_http/unicorn_http.rl +9 -4
- data/lib/unicorn.rb +1 -1
- data/lib/unicorn/configurator.rb +65 -7
- data/lib/unicorn/http_request.rb +89 -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/version.rb +1 -1
- 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 +6 -5
- 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 +4 -17
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
|
|
@@ -15,7 +15,7 @@
|
|
15
15
|
#include "global_variables.h"
|
16
16
|
#include "c_util.h"
|
17
17
|
|
18
|
-
void init_unicorn_httpdate(
|
18
|
+
void init_unicorn_httpdate(VALUE mark_ary);
|
19
19
|
|
20
20
|
#define UH_FL_CHUNKED 0x1
|
21
21
|
#define UH_FL_HASBODY 0x2
|
@@ -4211,8 +4211,11 @@ static VALUE HttpParser_rssget(VALUE self)
|
|
4211
4211
|
|
4212
4212
|
void Init_unicorn_http(void)
|
4213
4213
|
{
|
4214
|
+
static VALUE mark_ary;
|
4214
4215
|
VALUE mUnicorn, cHttpParser;
|
4215
4216
|
|
4217
|
+
mark_ary = rb_ary_new();
|
4218
|
+
rb_global_variable(&mark_ary);
|
4216
4219
|
mUnicorn = rb_define_module("Unicorn");
|
4217
4220
|
cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
|
4218
4221
|
eHttpParserError =
|
@@ -4222,7 +4225,7 @@ void Init_unicorn_http(void)
|
|
4222
4225
|
e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError",
|
4223
4226
|
eHttpParserError);
|
4224
4227
|
|
4225
|
-
init_globals();
|
4228
|
+
init_globals(mark_ary);
|
4226
4229
|
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
4227
4230
|
rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
|
4228
4231
|
rb_define_method(cHttpParser, "clear", HttpParser_clear, 0);
|
@@ -4258,14 +4261,16 @@ void Init_unicorn_http(void)
|
|
4258
4261
|
|
4259
4262
|
rb_define_singleton_method(cHttpParser, "max_header_len=", set_maxhdrlen, 1);
|
4260
4263
|
|
4261
|
-
init_common_fields();
|
4264
|
+
init_common_fields(mark_ary);
|
4262
4265
|
SET_GLOBAL(g_http_host, "HOST");
|
4263
4266
|
SET_GLOBAL(g_http_trailer, "TRAILER");
|
4264
4267
|
SET_GLOBAL(g_http_transfer_encoding, "TRANSFER_ENCODING");
|
4265
4268
|
SET_GLOBAL(g_content_length, "CONTENT_LENGTH");
|
4266
4269
|
SET_GLOBAL(g_http_connection, "CONNECTION");
|
4267
4270
|
id_set_backtrace = rb_intern("set_backtrace");
|
4268
|
-
init_unicorn_httpdate();
|
4271
|
+
init_unicorn_httpdate(mark_ary);
|
4272
|
+
|
4273
|
+
OBJ_FREEZE(mark_ary);
|
4269
4274
|
|
4270
4275
|
#ifndef HAVE_RB_HASH_CLEAR
|
4271
4276
|
id_clear = rb_intern("clear");
|
@@ -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,11 @@ 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();
|
924
|
+
rb_global_variable(&mark_ary);
|
922
925
|
mUnicorn = rb_define_module("Unicorn");
|
923
926
|
cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
|
924
927
|
eHttpParserError =
|
@@ -928,7 +931,7 @@ void Init_unicorn_http(void)
|
|
928
931
|
e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError",
|
929
932
|
eHttpParserError);
|
930
933
|
|
931
|
-
init_globals();
|
934
|
+
init_globals(mark_ary);
|
932
935
|
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
933
936
|
rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
|
934
937
|
rb_define_method(cHttpParser, "clear", HttpParser_clear, 0);
|
@@ -964,14 +967,16 @@ void Init_unicorn_http(void)
|
|
964
967
|
|
965
968
|
rb_define_singleton_method(cHttpParser, "max_header_len=", set_maxhdrlen, 1);
|
966
969
|
|
967
|
-
init_common_fields();
|
970
|
+
init_common_fields(mark_ary);
|
968
971
|
SET_GLOBAL(g_http_host, "HOST");
|
969
972
|
SET_GLOBAL(g_http_trailer, "TRAILER");
|
970
973
|
SET_GLOBAL(g_http_transfer_encoding, "TRANSFER_ENCODING");
|
971
974
|
SET_GLOBAL(g_content_length, "CONTENT_LENGTH");
|
972
975
|
SET_GLOBAL(g_http_connection, "CONNECTION");
|
973
976
|
id_set_backtrace = rb_intern("set_backtrace");
|
974
|
-
init_unicorn_httpdate();
|
977
|
+
init_unicorn_httpdate(mark_ary);
|
978
|
+
|
979
|
+
OBJ_FREEZE(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,38 @@ 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
|
+
#
|
176
|
+
# after_worker_exit is only available in unicorn 5.3.0+
|
177
|
+
def after_worker_exit(*args, &block)
|
178
|
+
set_hook(:after_worker_exit, block_given? ? block : args[0], 3)
|
179
|
+
end
|
180
|
+
|
181
|
+
# sets after_worker_ready hook to a given block. This block will be called
|
182
|
+
# by a worker process after it has been fully loaded, directly before it
|
183
|
+
# starts responding to requests:
|
184
|
+
#
|
185
|
+
# after_worker_ready do |server,worker|
|
186
|
+
# server.logger.info("worker #{worker.nr} ready, dropping privileges")
|
187
|
+
# worker.user('username', 'groupname')
|
188
|
+
# end
|
189
|
+
#
|
190
|
+
# Do not use Configurator#user if you rely on changing users in the
|
191
|
+
# after_worker_ready hook.
|
192
|
+
#
|
193
|
+
# after_worker_ready is only available in unicorn 5.3.0+
|
194
|
+
def after_worker_ready(*args, &block)
|
195
|
+
set_hook(:after_worker_ready, block_given? ? block : args[0])
|
196
|
+
end
|
197
|
+
|
154
198
|
# sets before_fork got be a given Proc object. This Proc
|
155
199
|
# object will be called by the master process before forking
|
156
200
|
# each worker.
|
@@ -200,6 +244,17 @@ def timeout(seconds)
|
|
200
244
|
set[:timeout] = seconds > max ? max : seconds
|
201
245
|
end
|
202
246
|
|
247
|
+
# Whether to exec in each worker process after forking. This changes the
|
248
|
+
# memory layout of each worker process, which is a security feature designed
|
249
|
+
# to defeat possible address space discovery attacks. Note that using
|
250
|
+
# worker_exec only makes sense if you are not preloading the application,
|
251
|
+
# and will result in higher memory usage.
|
252
|
+
#
|
253
|
+
# worker_exec is only available in unicorn 5.3.0+
|
254
|
+
def worker_exec(bool)
|
255
|
+
set_bool(:worker_exec, bool)
|
256
|
+
end
|
257
|
+
|
203
258
|
# sets the current number of worker_processes to +nr+. Each worker
|
204
259
|
# process will serve exactly one client at a time. You can
|
205
260
|
# increment or decrement this value at runtime by sending SIGTTIN
|
@@ -466,13 +521,12 @@ def preload_app(bool)
|
|
466
521
|
# Disabling rewindability can improve performance by lowering
|
467
522
|
# I/O and memory usage for applications that accept uploads.
|
468
523
|
# Keep in mind that the Rack 1.x spec requires
|
469
|
-
# \env[\"rack.input\"] to be rewindable,
|
470
|
-
#
|
524
|
+
# \env[\"rack.input\"] to be rewindable,
|
525
|
+
# but the Rack 2.x spec does not.
|
471
526
|
#
|
472
|
-
# +rewindable_input+ defaults to +true+
|
473
|
-
#
|
474
|
-
#
|
475
|
-
# (less demanding) spec.
|
527
|
+
# +rewindable_input+ defaults to +true+ for compatibility.
|
528
|
+
# Setting it to +false+ may be safe for applications and
|
529
|
+
# frameworks developed for Rack 2.x and later.
|
476
530
|
def rewindable_input(bool)
|
477
531
|
set_bool(:rewindable_input, bool)
|
478
532
|
end
|
@@ -548,6 +602,10 @@ def working_directory(path)
|
|
548
602
|
# This switch will occur after calling the after_fork hook, and only
|
549
603
|
# if the Worker#user method is not called in the after_fork hook
|
550
604
|
# +group+ is optional and will not change if unspecified.
|
605
|
+
#
|
606
|
+
# Do not use Configurator#user if you rely on changing users in the
|
607
|
+
# after_worker_ready hook. Instead, you need to call Worker#user
|
608
|
+
# directly in after_worker_ready.
|
551
609
|
def user(user, group = nil)
|
552
610
|
# raises ArgumentError on invalid user/group
|
553
611
|
Etc.getpwnam(user)
|
data/lib/unicorn/http_request.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
# :enddoc:
|
3
3
|
# no stable API here
|
4
4
|
require 'unicorn_http'
|
5
|
+
require 'raindrops'
|
5
6
|
|
6
7
|
# TODO: remove redundant names
|
7
8
|
Unicorn.const_set(:HttpRequest, Unicorn::HttpParser)
|
@@ -24,12 +25,11 @@ class Unicorn::HttpParser
|
|
24
25
|
NULL_IO = StringIO.new("")
|
25
26
|
|
26
27
|
# :stopdoc:
|
27
|
-
|
28
|
-
|
29
|
-
# 2.2+ optimizes hash assignments when used with literal string keys
|
30
|
-
HTTP_RESPONSE_START = [ 'HTTP', '/1.1 ']
|
28
|
+
HTTP_RESPONSE_START = [ 'HTTP'.freeze, '/1.1 '.freeze ]
|
29
|
+
EMPTY_ARRAY = [].freeze
|
31
30
|
@@input_class = Unicorn::TeeInput
|
32
31
|
@@check_client_connection = false
|
32
|
+
@@tcpi_inspect_ok = Socket.const_defined?(:TCP_INFO)
|
33
33
|
|
34
34
|
def self.input_class
|
35
35
|
@@input_class
|
@@ -83,11 +83,7 @@ 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,88 @@ 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(Socket::IPPROTO_TCP, Socket::TCP_INFO).inspect
|
159
|
+
if opt =~ /\bstate=(\S+)/
|
160
|
+
raise Errno::EPIPE, "client closed connection".freeze,
|
161
|
+
EMPTY_ARRAY if closed_state_str?($1)
|
162
|
+
else
|
163
|
+
@@tcpi_inspect_ok = false
|
164
|
+
write_http_header(socket)
|
165
|
+
end
|
166
|
+
opt.clear
|
167
|
+
else
|
168
|
+
write_http_header(socket)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def closed_state_str?(state)
|
173
|
+
case state
|
174
|
+
when 'ESTABLISHED'
|
175
|
+
false
|
176
|
+
# not a typo, ruby maps TCP_CLOSE (no 'D') to state=CLOSED (w/ 'D')
|
177
|
+
when 'CLOSE_WAIT', 'TIME_WAIT', 'CLOSED', 'LAST_ACK', 'CLOSING'
|
178
|
+
true
|
179
|
+
else
|
180
|
+
false
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def write_http_header(socket) # :nodoc:
|
186
|
+
if headers?
|
187
|
+
self.response_start_sent = true
|
188
|
+
HTTP_RESPONSE_START.each { |c| socket.write(c) }
|
189
|
+
end
|
190
|
+
end
|
111
191
|
end
|