about summary refs log tree commit homepage
diff options
context:
space:
mode:
-rw-r--r--.olddoc.yml1
-rw-r--r--ISSUES18
-rw-r--r--Links2
-rw-r--r--lib/unicorn.rb2
-rw-r--r--lib/unicorn/configurator.rb13
-rw-r--r--lib/unicorn/http_request.rb94
-rw-r--r--lib/unicorn/http_server.rb1
-rw-r--r--lib/unicorn/socket_helper.rb23
-rw-r--r--lib/unicorn/stream_input.rb9
-rw-r--r--lib/unicorn/tee_input.rb14
-rw-r--r--t/test-lib.sh4
-rw-r--r--test/exec/test_exec.rb9
-rw-r--r--test/unit/test_ccc.rb90
-rw-r--r--test/unit/test_http_parser.rb18
-rw-r--r--test/unit/test_socket_helper.rb12
-rw-r--r--test/unit/test_util.rb4
16 files changed, 248 insertions, 66 deletions
diff --git a/.olddoc.yml b/.olddoc.yml
index ee2d306..cacc0ab 100644
--- a/.olddoc.yml
+++ b/.olddoc.yml
@@ -12,7 +12,6 @@ noindex:
 - TODO
 - unicorn_rails_1
 public_email: unicorn-public@bogomips.org
-private_email: unicorn@bogomips.org
 nntp_url:
   - nntp://news.public-inbox.org/inbox.comp.lang.ruby.unicorn
   - nntp://news.gmane.org/gmane.comp.lang.ruby.unicorn.general
diff --git a/ISSUES b/ISSUES
index 291441a..10df4d6 100644
--- a/ISSUES
+++ b/ISSUES
@@ -9,14 +9,16 @@ submit patches and/or obtain support after you have searched the
 * Cc: all participants in a thread or commit, as subscription is optional
 * Do not {top post}[http://catb.org/jargon/html/T/top-post.html] in replies
 * Quote as little as possible of the message you're replying to
-* Do not send HTML mail or images, it will be flagged as spam
-* Anonymous and pseudonymous messages will always be welcome.
+* Do not send HTML mail or images,
+  they hurt reader privacy and will be flagged as spam
+* Anonymous and pseudonymous messages will ALWAYS be welcome
 * The email submission port (587) is enabled on the bogomips.org MX:
   https://bogomips.org/unicorn-public/20141004232241.GA23908@dcvr.yhbt.net/t/
 
 If your issue is of a sensitive nature or you're just shy in public,
-then feel free to email us privately at mailto:unicorn@bogomips.org
-instead and your issue will be handled discreetly.
+use anonymity tools such as Tor or Mixmaster; and rely on the public
+mail archives for responses.  Be sure to scrub sensitive log messages
+and such.
 
 If you don't get a response within a few days, we may have forgotten
 about it so feel free to ask again.
@@ -64,14 +66,14 @@ document distributed with git) on guidelines for patch submission.
 == Contact Info
 
 * public: mailto:unicorn-public@bogomips.org
-* private: mailto:unicorn@bogomips.org
 * nntp://news.gmane.org/gmane.comp.lang.ruby.unicorn.general
 * nntp://news.public-inbox.org/inbox.comp.lang.ruby.unicorn
 * https://bogomips.org/unicorn-public/
+* http://ou63pmih66umazou.onion/unicorn-public/
 
 Mailing list subscription is optional, so Cc: all participants.
 
-You can follow along via NNTP:
+You can follow along via NNTP (read-only):
 
         nntp://news.public-inbox.org/inbox.comp.lang.ruby.unicorn
         nntp://news.gmane.org/gmane.comp.lang.ruby.unicorn.general
@@ -79,6 +81,7 @@ You can follow along via NNTP:
 Or Atom feeds:
 
         https://bogomips.org/unicorn-public/new.atom
+        http://ou63pmih66umazou.onion/unicorn-public/new.atom
 
         The HTML archives at https://bogomips.org/unicorn-public/
         also has links to per-thread Atom feeds and downloadable
