File: tcp.rb

package info (click to toggle)
ruby-mongo 2.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 15,020 kB
  • sloc: ruby: 110,810; makefile: 5
file content (134 lines) | stat: -rw-r--r-- 4,244 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# frozen_string_literal: true
# rubocop:todo all

# Copyright (C) 2014-2020 MongoDB Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

module Mongo
  class Socket

    # Wrapper for TCP sockets.
    #
    # @since 2.0.0
    class TCP < Socket

      # Initializes a new TCP socket.
      #
      # @example Create the TCP socket.
      #   TCP.new('::1', 27017, 30, Socket::PF_INET)
      #   TCP.new('127.0.0.1', 27017, 30, Socket::PF_INET)
      #
      # @param [ String ] host The hostname or IP address.
      # @param [ Integer ] port The port number.
      # @param [ Float ] timeout The socket timeout value.
      # @param [ Integer ] family The socket family.
      # @param [ Hash ] options The options.
      #
      # @option options [ Float ] :connect_timeout Connect timeout.
      # @option options [ Address ] :connection_address Address of the
      #   connection that created this socket.
      # @option options [ Integer ] :connection_generation Generation of the
      #   connection (for non-monitoring connections) that created this socket.
      # @option options [ true | false ] :monitor Whether this socket was
      #   created by a monitoring connection.
      #
      # @since 2.0.0
      # @api private
      def initialize(host, port, timeout, family, options = {})
        if family.nil?
          raise ArgumentError, 'family must be specified'
        end
        super(timeout, options)
        @host, @port = host, port
        @family = family
        @socket = ::Socket.new(family, SOCK_STREAM, 0)
        begin
          set_socket_options(@socket)
          connect!
        rescue
          @socket.close
          raise
        end
      end

      # @return [ String ] host The host to connect to.
      attr_reader :host

      # @return [ Integer ] port The port to connect to.
      attr_reader :port

      # Establishes a socket connection.
      #
      # @example Connect the socket.
      #   sock.connect!
      #
      # @note This method mutates the object by setting the socket
      #   internally.
      #
      # @return [ TCP ] The connected socket instance.
      #
      # @since 2.0.0
      # @api private
      def connect!
        socket.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1)
        sockaddr = ::Socket.pack_sockaddr_in(port, host)
        connect_timeout = options[:connect_timeout]
        map_exceptions do
          if connect_timeout && connect_timeout != 0
            connect_with_timeout(sockaddr, connect_timeout)
          else
            connect_without_timeout(sockaddr)
          end
        end
        self
      end

      # @api private
      def connect_without_timeout(sockaddr)
        socket.connect(sockaddr)
      end

      # @api private
      def connect_with_timeout(sockaddr, connect_timeout)
        if connect_timeout <= 0
          raise Error::SocketTimeoutError, "The socket took over #{connect_timeout} seconds to connect"
        end

        deadline = Utils.monotonic_time + connect_timeout
        begin
          socket.connect_nonblock(sockaddr)
        rescue IO::WaitWritable
          select_timeout = deadline - Utils.monotonic_time
          if select_timeout <= 0
            raise Error::SocketTimeoutError, "The socket took over #{connect_timeout} seconds to connect"
          end
          if IO.select(nil, [socket], nil, select_timeout)
            retry
          else
            socket.close
            raise Error::SocketTimeoutError, "The socket took over #{connect_timeout} seconds to connect"
          end
        rescue Errno::EISCONN
          # Socket is connected, nothing more to do
        end
      end

      private

      def human_address
        "#{host}:#{port} (no TLS)"
      end
    end
  end
end