unicorn 6.0.0 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -12,6 +12,7 @@
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
17
  void init_unicorn_httpdate(void);
17
18
 
@@ -65,18 +66,6 @@ struct http_parser {
65
66
  static ID id_set_backtrace, id_is_chunked_p;
66
67
  static VALUE cHttpParser;
67
68
 
68
- #ifdef HAVE_RB_HASH_CLEAR /* Ruby >= 2.0 */
69
- # define my_hash_clear(h) (void)rb_hash_clear(h)
70
- #else /* !HAVE_RB_HASH_CLEAR - Ruby <= 1.9.3 */
71
-
72
- static ID id_clear;
73
-
74
- static void my_hash_clear(VALUE h)
75
- {
76
- rb_funcall(h, id_clear, 0);
77
- }
78
- #endif /* HAVE_RB_HASH_CLEAR */
79
-
80
69
  static void finalize_header(struct http_parser *hp);
81
70
 
82
71
  static void parser_raise(VALUE klass, const char *msg)
@@ -650,7 +639,7 @@ static VALUE HttpParser_clear(VALUE self)
650
639
  return HttpParser_init(self);
651
640
 
652
641
  http_parser_init(hp);
653
- my_hash_clear(hp->env);
642
+ rb_hash_clear(hp->env);
654
643
 
655
644
  return self;
656
645
  }
@@ -979,6 +968,7 @@ void Init_unicorn_http(void)
979
968
  e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError",
980
969
  eHttpParserError);
981
970
 
971
+ id_uminus = rb_intern("-@");
982
972
  init_globals();
983
973
  rb_define_alloc_func(cHttpParser, HttpParser_alloc);
984
974
  rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
@@ -1029,5 +1019,7 @@ void Init_unicorn_http(void)
1029
1019
  id_clear = rb_intern("clear");
1030
1020
  #endif
1031
1021
  id_is_chunked_p = rb_intern("is_chunked?");
1022
+
1023
+ init_epollexclusive(mUnicorn);
1032
1024
  }
1033
1025
  #undef SET_GLOBAL
@@ -609,7 +609,7 @@ class Unicorn::HttpServer
609
609
  def e100_response_write(client, env)
610
610
  # We use String#freeze to avoid allocations under Ruby 2.1+
611
611
  # Not many users hit this code path, so it's better to reduce the
612
- # 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
613
613
  # allocations here.