@@ -88,3 +91,6 @@ You may optionally subscribe via plain-text email:
 
         mailto:unicorn-public+subscribe@bogomips.org
         (and confirming the auto-reply)
+
+Just keep in mind we suck at delivering email, so using NNTP,
+or Atom feeds might be a better bet...
diff --git a/Links b/Links
index 7c113c8..475a6c0 100644
--- a/Links
+++ b/Links
@@ -23,7 +23,7 @@ or services behind them.
 * {golden_brindle}[https://github.com/simonoff/golden_brindle] - tool to
   manage multiple unicorn instances/applications on a single server
 
-* {raindrops}[http://raindrops.bogomips.org/] - real-time stats for
+* {raindrops}[https://bogomips.org/raindrops/] - real-time stats for
   preforking Rack servers
 
 * {UnXF}[https://bogomips.org/unxf/]  Un-X-Forward* the Rack environment,
diff --git a/lib/unicorn.rb b/lib/unicorn.rb
index f122563..4bd7bda 100644
--- a/lib/unicorn.rb
+++ b/lib/unicorn.rb
@@ -95,7 +95,7 @@ module Unicorn
 
   # returns an array of strings representing TCP listen socket addresses
   # and Unix domain socket paths.  This is useful for use with
-  # Raindrops::Middleware under Linux: http://raindrops.bogomips.org/
+  # Raindrops::Middleware under Linux: https://bogomips.org/raindrops/
   def self.listener_names
     Unicorn::HttpServer::LISTENERS.map do |io|
       Unicorn::SocketHelper.sock_name(io)
diff --git a/lib/unicorn/configurator.rb b/lib/unicorn/configurator.rb
index f69f220..f404aea 100644
--- a/lib/unicorn/configurator.rb
+++ b/lib/unicorn/configurator.rb
@@ -56,7 +56,7 @@ class Unicorn::Configurator
     :worker_exec => false,
     :preload_app => false,
     :check_client_connection => false,
-    :rewindable_input => true, # for Rack 2.x: (Rack::VERSION[0] <= 1),
+    :rewindable_input => true,
     :client_body_buffer_size => Unicorn::Const::MAX_BODY,
   }
   #:startdoc:
@@ -515,13 +515,12 @@ class Unicorn::Configurator
   # Disabling rewindability can improve performance by lowering
   # I/O and memory usage for applications that accept uploads.
   # Keep in mind that the Rack 1.x spec requires
-  # \env[\"rack.input\"] to be rewindable, so this allows
-  # intentionally violating the current Rack 1.x spec.
+  # \env[\"rack.input\"] to be rewindable,
+  # but the Rack 2.x spec does not.
   #
-  # +rewindable_input+ defaults to +true+ when used with Rack 1.x for
-  # Rack conformance.  When Rack 2.x is finalized, this will most
-  # likely default to +false+ while still conforming to the newer
-  # (less demanding) spec.
+  # +rewindable_input+ defaults to +true+ for compatibility.
+  # Setting it to +false+ may be safe for applications and
+  # frameworks developed for Rack 2.x and later.
   def rewindable_input(bool)
     set_bool(:rewindable_input, bool)
   end
diff --git a/lib/unicorn/http_request.rb b/lib/unicorn/http_request.rb
index c176083..7253497 100644
--- a/lib/unicorn/http_request.rb
+++ b/lib/unicorn/http_request.rb
@@ -2,6 +2,7 @@
 # :enddoc:
 # no stable API here
 require 'unicorn_http'
+require 'raindrops'
 
 # TODO: remove redundant names
 Unicorn.const_set(:HttpRequest, Unicorn::HttpParser)
@@ -25,8 +26,10 @@ class Unicorn::HttpParser
 
   # :stopdoc:
   HTTP_RESPONSE_START = [ 'HTTP'.freeze, '/1.1 '.freeze ]
+  EMPTY_ARRAY = [].freeze
   @@input_class = Unicorn::TeeInput
   @@check_client_connection = false
+  @@tcpi_inspect_ok = true
 
   def self.input_class
     @@input_class
@@ -80,11 +83,7 @@ class Unicorn::HttpParser
       false until add_parse(socket.kgio_read!(16384))
     end
 
-    # detect if the socket is valid by writing a partial response:
-    if @@check_client_connection && headers?
-      self.response_start_sent = true
-      HTTP_RESPONSE_START.each { |c| socket.write(c) }
-    end
+    check_client_connection(socket) if @@check_client_connection
 
     e['rack.input'] = 0 == content_length ?
                       NULL_IO : @@input_class.new(socket, self)
@@ -105,4 +104,89 @@ class Unicorn::HttpParser
   def hijacked?
     env.include?('rack.hijack_io'.freeze)
   end
+
+  if Raindrops.const_defined?(:TCP_Info)
+    TCPI = Raindrops::TCP_Info.allocate
+
+    def check_client_connection(socket) # :nodoc:
+      if Unicorn::TCPClient === socket
+        # Raindrops::TCP_Info#get!, #state (reads struct tcp_info#tcpi_state)
+        raise Errno::EPIPE, "client closed connection".freeze,
+              EMPTY_ARRAY if closed_state?(TCPI.get!(socket).state)
+      else
+        write_http_header(socket)
+      end
+    end
+
+    if Raindrops.const_defined?(:TCP)
+      # raindrops 0.18.0+ supports FreeBSD + Linux using the same names
+      # Evaluate these hash lookups at load time so we can
+      # generate an opt_case_dispatch instruction
+      eval <<-EOS
+      def closed_state?(state) # :nodoc:
+        case state
+        when #{Raindrops::TCP[:ESTABLISHED]}
+          false
+        when #{Raindrops::TCP.values_at(
+              :CLOSE_WAIT, :TIME_WAIT, :CLOSE, :LAST_ACK, :CLOSING).join(',')}
+          true
+        else
+          false
+        end
+      end
+      EOS
+    else
+      # raindrops before 0.18 only supported TCP_INFO under Linux
+      def closed_state?(state) # :nodoc:
+        case state
+        when 1 # ESTABLISHED
+          false
+        when 8, 6, 7, 9, 11 # CLOSE_WAIT, TIME_WAIT, CLOSE, LAST_ACK, CLOSING
+          true
+        else
+          false
+        end
+      end
+    end
+  else
+
+    # Ruby 2.2+ can show struct tcp_info as a string Socket::Option#inspect.
+    # Not that efficient, but probably still better than doing unnecessary
+    # work after a client gives up.
+    def check_client_connection(socket) # :nodoc:
+      if Unicorn::TCPClient === socket && @@tcpi_inspect_ok
+        opt = socket.getsockopt(:IPPROTO_TCP, :TCP_INFO).inspect
+        if opt =~ /\bstate=(\S+)/
+          @@tcpi_inspect_ok = true
+          raise Errno::EPIPE, "client closed connection".freeze,
+                EMPTY_ARRAY if closed_state_str?($1)
+        else
+          @@tcpi_inspect_ok = false
+          write_http_header(socket)
+        end
+        opt.clear
+      else
+        write_http_header(socket)
+      end
+    end
+
+    def closed_state_str?(state)
+      case state
+      when 'ESTABLISHED'
+        false
+      # not a typo, ruby maps TCP_CLOSE (no 'D') to state=CLOSED (w/ 'D')
+      when 'CLOSE_WAIT', 'TIME_WAIT', 'CLOSED', 'LAST_ACK', 'CLOSING'
+        true
+      else
+        false
+      end
+    end
+  end
+
+  def write_http_header(socket) # :nodoc:
+    if headers?
+      self.response_start_sent = true
+      HTTP_RESPONSE_START.each { |c| socket.write(c) }
+    end
+  end
 end
diff --git a/lib/unicorn/http_server.rb b/lib/unicorn/http_server.rb
index 40a154d..3827f2e 100644
--- a/lib/unicorn/http_server.rb
+++ b/lib/unicorn/http_server.rb
@@ -89,6 +89,7 @@ class Unicorn::HttpServer
     @self_pipe = []
     @workers = {} # hash maps PIDs to Workers
     @sig_queue = [] # signal queue used for self-piping
+    @pid = nil
 
     # we try inheriting listeners first, so we bind them later.
     # we don't write the pid file until we've bound listeners in case
diff --git a/lib/unicorn/socket_helper.rb b/lib/unicorn/socket_helper.rb
index df8315e..f52dde2 100644
--- a/lib/unicorn/socket_helper.rb
+++ b/lib/unicorn/socket_helper.rb
@@ -3,6 +3,18 @@
 require 'socket'
 
 module Unicorn
+
+  # Instead of using a generic Kgio::Socket for everything,
+  # tag TCP sockets so we can use TCP_INFO under Linux without
+  # incurring extra syscalls for Unix domain sockets.
+  # TODO: remove these when we remove kgio
+  TCPClient = Class.new(Kgio::Socket) # :nodoc:
+  class TCPSrv < Kgio::TCPServer # :nodoc:
+    def kgio_tryaccept # :nodoc:
+      super(TCPClient)
+    end
+  end
+
   module SocketHelper
 
     # internal interface
@@ -63,12 +75,15 @@ module Unicorn
       elsif respond_to?(:accf_arg)
         name = opt[:accept_filter]
         name = DEFAULTS[:accept_filter] if name.nil?
+        sock.listen(opt[:backlog])
+        got = (sock.getsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER) rescue nil).to_s
+        arg = accf_arg(name)
         begin
