lib/redis/client.rb



# frozen_string_literal: true

require 'redis-client'

class Redis
  class Client < ::RedisClient
    ERROR_MAPPING = {
      RedisClient::ConnectionError => Redis::ConnectionError,
      RedisClient::CommandError => Redis::CommandError,
      RedisClient::ReadTimeoutError => Redis::TimeoutError,
      RedisClient::CannotConnectError => Redis::CannotConnectError,
      RedisClient::AuthenticationError => Redis::CannotConnectError,
      RedisClient::FailoverError => Redis::CannotConnectError,
      RedisClient::PermissionError => Redis::PermissionError,
      RedisClient::WrongTypeError => Redis::WrongTypeError,
      RedisClient::ReadOnlyError => Redis::ReadOnlyError,
      RedisClient::ProtocolError => Redis::ProtocolError,
    }.freeze

    class << self
      def config(**kwargs)
        super(protocol: 2, **kwargs)
      end

      def sentinel(**kwargs)
        super(protocol: 2, **kwargs)
      end
    end

    def initialize(*)
      super
      @inherit_socket = false
      @pid = Process.pid
    end
    ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true)

    def id
      config.id
    end

    def server_url
      config.server_url
    end

    def timeout
      config.read_timeout
    end

    def db
      config.db
    end

    def host
      config.host unless config.path
    end

    def port
      config.port unless config.path
    end

    def path
      config.path
    end

    def username
      config.username
    end

    def password
      config.password
    end

    undef_method :call
    undef_method :call_once
    undef_method :call_once_v
    undef_method :blocking_call

    def call_v(command, &block)
      super(command, &block)
    rescue ::RedisClient::Error => error
      raise ERROR_MAPPING.fetch(error.class), error.message, error.backtrace
    end

    def blocking_call_v(timeout, command, &block)
      if timeout && timeout > 0
        # Can't use the command timeout argument as the connection timeout
        # otherwise it would be very racy. So we add an extra 100ms to account for
        # the network delay.
        timeout += 0.1
      end

      super(timeout, command, &block)
    rescue ::RedisClient::Error => error
      raise ERROR_MAPPING.fetch(error.class), error.message, error.backtrace
    end

    def pipelined
      super
    rescue ::RedisClient::Error => error
      raise ERROR_MAPPING.fetch(error.class), error.message, error.backtrace
    end

    def multi
      super
    rescue ::RedisClient::Error => error
      raise ERROR_MAPPING.fetch(error.class), error.message, error.backtrace
    end

    def disable_reconnection(&block)
      ensure_connected(retryable: false, &block)
    end

    def inherit_socket!
      @inherit_socket = true
    end

    private

    def ensure_connected(retryable: true)
      unless @inherit_socket || Process.pid == @pid
        raise InheritedError,
              "Tried to use a connection from a child process without reconnecting. " \
              "You need to reconnect to Redis after forking " \
              "or set :inherit_socket to true."
      end

      super
    end
  end
end