Method: FTW::Connection#connect

Defined in:
lib/ftw/connection.rb

#connect(timeout = nil) ⇒ nil, StandardError or subclass

Connect now.

Timeout value is optional. If no timeout is given, this method blocks until a connection is successful or an error occurs.

You should check the return value of this method to determine if a connection was successful.

Possible return values are on error include:

  • FTW::Connection::ConnectRefused

  • FTW::Connection::ConnectTimeout

Returns:

  • (nil)

    if the connection was successful

  • (StandardError or subclass)

    if the connection failed



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/ftw/connection.rb', line 133

def connect(timeout=nil)
  # TODO(sissel): Raise if we're already connected?
  disconnect("reconnecting") if connected?
  host, port = @destinations.first.split(":")
  @destinations = @destinations.rotate # round-robin

  # Do dns resolution on the host. If there are multiple
  # addresses resolved, return one at random.
  addresses = FTW::DNS.singleton.resolve(host)

  addresses.each do |address|
    # Try each address until one works.
    @remote_address = address
    # Addresses with colon ':' in them are assumed to be IPv6
    family = @remote_address.include?(":") ? Socket::AF_INET6 : Socket::AF_INET
    @logger.debug("Connecting", :address => @remote_address,
                  :host => host, :port => port, :family => family)
    @socket = Socket.new(family, Socket::SOCK_STREAM, 0)
    @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)

    # This api is terrible. pack_sockaddr_in? This isn't C, man...
    @logger.debug("packing", :data => [port.to_i, @remote_address])
    sockaddr = Socket.pack_sockaddr_in(port.to_i, @remote_address)
    # TODO(sissel): Support local address binding

    # Connect with timeout
    begin
      @socket.connect_nonblock(sockaddr)
    rescue IO::WaitWritable, Errno::EINPROGRESS
      # Ruby actually raises Errno::EINPROGRESS, but for some reason
      # the documentation says to use this IO::WaitWritable thing...
      # I don't get it, but whatever :(

      writable = writable?(timeout)

      # http://jira.codehaus.org/browse/JRUBY-6528; IO.select doesn't behave
      # correctly on JRuby < 1.7, so work around it.
      if writable || (RUBY_PLATFORM == "java" and JRUBY_VERSION < "1.7.0")
        begin
          @socket.connect_nonblock(sockaddr) # check connection failure
        rescue Errno::EISCONN 
          # Ignore, we're already connected.
        rescue Errno::ECONNREFUSED => e
          # Fire 'disconnected' event with reason :refused
          @socket.close
          return ConnectRefused.new("#{host}[#{@remote_address}]:#{port}")
        rescue Errno::ETIMEDOUT
          # This occurs when the system's TCP timeout hits, we have no
          # control over this, as far as I can tell. *maybe* setsockopt(2)
          # has a flag for this, but I haven't checked..
          # TODO(sissel): We should instead do 'retry' unless we've exceeded
          # the timeout.
          @socket.close
          return ConnectTimeout.new("#{host}[#{@remote_address}]:#{port}")
        rescue Errno::EINPROGRESS
          # If we get here, it's likely JRuby version < 1.7.0. EINPROGRESS at
          # this point in the code means that we have timed out.
          @socket.close
          return ConnectTimeout.new("#{host}[#{@remote_address}]:#{port}")
        end
      else
        # Connection timeout;
        return ConnectTimeout.new("#{host}[#{@remote_address}]:#{port}")
      end

      # If no error at this point, we're now connected.
      @connected = true
      break
    end # addresses.each
  end 
  return nil
end