unicorn 5.3.1 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
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