Skip to content

Commit 15aa420

Browse files
committed
Rollback transactions when the block returns earlier than expected
This behavior change is being announced via a deprecation message since Rails 6.1 and now we are making it the default behavior.
1 parent 5a98545 commit 15aa420

File tree

4 files changed

+40
-35
lines changed

4 files changed

+40
-35
lines changed

activerecord/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
* Rollback transactions when the block returns earlier than expected.
2+
3+
Before this change, when a transaction block returned early, the transaction would be committed.
4+
5+
The problem is that timeouts triggered inside the transaction block was also making the incomplete transaction
6+
to be committed, so in order to avoid this mistake, the transaction block is rolled back.
7+
8+
*Rafael Mendonça França*
9+
110
* Add middleware for automatic shard swapping.
211

312
Provides a basic middleware to perform automatic shard swapping. Applications will provide a resolver which will determine for an individual request which shard should be used. Example:

activerecord/lib/active_record/connection_adapters/abstract/transaction.rb

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -333,26 +333,19 @@ def within_new_transaction(isolation: nil, joinable: true)
333333
# @connection still holds an open or invalid transaction, so we must not
334334
# put it back in the pool for reuse.
335335
@connection.throw_away! unless transaction.state.rolledback?
336+
elsif Thread.current.status == "aborting" || (!completed && transaction.written)
337+
# The transaction is still open but the block returned earlier.
338+
#
339+
# The block could return early because of a timeout or becase the thread is aborting,
340+
# so we are rolling back to make sure the timeout didn't caused the transaction to be
341+
# committed incompletely.
342+
rollback_transaction
336343
else
337-
if Thread.current.status == "aborting"
338-
rollback_transaction
339-
else
340-
if !completed && transaction.written
341-
ActiveSupport::Deprecation.warn(<<~EOW)
342-
Using `return`, `break` or `throw` to exit a transaction block is
343-
deprecated without replacement. If the `throw` came from
344-
`Timeout.timeout(duration)`, pass an exception class as a second
345-
argument so it doesn't use `throw` to abort its block. This results
346-
in the transaction being committed, but in the next release of Rails
347-
it will rollback.
348-
EOW
349-
end
350-
begin
351-
commit_transaction
352-
rescue Exception
353-
rollback_transaction(transaction) unless transaction.state.completed?
354-
raise
355-
end
344+
begin
345+
commit_transaction
346+
rescue Exception
347+
rollback_transaction(transaction) unless transaction.state.completed?
348+
raise
356349
end
357350
end
358351
end

activerecord/test/cases/transactions_test.rb

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def test_add_to_null_transaction
184184
topic.send(:add_to_transaction)
185185
end
186186

187-
def test_successful_with_return
187+
def test_rollback_with_return
188188
committed = false
189189

190190
Topic.connection.class_eval do
@@ -195,33 +195,29 @@ def test_successful_with_return
195195
end
196196
end
197197

198-
assert_deprecated do
199-
transaction_with_return
200-
end
201-
assert committed
198+
transaction_with_return
199+
assert_not committed
202200

203-
assert_predicate Topic.find(1), :approved?, "First should have been approved"
204-
assert_not_predicate Topic.find(2), :approved?, "Second should have been unapproved"
201+
assert_not_predicate Topic.find(1), :approved?
202+
assert_predicate Topic.find(2), :approved?
205203
ensure
206204
Topic.connection.class_eval do
207205
remove_method :commit_db_transaction
208206
alias :commit_db_transaction :real_commit_db_transaction rescue nil
209207
end
210208
end
211209

212-
def test_deprecation_on_ruby_timeout
213-
assert_deprecated do
214-
catch do |timeout|
215-
Topic.transaction do
216-
@first.approved = true
217-
@first.save!
210+
def test_rollback_on_ruby_timeout
211+
catch do |timeout|
212+
Topic.transaction do
213+
@first.approved = true
214+
@first.save!
218215

219-
throw timeout
220-
end
216+
throw timeout
221217
end
222218
end
223219

224-
assert Topic.find(1).approved?, "First should have been approved"
220+
assert_not_predicate Topic.find(1), :approved?
225221
end
226222

227223
def test_early_return_from_transaction

guides/source/7_0_release_notes.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,13 @@ Please refer to the [Changelog][active-record] for detailed changes.
128128

129129
### Notable changes
130130

131+
* Rollback transactions when the block returns earlier than expected.
132+
133+
Before this change, when a transaction block returned early, the transaction would be committed.
134+
135+
The problem is that timeouts triggered inside the transaction block was also making the incomplete transaction
136+
to be committed, so in order to avoid this mistake, the transaction block is rolled back.
137+
131138
Active Storage
132139
--------------
133140

0 commit comments

Comments
 (0)