unicorn 5.7.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
@@ -629,6 +629,8 @@ class Unicorn::HttpServer
629
629
  end
630
630
  end
631
631
 
632
+ env["rack.after_reply"] = []
633
+
632
634
  status, headers, body = @app.call(env)
633
635
 
634
636
  begin
@@ -651,6 +653,8 @@ class Unicorn::HttpServer
651
653
  end
652
654
  rescue => e
653
655
  handle_error(client, e)
656
+ ensure
657
+ env["rack.after_reply"].each(&:call) if env
654
658
  end
655
659
 
656
660
  def nuke_listeners!(readers)
@@ -681,7 +685,6 @@ class Unicorn::HttpServer
681
685
  LISTENERS.each { |sock| sock.close_on_exec = true }
682
686
 
683
687
  worker.user(*user) if user.kind_of?(Array) && ! worker.switched
684
- self.timeout /= 2.0 # halve it for select()
685
688
  @config = nil
686
689
  build_app! unless preload_app
687
690
  @after_fork = @listener_opts = @orig_app = nil
@@ -695,59 +698,55 @@ class Unicorn::HttpServer
695
698
  logger.info "worker=#{worker_nr} reopening logs..."
696
699
  Unicorn::Util.reopen_logs
697
700
  logger.info "worker=#{worker_nr} done reopening logs"
701
+ false
698
702
  rescue => e
699
703
  logger.error(e) rescue nil
700
704
  exit!(77) # EX_NOPERM in sysexits.h
701
705
  end
702
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
+
703
717
  # runs inside each forked worker, this sits around and waits
704
718
  # for connections and doesn't die until the parent dies (or is
705
719
  # given a INT, QUIT, or TERM signal)
706
720
  def worker_loop(worker)
707
- ppid = @master_pid
708
721
  readers = init_worker_process(worker)
709
- nr = 0 # this becomes negative if we need to reopen logs
722
+ waiter = prep_readers(readers)
723
+ reopen = false
710
724
 
711
725
  # this only works immediately if the master sent us the signal
712
726
  # (which is the normal case)
713
- trap(:USR1) { nr = -65536 }
727
+ trap(:USR1) { reopen = true }
714
728
 
715
729
  ready = readers.dup
716
- nr_listeners = readers.size
717
730
  @after_worker_ready.call(self, worker)
718
731
 
719
732
  begin
720
- nr < 0 and reopen_worker_logs(worker.nr)
721
- nr = 0
733
+ reopen = reopen_worker_logs(worker.nr) if reopen
722
734
  worker.tick = time_now.to_i
723
- tmp = ready.dup
724
- while sock = tmp.shift
735
+ while sock = ready.shift
725
736
  # Unicorn::Worker#kgio_tryaccept is not like accept(2) at all,
726
737
  # but that will return false
727
738
  if client = sock.kgio_tryaccept
728
739
  process_client(client)
729
- nr += 1
730
740
  worker.tick = time_now.to_i
731
741
  end
732
- break if nr < 0
733
- end
734
-
735
- # make the following bet: if we accepted clients this round,
736
- # we're probably reasonably busy, so avoid calling select()
737
- # and do a speculative non-blocking accept() on ready listeners
738
- # before we sleep again in select().
739
- if nr == nr_listeners
740
- tmp = ready.dup
741
- redo
742
+ break if reopen
742
743
  end
743
744
 
744
- ppid == Process.ppid or return
745
-
746
- # timeout used so we can detect parent death:
745
+ # timeout so we can .tick and keep parent from SIGKILL-ing us
747
746
  worker.tick = time_now.to_i
748
- ret = IO.select(readers, nil, nil, @timeout) and ready = ret[0]
747
+ waiter.get_readers(ready, readers, @timeout)
749
748
  rescue => e
750
- redo if nr < 0 && readers[0]
749
+ redo if reopen && readers[0]
751
750
  Unicorn.log_error(@logger, "listen loop error", e) if readers[0]
752
751
  end while readers[0]
753
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.7.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
@@ -34,6 +34,24 @@ class TestEarlyHintsHandler
34
34
  end
35
35
  end
36
36
 
37
+ class TestRackAfterReply
38
+ def initialize
39
+ @called = false
40
+ end
41
+
42
+ def call(env)
43
+ while env['rack.input'].read(4096)
44
+ end
45
+
46
+ env["rack.after_reply"] << -> { @called = true }
47
+
48
+ [200, { 'Content-Type' => 'text/plain' }, ["after_reply_called: #{@called}"]]
49
+ rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
50
+ $stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
51
+ raise e
52
+ end
53
+ end
54
+
37
55
  class WebServerTest < Test::Unit::TestCase
38
56
 
39
57
  def setup
@@ -103,7 +121,7 @@ class WebServerTest < Test::Unit::TestCase
103
121
  @server.start
104
122
  end
105
123
 