-          sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER, accf_arg(name))
+          sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER, arg)
         rescue => e
           logger.error("#{sock_name(sock)} " \
                        "failed to set accept_filter=#{name} (#{e.inspect})")
-        end
+        end if arg != got
       end
     end
 
@@ -148,7 +163,7 @@ module Unicorn
       end
       sock.bind(Socket.pack_sockaddr_in(port, addr))
       sock.autoclose = false
-      Kgio::TCPServer.for_fd(sock.fileno)
+      TCPSrv.for_fd(sock.fileno)
     end
 
     # returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
@@ -185,7 +200,7 @@ module Unicorn
     def server_cast(sock)
       begin
         Socket.unpack_sockaddr_in(sock.getsockname)
-        Kgio::TCPServer.for_fd(sock.fileno)
+        TCPSrv.for_fd(sock.fileno)
       rescue ArgumentError
         Kgio::UNIXServer.for_fd(sock.fileno)
       end
diff --git a/lib/unicorn/stream_input.rb b/lib/unicorn/stream_input.rb
index de5aeea..41d28a0 100644
--- a/lib/unicorn/stream_input.rb
+++ b/lib/unicorn/stream_input.rb
@@ -1,16 +1,17 @@
 # -*- encoding: binary -*-
 
-# When processing uploads, Unicorn may expose a StreamInput object under
-# "rack.input" of the (future) Rack (2.x) environment.
+# When processing uploads, unicorn may expose a StreamInput object under
+# "rack.input" of the Rack environment when
+# Unicorn::Configurator#rewindable_input is set to +false+
 class Unicorn::StreamInput
   # The I/O chunk size (in +bytes+) for I/O operations where
   # the size cannot be user-specified when a method is called.
   # The default is 16 kilobytes.
