module Activerecord::Mysql::Reconnect

Constants

DEFAULT_EXECUTION_RETRY_WAIT
DEFAULT_EXECUTION_TRIES
DEFAULT_RETRY_MODE
HANDLE_ERROR
READ_SQL_REGEXP
RETRY_MODES
VERSION
WITHOUT_RETRY_KEY

Public Class Methods

enable_retry() click to toggle source
# File lib/activerecord/mysql/reconnect.rb, line 74
def enable_retry
  !!ActiveRecord::Base.enable_retry
end
execution_retry_wait() click to toggle source
# File lib/activerecord/mysql/reconnect.rb, line 69
def execution_retry_wait
  wait = ActiveRecord::Base.execution_retry_wait || DEFAULT_EXECUTION_RETRY_WAIT
  wait.kind_of?(BigDecimal) ? wait : BigDecimal(wait.to_s)
end
execution_tries() click to toggle source
# File lib/activerecord/mysql/reconnect.rb, line 65
def execution_tries
  ActiveRecord::Base.execution_tries || DEFAULT_EXECUTION_TRIES
end
handle_r_error_messages() click to toggle source
# File lib/activerecord/mysql/reconnect.rb, line 57
def handle_r_error_messages
  @@handle_r_error_messages
end
handle_rw_error_messages() click to toggle source
# File lib/activerecord/mysql/reconnect.rb, line 61
def handle_rw_error_messages
  @@handle_rw_error_messages
end
logger() click to toggle source
# File lib/activerecord/mysql/reconnect.rb, line 151
def logger
  if defined?(Rails)
    Rails.logger || ActiveRecord::Base.logger || Logger.new($stderr)
  else
    ActiveRecord::Base.logger || Logger.new($stderr)
  end
end
retry_databases() click to toggle source
# File lib/activerecord/mysql/reconnect.rb, line 114
def retry_databases
  @activerecord_mysql_reconnect_retry_databases || []
end
retry_databases=(v) click to toggle source
# File lib/activerecord/mysql/reconnect.rb, line 90
def retry_databases=(v)
  v ||= []

  unless v.kind_of?(Array)
    v = [v]
  end

  @activerecord_mysql_reconnect_retry_databases = v.map do |database|
    if database.instance_of?(Symbol)
      database = Regexp.escape(database.to_s)
      [/.*/, /\A#{database}\z/]
    else
      host = '%'
      database = database.to_s

      if database =~ /:/
        host, database = database.split(':', 2)
      end

      [create_pattern_match_regex(host), create_pattern_match_regex(database)]
    end
  end
end
retry_mode() click to toggle source
# File lib/activerecord/mysql/reconnect.rb, line 86
def retry_mode
  @activerecord_mysql_reconnect_retry_mode || DEFAULT_RETRY_MODE
end
retry_mode=(v) click to toggle source
# File lib/activerecord/mysql/reconnect.rb, line 78
def retry_mode=(v)
  unless RETRY_MODES.include?(v)
    raise "Invalid retry_mode. Please set one of the following: #{RETRY_MODES.map {|i| i.inspect }.join(', ')}"
  end

  @activerecord_mysql_reconnect_retry_mode = v
end
retryable(opts) click to toggle source
# File lib/activerecord/mysql/reconnect.rb, line 118
def retryable(opts)
  block     = opts.fetch(:proc)
  on_error  = opts[:on_error]
  conn      = opts[:connection]
  sql       = opts[:sql]
  tries     = self.execution_tries
  retval    = nil

  retryable_loop(tries) do |n|
    begin
      retval = block.call
      break
    rescue => e
      if enable_retry and (tries.zero? or n < tries) and should_handle?(e, opts)
        on_error.call if on_error
        wait = self.execution_retry_wait * n

        logger.warn("MySQL server has gone away. Trying to reconnect in #{wait.to_f} seconds. (#{build_error_message(e, sql, conn)})")
        sleep(wait)
        next
      else
        if enable_retry and n > 1
          logger.warn("Query retry failed. (#{build_error_message(e, sql, conn)})")
        end

        raise e
      end
    end
  end

  return retval
end
without_retry() { || ... } click to toggle source
# File lib/activerecord/mysql/reconnect.rb, line 159
def without_retry
  begin
    Thread.current[WITHOUT_RETRY_KEY] = true
    yield
  ensure
    Thread.current[WITHOUT_RETRY_KEY] = nil
  end
end
without_retry?() click to toggle source
# File lib/activerecord/mysql/reconnect.rb, line 168
def without_retry?
  !!Thread.current[WITHOUT_RETRY_KEY]
end

Private Class Methods

build_error_message(e, sql, conn) click to toggle source
# File lib/activerecord/mysql/reconnect.rb, line 255
def build_error_message(e, sql, conn)
  msgs = {cause: "#{e.message} [#{e.class}]"}
  msgs[:sql] = sql if sql

  if conn
    conn_info = connection_info(conn)
    msgs[:connection] = [:host, :database, :username].map {|k| "#{k}=#{conn_info[k]}" }.join(";")
  end

  msgs.map {|k, v| "#{k}: #{v}" }.join(", ")
end
connection_info(conn) click to toggle source
# File lib/activerecord/mysql/reconnect.rb, line 222
def connection_info(conn)
  conn_info = {}

  if conn.kind_of?(Mysql2::Client)
    [:host, :database, :username].each {|k| conn_info[k] = conn.query_options[k] }
  elsif conn.kind_of?(Hash)
    conn_info = conn.dup
  end

  return conn_info
end
create_pattern_match_regex(str) click to toggle source
# File lib/activerecord/mysql/reconnect.rb, line 234
def create_pattern_match_regex(str)
  ss = StringScanner.new(str)
  buf = []

  until ss.eos?
    if (tok = ss.scan(/[^\\%_]+/))
      buf << Regexp.escape(tok)
    elsif (tok = ss.scan(/\\/))
      buf << Regexp.escape(ss.getch)
    elsif (tok = ss.scan(/%/))
      buf << '.*'
    elsif (tok = ss.scan(/_/))
      buf << '.'
    else
      raise 'must not happen'
    end
  end

  /\A#{buf.join}\z/
end
retryable_loop(n) { |n| ... } click to toggle source
# File lib/activerecord/mysql/reconnect.rb, line 174
def retryable_loop(n)
  if n.zero?
    loop { n += 1 ; yield(n) }
  else
    n.times {|i| yield(i + 1) }
  end
end
should_handle?(e, opts = {}) click to toggle source
# File lib/activerecord/mysql/reconnect.rb, line 182
def should_handle?(e, opts = {})
  sql        = opts[:sql]
  retry_mode = opts[:retry_mode]
  conn       = opts[:connection]

  if without_retry?
    return false
  end

  if conn and not retry_databases.empty?
    conn_info = connection_info(conn)

    included = retry_databases.any? do |host, database|
      host =~ conn_info[:host] and database =~ conn_info[:database]
    end

    return false unless included
  end

  unless HANDLE_ERROR.any? {|i| e.kind_of?(i) }
    return false
  end

  unless Regexp.union(@@handle_r_error_messages.values + @@handle_rw_error_messages.values) =~ e.message
    return false
  end

  if sql and READ_SQL_REGEXP !~ sql
    if retry_mode == :r
      return false
    end

    if retry_mode != :force and Regexp.union(@@handle_r_error_messages.values) =~ e.message
      return false
    end
  end

  return true
end