Class: HTTP::Timeout::Global

Inherits:
Null
  • Object
show all
Defined in:
lib/http/timeout/global.rb,
sig/http.rbs

Overview

Timeout handler with a single global timeout for the entire request

Constant Summary collapse

WAIT_RESULTS =

I/O wait result symbols returned by non-blocking operations

Returns:

  • (Array[Symbol])
%i[wait_readable wait_writable].freeze

Constants inherited from Null

Null::NATIVE_CONNECT_TIMEOUT

Instance Attribute Summary

Attributes inherited from Null

#options, #socket

Instance Method Summary collapse

Methods inherited from Null

#close, #closed?, #native_timeout?, #open_socket, #open_with_timeout, #read_timeout, #rescue_readable, #rescue_writable, #start_tls, #write_timeout

Constructor Details

#initialize(global_timeout:, read_timeout: nil, write_timeout: nil, connect_timeout: nil) ⇒ HTTP::Timeout::Global

Initializes global timeout with options

Examples:

HTTP::Timeout::Global.new(global_timeout: 5)

Parameters:

  • global_timeout (Numeric)

    Global timeout in seconds

  • read_timeout (Numeric, nil) (defaults to: nil)

    Read timeout in seconds

  • write_timeout (Numeric, nil) (defaults to: nil)

    Write timeout in seconds

  • connect_timeout (Numeric, nil) (defaults to: nil)

    Connect timeout in seconds

  • global_timeout: (Numeric)
  • read_timeout: (Numeric, nil) (defaults to: nil)
  • write_timeout: (Numeric, nil) (defaults to: nil)
  • connect_timeout: (Numeric, nil) (defaults to: nil)


25
26
27
28
29
30
31
32
# File 'lib/http/timeout/global.rb', line 25

def initialize(global_timeout:, read_timeout: nil, write_timeout: nil, connect_timeout: nil)
  super

  @timeout = @time_left = global_timeout
  @read_timeout    = read_timeout
  @write_timeout   = write_timeout
  @connect_timeout = connect_timeout
end

Instance Method Details

#connect(socket_class, host, port, nodelay: false) ⇒ void

This method returns an undefined value.

Connects to a socket with global timeout

Examples:

timeout.connect(TCPSocket, "example.com", 80)

Parameters:

  • socket_class (Class)
  • host (String)
  • port (Integer)
  • nodelay (Boolean) (defaults to: false)
  • nodelay: (Boolean) (defaults to: false)


56
57
58
59
60
61
62
# File 'lib/http/timeout/global.rb', line 56

def connect(socket_class, host, port, nodelay: false)
  reset_timer
  @socket = open_socket(socket_class, host, port, connect_timeout: effective_timeout(@connect_timeout))
  @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if nodelay

  log_time
end

#connect_sslvoid

This method returns an undefined value.

Starts an SSL connection on a socket

Examples:

timeout.connect_ssl


71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/http/timeout/global.rb', line 71

def connect_ssl
  reset_timer

  begin
    @socket.connect_nonblock
  rescue IO::WaitReadable
    wait_readable_or_timeout(@connect_timeout)
    retry
  rescue IO::WaitWritable
    wait_writable_or_timeout(@connect_timeout)
    retry
  end
end

#effective_timeout(per_op_timeout) ⇒ Numeric

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Computes the effective timeout as the minimum of global and per-operation

Parameters:

  • per_op_timeout (Numeric, nil)

    per-operation timeout limit

Returns:

  • (Numeric)


203
204
205
206
207
# File 'lib/http/timeout/global.rb', line 203

def effective_timeout(per_op_timeout)
  return @time_left unless per_op_timeout

  [per_op_timeout, @time_left].min
end

#handle_io_result(result) ⇒ Object, Symbol

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Handles the result of an I/O operation

Parameters:

  • result (Object)

Returns:

  • (Object, Symbol)


154
155
156
# File 'lib/http/timeout/global.rb', line 154

def handle_io_result(result)
  result.nil? ? :eof : result
end

#log_timevoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Logs elapsed time and checks for timeout

Raises:



221
222
223
224
225
226
# File 'lib/http/timeout/global.rb', line 221

def log_time
  @time_left -= (Time.now - @started)
  raise TimeoutError, "Timed out after using the allocated #{@timeout} seconds" if @time_left <= 0

  reset_timer
end

#perform_io(per_op_timeout = nil) { ... } ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Performs I/O operation with timeout tracking