-  @@io_chunk_size = Unicorn::Const::CHUNK_SIZE
+  @@io_chunk_size = Unicorn::Const::CHUNK_SIZE # :nodoc:
 
   # Initializes a new StreamInput object.  You normally do not have to call
   # this unless you are writing an HTTP server.
-  def initialize(socket, request)
+  def initialize(socket, request) # :nodoc:
     @chunked = request.content_length.nil?
     @socket = socket
     @parser = request
diff --git a/lib/unicorn/tee_input.rb b/lib/unicorn/tee_input.rb
index 6f66162..2ccc2d9 100644
--- a/lib/unicorn/tee_input.rb
+++ b/lib/unicorn/tee_input.rb
@@ -1,6 +1,6 @@
 # -*- encoding: binary -*-
 
-# acts like tee(1) on an input input to provide a input-like stream
+# Acts like tee(1) on an input input to provide a input-like stream
 # while providing rewindable semantics through a File/StringIO backing
 # store.  On the first pass, the input is only read on demand so your
 # Rack application can use input notification (upload progress and
@@ -9,22 +9,22 @@
 # strict interpretation of Rack::Lint::InputWrapper functionality and
 # will not support any deviations from it.
 #
-# When processing uploads, Unicorn exposes a TeeInput object under
-# "rack.input" of the Rack environment.
+# When processing uploads, unicorn exposes a TeeInput object under
+# "rack.input" of the Rack environment by default.
 class Unicorn::TeeInput < Unicorn::StreamInput
   # The maximum size (in +bytes+) to buffer in memory before
   # resorting to a temporary file.  Default is 112 kilobytes.
