1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Add AR::TransactionSerializationError for transaction serialization failures or deadlocks

This commit is contained in:
Erol Fornoles 2016-05-21 19:25:01 +08:00
parent 694cbbf801
commit 4d525a6f75
6 changed files with 163 additions and 3 deletions

View file

@ -3,4 +3,9 @@
*Johannes Opper*
* Introduce ActiveRecord::TransactionSerializationError for catching
transaction serialization failures or deadlocks.
*Erol Fornoles*
Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activerecord/CHANGELOG.md) for previous changes.

View file

@ -727,14 +727,22 @@ module ActiveRecord
column_names.map {|name| quote_column_name(name) + option_strings[name]}
end
# See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html
ER_DUP_ENTRY = 1062
ER_NO_REFERENCED_ROW_2 = 1452
ER_DATA_TOO_LONG = 1406
ER_LOCK_DEADLOCK = 1213
def translate_exception(exception, message)
case error_number(exception)
when 1062
when ER_DUP_ENTRY
RecordNotUnique.new(message)
when 1452
when ER_NO_REFERENCED_ROW_2
InvalidForeignKey.new(message)
when 1406
when ER_DATA_TOO_LONG
ValueTooLong.new(message)
when ER_LOCK_DEADLOCK
TransactionSerializationError.new(message)
else
super
end

View file

@ -406,6 +406,7 @@ module ActiveRecord
VALUE_LIMIT_VIOLATION = "22001"
FOREIGN_KEY_VIOLATION = "23503"
UNIQUE_VIOLATION = "23505"
SERIALIZATION_FAILURE = "40001"
def translate_exception(exception, message)
return exception unless exception.respond_to?(:result)
@ -417,6 +418,8 @@ module ActiveRecord
InvalidForeignKey.new(message)
when VALUE_LIMIT_VIOLATION
ValueTooLong.new(message)
when SERIALIZATION_FAILURE
TransactionSerializationError.new(message)
else
super
end

View file

@ -285,6 +285,16 @@ module ActiveRecord
class TransactionIsolationError < ActiveRecordError
end
# TransactionSerializationError will be raised when a transaction is rolled
# back by the database due to a serialization failure or a deadlock.
#
# See the following:
#
# * http://www.postgresql.org/docs/current/static/transaction-iso.html
# * https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html#error_er_lock_deadlock
class TransactionSerializationError < ActiveRecordError
end
# IrreversibleOrderError is raised when a relation's order is too complex for
# +reverse_order+ to automatically reverse.
class IrreversibleOrderError < ActiveRecordError

View file

@ -0,0 +1,62 @@
require "cases/helper"
require 'support/connection_helper'
module ActiveRecord
class Mysql2TransactionTest < ActiveRecord::Mysql2TestCase
self.use_transactional_tests = false
class Sample < ActiveRecord::Base
self.table_name = 'samples'
end
setup do
@connection = ActiveRecord::Base.connection
@connection.clear_cache!
@connection.transaction do
@connection.drop_table 'samples', if_exists: true
@connection.create_table('samples') do |t|
t.integer 'value'
end
end
Sample.reset_column_information
end
teardown do
@connection.drop_table 'samples', if_exists: true
end
test "raises error when a serialization failure occurs" do
assert_raises(ActiveRecord::TransactionSerializationError) do
thread = Thread.new do
Sample.transaction isolation: :serializable do
Sample.delete_all
10.times do |i|
sleep 0.1
Sample.create value: i
end
end
end
sleep 0.1
Sample.transaction isolation: :serializable do
Sample.delete_all
10.times do |i|
sleep 0.1
Sample.create value: i
end
sleep 1
end
thread.join
end
end
end
end

View file

@ -0,0 +1,72 @@
require "cases/helper"
require 'support/connection_helper'
module ActiveRecord
class PostgresqlTransactionTest < ActiveRecord::PostgreSQLTestCase
self.use_transactional_tests = false
class Sample < ActiveRecord::Base
self.table_name = 'samples'
end
setup do
@connection = ActiveRecord::Base.connection
@connection.transaction do
@connection.drop_table 'samples', if_exists: true
@connection.create_table('samples') do |t|
t.integer 'value'
end
end
Sample.reset_column_information
end
teardown do
@connection.drop_table 'samples', if_exists: true
end
test "raises error when a serialization failure occurs" do
with_warning_suppression do
assert_raises(ActiveRecord::TransactionSerializationError) do
thread = Thread.new do
Sample.transaction isolation: :serializable do
Sample.delete_all
10.times do |i|
sleep 0.1
Sample.create value: i
end
end
end
sleep 0.1
Sample.transaction isolation: :serializable do
Sample.delete_all
10.times do |i|
sleep 0.1
Sample.create value: i
end
sleep 1
end
thread.join
end
end
end
protected
def with_warning_suppression
log_level = @connection.client_min_messages
@connection.client_min_messages = 'error'
yield
@connection.client_min_messages = log_level
end
end
end