614
614
  client.write(@request.response_start_sent ?
615
615
  "100 Continue\r\n\r\nHTTP/1.1 ".freeze :
@@ -685,7 +685,6 @@ class Unicorn::HttpServer
685
685
  LISTENERS.each { |sock| sock.close_on_exec = true }
686
686
 
687
687
  worker.user(*user) if user.kind_of?(Array) && ! worker.switched
688
- self.timeout /= 2.0 # halve it for select()
689
688
  @config = nil
690
689
  build_app! unless preload_app
691
690
  @after_fork = @listener_opts = @orig_app = nil
@@ -699,59 +698,55 @@ class Unicorn::HttpServer
699
698
  logger.info "worker=#{worker_nr} reopening logs..."
700
699
  Unicorn::Util.reopen_logs
701
700
  logger.info "worker=#{worker_nr} done reopening logs"
701
+ false
702
702
  rescue => e
703
703
  logger.error(e) rescue nil
704
704
  exit!(77) # EX_NOPERM in sysexits.h
705
705
  end
706
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
+
707
717
  # runs inside each forked worker, this sits around and waits
708
718
  # for connections and doesn't die until the parent dies (or is
709
719
  # given a INT, QUIT, or TERM signal)
710
720
  def worker_loop(worker)
711
- ppid = @master_pid
712
721
  readers = init_worker_process(worker)
713
- nr = 0 # this becomes negative if we need to reopen logs
722
+ waiter = prep_readers(readers)
723
+ reopen = false
714
724
 
715
725
  # this only works immediately if the master sent us the signal
716
726
  # (which is the normal case)
717
- trap(:USR1) { nr = -65536 }
727
+ trap(:USR1) { reopen = true }
718
728
 
719
729
  ready = readers.dup
720
- nr_listeners = readers.size
721
730
  @after_worker_ready.call(self, worker)
722
731
 
723
732
  begin
724
- nr < 0 and reopen_worker_logs(worker.nr)
725
- nr = 0
733
+ reopen = reopen_worker_logs(worker.nr) if reopen
726
734
  worker.tick = time_now.to_i
727
- tmp = ready.dup
728
- while sock = tmp.shift
735
+ while sock = ready.shift
729
736
  # Unicorn::Worker#kgio_tryaccept is not like accept(2) at all,
730
737
  # but that will return false
731
738
  if client = sock.kgio_tryaccept
732
739
  process_client(client)
733
- nr += 1
734
740
  worker.tick = time_now.to_i
735
741
  end
736
- break if nr < 0
742
+ break if reopen
737
743
  end
738
744
 
739
- # make the following bet: if we accepted clients this round,
740
- # we're probably reasonably busy, so avoid calling select()
741
- # and do a speculative non-blocking accept() on ready listeners
742
- # before we sleep again in select().
743
- if nr == nr_listeners
744
- tmp = ready.dup
745
- redo
746
- end
747
-
748
- ppid == Process.ppid or return
749
-
750
- # timeout used so we can detect parent death:
745
+ # timeout so we can .tick and keep parent from SIGKILL-ing us
751
746
  worker.tick = time_now.to_i
752
- ret = IO.select(readers, nil, nil, @timeout) and ready = ret[0]
747
+ waiter.get_readers(ready, readers, @timeout)
753
748
  rescue => e
754
- redo if nr < 0 && readers[0]
749
+ redo if reopen && readers[0]
755
750
  Unicorn.log_error(@logger, "listen loop error", e) if readers[0]
756
751
  end while readers[0]
757
752
  end
@@ -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
@@ -1 +1 @@
1
- Unicorn::Const::UNICORN_VERSION = '6.0.0'
1
+ Unicorn::Const::UNICORN_VERSION = '6.1.0'
data/lib/unicorn.rb CHANGED
@@ -114,8 +114,6 @@ module Unicorn
114
114
 
115
115
  def self.pipe # :nodoc:
116
116
  Kgio::Pipe.new.each do |io|
117
- io.close_on_exec = true # remove this when we only support Ruby >= 2.0
118
-
119
117
  # shrink pipes to minimize impact on /proc/sys/fs/pipe-user-pages-soft
120
118
  # limits.
121
119
  if defined?(F_SETPIPE_SZ)
data/t/README CHANGED
@@ -10,7 +10,7 @@ comfortable writing integration tests with.
10
10
 
11
11
  == Requirements
12
12
 
13
- * {Ruby 1.9.3+}[https://www.ruby-lang.org/en/] (duh!)
13
+ * {Ruby 2.0.0+}[https://www.ruby-lang.org/en/] (duh!)
14
14
  * {GNU make}[https://www.gnu.org/software/make/]
15
15
  * {socat}[http://www.dest-unreach.org/socat/]
16
16
  * {curl}[https://curl.haxx.se/]
data/t/test-lib.sh CHANGED
@@ -94,7 +94,8 @@ check_stderr () {
94
94
  set +u
95
95
  _r_err=${1-${r_err}}
96
96
  set -u
97
- if grep -v $T $_r_err | grep -i Error
97
+ if grep -v $T $_r_err | grep -i Error | \
98
+ grep -v NameError.*Unicorn::Waiter
98
99
  then
99
100
  die "Errors found in $_r_err"
100
101
  elif grep SIGKILL $_r_err
@@ -51,7 +51,7 @@ class TestUtil < Test::Unit::TestCase
51
51
  def test_reopen_logs_renamed_with_encoding
52
52
  tmp = Tempfile.new('')
53
53
  tmp_path = tmp.path.dup.freeze
54
- Encoding.list.each { |encoding|
54
+ Encoding.list.sample(5).each { |encoding|
55
55
  File.open(tmp_path, "a:#{encoding.to_s}") { |fp|
56
56
  fp.sync = true
57
57
  assert_equal encoding, fp.external_encoding
@@ -74,8 +74,9 @@ class TestUtil < Test::Unit::TestCase
74
74
  def test_reopen_logs_renamed_with_internal_encoding
75
75
  tmp = Tempfile.new('')
76
76
  tmp_path = tmp.path.dup.freeze
77
- Encoding.list.each { |ext|
78
- Encoding.list.each { |int|
77
+ full = Encoding.list
78
+ full.sample(2).each { |ext|
79
+ full.sample(2).each { |int|
79
80
  next if ext == int
80
81
  File.open(tmp_path, "a:#{ext.to_s}:#{int.to_s}") { |fp|
81
82
  fp.sync = true
@@ -0,0 +1,34 @@
1
+ require 'test/unit'
2
+ require 'unicorn'
3
+ require 'unicorn/select_waiter'
4
+ class TestSelectWaiter < Test::Unit::TestCase
5
+
6
+ def test_select_timeout # n.b. this is level-triggered
7
+ sw = Unicorn::SelectWaiter.new
8
+ IO.pipe do |r,w|
9
+ sw.get_readers(ready = [], [r], 0)
10
+ assert_equal [], ready
11
+ w.syswrite '.'
12
+ sw.get_readers(ready, [r], 1000)
13
+ assert_equal [r], ready
14
+ sw.get_readers(ready, [r], 0)
15
+ assert_equal [r], ready
16
+ end
17
+ end
18
+
19
+ def test_linux # ugh, also level-triggered, unlikely to change
20
+ IO.pipe do |r,w|
21
+ wtr = Unicorn::Waiter.prep_readers([r])
22
+ wtr.get_readers(ready = [], [r], 0)
23
+ assert_equal [], ready
24
+ w.syswrite '.'
25
+ wtr.get_readers(ready = [], [r], 1000)
26
+ assert_equal [r], ready
27
+ wtr.get_readers(ready = [], [r], 1000)
28
+ assert_equal [r], ready, 'still ready (level-triggered :<)'
29
+ assert_nil wtr.close
30
+ end
31
+ rescue SystemCallError => e
32
+ warn "#{e.message} (#{e.class})"
33
+ end if Unicorn.const_defined?(:Waiter)
34
+ end
data/unicorn.gemspec CHANGED
@@ -11,7 +11,7 @@ end.compact
11
11
 
12
12
  Gem::Specification.new do |s|
13
13
  s.name = %q{unicorn}
14
- s.version = (ENV['VERSION'] || '6.0.0').dup
14
+ s.version = (ENV['VERSION'] || '6.1.0').dup
15
15
  s.authors = ['unicorn hackers']
16
16
  s.summary = 'Rack HTTP server for fast clients and Unix'
17
17
  s.description = File.read('README').split("\n\n")[1]
@@ -25,11 +25,11 @@ Gem::Specification.new do |s|
25
25
  s.homepage = 'https://yhbt.net/unicorn/'
26
26
  s.test_files = test_files
27
27
 
28
- # 1.9.3 is the minumum supported version. We don't specify
28
+ # 2.0.0 is the minimum supported version. We don't specify
29
29
  # a maximum version to make it easier to test pre-releases,
30
30
  # but we do warn users if they install unicorn on an untested
31
31
  # version in extconf.rb
32
- s.required_ruby_version = ">= 1.9.3"
32
+ s.required_ruby_version = ">= 2.0.0"
33
33
 
34
34
  # We do not have a hard dependency on rack, it's possible to load
35
35
  # things which respond to #call. HTTP status lines in responses
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: 6.0.0
4
+ version: 6.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - unicorn hackers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-17 00:00:00.000000000 Z
11
+ date: 2021-12-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -157,6 +157,7 @@ files:
157
157
  - ext/unicorn_http/CFLAGS
158
158
  - ext/unicorn_http/c_util.h
159
159
  - ext/unicorn_http/common_field_optimization.h
160
+ - ext/unicorn_http/epollexclusive.h
160
161
  - ext/unicorn_http/ext_help.h
161
162
  - ext/unicorn_http/extconf.rb
162
163
  - ext/unicorn_http/global_variables.h
@@ -176,6 +177,7 @@ files:
176
177
  - lib/unicorn/launcher.rb
177
178
  - lib/unicorn/oob_gc.rb
178
179
  - lib/unicorn/preread_input.rb
180
+ - lib/unicorn/select_waiter.rb
179
181
  - lib/unicorn/socket_helper.rb
180
182
  - lib/unicorn/stream_input.rb
181
183
  - lib/unicorn/tee_input.rb
@@ -266,6 +268,7 @@ files:
266
268
  - test/unit/test_tee_input.rb
267
269
  - test/unit/test_upload.rb
268
270
  - test/unit/test_util.rb
271
+ - test/unit/test_waiter.rb
269
272
  - unicorn.gemspec
270
273
  - unicorn_1
271
274
  - unicorn_rails_1
@@ -282,7 +285,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
282
285
  requirements:
283
286
  - - ">="
284
287
  - !ruby/object:Gem::Version
285
- version: 1.9.3
288
+ version: 2.0.0
286
289
  required_rubygems_version: !ruby/object:Gem::Requirement
287
290
  requirements:
288
291
  - - ">="
@@ -301,3 +304,4 @@ test_files:
301
304
  - test/unit/test_server.rb
302
305
  - test/unit/test_upload.rb
303
306
  - test/unit/test_util.rb
307
+ - test/unit/test_waiter.rb