Simplify the implementation by only calling the checks in the right places

This commit is contained in:
Rafael Mendonça França 2021-06-16 21:17:34 +00:00
parent d9f2453827
commit 115d4a3994
No known key found for this signature in database
GPG Key ID: FC23B6D0F1EEE948
5 changed files with 61 additions and 73 deletions

View File

@ -524,6 +524,7 @@ GEM
zeitwerk (2.4.2)
PLATFORMS
ruby
x86_64-darwin-19
x86_64-darwin-20
x86_64-linux

View File

@ -1,3 +1,7 @@
* Do not try to rollback transactions that failed due to a `ActiveRecord::TransactionRollbackError`.
*Jamie McCarthy*
* Active Record Encryption will now encode values as UTF-8 when using deterministic
encryption. The encoding is part of the encrypted payload, so different encodings for
different values result in different ciphertexts. This can break unique constraints and

View File

@ -37,10 +37,6 @@ module ActiveRecord
@state == :invalidated
end
def uncommittable?
invalidated? || rolledback?
end
def fully_completed?
completed?
end
@ -59,6 +55,11 @@ module ActiveRecord
@state = :fully_rolledback
end
def invalidate!
@children&.each { |c| c.invalidate! }
@state = :invalidated
end
def commit!
@state = :committed
end
@ -67,11 +68,6 @@ module ActiveRecord
@state = :fully_committed
end
def invalidate!
@children.each { |c| c.invalidate! }
@state = :invalidated
end
def nullify!
@state = nil
end
@ -166,10 +162,6 @@ module ActiveRecord
ite&.each { |i| i.committed!(should_run_callbacks: false) }
end
def invalidate
@state.invalidate!
end
def full_rollback?; true; end
def joinable?; @joinable; end
def closed?; false; end
@ -195,14 +187,11 @@ module ActiveRecord
end
def rollback
connection.rollback_to_savepoint(savepoint_name) if materialized? && !state.invalidated?
connection.rollback_to_savepoint(savepoint_name) if materialized?
@state.rollback!
end
def commit
if state.invalidated?
raise ActiveRecord::StatementInvalid, "cannot commit after transaction invalidated"
end
connection.release_savepoint(savepoint_name) if materialized?
@state.commit!
end
@ -222,14 +211,11 @@ module ActiveRecord
end
def rollback
connection.rollback_db_transaction if materialized? && !state.invalidated?
connection.rollback_db_transaction if materialized?
@state.full_rollback!
end
def commit
if state.invalidated?
raise ActiveRecord::StatementInvalid, "cannot commit after transaction invalidated"
end
connection.commit_db_transaction if materialized?
@state.full_commit!
end
@ -307,9 +293,6 @@ module ActiveRecord
def commit_transaction
@connection.lock.synchronize do
transaction = @stack.last
if transaction.state.invalidated?
raise ActiveRecord::StatementInvalid, "cannot commit after transaction invalidated"
end
begin
transaction.before_commit_records
@ -342,12 +325,13 @@ module ActiveRecord
rollback_transaction
after_failure_actions(transaction, error)
end
raise
ensure
if transaction
if error
# @connection still holds an open transaction, so we must not
# put it back in the pool for reuse
# @connection still holds an open or invalid transaction, so we must not
# put it back in the pool for reuse.
@connection.throw_away! unless transaction.state.rolledback?
else
if Thread.current.status == "aborting"

View File