106
- sock = TCPSocket.new('127.0.0.1', @port)
124
+ sock = tcp_socket('127.0.0.1', @port)
107
125
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
108
126
 
109
127
  responses = sock.read(4096)
@@ -114,6 +132,32 @@ class WebServerTest < Test::Unit::TestCase
114
132
  assert_match %r{^HTTP/1.[01] 200\b}, responses
115
133
  end
116
134
 
135
+ def test_after_reply
136
+ teardown
137
+
138
+ redirect_test_io do
139
+ @server = HttpServer.new(TestRackAfterReply.new,
140
+ :listeners => [ "127.0.0.1:#@port"])
141
+ @server.start
142
+ end
143
+
144
+ sock = tcp_socket('127.0.0.1', @port)
145
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
146
+
147
+ responses = sock.read(4096)
148
+ assert_match %r{\AHTTP/1.[01] 200\b}, responses
149
+ assert_match %r{^after_reply_called: false}, responses
150
+
151
+ sock = tcp_socket('127.0.0.1', @port)
152
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
153
+
154
+ responses = sock.read(4096)
155
+ assert_match %r{\AHTTP/1.[01] 200\b}, responses
156
+ assert_match %r{^after_reply_called: true}, responses
157
+
158
+ sock.close
159
+ end
160
+
117
161
  def test_broken_app
118
162
  teardown
119
163
  app = lambda { |env| raise RuntimeError, "hello" }
@@ -122,7 +166,7 @@ class WebServerTest < Test::Unit::TestCase
122
166
  @server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"] )
123
167
  @server.start
124
168
  end
125
- sock = TCPSocket.new('127.0.0.1', @port)
169
+ sock = tcp_socket('127.0.0.1', @port)
126
170
  sock.syswrite("GET / HTTP/1.0\r\n\r\n")
127
171
  assert_match %r{\AHTTP/1.[01] 500\b}, sock.sysread(4096)
128
172
  assert_nil sock.close
@@ -135,7 +179,7 @@ class WebServerTest < Test::Unit::TestCase
135
179
 
136
180
  def test_client_shutdown_writes
137
181
  bs = 15609315 * rand
138
- sock = TCPSocket.new('127.0.0.1', @port)
182
+ sock = tcp_socket('127.0.0.1', @port)
139
183
  sock.syswrite("PUT /hello HTTP/1.1\r\n")
140
184
  sock.syswrite("Host: example.com\r\n")
141
185
  sock.syswrite("Transfer-Encoding: chunked\r\n")
@@ -162,7 +206,7 @@ class WebServerTest < Test::Unit::TestCase
162
206
 
163
207
  def test_client_shutdown_write_truncates
164
208
  bs = 15609315 * rand
165
- sock = TCPSocket.new('127.0.0.1', @port)
209
+ sock = tcp_socket('127.0.0.1', @port)
166
210
  sock.syswrite("PUT /hello HTTP/1.1\r\n")
167
211
  sock.syswrite("Host: example.com\r\n")
168
212
  sock.syswrite("Transfer-Encoding: chunked\r\n")
@@ -188,7 +232,7 @@ class WebServerTest < Test::Unit::TestCase
188
232
 
189
233
  def test_client_malformed_body
190
234
  bs = 15653984
191
- sock = TCPSocket.new('127.0.0.1', @port)
235
+ sock = tcp_socket('127.0.0.1', @port)
192
236
  sock.syswrite("PUT /hello HTTP/1.1\r\n")
193
237
  sock.syswrite("Host: example.com\r\n")
194
238
  sock.syswrite("Transfer-Encoding: chunked\r\n")
@@ -210,7 +254,7 @@ class WebServerTest < Test::Unit::TestCase
210
254
 
211
255
  def do_test(string, chunk, close_after=nil, shutdown_delay=0)
212
256
  # Do not use instance variables here, because it needs to be thread safe
213
- socket = TCPSocket.new("127.0.0.1", @port);
257
+ socket = tcp_socket("127.0.0.1", @port);
214
258
  request = StringIO.new(string)
215
259
  chunks_out = 0
216
260
 
@@ -255,14 +299,14 @@ class WebServerTest < Test::Unit::TestCase
255
299
  end
256
300
 
257
301
  def test_bad_client_400
258
- sock = TCPSocket.new('127.0.0.1', @port)
302
+ sock = tcp_socket('127.0.0.1', @port)
259
303
  sock.syswrite("GET / HTTP/1.0\r\nHost: foo\rbar\r\n\r\n")
260
304
  assert_match %r{\AHTTP/1.[01] 400\b}, sock.sysread(4096)
261
305
  assert_nil sock.close
262
306
  end
263
307
 
264
308
  def test_http_0_9
265
- sock = TCPSocket.new('127.0.0.1', @port)
309
+ sock = tcp_socket('127.0.0.1', @port)
266
310
  sock.syswrite("GET /hello\r\n")
267
311
  assert_match 'hello!\n', sock.sysread(4096)
268
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