mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge pull request #16284 from arthurnn/transactions
Transactions refactoring
This commit is contained in:
commit
6501aeb286
4 changed files with 101 additions and 62 deletions
|
@ -203,62 +203,30 @@ module ActiveRecord
|
||||||
if options[:isolation]
|
if options[:isolation]
|
||||||
raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
|
raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction"
|
||||||
end
|
end
|
||||||
|
|
||||||
yield
|
yield
|
||||||
else
|
else
|
||||||
within_new_transaction(options) { yield }
|
transaction_manager.within_new_transaction(options) { yield }
|
||||||
end
|
end
|
||||||
rescue ActiveRecord::Rollback
|
rescue ActiveRecord::Rollback
|
||||||
# rollbacks are silently swallowed
|
# rollbacks are silently swallowed
|
||||||
end
|
end
|
||||||
|
|
||||||
def within_new_transaction(options = {}) #:nodoc:
|
attr_reader :transaction_manager #:nodoc:
|
||||||
transaction = begin_transaction(options)
|
|
||||||
yield
|
|
||||||
rescue Exception => error
|
|
||||||
rollback_transaction if transaction
|
|
||||||
raise
|
|
||||||
ensure
|
|
||||||
begin
|
|
||||||
commit_transaction unless error
|
|
||||||
rescue Exception
|
|
||||||
rollback_transaction
|
|
||||||
raise
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def open_transactions
|
delegate :within_new_transaction, :open_transactions, :current_transaction, :begin_transaction, :commit_transaction, :rollback_transaction, to: :transaction_manager
|
||||||
@transaction.number
|
|
||||||
end
|
|
||||||
|
|
||||||
def current_transaction #:nodoc:
|
|
||||||
@transaction
|
|
||||||
end
|
|
||||||
|
|
||||||
def transaction_open?
|
def transaction_open?
|
||||||
@transaction.open?
|
current_transaction.open?
|
||||||
end
|
|
||||||
|
|
||||||
def begin_transaction(options = {}) #:nodoc:
|
|
||||||
@transaction = @transaction.begin(options)
|
|
||||||
end
|
|
||||||
|
|
||||||
def commit_transaction #:nodoc:
|
|
||||||
@transaction = @transaction.commit
|
|
||||||
end
|
|
||||||
|
|
||||||
def rollback_transaction #:nodoc:
|
|
||||||
@transaction = @transaction.rollback
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset_transaction #:nodoc:
|
def reset_transaction #:nodoc:
|
||||||
@transaction = ClosedTransaction.new(self)
|
@transaction_manager = TransactionManager.new(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Register a record with the current transaction so that its after_commit and after_rollback callbacks
|
# Register a record with the current transaction so that its after_commit and after_rollback callbacks
|
||||||
# can be called.
|
# can be called.
|
||||||
def add_transaction_record(record)
|
def add_transaction_record(record)
|
||||||
@transaction.add_record(record)
|
current_transaction.add_record(record)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Begins the transaction (and turns off auto-committing).
|
# Begins the transaction (and turns off auto-committing).
|
||||||
|
|
|
@ -1,5 +1,63 @@
|
||||||
module ActiveRecord
|
module ActiveRecord
|
||||||
module ConnectionAdapters
|
module ConnectionAdapters
|
||||||
|
class TransactionManager #:nodoc:
|
||||||
|
def initialize(connection)
|
||||||
|
@stack = []
|
||||||
|
@connection = connection
|
||||||
|
end
|
||||||
|
|
||||||
|
def begin_transaction(options = {})
|
||||||
|
transaction =
|
||||||
|
if @stack.empty?
|
||||||
|
RealTransaction.new(@connection, current_transaction, options)
|
||||||
|
else
|
||||||
|
SavepointTransaction.new(@connection, current_transaction, options)
|
||||||
|
end
|
||||||
|
|
||||||
|
@stack.push(transaction)
|
||||||
|
transaction
|
||||||
|
end
|
||||||
|
|
||||||
|
def commit_transaction
|
||||||
|
@stack.pop.commit
|
||||||
|
end
|
||||||
|
|
||||||
|
def rollback_transaction
|
||||||
|
@stack.pop.rollback
|
||||||
|
end
|
||||||
|
|
||||||
|
def within_new_transaction(options = {})
|
||||||
|
transaction = begin_transaction options
|
||||||
|
yield
|
||||||
|
rescue Exception => error
|
||||||
|
transaction.rollback if transaction
|
||||||
|
raise
|
||||||
|
ensure
|
||||||
|
begin
|
||||||
|
transaction.commit unless error
|
||||||
|
rescue Exception
|
||||||
|
transaction.rollback
|
||||||
|
raise
|
||||||
|
ensure
|
||||||
|
@stack.pop if transaction
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def open_transactions
|
||||||
|
@stack.size
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_transaction
|
||||||
|
@stack.last || closed_transaction
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def closed_transaction
|
||||||
|
@closed_transaction ||= ClosedTransaction.new(@connection)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class Transaction #:nodoc:
|
class Transaction #:nodoc:
|
||||||
attr_reader :connection
|
attr_reader :connection
|
||||||
|
|
||||||
|
@ -11,6 +69,10 @@ module ActiveRecord
|
||||||
def state
|
def state
|
||||||
@state
|
@state
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def savepoint_name
|
||||||
|
nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class TransactionState
|
class TransactionState
|
||||||
|
@ -78,45 +140,28 @@ module ActiveRecord
|
||||||
|
|
||||||
@parent = parent
|
@parent = parent
|
||||||
@records = []
|
@records = []
|
||||||
@finishing = false
|
|
||||||
@joinable = options.fetch(:joinable, true)
|
@joinable = options.fetch(:joinable, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
# This state is necessary so that we correctly handle stuff that might
|
|
||||||
# happen in a commit/rollback. But it's kinda distasteful. Maybe we can
|
|
||||||
# find a better way to structure it in the future.
|
|
||||||
def finishing?
|
|
||||||
@finishing
|
|
||||||
end
|
|
||||||
|
|
||||||
def joinable?
|
def joinable?
|
||||||
@joinable && !finishing?
|
@joinable
|
||||||
end
|
end
|
||||||
|
|
||||||
def number
|
def number
|
||||||
if finishing?
|
parent.number + 1
|
||||||
parent.number
|
|
||||||
else
|
|
||||||
parent.number + 1
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def begin(options = {})
|
def begin(options = {})
|
||||||
if finishing?
|
SavepointTransaction.new(connection, self, options)
|
||||||
parent.begin
|
|
||||||
else
|
|
||||||
SavepointTransaction.new(connection, self, options)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def rollback
|
def rollback
|
||||||
@finishing = true
|
|
||||||
perform_rollback
|
perform_rollback
|
||||||
parent
|
parent
|
||||||
end
|
end
|
||||||
|
|
||||||
def commit
|
def commit
|
||||||
@finishing = true
|
|
||||||
perform_commit
|
perform_commit
|
||||||
parent
|
parent
|
||||||
end
|
end
|
||||||
|
@ -183,24 +228,29 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
class SavepointTransaction < OpenTransaction #:nodoc:
|
class SavepointTransaction < OpenTransaction #:nodoc:
|
||||||
|
attr_reader :savepoint_name
|
||||||
|
|
||||||
def initialize(connection, parent, options = {})
|
def initialize(connection, parent, options = {})
|
||||||
if options[:isolation]
|
if options[:isolation]
|
||||||
raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
|
raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
|
||||||
end
|
end
|
||||||
|
|
||||||
super
|
super
|
||||||
connection.create_savepoint
|
|
||||||
|
# Savepoint name only counts the Savepoint transactions, so we need to subtract 1
|
||||||
|
@savepoint_name = "active_record_#{number - 1}"
|
||||||
|
connection.create_savepoint(@savepoint_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform_rollback
|
def perform_rollback
|
||||||
connection.rollback_to_savepoint
|
connection.rollback_to_savepoint(@savepoint_name)
|
||||||
rollback_records
|
rollback_records
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform_commit
|
def perform_commit
|
||||||
@state.set_state(:committed)
|
@state.set_state(:committed)
|
||||||
@state.parent = parent.state
|
@state.parent = parent.state
|
||||||
connection.release_savepoint
|
connection.release_savepoint(@savepoint_name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -45,6 +45,7 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
autoload_at 'active_record/connection_adapters/abstract/transaction' do
|
autoload_at 'active_record/connection_adapters/abstract/transaction' do
|
||||||
|
autoload :TransactionManager
|
||||||
autoload :ClosedTransaction
|
autoload :ClosedTransaction
|
||||||
autoload :RealTransaction
|
autoload :RealTransaction
|
||||||
autoload :SavepointTransaction
|
autoload :SavepointTransaction
|
||||||
|
@ -357,7 +358,7 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_savepoint_name
|
def current_savepoint_name
|
||||||
"active_record_#{open_transactions}"
|
current_transaction.savepoint_name
|
||||||
end
|
end
|
||||||
|
|
||||||
# Check the connection back in to the connection pool
|
# Check the connection back in to the connection pool
|
||||||
|
|
|
@ -424,6 +424,26 @@ class TransactionTest < ActiveRecord::TestCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_savepoints_name
|
||||||
|
Topic.transaction do
|
||||||
|
assert_nil Topic.connection.current_savepoint_name
|
||||||
|
assert_nil Topic.connection.current_transaction.savepoint_name
|
||||||
|
|
||||||
|
Topic.transaction(requires_new: true) do
|
||||||
|
assert_equal "active_record_1", Topic.connection.current_savepoint_name
|
||||||
|
assert_equal "active_record_1", Topic.connection.current_transaction.savepoint_name
|
||||||
|
|
||||||
|
Topic.transaction(requires_new: true) do
|
||||||
|
assert_equal "active_record_2", Topic.connection.current_savepoint_name
|
||||||
|
assert_equal "active_record_2", Topic.connection.current_transaction.savepoint_name
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal "active_record_1", Topic.connection.current_savepoint_name
|
||||||
|
assert_equal "active_record_1", Topic.connection.current_transaction.savepoint_name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_rollback_when_commit_raises
|
def test_rollback_when_commit_raises
|
||||||
Topic.connection.expects(:begin_db_transaction)
|
Topic.connection.expects(:begin_db_transaction)
|
||||||
Topic.connection.expects(:commit_db_transaction).raises('OH NOES')
|
Topic.connection.expects(:commit_db_transaction).raises('OH NOES')
|
||||||
|
|
Loading…
Reference in a new issue