unicorn 5.3.1 → 6.1.0

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.
Files changed (84) hide show
  1. checksums.yaml +5 -5
  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 +117 -57
  13. data/HACKING +2 -9
  14. data/ISSUES +33 -32
  15. data/KNOWN_ISSUES +2 -2
  16. data/LATEST +16 -95
  17. data/LICENSE +2 -2
  18. data/Links +13 -11
  19. data/NEWS +239 -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 +277 -237
  40. data/ext/unicorn_http/unicorn_http.rl +67 -27
  41. data/lib/unicorn/configurator.rb +26 -5
  42. data/lib/unicorn/http_request.rb +13 -3
  43. data/lib/unicorn/http_response.rb +3 -2
  44. data/lib/unicorn/http_server.rb +76 -51
  45. data/lib/unicorn/launcher.rb +1 -1
  46. data/lib/unicorn/oob_gc.rb +5 -5
  47. data/lib/unicorn/select_waiter.rb +6 -0
  48. data/lib/unicorn/socket_helper.rb +4 -3
  49. data/lib/unicorn/tmpio.rb +8 -2
  50. data/lib/unicorn/util.rb +3 -3
  51. data/lib/unicorn/version.rb +1 -1
  52. data/lib/unicorn/worker.rb +16 -2
  53. data/lib/unicorn.rb +25 -10
  54. data/man/man1/unicorn.1 +88 -85
  55. data/man/man1/unicorn_rails.1 +79 -81
  56. data/t/GNUmakefile +3 -72
  57. data/t/README +4 -4
  58. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  59. data/t/t0301.ru +13 -0
  60. data/t/test-lib.sh +2 -1
  61. data/test/benchmark/README +14 -4
  62. data/test/benchmark/ddstream.ru +50 -0
  63. data/test/benchmark/readinput.ru +40 -0
  64. data/test/benchmark/uconnect.perl +66 -0
  65. data/test/exec/test_exec.rb +20 -19
  66. data/test/test_helper.rb +38 -30
  67. data/test/unit/test_ccc.rb +5 -4
  68. data/test/unit/test_droplet.rb +1 -1
  69. data/test/unit/test_http_parser.rb +16 -0
  70. data/test/unit/test_http_parser_ng.rb +81 -0
  71. data/test/unit/test_request.rb +10 -10
  72. data/test/unit/test_server.rb +86 -12
  73. data/test/unit/test_signals.rb +8 -8
  74. data/test/unit/test_socket_helper.rb +5 -5
  75. data/test/unit/test_upload.rb +9 -14
  76. data/test/unit/test_util.rb +29 -3
  77. data/test/unit/test_waiter.rb +34 -0
  78. data/unicorn.gemspec +8 -7
  79. metadata +19 -13
  80. data/Documentation/GNUmakefile +0 -30
  81. data/Documentation/unicorn.1.txt +0 -187
  82. data/Documentation/unicorn_rails.1.txt +0 -175
  83. data/t/hijack.ru +0 -43
  84. data/t/t0200-rack-hijack.sh +0 -30
@@ -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
@@ -26,6 +27,7 @@ void init_unicorn_httpdate(VALUE mark_ary);
26
27
  #define UH_FL_HASHEADER 0x100
27
28
  #define UH_FL_TO_CLEAR 0x200
28
29
  #define UH_FL_RESSTART 0x400 /* for check_client_connection */
30
+ #define UH_FL_HIJACK 0x800
29
31
 
30
32
  /* all of these flags need to be set for keepalive to be supported */
31
33
  #define UH_FL_KEEPALIVE (UH_FL_KAVERSION | UH_FL_REQEOF | UH_FL_HASHEADER)
@@ -61,19 +63,8 @@ struct http_parser {
61
63
  } len;
62
64
  };
63
65
 
