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.
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