unicorn 6.0.0 → 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.
@@ -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