64
- static ID id_set_backtrace;
65
-
66
- #ifdef HAVE_RB_HASH_CLEAR /* Ruby >= 2.0 */
67
- # define my_hash_clear(h) (void)rb_hash_clear(h)
68
- #else /* !HAVE_RB_HASH_CLEAR - Ruby <= 1.9.3 */
69
-
70
- static ID id_clear;
71
-
72
- static void my_hash_clear(VALUE h)
73
- {
74
- rb_funcall(h, id_clear, 0);
75
- }
76
- #endif /* HAVE_RB_HASH_CLEAR */
66
+ static ID id_set_backtrace, id_is_chunked_p;
67
+ static VALUE cHttpParser;
77
68
 
78
69
  static void finalize_header(struct http_parser *hp);
79
70
 
@@ -219,6 +210,19 @@ static void write_cont_value(struct http_parser *hp,
219
210
  rb_str_buf_cat(hp->cont, vptr, end + 1);
220
211
  }
221
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
+
222
226
  static void write_value(struct http_parser *hp,
223
227
  const char *buffer, const char *p)
224
228
  {
@@ -245,7 +249,9 @@ static void write_value(struct http_parser *hp,
245
249
  f = uncommon_field(field, flen);
246
250
  } else if (f == g_http_connection) {
247
251
  hp_keepalive_connection(hp, v);
248
- } 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");
249
255
  hp->len.content = parse_length(RSTRING_PTR(v), RSTRING_LEN(v));
250
256
  if (hp->len.content < 0)
251
257
  parser_raise(eHttpParserError, "invalid Content-Length");
@@ -253,9 +259,30 @@ static void write_value(struct http_parser *hp,
253
259
  HP_FL_SET(hp, HASBODY);
254
260
  hp_invalid_if_trailer(hp);
255
261
  } else if (f == g_http_transfer_encoding) {
256
- 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
+
257
271
  HP_FL_SET(hp, CHUNKED);
258
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");
259
286
  }
260
287
  hp_invalid_if_trailer(hp);
261
288
  } else if (f == g_http_trailer) {
@@ -486,7 +513,7 @@ static void set_url_scheme(VALUE env, VALUE *server_port)
486
513
  * and X-Forwarded-Proto handling from this parser? We've had it
487
514
  * forever and nobody has said anything against it, either.
488
515
  * Anyways, please send comments to our public mailing list:
489
- * unicorn-public@bogomips.org (no HTML mail, no subscription necessary)
516
+ * unicorn-public@yhbt.net (no HTML mail, no subscription necessary)
490
517
  */
491
518
  scheme = rb_hash_aref(env, g_http_x_forwarded_ssl);
492
519
  if (!NIL_P(scheme) && STR_CSTR_EQ(scheme, "on")) {
@@ -607,8 +634,12 @@ static VALUE HttpParser_clear(VALUE self)
607
634
  {
608
635
  struct http_parser *hp = data_get(self);
609
636
 
637
+ /* we can't safely reuse .buf and .env if hijacked */
638
+ if (HP_FL_TEST(hp, HIJACK))
639
+ return HttpParser_init(self);
640
+
610
641
  http_parser_init(hp);
611
- my_hash_clear(hp->env);
642
+ rb_hash_clear(hp->env);
612
643
 
613
644
  return self;
614
645
  }
@@ -813,6 +844,15 @@ static VALUE HttpParser_env(VALUE self)
813
844
  return data_get(self)->env;
814
845
  }
815
846
 
847
+ static VALUE HttpParser_hijacked_bang(VALUE self)
848
+ {
849
+ struct http_parser *hp = data_get(self);
850
+
851
+ HP_FL_SET(hp, HIJACK);
852
+
853
+ return self;
854
+ }
855
+
816
856
  /**
817
857
  * call-seq:
818
858
  * parser.filter_body(dst, src) => nil/src
@@ -917,11 +957,8 @@ static VALUE HttpParser_rssget(VALUE self)
917
957
 
918
958
  void Init_unicorn_http(void)
919
959
  {
920
- static VALUE mark_ary;
921
- VALUE mUnicorn, cHttpParser;
960
+ VALUE mUnicorn;
922
961
 
923
- mark_ary = rb_ary_new();
924
- rb_global_variable(&mark_ary);
925
962
  mUnicorn = rb_define_module("Unicorn");
926
963
  cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
927
964
  eHttpParserError =
@@ -931,7 +968,8 @@ void Init_unicorn_http(void)
931
968
  e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError",
932
969
  eHttpParserError);
933
970
 
934
- init_globals(mark_ary);
971
+ id_uminus = rb_intern("-@");
972
+ init_globals();
935
973
  rb_define_alloc_func(cHttpParser, HttpParser_alloc);
936
974
  rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
937
975
  rb_define_method(cHttpParser, "clear", HttpParser_clear, 0);
@@ -947,6 +985,7 @@ void Init_unicorn_http(void)
947
985
  rb_define_method(cHttpParser, "next?", HttpParser_next, 0);
948
986
  rb_define_method(cHttpParser, "buf", HttpParser_buf, 0);
949
987
  rb_define_method(cHttpParser, "env", HttpParser_env, 0);
988
+ rb_define_method(cHttpParser, "hijacked!", HttpParser_hijacked_bang, 0);
950
989
  rb_define_method(cHttpParser, "response_start_sent=", HttpParser_rssset, 1);
951
990
  rb_define_method(cHttpParser, "response_start_sent", HttpParser_rssget, 0);
952
991
 
@@ -967,19 +1006,20 @@ void Init_unicorn_http(void)
967
1006
 
968
1007
  rb_define_singleton_method(cHttpParser, "max_header_len=", set_maxhdrlen, 1);
969
1008
 
970
- init_common_fields(mark_ary);
1009
+ init_common_fields();
971
1010
  SET_GLOBAL(g_http_host, "HOST");
972
1011
  SET_GLOBAL(g_http_trailer, "TRAILER");
973
1012
  SET_GLOBAL(g_http_transfer_encoding, "TRANSFER_ENCODING");
974
1013
  SET_GLOBAL(g_content_length, "CONTENT_LENGTH");
975
1014
  SET_GLOBAL(g_http_connection, "CONNECTION");
976
1015
  id_set_backtrace = rb_intern("set_backtrace");
977
- init_unicorn_httpdate(mark_ary);
978
-
979
- OBJ_FREEZE(mark_ary);
1016
+ init_unicorn_httpdate();
980
1017
 
981
1018
  #ifndef HAVE_RB_HASH_CLEAR
982
1019
  id_clear = rb_intern("clear");
983
1020
  #endif
1021
+ id_is_chunked_p = rb_intern("is_chunked?");
1022
+
1023
+ init_epollexclusive(mUnicorn);
984
1024
  }
985
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
@@ -587,7 +608,7 @@ class Unicorn::Configurator
587
608
  # just let chdir raise errors
588
609
  path = File.expand_path(path)
589
610
  if config_file &&
590
- config_file[0] != ?/ &&
611
+ ! config_file.start_with?('/') &&
591
612
  ! File.readable?("#{path}/#{config_file}")
592
613
  raise ArgumentError,
593
614
  "config_file=#{config_file} would not be accessible in" \
@@ -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
@@ -98,6 +96,7 @@ class Unicorn::HttpParser
98
96
  # for rack.hijack, we respond to this method so no extra allocation
99
97
  # of a proc object
100
98
  def call
99
+ hijacked!
101
100
  env['rack.hijack_io'] = env['unicorn.socket']
102
101
  end
103
102
 
@@ -188,4 +187,15 @@ class Unicorn::HttpParser
188
187
  HTTP_RESPONSE_START.each { |c| socket.write(c) }
189
188
  end
190
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
191
201
  end
@@ -21,13 +21,13 @@ module Unicorn::HttpResponse
21
21
 
22
22
  # writes the rack_response to socket as an HTTP response
23
23
  def http_response_write(socket, status, headers, body,
24
- response_start_sent=false)
24
+ req = Unicorn::HttpRequest.new)
25
25
  hijack = nil
26
26
 
27
27
  if headers
28
28
  code = status.to_i
29
29
  msg = STATUS_CODES[code]
30
- start = response_start_sent ? ''.freeze : 'HTTP/1.1 '.freeze
30
+ start = req.response_start_sent ? ''.freeze : 'HTTP/1.1 '.freeze
31
31
  buf = "#{start}#{msg ? %Q(#{code} #{msg}) : status}\r\n" \
32
32
  "Date: #{httpdate}\r\n" \
33
33
  "Connection: close\r\n"
@@ -52,6 +52,7 @@ module Unicorn::HttpResponse
52
52
  end
53
53
 
54
54
  if hijack
55
+ req.hijacked!
55
56
  hijack.call(socket)
56
57
  else
57
58
  body.each { |chunk| socket.write(chunk) }
@@ -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.
@@ -148,7 +149,7 @@ class Unicorn::HttpServer
148
149
  def listeners=(listeners)
149
150
  cur_names, dead_names = [], []
150
151
  listener_names.each do |name|
151
- if ?/ == name[0]
152
+ if name.start_with?('/')
152
153
  # mark unlinked sockets as dead so we can rebind them
153
154
  (File.socket?(name) ? cur_names : dead_names) << name
154
155
  else
@@ -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)
@@ -553,9 +551,9 @@ class Unicorn::HttpServer
553
551
  @workers[pid] = worker
554
552
  worker.atfork_parent
555
553
  end
556
- rescue => e
557
- @logger.error(e) rescue nil
558
- exit!
554
+ rescue => e
555
+ @logger.error(e) rescue nil
556
+ exit!
559
557
  end
560
558
 
561
559
  def maintain_worker_count
@@ -586,13 +584,32 @@ class Unicorn::HttpServer
586
584
  client.kgio_trywrite(err_response(code, @request.response_start_sent))
587
585
  end
588
586
  client.close
589
- rescue
587
+ rescue
588
+ end
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)
590
607
  end
591
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?
@@ -614,8 +642,7 @@ class Unicorn::HttpServer
614
642
  return if @request.hijacked?
615
643
  end
616
644
  @request.headers? or headers = nil
617
- http_response_write(client, status, headers, body,
618
- @request.response_start_sent)
645
+ http_response_write(client, status, headers, body, @request)
619
646
  ensure
620
647
  body.respond_to?(:close) and body.close
621
648
  end
@@ -626,6 +653,8 @@ class Unicorn::HttpServer
626
653
  end
627
654
  rescue => e
628
655
  handle_error(client, e)
656
+ ensure
657
+ env["rack.after_reply"].each(&:call) if env
629
658
  end
630
659
 
631
660
  def nuke_listeners!(readers)
@@ -656,7 +685,6 @@ class Unicorn::HttpServer
656
685
  LISTENERS.each { |sock| sock.close_on_exec = true }
657
686
 
658
687
  worker.user(*user) if user.kind_of?(Array) && ! worker.switched
659
- self.timeout /= 2.0 # halve it for select()
660
688
  @config = nil
661
689
  build_app! unless preload_app
662
690
  @after_fork = @listener_opts = @orig_app = nil
@@ -670,58 +698,55 @@ class Unicorn::HttpServer
670
698
  logger.info "worker=#{worker_nr} reopening logs..."
671
699
  Unicorn::Util.reopen_logs
672
700
  logger.info "worker=#{worker_nr} done reopening logs"
673
- rescue => e
674
- logger.error(e) rescue nil
675
- exit!(77) # EX_NOPERM in sysexits.h
701
+ false
702
+ rescue => e
703
+ logger.error(e) rescue nil
704
+ exit!(77) # EX_NOPERM in sysexits.h
705
+ end
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
676
715
  end
677
716
 
678
717
  # runs inside each forked worker, this sits around and waits
679
718
  # for connections and doesn't die until the parent dies (or is
680
719
  # given a INT, QUIT, or TERM signal)
681
720
  def worker_loop(worker)
682
- ppid = @master_pid
683
721
  readers = init_worker_process(worker)
684
- nr = 0 # this becomes negative if we need to reopen logs
722
+ waiter = prep_readers(readers)
723
+ reopen = false
685
724
 
686
725
  # this only works immediately if the master sent us the signal
687
726
  # (which is the normal case)
688
- trap(:USR1) { nr = -65536 }
727
+ trap(:USR1) { reopen = true }
689
728
 
690
729
  ready = readers.dup
691
730
  @after_worker_ready.call(self, worker)
692
731
 
693
732
  begin
694
- nr < 0 and reopen_worker_logs(worker.nr)
695
- nr = 0
733
+ reopen = reopen_worker_logs(worker.nr) if reopen
696
734
  worker.tick = time_now.to_i
697
- tmp = ready.dup
698
- while sock = tmp.shift
735
+ while sock = ready.shift
699
736
  # Unicorn::Worker#kgio_tryaccept is not like accept(2) at all,
700
737
  # but that will return false
701
738
  if client = sock.kgio_tryaccept
702
739
  process_client(client)
703
- nr += 1
704
740
  worker.tick = time_now.to_i
705
741
  end
706
- break if nr < 0
742
+ break if reopen
707
743
  end
708
744
 
709
- # make the following bet: if we accepted clients this round,
710
- # we're probably reasonably busy, so avoid calling select()
711
- # and do a speculative non-blocking accept() on ready listeners
712
- # before we sleep again in select().
713
- unless nr == 0
714
- tmp = ready.dup
715
- redo
716
- end
717
-
718
- ppid == Process.ppid or return
719
-
720
- # timeout used so we can detect parent death:
745
+ # timeout so we can .tick and keep parent from SIGKILL-ing us
721
746
  worker.tick = time_now.to_i
722
- ret = IO.select(readers, nil, nil, @timeout) and ready = ret[0]
747
+ waiter.get_readers(ready, readers, @timeout)
723
748
  rescue => e
724
- redo if nr < 0 && readers[0]
749
+ redo if reopen && readers[0]
725
750
  Unicorn.log_error(@logger, "listen loop error", e) if readers[0]
726
751
  end while readers[0]
727
752
  end
@@ -758,11 +783,11 @@ class Unicorn::HttpServer
758
783
  wpid <= 0 and return
759
784
  Process.kill(0, wpid)
760
785
  wpid
761
- rescue Errno::EPERM
762
- logger.info "pid=#{path} possibly stale, got EPERM signalling PID:#{wpid}"
763
- nil
764
- rescue Errno::ESRCH, Errno::ENOENT
765
- # don't unlink stale pid files, racy without non-portable locking...
786
+ rescue Errno::EPERM
787
+ logger.info "pid=#{path} possibly stale, got EPERM signalling PID:#{wpid}"
788
+ nil
789
+ rescue Errno::ESRCH, Errno::ENOENT
790
+ # don't unlink stale pid files, racy without non-portable locking...
766
791
  end
767
792
 
768
793
  def load_config!
@@ -788,12 +813,12 @@ class Unicorn::HttpServer
788
813
  end
789
814
 
790
815
  def build_app!
791
- if app.respond_to?(:arity) && app.arity == 0
816
+ if app.respond_to?(:arity) && (app.arity == 0 || app.arity == 2)
792
817
  if defined?(Gem) && Gem.respond_to?(:refresh)
793
818
  logger.info "Refreshing Gem list"
794
819
  Gem.refresh
795
820
  end
796
- self.app = app.call
821
+ self.app = app.arity == 0 ? app.call : app.call(nil, self)
797
822
  end
798
823
  end
799
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
@@ -100,8 +101,8 @@ module Unicorn
100
101
  log_buffer_sizes(sock, " after: ")
101
102
  end
102
103
  sock.listen(opt[:backlog])
103
- rescue => e
104
- Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
104
+ rescue => e
105
+ Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
105
106
  end
106
107
 
107
108
  def log_buffer_sizes(sock, pfx = '')
@@ -116,7 +117,7 @@ module Unicorn
116
117
  def bind_listen(address = '0.0.0.0:8080', opt = {})
117
118
  return address unless String === address
118
119
 
119
- sock = if address[0] == ?/
120
+ sock = if address.start_with?('/')
120
121
  if File.exist?(address)
121
122
  if File.socket?(address)
122
123
  begin