mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge pull request #25093 from Erol/activerecord-transaction-serialization-error
Introduce AR::TransactionSerializationError for transaction serialization failures or deadlocks
This commit is contained in:
commit
c0d4aa2293
6 changed files with 163 additions and 3 deletions
|
@ -8,4 +8,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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
62
activerecord/test/cases/adapters/mysql2/transaction_test.rb
Normal file
62
activerecord/test/cases/adapters/mysql2/transaction_test.rb
Normal 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
|
|
@ -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
|
Loading…
Reference in a new issue