class Dalli::Protocol::ConnectionManager

Manages the socket connection to the server, including ensuring liveness and retries.

Constants

DEFAULTS

Attributes

hostname[RW]
options[RW]
port[RW]
sock[R]
socket_type[RW]

Public Class Methods

new(hostname, port, socket_type, client_options) click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 29
def initialize(hostname, port, socket_type, client_options)
  @hostname = hostname
  @port = port
  @socket_type = socket_type
  @options = DEFAULTS.merge(client_options)
  @request_in_progress = false
  @sock = nil
  @pid = nil

  reset_down_info
end

Public Instance Methods

abort_request!() click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 131
def abort_request!
  @request_in_progress = false
end
close() click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 102
def close
  return unless @sock

  begin
    @sock.close
  rescue StandardError
    nil
  end
  @sock = nil
  @pid = nil
  abort_request!
end
close_on_fork() click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 218
def close_on_fork
  message = 'Fork detected, re-connecting child process...'
  Dalli.logger.info { message }
  # Close socket on a fork, setting us up for reconnect
  # on next request.
  close
  raise Dalli::NetworkError, message
end
confirm_ready!() click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 97
def confirm_ready!
  error_on_request!(RuntimeError.new('Already writing to socket')) if request_in_progress?
  close_on_fork if fork_detected?
end
connected?() click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 115
def connected?
  !@sock.nil?
end
down!() click to toggle source

Marks the server instance as down. Updates the down_at state and raises an Dalli::NetworkError that includes the underlying error in the message. Calls close to clean up socket state

# File lib/dalli/protocol/connection_manager.rb, line 80
def down!
  close
  log_down_detected

  @error = $ERROR_INFO&.class&.name
  @msg ||= $ERROR_INFO&.message
  raise_down_error
end
error_on_request!(err_or_string) click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 175
def error_on_request!(err_or_string)
  log_warn_message(err_or_string)

  @fail_count += 1
  if @fail_count >= max_allowed_failures
    down!
  else
    # Closes the existing socket, setting up for a reconnect
    # on next request
    reconnect!('Socket operation failed, retrying...')
  end
end
establish_connection() click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 49
def establish_connection
  Dalli.logger.debug { "Dalli::Server#connect #{name}" }

  @sock = memcached_socket
  @pid = Process.pid
rescue SystemCallError, Timeout::Error, EOFError, SocketError => e
  # SocketError = DNS resolution failure
  error_on_request!(e)
end
finish_request!() click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 127
def finish_request!
  @request_in_progress = false
end
fork_detected?() click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 227
def fork_detected?
  @pid && @pid != Process.pid
end
log_down_detected() click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 231
def log_down_detected
  @last_down_at = Time.now

  if @down_at
    time = Time.now - @down_at
    Dalli.logger.debug { format('%<name>s is still down (for %<time>.3f seconds now)', name: name, time: time) }
  else
    @down_at = @last_down_at
    Dalli.logger.warn("#{name} is down")
  end
end
log_up_detected() click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 243
def log_up_detected
  return unless @down_at

  time = Time.now - @down_at
  Dalli.logger.warn { format('%<name>s is back (downtime was %<time>.3f seconds)', name: name, time: time) }
end
log_warn_message(err_or_string) click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 210
def log_warn_message(err_or_string)
  detail = err_or_string.is_a?(String) ? err_or_string : "#{err_or_string.class}: #{err_or_string.message}"
  Dalli.logger.warn do
    detail = err_or_string.is_a?(String) ? err_or_string : "#{err_or_string.class}: #{err_or_string.message}"
    "#{name} failed (count: #{@fail_count}) #{detail}"
  end
end
max_allowed_failures() click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 171
def max_allowed_failures
  @max_allowed_failures ||= @options[:socket_max_failures] || 2
end
memcached_socket() click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 202
def memcached_socket
  if socket_type == :unix
    Dalli::Socket::UNIX.open(hostname, options)
  else
    Dalli::Socket::TCP.open(hostname, port, options)
  end
end
name() click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 41
def name
  if socket_type == :unix
    hostname
  else
    "#{hostname}:#{port}"
  end
end
raise_down_error() click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 89
def raise_down_error
  raise Dalli::NetworkError, "#{name} is down: #{@error} #{@msg}"
end
read(count) click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 145
def read(count)
  start_request!
  data = @sock.readfull(count)
  finish_request!
  data
rescue SystemCallError, Timeout::Error, EOFError => e
  error_on_request!(e)
end
read_line() click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 135
def read_line
  start_request!
  data = @sock.gets("\r\n")
  error_on_request!('EOF in read_line') if data.nil?
  finish_request!
  data
rescue SystemCallError, Timeout::Error, EOFError => e
  error_on_request!(e)
end
read_nonblock() click to toggle source

Non-blocking read. Should only be used in the context of a caller who has called start_request!, but not yet called finish_request!. Here to support the operation of the get_multi operation

# File lib/dalli/protocol/connection_manager.rb, line 167
def read_nonblock
  @sock.read_available
end
reconnect!(message) click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 188
def reconnect!(message)
  close
  sleep(options[:socket_failure_delay]) if options[:socket_failure_delay]
  raise Dalli::NetworkError, message
end
reconnect_down_server?() click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 59
def reconnect_down_server?
  return true unless @last_down_at

  time_to_next_reconnect = @last_down_at + options[:down_retry_delay] - Time.now
  return true unless time_to_next_reconnect.positive?

  Dalli.logger.debug do
    format('down_retry_delay not reached for %<name>s (%<time>.3f seconds left)', name: name,
                                                                                  time: time_to_next_reconnect)
  end
  false
end
request_in_progress?() click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 119
def request_in_progress?
  @request_in_progress
end
reset_down_info() click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 194
def reset_down_info
  @fail_count = 0
  @down_at = nil
  @last_down_at = nil
  @msg = nil
  @error = nil
end
socket_timeout() click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 93
def socket_timeout
  @socket_timeout ||= @options[:socket_timeout]
end
start_request!() click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 123
def start_request!
  @request_in_progress = true
end
up!() click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 72
def up!
  log_up_detected
  reset_down_info
end
write(bytes) click to toggle source
# File lib/dalli/protocol/connection_manager.rb, line 154
def write(bytes)
  start_request!
  result = @sock.write(bytes)
  finish_request!
  result
rescue SystemCallError, Timeout::Error => e
  error_on_request!(e)
end