unicorn 5.4.1 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.manifest +10 -5
  3. data/.olddoc.yml +15 -7
  4. data/Application_Timeouts +4 -4
  5. data/CONTRIBUTORS +6 -2
  6. data/Documentation/.gitignore +1 -3
  7. data/Documentation/unicorn.1 +222 -0
  8. data/Documentation/unicorn_rails.1 +207 -0
  9. data/FAQ +1 -1
  10. data/GIT-VERSION-FILE +1 -1
  11. data/GIT-VERSION-GEN +1 -1
  12. data/GNUmakefile +112 -57
  13. data/HACKING +2 -9
  14. data/ISSUES +29 -33
  15. data/KNOWN_ISSUES +2 -2
  16. data/LATEST +16 -21
  17. data/LICENSE +2 -2
  18. data/Links +13 -11
  19. data/NEWS +197 -0
  20. data/README +27 -14
  21. data/SIGNALS +1 -1
  22. data/Sandbox +5 -5
  23. data/archive/slrnpull.conf +1 -1
  24. data/bin/unicorn +3 -1
  25. data/bin/unicorn_rails +2 -2
  26. data/examples/big_app_gc.rb +1 -1
  27. data/examples/logrotate.conf +3 -3
  28. data/examples/nginx.conf +4 -3
  29. data/examples/unicorn.conf.minimal.rb +2 -2
  30. data/examples/unicorn.conf.rb +2 -2
  31. data/examples/unicorn@.service +7 -0
  32. data/ext/unicorn_http/c_util.h +5 -13
  33. data/ext/unicorn_http/common_field_optimization.h +23 -6
  34. data/ext/unicorn_http/epollexclusive.h +124 -0
  35. data/ext/unicorn_http/ext_help.h +0 -24
  36. data/ext/unicorn_http/extconf.rb +32 -6
  37. data/ext/unicorn_http/global_variables.h +3 -3
  38. data/ext/unicorn_http/httpdate.c +3 -2
  39. data/ext/unicorn_http/unicorn_http.c +262 -237
  40. data/ext/unicorn_http/unicorn_http.rl +52 -27
  41. data/lib/unicorn/configurator.rb +25 -4
  42. data/lib/unicorn/http_request.rb +12 -3
  43. data/lib/unicorn/http_server.rb +62 -36
  44. data/lib/unicorn/launcher.rb +1 -1
  45. data/lib/unicorn/oob_gc.rb +5 -5
  46. data/lib/unicorn/select_waiter.rb +6 -0
  47. data/lib/unicorn/socket_helper.rb +1 -0
  48. data/lib/unicorn/tmpio.rb +8 -2
  49. data/lib/unicorn/util.rb +1 -1
  50. data/lib/unicorn/version.rb +1 -1
  51. data/lib/unicorn/worker.rb +16 -2
  52. data/lib/unicorn.rb +21 -9
  53. data/man/man1/unicorn.1 +89 -88
  54. data/man/man1/unicorn_rails.1 +78 -83
  55. data/t/GNUmakefile +3 -72
  56. data/t/README +4 -4
  57. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  58. data/t/t0301.ru +13 -0
  59. data/t/test-lib.sh +2 -1
  60. data/test/benchmark/README +14 -4
  61. data/test/benchmark/ddstream.ru +50 -0
  62. data/test/benchmark/readinput.ru +40 -0
  63. data/test/benchmark/uconnect.perl +66 -0
  64. data/test/exec/test_exec.rb +14 -12
  65. data/test/test_helper.rb +38 -30
  66. data/test/unit/test_ccc.rb +4 -3
  67. data/test/unit/test_http_parser.rb +16 -0
  68. data/test/unit/test_http_parser_ng.rb +81 -0
  69. data/test/unit/test_server.rb +81 -7
  70. data/test/unit/test_signals.rb +6 -6
  71. data/test/unit/test_socket_helper.rb +1 -1
  72. data/test/unit/test_upload.rb +9 -14
  73. data/test/unit/test_util.rb +29 -3
  74. data/test/unit/test_waiter.rb +34 -0
  75. data/unicorn.gemspec +8 -7
  76. metadata +19 -13
  77. data/Documentation/GNUmakefile +0 -30
  78. data/Documentation/unicorn.1.txt +0 -187
  79. data/Documentation/unicorn_rails.1.txt +0 -175
  80. data/t/hijack.ru +0 -55
  81. data/t/t0200-rack-hijack.sh +0 -51
@@ -12,8 +12,9 @@
12
12
  #include "common_field_optimization.h"
13
13
  #include "global_variables.h"
14
14
  #include "c_util.h"
