summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornormal <normal@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2017-04-19 01:08:16 +0000
committernormal <normal@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2017-04-19 01:08:16 +0000
commitc32fc82d0ed8bcf0d6e4de8518bbb7bd808a69b8 (patch)
treea92b3b9275d623968562e7e984375e80a67ff8b8
parent0013fdaaa5145f2003d96b6e54a5f16f16f3678b (diff)
socket: avoid fcntl for read/write_nonblock on Linux
On platforms where MSG_DONTWAIT works reliably on all sockets (so far, I know of Linux), we can avoid fcntl syscalls and implement IO#write_nonblock and IO#read_nonblock in terms of the socket-specific send and recv family of syscalls. This avoids side effects on the socket, and also encourages generic code to be written in cases where IO wrappers like OpenSSL::SSL::SSLSocket are used. Perhaps in the future, side-effect-free non-blocking I/O can be standard on all files and OSes: https://cr.yp.to/unix/nonblock.html * ext/socket/lib/socket.rb (read_nonblock, write_nonblock): Linux-specific wrapper without side effects [ruby-core:80780] [Feature #13362] * test/socket/test_basicsocket.rb (test_read_write_nonblock): new test git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@58400 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
-rw-r--r--NEWS4
-rw-r--r--ext/socket/lib/socket.rb18
-rw-r--r--test/socket/test_basicsocket.rb50
3 files changed, 72 insertions, 0 deletions
diff --git a/NEWS b/NEWS
index ad76ac77cf..2325da2913 100644
--- a/NEWS
+++ b/NEWS
@@ -64,6 +64,10 @@ with all sufficient information, see the ChangeLog file or Redmine
* Random.raw_seed renamed to become Random.urandom. It is now
applicable to non-seeding purposes due to [Bug #9569].
+* BasicSocket#read_nonblock and BasicSocket#write_nonblock no
+ longer sets the O_NONBLOCK file description flag as side effect
+ [Feature #13362]
+
=== Stdlib compatibility issues (excluding feature bug fixes)
=== C API updates
diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb
index de5eda3991..8127009fff 100644
--- a/ext/socket/lib/socket.rb
+++ b/ext/socket/lib/socket.rb
@@ -442,6 +442,24 @@ class BasicSocket < IO
scm_rights: false, exception: true)
__recvmsg_nonblock(dlen, flags, clen, scm_rights, exception)
end
+
+ # Linux-specific optimizations to avoid fcntl for IO#read_nonblock
+ # and IO#write_nonblock using MSG_DONTWAIT
+ # Do other platforms suport MSG_DONTWAIT reliably?
+ if RUBY_PLATFORM =~ /linux/ && Socket.const_defined?(:MSG_DONTWAIT)
+ def read_nonblock(len, str = nil, exception: true) # :nodoc:
+ case rv = __recv_nonblock(len, 0, str, exception)
+ when '' # recv_nonblock returns empty string on EOF
+ exception ? raise(EOFError, 'end of file reached') : nil
+ else
+ rv
+ end
+ end
+
+ def write_nonblock(buf, exception: true) # :nodoc:
+ __sendmsg_nonblock(buf, 0, nil, nil, exception)
+ end
+ end
end
class Socket < BasicSocket
diff --git a/test/socket/test_basicsocket.rb b/test/socket/test_basicsocket.rb
index e17a675d8a..0b13a7f1af 100644
--- a/test/socket/test_basicsocket.rb
+++ b/test/socket/test_basicsocket.rb
@@ -3,6 +3,7 @@
begin
require "socket"
require "test/unit"
+ require "io/nonblock"
rescue LoadError
end
@@ -152,4 +153,53 @@ class TestSocket_BasicSocket < Test::Unit::TestCase
sock.close
end
end
+
+ def test_read_write_nonblock
+ socks do |sserv, ssock, csock|
+ set_nb = true
+ buf = String.new
+ if ssock.respond_to?(:nonblock?)
+ assert_not_predicate(ssock, :nonblock?)
+ assert_not_predicate(csock, :nonblock?)
+
+ # Linux may use MSG_DONTWAIT to avoid setting O_NONBLOCK
+ if RUBY_PLATFORM.match?(/linux/) && Socket.const_defined?(:MSG_DONTWAIT)
+ set_nb = false
+ end
+ end
+ assert_equal :wait_readable, ssock.read_nonblock(1, buf, exception: false)
+ assert_equal 5, csock.write_nonblock('hello')
+ IO.select([ssock])
+ assert_same buf, ssock.read_nonblock(5, buf, exception: false)
+ assert_equal 'hello', buf
+ buf = '*' * 16384
+ n = 0
+
+ case w = csock.write_nonblock(buf, exception: false)
+ when Integer
+ n += w
+ when :wait_writable
+ break
+ end while true
+
+ assert_equal :wait_writable, w
+ assert_raise(IO::WaitWritable) { loop { csock.write_nonblock(buf) } }
+ assert_operator n, :>, 0
+ assert_not_predicate(csock, :nonblock?, '[Feature #13362]') unless set_nb
+ csock.close
+
+ case r = ssock.read_nonblock(16384, buf, exception: false)
+ when String
+ next
+ when nil
+ break
+ else
+ flunk "unexpected read_nonblock return: #{r.inspect}"
+ end while true
+
+ assert_raise(EOFError) { ssock.read_nonblock(1) }
+
+ assert_not_predicate(ssock, :nonblock?) unless set_nb
+ end
+ end
end if defined?(BasicSocket)