diff --git a/.travis.yml b/.travis.yml index da46825..e14f298 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,9 @@ rvm: - 1.9.3 - ruby-head - jruby-19mode - - jruby-head + +# Non-blocking connect is causing deadlocks on jruby-head +# - jruby-head # See https://github.com/rubinius/rubinius/issues/1611 # - rbx-19mode diff --git a/CHANGES.md b/CHANGES.md index 98db3bf..23e12b8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +HEAD +---- +* True non-blocking connect support (async DNS support still pending) + 0.9.0 ----- * TCPServer, TCPSocket, and UDPSocket classes in Celluloid::IO namespace diff --git a/lib/celluloid/io/tcp_socket.rb b/lib/celluloid/io/tcp_socket.rb index 2e957e5..584605a 100644 --- a/lib/celluloid/io/tcp_socket.rb +++ b/lib/celluloid/io/tcp_socket.rb @@ -1,4 +1,5 @@ require 'socket' +require 'resolv' module Celluloid module IO @@ -22,8 +23,47 @@ def self.from_ruby_socket(ruby_socket) # and local_port are specified, then those parameters are used on the # local end to establish the connection. def initialize(remote_host, remote_port, local_host = nil, local_port = nil) - # FIXME: not using non-blocking connect - @socket = ::TCPSocket.new(remote_host, remote_port, local_host, local_port) + # Is it an IPv4 address? + begin + @addr = Resolv::IPv4.create(remote_host) + rescue ArgumentError + end + + # Guess it's not IPv4! Is it IPv6? + unless @addr + begin + @addr = Resolv::IPv6.create(remote_host) + rescue ArgumentError + end + end + + # Guess it's not an IP address, so let's try DNS + unless @addr + # TODO: suppport asynchronous DNS + # Even EventMachine doesn't do async DNS by default o_O + @addr = Resolv::DNS.new.getaddress(remote_host) + end + + case @addr + when Resolv::IPv4 + family = Socket::AF_INET + when Resolv::IPv6 + family = Socket::AF_INET6 + else raise ArgumentError, "unsupported address class: #{@addr.class}" + end + + @socket = Socket.new(family, Socket::SOCK_STREAM, 0) + @socket.bind Addrinfo.tcp(local_host, local_port) if local_host + + begin + @socket.connect_nonblock Socket.sockaddr_in(remote_port, @addr.to_s) + rescue Errno::EINPROGRESS + wait_writable + retry + rescue Errno::EISCONN + # We're now connected! Yay exceptions for flow control + # NOTE: This is the approach the Ruby stdlib docs suggest ;_; + end end def to_io