15
+ #include "epollexclusive.h"
15
16
 
16
- void init_unicorn_httpdate(VALUE mark_ary);
17
+ void init_unicorn_httpdate(void);
17
18
 
18
19
  #define UH_FL_CHUNKED 0x1
19
20
  #define UH_FL_HASBODY 0x2
@@ -62,19 +63,8 @@ struct http_parser {
62
63
  } len;
63
64
  };
64
65
 
65
- static ID id_set_backtrace;
66
-
67
- #ifdef HAVE_RB_HASH_CLEAR /* Ruby >= 2.0 */
68
- # define my_hash_clear(h) (void)rb_hash_clear(h)
69
- #else /* !HAVE_RB_HASH_CLEAR - Ruby <= 1.9.3 */
70
-
71
- static ID id_clear;
72
-
73
- static void my_hash_clear(VALUE h)
74
- {
75
- rb_funcall(h, id_clear, 0);
76
- }
77
- #endif /* HAVE_RB_HASH_CLEAR */
66
+ static ID id_set_backtrace, id_is_chunked_p;
67
+ static VALUE cHttpParser;
78
68
 
79
69
  static void finalize_header(struct http_parser *hp);
80
70
 
@@ -220,6 +210,19 @@ static void write_cont_value(struct http_parser *hp,
220
210
  rb_str_buf_cat(hp->cont, vptr, end + 1);
221
211
  }
222
212
 
213
+ static int is_chunked(VALUE v)
214
+ {
215
+ /* common case first */
216
+ if (STR_CSTR_CASE_EQ(v, "chunked"))
217
+ return 1;
218
+
219
+ /*
220
+ * call Ruby function in unicorn/http_request.rb to deal with unlikely
221
+ * comma-delimited case
222
+ */
223
+ return rb_funcall(cHttpParser, id_is_chunked_p, 1, v) != Qfalse;
224
+ }
225
+
223
226
  static void write_value(struct http_parser *hp,
224
227
  const char *buffer, const char *p)
