unicorn 5.2.0 → 5.3.1

Sign up to get free protection for your applications and to get access to all the features.
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(void)
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
- rb_global_variable(&cf->value);
80
+ rb_ary_push(mark_ary, cf->value);
81
81
  }
82
82
  }
83
83
 
@@ -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")
@@ -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
- rb_global_variable(&g_##N); \
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(void)
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");
@@ -64,13 +64,13 @@ static VALUE httpdate(VALUE self)
64
64
  return buf;
65
65
  }
66
66
 
67
- void init_unicorn_httpdate(void)
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
- rb_global_variable(&buf);
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(void);
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(void);
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: http://raindrops.bogomips.org/
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)
@@ -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, # for Rack 2.x: (Rack::VERSION[0] <= 1),
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, so this allows
470
- # intentionally violating the current Rack 1.x spec.
524
+ # \env[\"rack.input\"] to be rewindable,
525
+ # but the Rack 2.x spec does not.
471
526
  #
472
- # +rewindable_input+ defaults to +true+ when used with Rack 1.x for
473
- # Rack conformance. When Rack 2.x is finalized, this will most
474
- # likely default to +false+ while still conforming to the newer
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)
@@ -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
- # A frozen format for this is about 15% faster
28
- # Drop these frozen strings when Ruby 2.2 becomes more prevalent,
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
- # detect if the socket is valid by writing a partial response:
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