unicorn 5.2.0 → 5.3.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 75575863e5a09d4ac351269084628052c8ec554c
4
- data.tar.gz: 5f7fc37e895b8982c3076fc56642ec90d8632d46
3
+ metadata.gz: 6625aff20e7f744a10b6f9724b1ddb30f60e9b51
4
+ data.tar.gz: 0e33bab2037c0dbfdb91bc463bf41dbb502e6f16
5
5
  SHA512:
6
- metadata.gz: 85c7d9f5d6c3056508758a7375809d477ee7e640167470711e4fda53e7f6c52ea6688e708fde80af5e54262dcdd218c0ef9fc966221c1e30762b0bdabf5c41fb
7
- data.tar.gz: 1829ab9e474b1932617c97e37d6efa1075c1c147f8aa726777478741b5ae9106a163194647e03ff8d73786017dd2142afad8804d16625fde48876e10dc0e3a30
6
+ metadata.gz: 18a1e1143713bc8f4d7491ef3684dc0154bc5e1e335d5d2b8ace3f2ca99cbd57d686c7f03601919d8ebfcb68d4472ad1cc8c5802479a45951af5808fa6c53572
7
+ data.tar.gz: '08a6ab6905c240e6fd1e99d26d004b697c76e535ff37832bf71a342ea29fc424a10f790f65ed5496cc10312c3323339dcfe545d3b539130dc6310e6ba81c3d0d'
@@ -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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- DEF_VER = "v5.2.0"
2
+ DEF_VER = "v5.3.0.pre1"
3
3
  CONSTANT = "Unicorn::Const::UNICORN_VERSION"
4
4
  RVF = "lib/unicorn/version.rb"
5
5
  GVF = "GIT-VERSION-FILE"
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, it will be flagged as spam
13
- * Anonymous and pseudonymous messages will always be welcome.
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
- then feel free to email us privately at mailto:unicorn@bogomips.org
19
- instead and your issue will be handled discreetly.
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}[http://raindrops.bogomips.org/] - real-time stats for
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(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
 
@@ -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,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");
@@ -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,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, so this allows
470
- # intentionally violating the current Rack 1.x spec.
522
+ # \env[\"rack.input\"] to be rewindable,
523
+ # but the Rack 2.x spec does not.
471
524
  #
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.
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)
@@ -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 = 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
- # 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,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
@@ -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
- m = "reaped #{status.inspect} worker=#{worker.nr rescue 'unknown'}"
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
- (3..1024).each do |io|
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
- if pid = fork
504
- @workers[pid] = worker
505
- worker.atfork_parent
506
- else
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
- @logger.info "worker=#{worker.nr} ready"
691
+ @after_worker_ready.call(self, worker)
648
692
 
649
693
  begin
650
694
  nr < 0 and reopen_worker_logs(worker.nr)
@@ -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, accf_arg(name))
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
- Kgio::TCPServer.for_fd(sock.fileno)
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
- Kgio::TCPServer.for_fd(sock.fileno)
203
+ TCPSrv.for_fd(sock.fileno)
189
204
  rescue ArgumentError
190
205
  Kgio::UNIXServer.for_fd(sock.fileno)
191
206
  end
@@ -1,16 +1,17 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
- # When processing uploads, Unicorn may expose a StreamInput object under
4
- # "rack.input" of the (future) Rack (2.x) environment.
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
@@ -1,6 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
- # acts like tee(1) on an input input to provide a input-like stream
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, Unicorn exposes a TeeInput object under
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
- if buffer && buffer.size > 0
129
- @tmp.write(buffer)
130
- end
128
+ @tmp.write(buffer) if buffer
131
129
  buffer
132
130
  end
133
131
  end
@@ -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
- def user(user, group = nil)
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
@@ -52,7 +52,7 @@ t_begin "worker pid unchanged (again)" && {
52
52
  }
53
53
 
54
54
  t_begin "nuking the existing Unicorn succeeds" && {
55
- kill -9 $unicorn_pid $worker_pid
55
+ kill -9 $unicorn_pid
56
56
  while kill -0 $unicorn_pid
57
57
  do
58
58
  sleep 1
@@ -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
  }
@@ -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
@@ -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
- # disabled test on old Rubies: https://bugs.ruby-lang.org/issues/11336
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
- assert_equal 1, sock.getsockopt(:SOL_SOCKET, :SO_KEEPALIVE).int,
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 if defined?(TCP_DEFER_ACCEPT)
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 if defined?(TCP_DEFER_ACCEPT)
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 if defined?(TCP_DEFER_ACCEPT)
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 if defined?(Socket::SO_REUSEPORT)
200
+ end
197
201
  end
@@ -69,7 +69,7 @@ def test_reopen_logs_renamed_with_encoding
69
69
  }
70
70
  }
71
71
  tmp.close!
72
- end if STDIN.respond_to?(:external_encoding)
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 if STDIN.respond_to?(:external_encoding)
104
+ end
105
105
  end
@@ -1,9 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
- ENV["VERSION"] or abort "VERSION= must be specified"
3
- manifest = File.readlines('.manifest').map! { |x| x.chomp! }
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["VERSION"].dup
18
- s.authors = ["#{name} hackers"]
19
- s.summary = summary
20
- s.description = readme_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 = extra_rdoc_files(manifest)
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 = Olddoc.config['rdoc_url']
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.2.0
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: 2016-10-31 00:00:00.000000000 Z
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: '0'
288
+ version: 1.3.1
302
289
  requirements: []
303
290
  rubyforge_project:
304
- rubygems_version: 2.6.7
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