Class: Petra::Components::TransactionManager
- Inherits:
-
Object
- Object
- Petra::Components::TransactionManager
- Defined in:
- lib/petra/components/transaction_manager.rb
Overview
A transaction manager handles the transactions in a single petra section, when speaking in terms of Rails, a new transaction manager is built every request.
Each TransactionManager has a stack of transactions which are currently active and is currently stored within the current thread space. Once all active transactions are either persisted or otherwise finished, it is removed again to (more or less) ensure thread safety.
Class Method Summary collapse
-
.instance ⇒ Petra::Components::TransactionManager, NilClass
The currently active TransactionManager if there is at least one running transaction.
-
.instance? ⇒ Boolean
Please note that a transaction is only considered running if the process is currently in its execution block, even if it has uncommitted changes.
-
.with_transaction(identifier: SecureRandom.uuid, &block) ⇒ String
Wraps the given block in a petra transaction (section).
-
.within_instance(&block) ⇒ Object
Performs the given block on the current instance of TransactionManager.
Instance Method Summary collapse
-
#commit_transaction ⇒ Object
Commits the currently innermost transaction.
- #current_transaction ⇒ Object
-
#initialize ⇒ TransactionManager
constructor
A new instance of TransactionManager.
-
#persist_transaction ⇒ Object
Persists the currently innermost transaction, meaning that its actions will be written to storage using the chosen persistence adapter.
- #persistence_adapter ⇒ Object
-
#reset_transaction ⇒ Object
Resets the currently innermost transaction.
-
#rollback_transaction ⇒ Object
Performs a rollback on the currently innermost transaction.
-
#transaction_count ⇒ Fixnum
The number of currently active transactions.
Constructor Details
#initialize ⇒ TransactionManager
Returns a new instance of TransactionManager.
56 57 58 |
# File 'lib/petra/components/transaction_manager.rb', line 56 def initialize @stack = [] end |
Class Method Details
.instance ⇒ Petra::Components::TransactionManager, NilClass
Returns the currently active TransactionManager if there is at least one running transaction.
42 43 44 |
# File 'lib/petra/components/transaction_manager.rb', line 42 def self.instance Thread.current[:__petra_transaction_manager] || fail(Petra::PetraError, 'There are no running transactions') end |
.instance? ⇒ Boolean
Please note that a transaction is only considered running if the process is currently in its execution block, even if it has uncommitted changes.
52 53 54 |
# File 'lib/petra/components/transaction_manager.rb', line 52 def self.instance? !!Thread.current[:__petra_transaction_manager] end |
.with_transaction(identifier: SecureRandom.uuid, &block) ⇒ String
Wraps the given block in a petra transaction (section)
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/petra/components/transaction_manager.rb', line 110 def self.with_transaction(identifier: SecureRandom.uuid, &block) within_instance do Petra.logger.info "Starting transaction #{identifier}", :green begin transaction = begin_transaction(identifier) yield rescue Petra::Retry Petra.logger.debug "Re-trying transaction #{identifier}", :blue # We have to persist certain log entries before triggering a rollback # as we'd lose read / write overrides otherwise transaction.prepare_for_retry! @stack.pop retry rescue Exception => error handle_exception(error, transaction: transaction, &block) ensure # If we made it through the transaction section without raising # any exception, we simply want to persist the performed transaction steps. # If an exception happens during this persistence, # a simple rollback is triggered as long as the transaction wasn't already persisted. # TODO: See if this behaviour could cause trouble unless error begin persist_transaction unless transaction.committed? rescue Exception transaction.rollback! unless transaction.persisted? raise end end # Remove the current transaction from the stack @stack.pop end end identifier end |
.within_instance(&block) ⇒ Object
Performs the given block on the current instance of TransactionManager. For nested transactions, the outer transaction manager is re-used, for outer transactions, a new manager is created.
Once all running transactions either failed or were committed/persisted, the transaction manager instance is removed from the thread local space again.
@todo: See if that is still a good practise when it comes to
offering further actions through exception callbacks
29 30 31 32 33 34 35 36 |
# File 'lib/petra/components/transaction_manager.rb', line 29 def self.within_instance(&block) instance = Thread.current[:__petra_transaction_manager] ||= TransactionManager.new begin instance.instance_eval(&block) ensure Thread.current[:__petra_transaction_manager] = nil if instance&.transaction_count&.zero? end end |
Instance Method Details
#commit_transaction ⇒ Object
Commits the currently innermost transaction
85 86 87 88 |
# File 'lib/petra/components/transaction_manager.rb', line 85 def commit_transaction @stack.last.commit! fail Petra::AbortTransaction end |
#current_transaction ⇒ Object
160 161 162 |
# File 'lib/petra/components/transaction_manager.rb', line 160 def current_transaction @stack.last end |
#persist_transaction ⇒ Object
Persists the currently innermost transaction, meaning that its actions will be written to storage using the chosen persistence adapter. This usually happens when a #with_transaction block ends and no commit flag was set using the corresponding exception class
96 97 98 99 |
# File 'lib/petra/components/transaction_manager.rb', line 96 def persist_transaction @stack.last.persist! @stack.pop end |
#persistence_adapter ⇒ Object
149 150 151 |
# File 'lib/petra/components/transaction_manager.rb', line 149 def persistence_adapter @persistence_adapter ||= Petra.configuration.persistence_adapter.new end |
#reset_transaction ⇒ Object
Resets the currently innermost transaction. This means that everything this transaction has done so far will be discarded and the identifier freed again. TODO: Nested transactions again, what would happen?
66 67 68 69 |
# File 'lib/petra/components/transaction_manager.rb', line 66 def reset_transaction @stack.last.reset! fail Petra::AbortTransaction end |
#rollback_transaction ⇒ Object
Performs a rollback on the currently innermost transaction. This means that everything up until the transaction’s latest savepoint will be discarded. TODO: Can we jump to a custom savepoint? What would happen if we were using the outer transaction’s data?
77 78 79 80 |
# File 'lib/petra/components/transaction_manager.rb', line 77 def rollback_transaction @stack.last.rollback! fail Petra::AbortTransaction end |
#transaction_count ⇒ Fixnum
Returns the number of currently active transactions.
156 157 158 |
# File 'lib/petra/components/transaction_manager.rb', line 156 def transaction_count @stack.size end |