unicorn 5.8.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
@@ -62,7 +62,6 @@ class Unicorn::HttpParser
62
62
  # This does minimal exception trapping and it is up to the caller
63
63
  # to handle any socket errors (e.g. user aborted upload).
64
64
  def read(socket)
65
- clear
66
65
  e = env
67
66
 
68
67
  # From https://www.ietf.org/rfc/rfc3875:
@@ -69,7 +69,6 @@ class Unicorn::HttpServer
69
69
  # incoming requests on the socket.
70
70
  def initialize(app, options = {})
71
71
  @app = app
72
- @request = Unicorn::HttpRequest.new
73
72
  @reexec_pid = 0
74
73
  @default_middleware = true
75
74
  options = options.dup
@@ -610,7 +609,7 @@ class Unicorn::HttpServer
610
609
  def e100_response_write(client, env)
611
610
  # We use String#freeze to avoid allocations under Ruby 2.1+
612
611
  # Not many users hit this code path, so it's better to reduce the
613
- # 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
614
613
  # allocations here.
615
614
  client.write(@request.response_start_sent ?
616
615
  "100 Continue\r\n\r\nHTTP/1.1 ".freeze :
@@ -621,6 +620,7 @@ class Unicorn::HttpServer
621
620
  # once a client is accepted, it is processed in its entirety here
622
621
  # in 3 easy steps: read request, call app, write app response
623
622
  def process_client(client)
623
+ @request = Unicorn::HttpRequest.new
624
624
  env = @request.read(client)
625
625
 
626
626
  if early_hints
@@ -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
737
- end
738
-
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
742
+ break if reopen
746
743
  end
747
744
 
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
@@ -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
@@ -1 +1 @@
1
- Unicorn::Const::UNICORN_VERSION = '5.8.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
@@ -574,7 +574,7 @@ EOF
574
574
  assert_equal String, results[0].class
575
575
  worker_pid = results[0].to_i
576
576
  assert_not_equal pid, worker_pid
577
- s = UNIXSocket.new(tmp.path)
577
+ s = unix_socket(tmp.path)
578
578
  s.syswrite("GET / HTTP/1.0\r\n\r\n")
579
579
  results = ''
580
580
  loop { results << s.sysread(4096) } rescue nil
@@ -732,7 +732,7 @@ EOF
732
732
  wait_for_file(sock_path)
733
733
  assert File.socket?(sock_path)
734
734
 
735
- sock = UNIXSocket.new(sock_path)
735
+ sock = unix_socket(sock_path)
736
736
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
737
737
  results = sock.sysread(4096)
738
738
 
@@ -742,7 +742,7 @@ EOF
742
742
  wait_for_file(sock_path)
743
743
  assert File.socket?(sock_path)
744
744
 
745
- sock = UNIXSocket.new(sock_path)
745
+ sock = unix_socket(sock_path)
746
746
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
747
747
  results = sock.sysread(4096)
748
748
 
@@ -777,7 +777,7 @@ EOF
777
777
  assert_equal pid, File.read(pid_file).to_i
778
778
  assert File.socket?(sock_path), "socket created"
779
779
 
780
- sock = UNIXSocket.new(sock_path)
780
+ sock = unix_socket(sock_path)
781
781
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
782
782
  results = sock.sysread(4096)
783
783
 
@@ -803,7 +803,7 @@ EOF
803
803
  wait_for_file(new_sock_path)
804
804
  assert File.socket?(new_sock_path), "socket exists"
805
805
  @sockets.each do |path|
806
- sock = UNIXSocket.new(path)
806
+ sock = unix_socket(path)
807
807
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
808
808
  results = sock.sysread(4096)
809
809
  assert_equal String, results.class
data/test/test_helper.rb CHANGED
@@ -28,6 +28,7 @@ require 'tempfile'
28
28
  require 'fileutils'
29
29
  require 'logger'
30
30
  require 'unicorn'
31
+ require 'io/nonblock'
31
32
 
32
33
  if ENV['DEBUG']
33
34
  require 'ruby-debug'
@@ -42,6 +43,7 @@ end
42
43
  def redirect_test_io
43
44
  orig_err = STDERR.dup
44
45
  orig_out = STDOUT.dup
46
+ rdr_pid = $$
45
47
  new_out = File.open("test_stdout.#$$.log", "a")
46
48
  new_err = File.open("test_stderr.#$$.log", "a")
47
49
  new_out.sync = new_err.sync = true
@@ -59,8 +61,10 @@ def redirect_test_io
59
61
  STDERR.sync = STDOUT.sync = true
60
62
 
61
63
  at_exit do
62
- File.unlink(new_out.path) rescue nil
63
- File.unlink(new_err.path) rescue nil
64
+ if rdr_pid == $$
65
+ File.unlink(new_out.path) rescue nil
66
+ File.unlink(new_err.path) rescue nil
67
+ end
64
68
  end
65
69
 
66
70
  begin
@@ -288,3 +292,15 @@ def reset_sig_handlers
288
292
  trap(sig, "DEFAULT")
289
293
  end
290
294
  end
295
+
296
+ def tcp_socket(*args)
297
+ sock = TCPSocket.new(*args)
298
+ sock.nonblock = false
299
+ sock
300
+ end
301
+
302
+ def unix_socket(*args)
303
+ sock = UNIXSocket.new(*args)
304
+ sock.nonblock = false
305
+ sock
306
+ end
@@ -3,6 +3,7 @@ require 'unicorn'
3
3
  require 'io/wait'
4
4
  require 'tempfile'
5
5
  require 'test/unit'
6
+ require './test/test_helper'
6
7
 
7
8
  class TestCccTCPI < Test::Unit::TestCase
8
9
  def test_ccc_tcpi
@@ -42,7 +43,7 @@ class TestCccTCPI < Test::Unit::TestCase
42
43
  wr.close
43
44
 
44
45
  # make sure the server is running, at least
45
- client = TCPSocket.new(host, port)
46
+ client = tcp_socket(host, port)
46
47
  client.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
47
48
  assert client.wait(10), 'never got response from server'
48
49
  res = client.read
@@ -51,13 +52,13 @@ class TestCccTCPI < Test::Unit::TestCase
51
52
  client.close
52
53
 
53
54
  # start a slow request...
54
- sleeper = TCPSocket.new(host, port)
55
+ sleeper = tcp_socket(host, port)
55
56
  sleeper.write("GET /sleep HTTP/1.1\r\nHost: example.com\r\n\r\n")
56
57
 
57
58
  # and a bunch of aborted ones
58
59
  nr = 100
59
60
  nr.times do |i|
60
- client = TCPSocket.new(host, port)
61
+ client = tcp_socket(host, port)
61
62
  client.write("GET /collections/#{rand(10000)} HTTP/1.1\r\n" \
62
63
  "Host: example.com\r\n\r\n")
63
64
  client.close
@@ -121,7 +121,7 @@ class WebServerTest < Test::Unit::TestCase
121
121
  @server.start
122
122
  end
123
123
 
124
- sock = TCPSocket.new('127.0.0.1', @port)
124
+ sock = tcp_socket('127.0.0.1', @port)
125
125
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
126
126
 
127
127
  responses = sock.read(4096)
@@ -141,14 +141,14 @@ class WebServerTest < Test::Unit::TestCase
141
141
  @server.start
142
142
  end
143
143
 
144
- sock = TCPSocket.new('127.0.0.1', @port)
144
+ sock = tcp_socket('127.0.0.1', @port)
145
145
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
146
146
 
147
147
  responses = sock.read(4096)
148
148
  assert_match %r{\AHTTP/1.[01] 200\b}, responses
149
149
  assert_match %r{^after_reply_called: false}, responses
150
150
 
151
- sock = TCPSocket.new('127.0.0.1', @port)
151
+ sock = tcp_socket('127.0.0.1', @port)
152
152
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
153
153
 
154
154
  responses = sock.read(4096)
@@ -166,7 +166,7 @@ class WebServerTest < Test::Unit::TestCase
166
166
  @server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"] )
167
167
  @server.start
168
168
  end
169
- sock = TCPSocket.new('127.0.0.1', @port)
169
+ sock = tcp_socket('127.0.0.1', @port)
170
170
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
171
171
  assert_match %r{\AHTTP/1.[01] 500\b}, sock.sysread(4096)
172
172
  assert_nil sock.close
@@ -179,7 +179,7 @@ class WebServerTest < Test::Unit::TestCase
179
179
 
180
180
  def test_client_shutdown_writes
181
181
  bs = 15609315 * rand
182
- sock = TCPSocket.new('127.0.0.1', @port)
182
+ sock = tcp_socket('127.0.0.1', @port)
183
183
  sock.syswrite("PUT /hello HTTP/1.1\r\n")
184
184
  sock.syswrite("Host: example.com\r\n")
185
185
  sock.syswrite("Transfer-Encoding: chunked\r\n")
@@ -206,7 +206,7 @@ class WebServerTest < Test::Unit::TestCase
206
206
 
207
207
  def test_client_shutdown_write_truncates
208
208
  bs = 15609315 * rand
209
- sock = TCPSocket.new('127.0.0.1', @port)
209
+ sock = tcp_socket('127.0.0.1', @port)
210
210
  sock.syswrite("PUT /hello HTTP/1.1\r\n")
211
211
  sock.syswrite("Host: example.com\r\n")
212
212
  sock.syswrite("Transfer-Encoding: chunked\r\n")
@@ -232,7 +232,7 @@ class WebServerTest < Test::Unit::TestCase
232
232
 
233
233
  def test_client_malformed_body
234
234
  bs = 15653984
235
- sock = TCPSocket.new('127.0.0.1', @port)
235
+ sock = tcp_socket('127.0.0.1', @port)
236
236
  sock.syswrite("PUT /hello HTTP/1.1\r\n")
237
237
  sock.syswrite("Host: example.com\r\n")
238
238
  sock.syswrite("Transfer-Encoding: chunked\r\n")
@@ -254,7 +254,7 @@ class WebServerTest < Test::Unit::TestCase
254
254
 
255
255
  def do_test(string, chunk, close_after=nil, shutdown_delay=0)
256
256
  # Do not use instance variables here, because it needs to be thread safe
257
- socket = TCPSocket.new("127.0.0.1", @port);
257
+ socket = tcp_socket("127.0.0.1", @port);
258
258
  request = StringIO.new(string)
259
259
  chunks_out = 0
260
260
 
@@ -299,14 +299,14 @@ class WebServerTest < Test::Unit::TestCase
299
299
  end
300
300
 
301
301
  def test_bad_client_400
302
- sock = TCPSocket.new('127.0.0.1', @port)
302
+ sock = tcp_socket('127.0.0.1', @port)
303
303
  sock.syswrite("GET / HTTP/1.0\r\nHost: foo\rbar\r\n\r\n")
304
304
  assert_match %r{\AHTTP/1.[01] 400\b}, sock.sysread(4096)
305
305
  assert_nil sock.close
306
306
  end
307
307
 
308
308
  def test_http_0_9
309
- sock = TCPSocket.new('127.0.0.1', @port)
309
+ sock = tcp_socket('127.0.0.1', @port)
310
310
  sock.syswrite("GET /hello\r\n")
311
311
  assert_match 'hello!\n', sock.sysread(4096)
312
312
  assert_nil sock.close
@@ -52,7 +52,7 @@ class SignalsTest < Test::Unit::TestCase
52
52
  redirect_test_io { HttpServer.new(app, opts).start.join }
53
53
  }
