Skip to content

Commit

Permalink
Merge pull request #25107 from Erol/introduce-new-ar-transaction-erro…
Browse files Browse the repository at this point in the history
…r-classes

Introduce new ActiveRecord transaction error classes

Closes #26018
  • Loading branch information
rafaelfranca committed Aug 3, 2016
2 parents 8ada07b + f5c0c7f commit fac9938
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 26 deletions.
Expand Up @@ -753,7 +753,7 @@ def translate_exception(exception, message)
when ER_DATA_TOO_LONG
ValueTooLong.new(message)
when ER_LOCK_DEADLOCK
TransactionSerializationError.new(message)
DeadlockDetected.new(message)
else
super
end
Expand Down
Expand Up @@ -407,6 +407,7 @@ def postgresql_version
FOREIGN_KEY_VIOLATION = "23503"
UNIQUE_VIOLATION = "23505"
SERIALIZATION_FAILURE = "40001"
DEADLOCK_DETECTED = "40P01"

def translate_exception(exception, message)
return exception unless exception.respond_to?(:result)
Expand All @@ -419,7 +420,9 @@ def translate_exception(exception, message)
when VALUE_LIMIT_VIOLATION
ValueTooLong.new(message)
when SERIALIZATION_FAILURE
TransactionSerializationError.new(message)
SerializationFailure.new(message)
when DEADLOCK_DETECTED
DeadlockDetected.new(message)
else
super
end
Expand Down
14 changes: 12 additions & 2 deletions activerecord/lib/active_record/errors.rb
Expand Up @@ -285,14 +285,24 @@ class ImmutableRelation < ActiveRecordError
class TransactionIsolationError < ActiveRecordError
end

# TransactionSerializationError will be raised when a transaction is rolled
# TransactionRollbackError 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 < StatementInvalid
class TransactionRollbackError < StatementInvalid
end

# SerializationFailure will be raised when a transaction is rolled
# back by the database due to a serialization failure.
class SerializationFailure < TransactionRollbackError
end

# DeadlockDetected will be raised when a transaction is rolled
# back by the database when a deadlock is encountered.
class DeadlockDetected < TransactionRollbackError
end

# IrreversibleOrderError is raised when a relation's order is too complex for
Expand Down
33 changes: 13 additions & 20 deletions activerecord/test/cases/adapters/mysql2/transaction_test.rb
Expand Up @@ -27,32 +27,25 @@ class Sample < ActiveRecord::Base
@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
test "raises DeadlockDetected when a deadlock is encountered" do
assert_raises(ActiveRecord::DeadlockDetected) do
s1 = Sample.create value: 1
s2 = Sample.create value: 2

Sample.create value: i
end
thread = Thread.new do
Sample.transaction do
s1.lock!
sleep 1
s2.update_attributes value: 1
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 0.5

Sample.transaction do
s2.lock!
sleep 1
s1.update_attributes value: 2
end

thread.join
Expand Down
29 changes: 27 additions & 2 deletions activerecord/test/cases/adapters/postgresql/transaction_test.rb
Expand Up @@ -26,9 +26,9 @@ class Sample < ActiveRecord::Base
@connection.drop_table 'samples', if_exists: true
end

test "raises error when a serialization failure occurs" do
test "raises SerializationFailure when a serialization failure occurs" do
with_warning_suppression do
assert_raises(ActiveRecord::TransactionSerializationError) do
assert_raises(ActiveRecord::SerializationFailure) do
thread = Thread.new do
Sample.transaction isolation: :serializable do
Sample.delete_all
Expand All @@ -51,8 +51,33 @@ class Sample < ActiveRecord::Base

Sample.create value: i
end
end

thread.join
end
end
end

test "raises DeadlockDetected when a deadlock is encountered" do
with_warning_suppression do
assert_raises(ActiveRecord::DeadlockDetected) do
s1 = Sample.create value: 1
s2 = Sample.create value: 2

thread = Thread.new do
Sample.transaction do
s1.lock!
sleep 1
s2.update_attributes value: 1
end
end

sleep 0.5

Sample.transaction do
s2.lock!
sleep 1
s1.update_attributes value: 2
end

thread.join
Expand Down

0 comments on commit fac9938

Please sign in to comment.