module TransactionReliability

Provide utilities for the (more) reliable handling of database errors related to concurrency or connectivity.

Currently only handles Postgresql and Mysql, assuming use of the PG and Mysql2 drivers respectively

Constants

SQLSTATE_CONNECTION_ERRORS
SQLSTATE_DEADLOCK_ERRORS
SQLSTATE_ISOLATION_ERRORS
VERSION

Public Class Methods

rewrap_exception(exception) click to toggle source

Unwrap ActiveRecord::StatementInvalid into some more specific exceptions that we define.

Only defined for Mysql2 and PG drivers at the moment

# File lib/transaction_reliability.rb, line 149
def self.rewrap_exception(exception)
  if exception.message.start_with?('PG::') || exception.class.name.start_with?('PG::')
    rewrap_pg_exception exception
  elsif exception.message =~ /^mysql2::/i
    rewrap_mysql2_exception exception
  else
    exception
  end
end

Protected Class Methods

rewrap_mysql_exception(exception) click to toggle source

Ugh. This may not work if you're using non-English localization for your MySQL server.

# File lib/transaction_reliability.rb, line 216
def self.rewrap_mysql_exception(exception)
  orig    = exception.original_exception
  message = exception.message

  case 
  when message =~ /Serialization failure/i
    SerializationFailure.new(message, orig)
  when message =~ /Deadlock found when trying to get lock/i || 
       message =~ /Lock wait timeout exceeded/i
    DeadlockDetected.new(message, orig)
  when message =~ /Lost connection to MySQL server/i        ||
       message =~ /Invalid connection handle/i              ||
       message =~ /MySQL server has gone away/i             ||
       message =~ /Broken pipe/i                            ||
       message =~ /Server shutdown in progress/i
    ConnectionLost.new(message, orig)
  else
    exception
  end
end
rewrap_pg_exception(exception) click to toggle source
# File lib/transaction_reliability.rb, line 183
def self.rewrap_pg_exception(exception)
  message = exception.message
  orig    = case
            when exception.is_a?(PG::Error)
              exception
            when exception.respond_to?(:original_exception)
              exception.original_exception
            else
              exception
            end

  if orig.is_a? PG::Error
    sqlstate = orig.result.result_error_field(PGresult::PG_DIAG_SQLSTATE) rescue nil
  else
    sqlstate = nil
  end

  case
    when orig.is_a?(PG::ConnectionBad)           || SQLSTATE_CONNECTION_ERRORS.include?(sqlstate)
      ConnectionLost.new(message, orig)
    when orig.is_a?(PG::TRDeadlockDetected)      || SQLSTATE_DEADLOCK_ERRORS.include?(sqlstate)
      DeadlockDetected.new(message, orig)
    when orig.is_a?(PG::TRSerializationFailure)  || SQLSTATE_ISOLATION_ERRORS.include?(sqlstate)
      SerializationFailure.new(message, orig)
    else
      exception
  end
end