54
54
  wait_workers_ready("test_stderr.#{pid}.log", 1)
55
- sock = TCPSocket.new('127.0.0.1', @port)
55
+ sock = tcp_socket('127.0.0.1', @port)
56
56
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
57
57
  buf = sock.readpartial(4096)
58
58
  assert_nil sock.close
@@ -79,7 +79,7 @@ class SignalsTest < Test::Unit::TestCase
79
79
  }
80
80
  wr.close
81
81
  wait_workers_ready("test_stderr.#{pid}.log", 1)
82
- sock = TCPSocket.new('127.0.0.1', @port)
82
+ sock = tcp_socket('127.0.0.1', @port)
83
83
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
84
84
  buf = rd.readpartial(1)
85
85
  wait_master_ready("test_stderr.#{pid}.log")
@@ -102,7 +102,7 @@ class SignalsTest < Test::Unit::TestCase
102
102
  }
103
103
  t0 = Time.now
104
104
  wait_workers_ready("test_stderr.#{pid}.log", 1)
105
- sock = TCPSocket.new('127.0.0.1', @port)
105
+ sock = tcp_socket('127.0.0.1', @port)
106
106
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
107
107
 
108
108
  buf = nil
@@ -125,7 +125,7 @@ class SignalsTest < Test::Unit::TestCase
125
125
  }
