Class: HTTP::Timeout::PerOperation
- Defined in:
- lib/http/timeout/per_operation.rb
Overview
Timeout handler with separate timeouts for connect, read, and write
Constant Summary collapse
- KEYS =
Mapping of shorthand option keys to their full forms
%i[read write connect].to_h { |k| [k, :"#{k}_timeout"] }.freeze
- WAIT_RESULTS =
I/O wait result symbols returned by non-blocking operations
%i[wait_readable wait_writable].freeze
Constants inherited from Null
Instance Attribute Summary
Attributes inherited from Null
Class Method Summary collapse
-
.normalize_options(options) ⇒ Hash
Normalize and validate timeout options.
Instance Method Summary collapse
-
#connect(socket_class, host, port, nodelay: false) ⇒ void
Connects to a socket with connect timeout.
-
#connect_ssl ⇒ void
Starts an SSL connection with connect timeout.
-
#initialize(read_timeout: nil, write_timeout: nil, connect_timeout: nil) ⇒ HTTP::Timeout::PerOperation
constructor
Initializes per-operation timeout with options.
-
#readpartial(size, buffer = nil) ⇒ String, :eof
Read data from the socket.
-
#write(data) ⇒ Integer
Write data to the socket.
Methods inherited from Null
Constructor Details
#initialize(read_timeout: nil, write_timeout: nil, connect_timeout: nil) ⇒ HTTP::Timeout::PerOperation
Initializes per-operation timeout with options
85 86 87 88 89 90 91 |
# File 'lib/http/timeout/per_operation.rb', line 85 def initialize(read_timeout: nil, write_timeout: nil, connect_timeout: nil) super @read_timeout = read_timeout @write_timeout = write_timeout @connect_timeout = connect_timeout end |
Class Method Details
.normalize_options(options) ⇒ Hash
Normalize and validate timeout options
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/http/timeout/per_operation.rb', line 23 def self.() remaining = .dup normalized = {} #: Hash[Symbol, Numeric] KEYS.each do |short, long| next if !remaining.key?(short) && !remaining.key?(long) normalized[long] = resolve_timeout_value!(remaining, short, long) end raise ArgumentError, "unknown timeout options: #{remaining.keys.join(', ')}" unless remaining.empty? raise ArgumentError, "no timeout options given" if normalized.empty? normalized end |
Instance Method Details
#connect(socket_class, host, port, nodelay: false) ⇒ void
This method returns an undefined value.
Connects to a socket with connect timeout
104 105 106 107 |
# File 'lib/http/timeout/per_operation.rb', line 104 def connect(socket_class, host, port, nodelay: false) @socket = open_socket(socket_class, host, port, connect_timeout: @connect_timeout) @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if nodelay end |
#connect_ssl ⇒ void
This method returns an undefined value.
Starts an SSL connection with connect timeout
116 117 118 119 120 121 122 |
# File 'lib/http/timeout/per_operation.rb', line 116 def connect_ssl rescue_readable(@connect_timeout) do rescue_writable(@connect_timeout) do @socket.connect_nonblock end end end |
#readpartial(size, buffer = nil) ⇒ String, :eof
Read data from the socket
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/http/timeout/per_operation.rb', line 136 def readpartial(size, buffer = nil) timeout = false loop do result = @socket.read_nonblock(size, buffer, exception: false) return :eof if result.nil? return result unless WAIT_RESULTS.include?(result) raise TimeoutError, "Read timed out after #{@read_timeout} seconds" if timeout # marking the socket for timeout. Why is this not being raised immediately? # it seems there is some race-condition on the network level between calling # #read_nonblock and #wait_readable, in which #read_nonblock signalizes waiting # for reads, and when waiting for x seconds, it returns nil suddenly without completing # the x seconds. In a normal case this would be a timeout on wait/read, but it can # also mean that the socket has been closed by the server. Therefore we "mark" the # socket for timeout and try to read more bytes. If it returns :eof, it's all good, no # timeout. Else, the first timeout was a proper timeout. # This hack has to be done because io/wait#wait_readable doesn't provide a value for when # the socket is closed by the server, and HTTP::Parser doesn't provide the limit for the chunks. timeout = true unless wait_for_io(result, @read_timeout) end end |
#write(data) ⇒ Integer
Write data to the socket
168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/http/timeout/per_operation.rb', line 168 def write(data) timeout = false loop do result = @socket.write_nonblock(data, exception: false) return result unless WAIT_RESULTS.include?(result) raise TimeoutError, "Write timed out after #{@write_timeout} seconds" if timeout timeout = true unless wait_for_io(result, @write_timeout) end end |