unicorn 5.2.0 → 5.3.0.pre1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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