126
126
  redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
127
127
  wait_workers_ready("test_stderr.#{$$}.log", 1)
128
- sock = TCPSocket.new('127.0.0.1', @port)
128
+ sock = tcp_socket('127.0.0.1', @port)
129
129
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
130
130
  buf = ''
131
131
  header_len = pid = nil
@@ -163,13 +163,13 @@ class SignalsTest < Test::Unit::TestCase
163
163
  redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
164
164
 
165
165
  wait_workers_ready("test_stderr.#{$$}.log", 1)
166
- sock = TCPSocket.new('127.0.0.1', @port)
166
+ sock = tcp_socket('127.0.0.1', @port)
167
167
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
168
168
  pid = sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
169
169
  assert_nil sock.close
170
170
 
171
171
  assert pid > 0, "pid not positive: #{pid.inspect}"
172
- sock = TCPSocket.new('127.0.0.1', @port)
172
+ sock = tcp_socket('127.0.0.1', @port)
173
173
  sock.syswrite("PUT / HTTP/1.0\r\n")
174
174
  sock.syswrite("Content-Length: #{@bs * @count}\r\n\r\n")
175
175
  1000.times { Process.kill(:HUP, pid) }
@@ -116,7 +116,7 @@ class TestSocketHelper < Test::Unit::TestCase
116
116
  client.syswrite('abcde')