Parameters:

  • per_op_timeout (Numeric, nil) (defaults to: nil)

    per-operation timeout limit

Yields:

Yield Returns:

  • (Object)

Returns:

  • (Object)


135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/http/timeout/global.rb', line 135

def perform_io(per_op_timeout = nil)
  reset_timer

  loop do
    result = yield
    return handle_io_result(result) unless WAIT_RESULTS.include?(result)

    wait_for_io(result, per_op_timeout)
  rescue IO::WaitReadable then wait_readable_or_timeout(per_op_timeout)
  rescue IO::WaitWritable then wait_writable_or_timeout(per_op_timeout)
  end
rescue EOFError
  :eof
end

#read_nonblock(size, buffer = nil) ⇒ String, Symbol

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Reads from socket in non-blocking mode

Parameters:

  • size (Integer)
  • buffer (String, nil) (defaults to: nil)

Returns:

  • (String, Symbol)


118
119
120
# File 'lib/http/timeout/global.rb', line 118

def read_nonblock(size, buffer = nil)
  @socket.read_nonblock(size, buffer, exception: false)
end

#readpartial(size, buffer = nil) ⇒ String, :eof

Read from the socket

Examples:

timeout.readpartial(1024)

Parameters:

  • size (Integer)
  • buffer (String, nil) (defaults to: nil)

Returns:

  • (String, :eof)


94
95
96
# File 'lib/http/timeout/global.rb', line 94

def readpartial(size, buffer = nil)
  perform_io(@read_timeout) { read_nonblock(size, buffer) }
end

#reset_counterNumeric

Resets the time left counter to initial timeout

Examples:

timeout.reset_counter

Returns:

  • (Numeric)


41
42
43
# File 'lib/http/timeout/global.rb', line 41

def reset_counter
  @time_left = @timeout
end

#reset_timerTime

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Resets the I/O timer to current time

Returns:

  • (Time)


213
214
215
# File 'lib/http/timeout/global.rb', line 213

def reset_timer
  @started = Time.now
end

#wait_for_io(result, per_op_timeout = nil) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Waits for an I/O readiness based on the result type

Parameters:

  • result (Symbol)

    the I/O wait type

  • per_op_timeout (Numeric, nil) (defaults to: nil)

    per-operation timeout limit



164
165
166
167
168
169
170
# File 'lib/http/timeout/global.rb', line 164

def wait_for_io(result, per_op_timeout = nil)
  if result == :wait_readable
    wait_readable_or_timeout(per_op_timeout)
  else
    wait_writable_or_timeout(per_op_timeout)
  end
end

#wait_readable_or_timeout(per_op = nil) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Waits for a socket to become readable

Parameters:

  • per_op (Numeric, nil) (defaults to: nil)

    per-operation timeout limit

Raises:



177
178
179
180
181
182
183
# File 'lib/http/timeout/global.rb', line 177

def wait_readable_or_timeout(per_op = nil)
  timeout = effective_timeout(per_op)
  result = @socket.to_io.wait_readable(timeout)
  log_time

  raise TimeoutError, "Read timed out after #{per_op} seconds" if per_op && result.nil?
end

#wait_writable_or_timeout(per_op = nil) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Waits for a socket to become writable

Parameters:

  • per_op (Numeric, nil) (defaults to: nil)

    per-operation timeout limit

Raises:



190
191
192
193
194
195
196
# File 'lib/http/timeout/global.rb', line 190

def wait_writable_or_timeout(per_op = nil)
  timeout = effective_timeout(per_op)
  result = @socket.to_io.wait_writable(timeout)
  log_time

  raise TimeoutError, "Write timed out after #{per_op} seconds" if per_op && result.nil?
end

#write(data) ⇒ Integer, :eof Also known as: <<

Write to the socket

Examples:

timeout.write("GET / HTTP/1.1")

Parameters:

  • data (String)

Returns:

  • (Integer, :eof)


106
107
108
# File 'lib/http/timeout/global.rb', line 106

def write(data)
  perform_io(@write_timeout) { write_nonblock(data) }
end

#write_nonblock(data) ⇒ Integer, Symbol

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Writes to socket in non-blocking mode

Parameters:

  • data (String)

Returns:

  • (Integer, Symbol)


126
127
128
# File 'lib/http/timeout/global.rb', line 126

def write_nonblock(data)
  @socket.write_nonblock(data, exception: false)
end