-  @@client_body_buffer_size = Unicorn::Const::MAX_BODY
+  @@client_body_buffer_size = Unicorn::Const::MAX_BODY # :nodoc:
 
   # sets the maximum size of request bodies to buffer in memory,
   # amounts larger than this are buffered to the filesystem
-  def self.client_body_buffer_size=(bytes)
+  def self.client_body_buffer_size=(bytes) # :nodoc:
     @@client_body_buffer_size = bytes
   end
 
   # returns the maximum size of request bodies to buffer in memory,
   # amounts larger than this are buffered to the filesystem
-  def self.client_body_buffer_size
+  def self.client_body_buffer_size # :nodoc:
     @@client_body_buffer_size
   end
 
@@ -37,7 +37,7 @@ class Unicorn::TeeInput < Unicorn::StreamInput
 
   # Initializes a new TeeInput object.  You normally do not have to call
   # this unless you are writing an HTTP server.
-  def initialize(socket, request)
+  def initialize(socket, request) # :nodoc:
     @len = request.content_length
     super
     @tmp = @len && @len <= @@client_body_buffer_size ?
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 28d6a88..7f97958 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -106,8 +106,8 @@ check_stderr () {
 # unicorn_setup
 unicorn_setup () {
         eval $(unused_listen)
-        port=$(expr $listen : '[^:]*:\([0-9]\+\)')
-        host=$(expr $listen : '\([^:]*\):[0-9]\+')
+        port=$(expr $listen : '[^:]*:\([0-9]*\)')
+        host=$(expr $listen : '\([^:][^:]*\):[0-9][0-9]*')
 
         rtmpfiles unicorn_config pid r_err r_out fifo tmp ok
         cat > $unicorn_config <<EOF
diff --git a/test/exec/test_exec.rb b/test/exec/test_exec.rb
index ca0b7bc..08f92ae 100644
--- a/test/exec/test_exec.rb
+++ b/test/exec/test_exec.rb
@@ -97,6 +97,9 @@ run lambda { |env|
   end
 
   def test_sd_listen_fds_emulation
+    # [ruby-core:69895] [Bug #11336] fixed by r51576
+    return if RUBY_VERSION.to_f < 2.3
+
     File.open("config.ru", "wb") { |fp| fp.write(HI) }
     sock = TCPServer.new(@addr, @port)
 
@@ -124,9 +127,7 @@ run lambda { |env|
     end
   ensure
     sock.close if sock
-    # disabled test on old Rubies: https://bugs.ruby-lang.org/issues/11336
-    # [ruby-core:69895] [Bug #11336] fixed by r51576
-  end if RUBY_VERSION.to_f >= 2.3
+  end
 
   def test_inherit_listener_unspecified
     File.open("config.ru", "wb") { |fp| fp.write(HI) }
@@ -142,7 +143,7 @@ run lambda { |env|
     res = hit(["http://#@addr:#@port/"])
     assert_equal [ "HI\n" ], res
     assert_shutdown(pid)
-    assert_equal 1, sock.getsockopt(:SOL_SOCKET, :SO_KEEPALIVE).int,
+    assert sock.getsockopt(:SOL_SOCKET, :SO_KEEPALIVE).bool,
                 'unicorn should always set SO_KEEPALIVE on inherited sockets'
   ensure
     sock.close if sock
diff --git a/test/unit/test_ccc.rb b/test/unit/test_ccc.rb
new file mode 100644
index 0000000..0db0c38
--- /dev/null
+++ b/test/unit/test_ccc.rb
@@ -0,0 +1,90 @@
+require 'socket'
+require 'unicorn'
+require 'io/wait'
+require 'tempfile'
+require 'test/unit'
+
+class TestCccTCPI < Test::Unit::TestCase
+  def test_ccc_tcpi
+    start_pid = $$
+    host = '127.0.0.1'
+    srv = TCPServer.new(host, 0)
+    port = srv.addr[1]
+    err = Tempfile.new('unicorn_ccc')
+    rd, wr = IO.pipe
+    sleep_pipe = IO.pipe
+    pid = fork do
+      sleep_pipe[1].close
+      reqs = 0
+      rd.close
+      worker_pid = nil
+      app = lambda do |env|
+        worker_pid ||= begin
+          at_exit { wr.write(reqs.to_s) if worker_pid == $$ }
+          $$
+        end
+        reqs += 1
+
+        # will wake up when writer closes
+        sleep_pipe[0].read if env['PATH_INFO'] == '/sleep'
+
+        [ 200, [ %w(Content-Length 0),  %w(Content-Type text/plain) ], [] ]
+      end
+      ENV['UNICORN_FD'] = srv.fileno.to_s
+      opts = {
+        listeners: [ "#{host}:#{port}" ],
+        stderr_path: err.path,
+        check_client_connection: true,
+      }
+      uni = Unicorn::HttpServer.new(app, opts)
+      uni.start.join
+    end
+    wr.close
+
+    # make sure the server is running, at least
+    client = TCPSocket.new(host, port)
+    client.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
+    assert client.wait_readable(10), 'never got response from server'
+    res = client.read
+    assert_match %r{\AHTTP/1\.1 200}, res, 'got part of first response'
+    assert_match %r{\r\n\r\n\z}, res, 'got end of response, server is ready'
+    client.close
+
+    # start a slow request...
+    sleeper = TCPSocket.new(host, port)
+    sleeper.write("GET /sleep HTTP/1.1\r\nHost: example.com\r\n\r\n")
+
+    # and a bunch of aborted ones
+    nr = 100
+    nr.times do |i|
+      client = TCPSocket.new(host, port)
+      client.write("GET /collections/#{rand(10000)} HTTP/1.1\r\n" \
+                   "Host: example.com\r\n\r\n")
+      client.close
+    end
+    sleep_pipe[1].close # wake up the reader in the worker
+    res = sleeper.read
+    assert_match %r{\AHTTP/1\.1 200}, res, 'got part of first sleeper response'
+    assert_match %r{\r\n\r\n\z}, res, 'got end of sleeper response'
+    sleeper.close
+    kpid = pid
+    pid = nil
+    Process.kill(:QUIT, kpid)
+    _, status = Process.waitpid2(kpid)
+    assert status.success?
+    reqs = rd.read.to_i
+    warn "server got #{reqs} requests with #{nr} CCC aborted\n" if $DEBUG
+    assert_operator reqs, :<, nr
+    assert_operator reqs, :>=, 2, 'first 2 requests got through, at least'
+  ensure
+    return if start_pid != $$
+    srv.close if srv
+    if pid
+      Process.kill(:QUIT, pid)
+      _, status = Process.waitpid2(pid)
+      assert status.success?
+    end
+    err.close! if err
+    rd.close if rd
+  end
+end
diff --git a/test/unit/test_http_parser.rb b/test/unit/test_http_parser.rb
index 7cbc0f8..31e6f71 100644
--- a/test/unit/test_http_parser.rb
+++ b/test/unit/test_http_parser.rb
@@ -851,24 +851,6 @@ class HttpParserTest < Test::Unit::TestCase
     assert_equal '', parser.env['HTTP_HOST']
   end
 
-  # so we don't  care about the portability of this test
-  # if it doesn't leak on Linux, it won't leak anywhere else
-  # unless your C compiler or platform is otherwise broken
-  LINUX_PROC_PID_STATUS = "/proc/self/status"
-  def test_memory_leak
-    match_rss = /^VmRSS:\s+(\d+)/
-    if File.read(LINUX_PROC_PID_STATUS) =~ match_rss
-      before = $1.to_i
-      1000000.times { Unicorn::HttpParser.new }
-      File.read(LINUX_PROC_PID_STATUS) =~ match_rss
-      after = $1.to_i
-      diff = after - before
-      assert(diff < 10000, "memory grew more than 10M: #{diff}")
-    end
-  end if RUBY_PLATFORM =~ /linux/ &&
-         File.readable?(LINUX_PROC_PID_STATUS) &&
-         !defined?(RUBY_ENGINE)
-
   def test_memsize
     require 'objspace'
     if ObjectSpace.respond_to?(:memsize_of)
diff --git a/test/unit/test_socket_helper.rb b/test/unit/test_socket_helper.rb
index 7526e82..8699409 100644
--- a/test/unit/test_socket_helper.rb
+++ b/test/unit/test_socket_helper.rb
@@ -150,28 +150,31 @@ class TestSocketHelper < Test::Unit::TestCase
   end
 
   def test_tcp_defer_accept_default
+    return unless defined?(TCP_DEFER_ACCEPT)
     port = unused_port @test_addr
     name = "#@test_addr:#{port}"
     sock = bind_listen(name)
     cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
     assert cur >= 1
-  end if defined?(TCP_DEFER_ACCEPT)
+  end
 
   def test_tcp_defer_accept_disable
+    return unless defined?(TCP_DEFER_ACCEPT)
     port = unused_port @test_addr
     name = "#@test_addr:#{port}"
     sock = bind_listen(name, :tcp_defer_accept => false)
     cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
     assert_equal 0, cur
-  end if defined?(TCP_DEFER_ACCEPT)
+  end
 
   def test_tcp_defer_accept_nr
+    return unless defined?(TCP_DEFER_ACCEPT)
     port = unused_port @test_addr
     name = "#@test_addr:#{port}"
     sock = bind_listen(name, :tcp_defer_accept => 60)
     cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0]
     assert cur > 1
-  end if defined?(TCP_DEFER_ACCEPT)
+  end
 
   def test_ipv6only
     port = begin
@@ -186,6 +189,7 @@ class TestSocketHelper < Test::Unit::TestCase
   end
 
   def test_reuseport
+    return unless defined?(Socket::SO_REUSEPORT)
     port = unused_port @test_addr
     name = "#@test_addr:#{port}"
     sock = bind_listen(name, :reuseport => true)
@@ -193,5 +197,5 @@ class TestSocketHelper < Test::Unit::TestCase
     assert_operator cur, :>, 0
   rescue Errno::ENOPROTOOPT
     # kernel does not support SO_REUSEPORT (older Linux)
-  end if defined?(Socket::SO_REUSEPORT)
+  end
 end
diff --git a/test/unit/test_util.rb b/test/unit/test_util.rb
index 4d17a16..dc6302e 100644
--- a/test/unit/test_util.rb
+++ b/test/unit/test_util.rb
@@ -69,7 +69,7 @@ class TestUtil < Test::Unit::TestCase
       }
     }
     tmp.close!
-  end if STDIN.respond_to?(:external_encoding)
+  end
 
   def test_reopen_logs_renamed_with_internal_encoding
     tmp = Tempfile.new('')
@@ -101,5 +101,5 @@ class TestUtil < Test::Unit::TestCase
       }
     }
     tmp.close!
-  end if STDIN.respond_to?(:external_encoding)
+  end
 end