117
117
  exit 0
118
118
  end
119
- s = UNIXSocket.new(@unix_listener_path)
119
+ s = unix_socket(@unix_listener_path)
120
120
  IO.select([s])
121
121
  assert_equal 'abcde', s.sysread(5)
122
122
  pid, status = Process.waitpid2(pid)
@@ -60,7 +60,7 @@ class UploadTest < Test::Unit::TestCase
60
60
 
61
61
  def test_put
62
62
  start_server(@sha1_app)
63
- sock = TCPSocket.new(@addr, @port)
63
+ sock = tcp_socket(@addr, @port)
64
64
  sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
65
65
  @count.times do |i|
66
66
  buf = @random.sysread(@bs)
@@ -77,7 +77,7 @@ class UploadTest < Test::Unit::TestCase
77
77
  def test_put_content_md5
78
78
  md5 = Digest::MD5.new
79
79
  start_server(@sha1_app)
80
- sock = TCPSocket.new(@addr, @port)
80
+ sock = tcp_socket(@addr, @port)
81
81
  sock.syswrite("PUT / HTTP/1.0\r\nTransfer-Encoding: chunked\r\n" \
82
82
  "Trailer: Content-MD5\r\n\r\n")
83
83
  @count.times do |i|
@@ -103,7 +103,7 @@ class UploadTest < Test::Unit::TestCase
103
103
  @count, @bs = 2, 128
104
104
  start_server(@sha1_app)
105
105
  assert_equal 256, length
106
- sock = TCPSocket.new(@addr, @port)
106
+ sock = tcp_socket(@addr, @port)
107
107
  hdr = "PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n"
108
108
  @count.times do
109
109
  buf = @random.sysread(@bs)
@@ -122,7 +122,7 @@ class UploadTest < Test::Unit::TestCase
122
122
 
123
123
  def test_put_keepalive_truncates_small_overwrite
124
124
  start_server(@sha1_app)
125
- sock = TCPSocket.new(@addr, @port)
125
+ sock = tcp_socket(@addr, @port)
126
126
  to_upload = length + 1
127
127
  sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{to_upload}\r\n\r\n")
128
128
  @count.times do
@@ -155,7 +155,7 @@ class UploadTest < Test::Unit::TestCase
155
155
  tmp.write(nr.to_s)
156
156
  [ 200, @hdr, [] ]
157
157
  })
158
- sock = TCPSocket.new(@addr, @port)
158
+ sock = tcp_socket(@addr, @port)
159
159
  buf = ' ' * @bs
160
160
  sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
161
161
 
@@ -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'] || '5.8.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