225
228
  {
@@ -246,7 +249,9 @@ static void write_value(struct http_parser *hp,
246
249
  f = uncommon_field(field, flen);
247
250
  } else if (f == g_http_connection) {
248
251
  hp_keepalive_connection(hp, v);
249
- } else if (f == g_content_length) {
252
+ } else if (f == g_content_length && !HP_FL_TEST(hp, CHUNKED)) {
253
+ if (hp->len.content)
254
+ parser_raise(eHttpParserError, "Content-Length already set");
250
255
  hp->len.content = parse_length(RSTRING_PTR(v), RSTRING_LEN(v));
251
256
  if (hp->len.content < 0)
252
257
  parser_raise(eHttpParserError, "invalid Content-Length");
@@ -254,9 +259,30 @@ static void write_value(struct http_parser *hp,
254
259
  HP_FL_SET(hp, HASBODY);
255
260
  hp_invalid_if_trailer(hp);
256
261
  } else if (f == g_http_transfer_encoding) {
257
- if (STR_CSTR_CASE_EQ(v, "chunked")) {
262
+ if (is_chunked(v)) {
263
+ if (HP_FL_TEST(hp, CHUNKED))
264
+ /*
265
+ * RFC 7230 3.3.1:
266
+ * A sender MUST NOT apply chunked more than once to a message body
267
+ * (i.e., chunking an already chunked message is not allowed).
268
+ */
269
+ parser_raise(eHttpParserError, "Transfer-Encoding double chunked");
270
+
258
271
  HP_FL_SET(hp, CHUNKED);
259
272
  HP_FL_SET(hp, HASBODY);
273
+
274
+ /* RFC 7230 3.3.3, 3: favor chunked if Content-Length exists */
275
+ hp->len.content = 0;
276
+ } else if (HP_FL_TEST(hp, CHUNKED)) {
277
+ /*
278
+ * RFC 7230 3.3.3, point 3 states:
279
+ * If a Transfer-Encoding header field is present in a request and
280
+ * the chunked transfer coding is not the final encoding, the
281
+ * message body length cannot be determined reliably; the server
282
+ * MUST respond with the 400 (Bad Request) status code and then
283
+ * close the connection.
284
+ */
285
+ parser_raise(eHttpParserError, "invalid Transfer-Encoding");
260
286
  }
261
287
  hp_invalid_if_trailer(hp);
262
288
  } else if (f == g_http_trailer) {
@@ -487,7 +513,7 @@ static void set_url_scheme(VALUE env, VALUE *server_port)
487
513
  * and X-Forwarded-Proto handling from this parser? We've had it
488
514
  * forever and nobody has said anything against it, either.
489
515
  * Anyways, please send comments to our public mailing list:
490
- * unicorn-public@bogomips.org (no HTML mail, no subscription necessary)
516
+ * unicorn-public@yhbt.net (no HTML mail, no subscription necessary)
491
517
  */
492
518
  scheme = rb_hash_aref(env, g_http_x_forwarded_ssl);
493
519
  if (!NIL_P(scheme) && STR_CSTR_EQ(scheme, "on")) {
@@ -613,7 +639,7 @@ static VALUE HttpParser_clear(VALUE self)
613
639
  return HttpParser_init(self);
614
640
 
615
641
  http_parser_init(hp);
616
- my_hash_clear(hp->env);
642
+ rb_hash_clear(hp->env);
617
643
 
618
644
  return self;
619
645
  }
@@ -931,11 +957,8 @@ static VALUE HttpParser_rssget(VALUE self)
931
957
 
932
958
  void Init_unicorn_http(void)
933
959
  {
934
- static VALUE mark_ary;
935
- VALUE mUnicorn, cHttpParser;
960
+ VALUE mUnicorn;
936
961
 
937
- mark_ary = rb_ary_new();
938
- rb_global_variable(&mark_ary);
939
962
  mUnicorn = rb_define_module("Unicorn");
940
963
  cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
941
964
  eHttpParserError =
@@ -945,7 +968,8 @@ void Init_unicorn_http(void)
945
968
  e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError",
946
969
  eHttpParserError);
947
970
 
948
- init_globals(mark_ary);
971
+ id_uminus = rb_intern("-@");
972
+ init_globals();
949
973
  rb_define_alloc_func(cHttpParser, HttpParser_alloc);
950
974
  rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
951
975
  rb_define_method(cHttpParser, "clear", HttpParser_clear, 0);
@@ -982,19 +1006,20 @@ void Init_unicorn_http(void)
982
1006
 
983
1007
  rb_define_singleton_method(cHttpParser, "max_header_len=", set_maxhdrlen, 1);
984
1008
 
985
- init_common_fields(mark_ary);
1009
+ init_common_fields();
986
1010
  SET_GLOBAL(g_http_host, "HOST");
987
1011
  SET_GLOBAL(g_http_trailer, "TRAILER");
988
1012
  SET_GLOBAL(g_http_transfer_encoding, "TRANSFER_ENCODING");
989
1013
  SET_GLOBAL(g_content_length, "CONTENT_LENGTH");
990
1014
  SET_GLOBAL(g_http_connection, "CONNECTION");
991
1015
  id_set_backtrace = rb_intern("set_backtrace");
992
- init_unicorn_httpdate(mark_ary);
993
-
994
- OBJ_FREEZE(mark_ary);
1016
+ init_unicorn_httpdate();
995
1017
 
996
1018
  #ifndef HAVE_RB_HASH_CLEAR
997
1019
  id_clear = rb_intern("clear");
998
1020
  #endif
1021
+ id_is_chunked_p = rb_intern("is_chunked?");
1022
+
1023
+ init_epollexclusive(mUnicorn);
999
1024
  }
1000
1025
  #undef SET_GLOBAL
@@ -3,11 +3,11 @@ require 'logger'
3
3
 
4
4
  # Implements a simple DSL for configuring a unicorn server.
5
5
  #
6
- # See https://bogomips.org/unicorn/examples/unicorn.conf.rb and
7
- # https://bogomips.org/unicorn/examples/unicorn.conf.minimal.rb
6
+ # See https://yhbt.net/unicorn/examples/unicorn.conf.rb and
7
+ # https://yhbt.net/unicorn/examples/unicorn.conf.minimal.rb
8
8
  # example configuration files. An example config file for use with
9
9
  # nginx is also available at
10
- # https://bogomips.org/unicorn/examples/nginx.conf
10
+ # https://yhbt.net/unicorn/examples/nginx.conf
11
11
  #
12
12
  # See the link:/TUNING.html document for more information on tuning unicorn.
13
13
  class Unicorn::Configurator
@@ -53,6 +53,7 @@ class Unicorn::Configurator
53
53
  server.logger.info("worker=#{worker.nr} ready")
54
54
  },
55
55
  :pid => nil,
56
+ :early_hints => false,
56
57
  :worker_exec => false,
57
58
  :preload_app => false,
58
59
  :check_client_connection => false,
@@ -88,6 +89,9 @@ class Unicorn::Configurator
88
89
  RACKUP[:set_listener] and
89
90
  set[:listeners] << "#{RACKUP[:host]}:#{RACKUP[:port]}"
90
91
 
92
+ RACKUP[:no_default_middleware] and
93
+ set[:default_middleware] = false
94
+
91
95
  # unicorn_rails creates dirs here after working_directory is bound
92
96
  after_reload.call if after_reload
93
97
 
@@ -235,7 +239,7 @@ class Unicorn::Configurator
235
239
  # server 192.168.0.9:8080 fail_timeout=0;
236
240
  # }
237
241
  #
238
- # See http://nginx.org/en/docs/http/ngx_http_upstream_module.html
242
+ # See https://nginx.org/en/docs/http/ngx_http_upstream_module.html
239
243
  # for more details on nginx upstream configuration.
240
244
  def timeout(seconds)
241
245
  set_int(:timeout, seconds, 3)
@@ -265,6 +269,23 @@ class Unicorn::Configurator
265
269
  set_int(:worker_processes, nr, 1)
266
270
  end
267
271
 
272
+ # sets whether to add default middleware in the development and
273
+ # deployment RACK_ENVs.
274
+ #
275
+ # default_middleware is only available in unicorn 5.5.0+
276
+ def default_middleware(bool)
277
+ set_bool(:default_middleware, bool)
278
+ end
279
+
280
+ # sets whether to enable the proposed early hints Rack API.
281
+ # If enabled, Rails 5.2+ will automatically send a 103 Early Hint
282
+ # for all the `javascript_include_tag` and `stylesheet_link_tag`
283
+ # in your response. See: https://api.rubyonrails.org/v5.2/classes/ActionDispatch/Request.html#method-i-send_early_hints
284
+ # See also https://tools.ietf.org/html/rfc8297
285
+ def early_hints(bool)
286
+ set_bool(:early_hints, bool)
287
+ end
288
+
268
289
  # sets listeners to the given +addresses+, replacing or augmenting the
269
290
  # current set. This is for the global listener pool shared by all
270
291
  # worker processes. For per-worker listeners, see the after_fork example
@@ -2,7 +2,6 @@
2
2
  # :enddoc:
3
3
  # no stable API here
4
4
  require 'unicorn_http'
5
- require 'raindrops'
6
5
 
7
6
  # TODO: remove redundant names
8
7
  Unicorn.const_set(:HttpRequest, Unicorn::HttpParser)
@@ -63,10 +62,9 @@ class Unicorn::HttpParser
63
62
  # This does minimal exception trapping and it is up to the caller
64
63
  # to handle any socket errors (e.g. user aborted upload).
65
64
  def read(socket)
66
- clear
67
65
  e = env
68
66
 
69
- # From http://www.ietf.org/rfc/rfc3875:
67
+ # From https://www.ietf.org/rfc/rfc3875:
70
68
  # "Script authors should be aware that the REMOTE_ADDR and
71
69
  # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
72
70
  # may not identify the ultimate source of the request. They
@@ -189,4 +187,15 @@ class Unicorn::HttpParser
189
187
  HTTP_RESPONSE_START.each { |c| socket.write(c) }
190
188
  end
191
189
  end
190
+
191
+ # called by ext/unicorn_http/unicorn_http.rl via rb_funcall
192
+ def self.is_chunked?(v) # :nodoc:
193
+ vals = v.split(/[ \t]*,[ \t]*/).map!(&:downcase)
194
+ if vals.pop == 'chunked'.freeze
195
+ return true unless vals.include?('chunked'.freeze)
196
+ raise Unicorn::HttpParserError, 'double chunked', []
197
+ end
198
+ return false unless vals.include?('chunked'.freeze)
199
+ raise Unicorn::HttpParserError, 'chunked not last', []
200
+ end
192
201
  end
@@ -6,7 +6,7 @@
6
6
  # forked worker children.
7
7
  #
8
8
  # Users do not need to know the internals of this class, but reading the
9
- # {source}[https://bogomips.org/unicorn.git/tree/lib/unicorn/http_server.rb]
9
+ # {source}[https://yhbt.net/unicorn.git/tree/lib/unicorn/http_server.rb]
10
10
  # is education for programmers wishing to learn how unicorn works.
11
11
  # See Unicorn::Configurator for information on how to configure unicorn.
12
12
  class Unicorn::HttpServer
@@ -14,7 +14,8 @@ class Unicorn::HttpServer
14
14
  attr_accessor :app, :timeout, :worker_processes,
15
15
  :before_fork, :after_fork, :before_exec,
16
16
  :listener_opts, :preload_app,
17
- :orig_app, :config, :ready_pipe, :user
17
+ :orig_app, :config, :ready_pipe, :user,
18
+ :default_middleware, :early_hints
18
19
  attr_writer :after_worker_exit, :after_worker_ready, :worker_exec
19
20
 
20
21
  attr_reader :pid, :logger
@@ -68,8 +69,8 @@ class Unicorn::HttpServer
68
69
  # incoming requests on the socket.
69
70
  def initialize(app, options = {})
70
71
  @app = app
71
- @request = Unicorn::HttpRequest.new
72
72
  @reexec_pid = 0
73
+ @default_middleware = true
73
74
  options = options.dup
74
75
  @ready_pipe = options.delete(:ready_pipe)
75
76
  @init_listeners = options[:listeners] ? options[:listeners].dup : []
@@ -82,7 +83,7 @@ class Unicorn::HttpServer
82
83
  # * The master process never closes or reinitializes this once
83
84
  # initialized. Signal handlers in the master process will write to
84
85
  # it to wake up the master from IO.select in exactly the same manner
85
- # djb describes in http://cr.yp.to/docs/selfpipe.html
86
+ # djb describes in https://cr.yp.to/docs/selfpipe.html
86
87
  #
87
88
  # * The workers immediately close the pipe they inherit. See the
88
89
  # Unicorn::Worker class for the pipe workers use.
@@ -380,7 +381,7 @@ class Unicorn::HttpServer
380
381
 
381
382
  # wait for a signal hander to wake us up and then consume the pipe
382
383
  def master_sleep(sec)
383
- @self_pipe[0].kgio_wait_readable(sec) or return
384
+ @self_pipe[0].wait(sec) or return
384
385
  # 11 bytes is the maximum string length which can be embedded within
385
386
  # the Ruby itself and not require a separate malloc (on 32-bit MRI 1.9+).
386
387
  # Most reads are only one byte here and uncommon, so it's not worth a
@@ -520,9 +521,6 @@ class Unicorn::HttpServer
520
521
  Unicorn::Configurator::RACKUP.clear
521
522
  @ready_pipe = @init_listeners = @before_exec = @before_fork = nil
522
523
 
523
- # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/36450
524
- srand # remove in unicorn 6
525
-
526
524
  # The OpenSSL PRNG is seeded with only the pid, and apps with frequently
527
525
  # dying workers can recycle pids
528
526
  OpenSSL::Random.seed(rand.to_s) if defined?(OpenSSL::Random)
@@ -589,10 +587,29 @@ class Unicorn::HttpServer
589
587
  rescue
590
588
  end
591
589
 
590
+ def e103_response_write(client, headers)
591
+ response = if @request.response_start_sent
592
+ "103 Early Hints\r\n"
593
+ else
594
+ "HTTP/1.1 103 Early Hints\r\n"
595
+ end
596
+
597
+ headers.each_pair do |k, vs|
598
+ next if !vs || vs.empty?
599
+ values = vs.to_s.split("\n".freeze)
600
+ values.each do |v|
601
+ response << "#{k}: #{v}\r\n"
602
+ end
603
+ end
604
+ response << "\r\n".freeze
605
+ response << "HTTP/1.1 ".freeze if @request.response_start_sent
606
+ client.write(response)
607
+ end
608
+
592
609
  def e100_response_write(client, env)
593
610
  # We use String#freeze to avoid allocations under Ruby 2.1+
594
611
  # Not many users hit this code path, so it's better to reduce the
595
- # constant table sizes even for 1.9.3-2.0 users who'll hit extra
612
+ # constant table sizes even for Ruby 2.0 users who'll hit extra
596
613
  # allocations here.
597
614
  client.write(@request.response_start_sent ?
598
615
  "100 Continue\r\n\r\nHTTP/1.1 ".freeze :
@@ -603,7 +620,18 @@ class Unicorn::HttpServer
603
620
  # once a client is accepted, it is processed in its entirety here
604
621
  # in 3 easy steps: read request, call app, write app response
605
622
  def process_client(client)
606
- status, headers, body = @app.call(env = @request.read(client))
623
+ @request = Unicorn::HttpRequest.new
624
+ env = @request.read(client)
625
+
626
+ if early_hints
627
+ env["rack.early_hints"] = lambda do |headers|
628
+ e103_response_write(client, headers)
629
+ end
630
+ end
631
+
632
+ env["rack.after_reply"] = []
633
+
634
+ status, headers, body = @app.call(env)
607
635
 
608
636
  begin
609
637
  return if @request.hijacked?
@@ -625,6 +653,8 @@ class Unicorn::HttpServer
625
653
  end
626
654
  rescue => e
627
655
  handle_error(client, e)
656
+ ensure
657
+ env["rack.after_reply"].each(&:call) if env
628
658
  end
629
659
 
630
660
  def nuke_listeners!(readers)
@@ -655,7 +685,6 @@ class Unicorn::HttpServer
655
685
  LISTENERS.each { |sock| sock.close_on_exec = true }
656
686
 
657
687
  worker.user(*user) if user.kind_of?(Array) && ! worker.switched
658
- self.timeout /= 2.0 # halve it for select()
659
688
  @config = nil
660
689
  build_app! unless preload_app
661
690
  @after_fork = @listener_opts = @orig_app = nil
@@ -669,58 +698,55 @@ class Unicorn::HttpServer
669
698
  logger.info "worker=#{worker_nr} reopening logs..."
670
699
  Unicorn::Util.reopen_logs
671
700
  logger.info "worker=#{worker_nr} done reopening logs"
701
+ false
672
702
  rescue => e
673
703
  logger.error(e) rescue nil
674
704
  exit!(77) # EX_NOPERM in sysexits.h
675
705
  end
676
706
 
707
+ def prep_readers(readers)
708
+ wtr = Unicorn::Waiter.prep_readers(readers)
709
+ @timeout *= 500 # to milliseconds for epoll, but halved
710
+ wtr
711
+ rescue
712
+ require_relative 'select_waiter'
713
+ @timeout /= 2.0 # halved for IO.select
714
+ Unicorn::SelectWaiter.new
715
+ end
716
+
677
717
  # runs inside each forked worker, this sits around and waits
678
718
  # for connections and doesn't die until the parent dies (or is
679
719
  # given a INT, QUIT, or TERM signal)
680
720
  def worker_loop(worker)
681
- ppid = @master_pid
682
721
  readers = init_worker_process(worker)
683
- nr = 0 # this becomes negative if we need to reopen logs
722
+ waiter = prep_readers(readers)
723
+ reopen = false
684
724
 
685
725
  # this only works immediately if the master sent us the signal
686
726
  # (which is the normal case)
687
- trap(:USR1) { nr = -65536 }
727
+ trap(:USR1) { reopen = true }
688
728
 
689
729
  ready = readers.dup
690
730
  @after_worker_ready.call(self, worker)
691
731
 
692
732
  begin
693
- nr < 0 and reopen_worker_logs(worker.nr)
694
- nr = 0
733
+ reopen = reopen_worker_logs(worker.nr) if reopen
695
734
  worker.tick = time_now.to_i
696
- tmp = ready.dup
697
- while sock = tmp.shift
735
+ while sock = ready.shift
698
736
  # Unicorn::Worker#kgio_tryaccept is not like accept(2) at all,
699
737
  # but that will return false
700
738
  if client = sock.kgio_tryaccept
701
739
  process_client(client)
702
- nr += 1
703
740
  worker.tick = time_now.to_i
704
741
  end
705
- break if nr < 0
742
+ break if reopen
706
743
  end
707
744
 
708
- # make the following bet: if we accepted clients this round,
709
- # we're probably reasonably busy, so avoid calling select()
710
- # and do a speculative non-blocking accept() on ready listeners
711
- # before we sleep again in select().
712
- unless nr == 0
713
- tmp = ready.dup
714
- redo
715
- end
716
-
717
- ppid == Process.ppid or return
718
-
719
- # timeout used so we can detect parent death:
745
+ # timeout so we can .tick and keep parent from SIGKILL-ing us
720
746
  worker.tick = time_now.to_i
721
- ret = IO.select(readers, nil, nil, @timeout) and ready = ret[0]
747
+ waiter.get_readers(ready, readers, @timeout)
722
748
  rescue => e
723
- redo if nr < 0 && readers[0]
749
+ redo if reopen && readers[0]
724
750
  Unicorn.log_error(@logger, "listen loop error", e) if readers[0]
725
751
  end while readers[0]
726
752
  end
@@ -787,12 +813,12 @@ class Unicorn::HttpServer
787
813
  end
788
814
 
789
815
  def build_app!
790
- if app.respond_to?(:arity) && app.arity == 0
816
+ if app.respond_to?(:arity) && (app.arity == 0 || app.arity == 2)
791
817
  if defined?(Gem) && Gem.respond_to?(:refresh)
792
818
  logger.info "Refreshing Gem list"
793
819
  Gem.refresh
794
820
  end
795
- self.app = app.call
821
+ self.app = app.arity == 0 ? app.call : app.call(nil, self)
796
822
  end
797
823
  end
798
824
 
@@ -31,7 +31,7 @@ module Unicorn::Launcher
31
31
  # \_ parent - exits immediately ASAP
32
32
  # \_ unicorn master - writes to pipe when ready
33
33
 
34
- rd, wr = IO.pipe
34
+ rd, wr = Unicorn.pipe
35
35
  grandparent = $$
36
36
  if fork
37
37
  wr.close # grandparent does not write
@@ -43,8 +43,8 @@
43
43
  # use Unicorn::OobGC, 2, %r{\A/(?:expensive/foo|more_expensive/foo)}
44
44
  #
45
45
  # Feedback from users of early implementations of this module:
46
- # * https://bogomips.org/unicorn-public/0BFC98E9-072B-47EE-9A70-05478C20141B@lukemelia.com/
47
- # * https://bogomips.org/unicorn-public/AANLkTilUbgdyDv9W1bi-s_W6kq9sOhWfmuYkKLoKGOLj@mail.gmail.com/
46
+ # * https://yhbt.net/unicorn-public/0BFC98E9-072B-47EE-9A70-05478C20141B@lukemelia.com/
47
+ # * https://yhbt.net/unicorn-public/AANLkTilUbgdyDv9W1bi-s_W6kq9sOhWfmuYkKLoKGOLj@mail.gmail.com/
48
48
 
49
49
  module Unicorn::OobGC
50
50
 
@@ -60,7 +60,6 @@ module Unicorn::OobGC
60
60
  self.const_set :OOBGC_INTERVAL, interval
61
61
  ObjectSpace.each_object(Unicorn::HttpServer) do |s|
62
62
  s.extend(self)
63
- self.const_set :OOBGC_ENV, s.instance_variable_get(:@request).env
64
63
  end
65
64
  app # pretend to be Rack middleware since it was in the past
66
65
  end
@@ -68,9 +67,10 @@ module Unicorn::OobGC
68
67
  #:stopdoc:
69
68
  def process_client(client)
70
69
  super(client) # Unicorn::HttpServer#process_client
71
- if OOBGC_PATH =~ OOBGC_ENV['PATH_INFO'] && ((@@nr -= 1) <= 0)
70
+ env = instance_variable_get(:@request).env
71
+ if OOBGC_PATH =~ env['PATH_INFO'] && ((@@nr -= 1) <= 0)
72
72
  @@nr = OOBGC_INTERVAL
73
- OOBGC_ENV.clear
73
+ env.clear
74
74
  disabled = GC.enable
75
75
  GC.start
76
76
  GC.disable if disabled
@@ -0,0 +1,6 @@
1
+ # fallback for non-Linux and Linux <4.5 systems w/o EPOLLEXCLUSIVE
2
+ class Unicorn::SelectWaiter # :nodoc:
3
+ def get_readers(ready, readers, timeout) # :nodoc:
4
+ ret = IO.select(readers, nil, nil, timeout) and ready.replace(ret[0])
5
+ end
6
+ end
@@ -83,6 +83,7 @@ module Unicorn
83
83
  rescue => e
84
84
  logger.error("#{sock_name(sock)} " \
85
85
  "failed to set accept_filter=#{name} (#{e.inspect})")
86
+ logger.error("perhaps accf_http(9) needs to be loaded".freeze)
86
87
  end if arg != got
87
88
  end
88
89
  end
data/lib/unicorn/tmpio.rb CHANGED
@@ -11,12 +11,18 @@ class Unicorn::TmpIO < File
11
11
  # immediately, switched to binary mode, and userspace output
12
12
  # buffering is disabled
13
13
  def self.new
14
+ path = nil
15
+
16
+ # workaround File#path being tainted:
17
+ # https://bugs.ruby-lang.org/issues/14485
14
18
  fp = begin
15
- super("#{Dir::tmpdir}/#{rand}", RDWR|CREAT|EXCL, 0600)
19
+ path = "#{Dir::tmpdir}/#{rand}"
20
+ super(path, RDWR|CREAT|EXCL, 0600)
16
21
  rescue Errno::EEXIST
17
22
  retry
18
23
  end
19
- unlink(fp.path)
24
+
25
+ unlink(path)
20
26
  fp.binmode
21
27
  fp.sync = true
22
28
  fp
data/lib/unicorn/util.rb CHANGED
@@ -64,7 +64,7 @@ module Unicorn::Util # :nodoc:
64
64
  fp.reopen(fp.path, "a")
65
65
  else
66
66
  # We should not need this workaround, Ruby can be fixed:
67
- # http://bugs.ruby-lang.org/issues/9036
67
+ # https://bugs.ruby-lang.org/issues/9036
68
68
  # MRI will not call call fclose(3) or freopen(3) here
69
69
  # since there's no associated std{in,out,err} FILE * pointer
70
70
  # This should atomically use dup3(2) (or dup2(2)) syscall
@@ -1 +1 @@
1
- Unicorn::Const::UNICORN_VERSION = '5.4.1'
1
+ Unicorn::Const::UNICORN_VERSION = '6.1.0'
@@ -122,6 +122,11 @@ class Unicorn::Worker
122
122
  # the +after_fork+ hook after any privileged functions need to be
123
123
  # run (e.g. to set per-worker CPU affinity, niceness, etc)
124
124
  #
125
+ # +group+ can be specified as a string, or as an array of two
126
+ # strings. If an array of two strings is given, the first string
127
+ # is used as the primary group of the process, and the second is
128
+ # used as the group of the log files.
129
+ #
125
130
  # Any and all errors raised within this method will be propagated
126
131
  # directly back to the caller (usually the +after_fork+ hook.
127
132
  # These errors commonly include ArgumentError for specifying an
@@ -134,8 +139,17 @@ class Unicorn::Worker
134
139
  # insufficient because modern systems have fine-grained
135
140
  # capabilities. Let the caller handle any and all errors.
136
141
  uid = Etc.getpwnam(user).uid
137
- gid = Etc.getgrnam(group).gid if group
138
- Unicorn::Util.chown_logs(uid, gid)
142
+
143
+ if group
144
+ if group.is_a?(Array)
145
+ group, log_group = group
146
+ log_gid = Etc.getgrnam(log_group).gid
147
+ end
148
+ gid = Etc.getgrnam(group).gid
149
+ log_gid ||= gid
150
+ end
151
+
152
+ Unicorn::Util.chown_logs(uid, log_gid)
139
153
  if gid && Process.egid != gid
140
154
  Process.initgroups(user, gid)
141
155
  Process::GID.change_privilege(gid)
data/lib/unicorn.rb CHANGED
@@ -2,6 +2,8 @@
2
2
  require 'etc'
3
3
  require 'stringio'
4
4
  require 'kgio'
5
+ require 'raindrops'
6
+ require 'io/wait'
5
7
 
6
8
  begin
7
9
  require 'rack'
@@ -43,12 +45,8 @@ module Unicorn
43
45
  abort "rack and Rack::Builder must be available for processing #{ru}"
44
46
  end
45
47
 
46
- # Op is going to get cleared before the returned lambda is called, so
47
- # save this value so that it's still there when we need it:
48
- no_default_middleware = op[:no_default_middleware]
49
-
50
48
  # always called after config file parsing, may be called after forking
51
- lambda do ||
49
+ lambda do |_, server|
52
50
  inner_app = case ru
53
51
  when /\.ru$/
54
52
  raw = File.read(ru)
@@ -64,7 +62,7 @@ module Unicorn
64
62
  pp({ :inner_app => inner_app })
65
63
  end
66
64
 
67
- return inner_app if no_default_middleware
65
+ return inner_app unless server.default_middleware
68
66
 
69
67
  middleware = { # order matters
70
68
  ContentLength: nil,
@@ -98,7 +96,7 @@ module Unicorn
98
96
 
99
97
  # returns an array of strings representing TCP listen socket addresses
100
98
  # and Unix domain socket paths. This is useful for use with
101
- # Raindrops::Middleware under Linux: https://bogomips.org/raindrops/
99
+ # Raindrops::Middleware under Linux: https://yhbt.net/raindrops/
102
100
  def self.listener_names
103
101
  Unicorn::HttpServer::LISTENERS.map do |io|
104
102
  Unicorn::SocketHelper.sock_name(io)
@@ -112,9 +110,23 @@ module Unicorn
112
110
  exc.backtrace.each { |line| logger.error(line) }
113
111
  end
114
112
 
115
- # remove this when we only support Ruby >= 2.0
113
+ F_SETPIPE_SZ = 1031 if RUBY_PLATFORM =~ /linux/
114
+
116
115
  def self.pipe # :nodoc:
117
- Kgio::Pipe.new.each { |io| io.close_on_exec = true }
116
+ Kgio::Pipe.new.each do |io|
117
+ # shrink pipes to minimize impact on /proc/sys/fs/pipe-user-pages-soft
118
+ # limits.
119
+ if defined?(F_SETPIPE_SZ)
120
+ begin
121
+ io.fcntl(F_SETPIPE_SZ, Raindrops::PAGE_SIZE)
122
+ rescue Errno::EINVAL
123
+ # old kernel
124
+ rescue Errno::EPERM
125
+ # resizes fail if Linux is close to the pipe limit for the user
126
+ # or if the user does not have permissions to resize
127
+ end
128
+ end
129
+ end
118
130
  end
119
131
  # :startdoc:
120
132
  end