@ -13,12 +13,12 @@ module ActiveRecord
setup do
@abort, Thread.abort_on_exception = Thread.abort_on_exception, false
Thread.report_on_exception, @original_report_on_exception = false, Thread.report_on_exception
@connection = ActiveRecord::Base.connection
@connection.clear_cache!
@connection.drop_table "samples", if_exists: true
@connection.create_table("samples") do |t|
@connection.create_table("samples", force: true) do |t|
t.integer "value"
end
@ -30,6 +30,7 @@ module ActiveRecord
@connection.drop_table "samples", if_exists: true
Thread.abort_on_exception = @abort
Thread.report_on_exception = @original_report_on_exception
end
test "deadlock correctly raises Deadlocked inside nested SavepointTransaction" do
@ -45,7 +46,7 @@ module ActiveRecord
Sample.transaction(requires_new: true) do
s1.lock!
barrier.wait
s2.update_attributes value: 1
s2.update value: 1
end
end
end
@ -55,16 +56,15 @@ module ActiveRecord
Sample.transaction(requires_new: true) do
s2.lock!
barrier.wait
s1.update_attributes value: 2
s1.update value: 2
end
end
ensure
thread.join
end
rescue ActiveRecord::StatementInvalid => e
if /SAVEPOINT active_record_. does not exist: ROLLBACK TO SAVEPOINT/ =~ e.to_s
assert nil, "ROLLBACK TO SAVEPOINT query issued for savepoint that no longer exists due to deadlock: #{e}"
if /SAVEPOINT active_record_. does not exist/ =~ e.to_s
flunk "ROLLBACK TO SAVEPOINT query issued for savepoint that no longer exists due to deadlock: #{e}"
else
raise e
end

View File

@ -14,12 +14,15 @@ module ActiveRecord
setup do
@abort, Thread.abort_on_exception = Thread.abort_on_exception, false
Thread.report_on_exception, @original_report_on_exception = false, Thread.report_on_exception
@connection = ActiveRecord::Base.connection
@connection.drop_table "samples", if_exists: true
@connection.create_table("samples") do |t|
t.integer "value"
@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
@ -29,6 +32,7 @@ module ActiveRecord
@connection.drop_table "samples", if_exists: true
Thread.abort_on_exception = @abort
Thread.report_on_exception = @original_report_on_exception
end
test "unserializable transaction raises SerializationFailure inside nested SavepointTransaction" do
@ -36,32 +40,30 @@ module ActiveRecord
before = Concurrent::CyclicBarrier.new(2)
after = Concurrent::CyclicBarrier.new(2)
begin
thread = Thread.new do
with_warning_suppression do
Sample.transaction(isolation: :serializable, requires_new: false) do
Sample.transaction(requires_new: true) do
before.wait
Sample.create value: Sample.sum(:value)
after.wait
end
thread = Thread.new do
with_warning_suppression do
Sample.transaction(isolation: :serializable, requires_new: false) do
Sample.transaction(requires_new: true) do
before.wait
Sample.create value: Sample.sum(:value)
after.wait
end
end
end
end
begin
with_warning_suppression do
Sample.transaction(isolation: :serializable, requires_new: false) do
Sample.transaction(requires_new: true) do
before.wait
Sample.create value: Sample.sum(:value)
after.wait
end
begin
with_warning_suppression do
Sample.transaction(isolation: :serializable, requires_new: false) do
Sample.transaction(requires_new: true) do
before.wait
Sample.create value: Sample.sum(:value)
after.wait
end
end
ensure
thread.join
end
ensure
thread.join
end
end
end
@ -74,35 +76,32 @@ module ActiveRecord
s1 = Sample.create value: 1
s2 = Sample.create value: 2
begin
thread = Thread.new do
Sample.transaction(requires_new: false) do
Sample.transaction(requires_new: true) do
s1.lock!
barrier.wait
s2.update_attributes value: 1
end
thread = Thread.new do
Sample.transaction(requires_new: false) do
Sample.transaction(requires_new: true) do
s1.lock!
barrier.wait
s2.update value: 1
end
end
end
begin
Sample.transaction(requires_new: false) do
Sample.transaction(requires_new: true) do
s2.lock!
barrier.wait
s1.update_attributes value: 2
end
begin
Sample.transaction(requires_new: false) do
Sample.transaction(requires_new: true) do
s2.lock!
barrier.wait
s1.update value: 2
end
ensure
thread.join
end
ensure
thread.join
end
end
end
end
private
def with_warning_suppression
log_level = ActiveRecord::Base.connection.client_min_messages
ActiveRecord::Base.connection.client_min_messages = "error"